diff --git a/Cargo.lock b/Cargo.lock index 07eb3458..df41f98a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2498,7 +2498,7 @@ checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" [[package]] name = "mayhem-attestation" -version = "0.2.197" +version = "0.2.264" dependencies = [ "aes", "base64 0.22.1", @@ -2520,7 +2520,7 @@ dependencies = [ [[package]] name = "mayhem-attestation-verifier" -version = "0.2.197" +version = "0.2.264" dependencies = [ "base64 0.22.1", "dcap-qvl", @@ -2543,7 +2543,7 @@ dependencies = [ [[package]] name = "mayhem-bridge" -version = "0.2.197" +version = "0.2.264" dependencies = [ "anyhow", "futures-util", @@ -2558,7 +2558,7 @@ dependencies = [ [[package]] name = "mayhem-cli" -version = "0.2.197" +version = "0.2.264" dependencies = [ "anyhow", "base64 0.22.1", @@ -2596,7 +2596,7 @@ dependencies = [ [[package]] name = "mayhem-enclave" -version = "0.2.197" +version = "0.2.264" dependencies = [ "aes-gcm", "blake3", @@ -2620,29 +2620,32 @@ dependencies = [ [[package]] name = "mayhem-engine" -version = "0.2.197" +version = "0.2.264" dependencies = [ "base64 0.22.1", "blake3", "encoding_rs", "flate2", + "futures-util", "jsonschema", "llama-cpp-2", "mayhem-enclave", "mayhem-proto", + "reqwest", "serde", "serde_json", "sha2 0.10.9", "tar", "tempfile", "thiserror 2.0.18", + "tokio", "wait-timeout", "win32job", ] [[package]] name = "mayhem-gateway" -version = "0.2.197" +version = "0.2.264" dependencies = [ "aes", "aes-gcm", @@ -2657,6 +2660,7 @@ dependencies = [ "hex", "hmac", "image", + "jsonschema", "jsonwebtoken", "mayhem-attestation", "mayhem-bridge", @@ -2682,7 +2686,7 @@ dependencies = [ [[package]] name = "mayhem-hwprobe" -version = "0.2.197" +version = "0.2.264" dependencies = [ "fs2", "serde", @@ -2692,7 +2696,7 @@ dependencies = [ [[package]] name = "mayhem-pay" -version = "0.2.197" +version = "0.2.264" dependencies = [ "anyhow", "clap", @@ -2702,7 +2706,7 @@ dependencies = [ [[package]] name = "mayhem-paygate" -version = "0.2.197" +version = "0.2.264" dependencies = [ "axum", "blake3", @@ -2725,7 +2729,7 @@ dependencies = [ [[package]] name = "mayhem-proto" -version = "0.2.197" +version = "0.2.264" dependencies = [ "base64 0.22.1", "blake3", @@ -2738,7 +2742,7 @@ dependencies = [ [[package]] name = "mayhem-windows-sandbox" -version = "0.2.197" +version = "0.2.264" dependencies = [ "thiserror 2.0.18", "windows-sys 0.60.2", @@ -2746,7 +2750,7 @@ dependencies = [ [[package]] name = "mayhemd" -version = "0.2.197" +version = "0.2.264" dependencies = [ "anyhow", "clap", diff --git a/Cargo.toml b/Cargo.toml index 795c7f82..2d46f122 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,7 +18,7 @@ members = [ exclude = ["third_party/xet-core"] [workspace.package] -version = "0.2.197" +version = "0.2.264" edition = "2021" rust-version = "1.89" license = "MIT" diff --git a/MODEL-CHEATSHEET.md b/MODEL-CHEATSHEET.md index c6db1ead..b37952ca 100644 --- a/MODEL-CHEATSHEET.md +++ b/MODEL-CHEATSHEET.md @@ -1008,6 +1008,126 @@ On a supported NVIDIA host, use `mayhem doctor --provider-backend needle-gpu` before the same managed start. Do not map MPS to `needle-gpu` or add a third canonical market. +## Qwen3 Embedding 4B + +**Selector and source** + +- Model: `Qwen/Qwen3-Embedding-4B` +- Backend/artifact: vLLM 0.24 pooling / BF16 safetensors +- Admin mirror: + `TracNetwork/mayhem-catalog-Qwen-Qwen3-Embedding-4B-BF16@909825755dc39379f3eb31256602da9cafb29c95` +- Upstream pin: + `Qwen/Qwen3-Embedding-4B@5cf2132abc99cad020ac570b19d031efec650f2b` +- Primary artifact root: + `e228d4e36517c331c3ddf034753cf974a7f73d2ac3dd0be4ef145722eccdc26c` +- Two weight shards total 8,043,548,672 bytes. +- Canary: + [`canary-qwen3-embedding-4b-bf16-v1.json`](catalog/canaries/canary-qwen3-embedding-4b-bf16-v1.json) + +**Hard requirements and surface** + +- Linux NVIDIA with compute capability 12.1 is the only calibrated platform. + Windows and other backends remain unavailable until they receive separate + calibration proof. +- 16 GiB full-offload target and 32,768 model tokens. The longest accepted + caller input is 32,767 tokens because the runtime adds one internal token. +- Endpoints: OpenAI `/v1/embeddings` and Hugging Face feature extraction. + Both accept a string or an ordered array of up to 128 strings. The signed + aggregate limit is 256 in-flight items, so two full batches can overlap. +- Native dimension is 2,560. Matryoshka output supports 32 through 2,560 + dimensions, including exact 1,536-dimensional vectors. Truncation is followed + by L2 normalization. +- Pooling uses the last non-padding token. Documents have no implicit prefix. + Retrieval queries should use + `Instruct: {task_description}\nQuery:{query}` with a task-specific instruction. +- Float and base64 response encodings are supported. Billing counts exact input + tokens; embeddings have no output-token charge. + +**Measured guidance** + +- Cold load: 57.21 seconds. +- Short-input throughput: 378.51 input tok/s at batch 1, 2,358.46 at batch 8, + and 2,432.98 at batch 32. +- Four concurrent batches of eight reached 5,064.32 input tok/s; eight reached + 5,920.53 input tok/s without sustained swap growth. +- Large-batch proof reached 200.25 items/s at batch 128 and 196.45 items/s for + two overlapping batches of 128. Two overlapping batches of 256 also passed + as unpublished headroom at 189.68 items/s. Process-tree RSS remained about + 3.49 GB with about 84.2 GB of system memory available. +- Sustained p50/p90/p99 latency was 38.4/39.5/41.6 ms at batch 1, + 46.2/79.4/82.1 ms at batch 8, and 169.8/205.6/206.9 ms at batch 32. +- Exact-runtime vectors matched the official Transformers reference with a + minimum cosine similarity of 0.999717 across native and 1,536-dimensional + query/document cases. + +**Start** + +```bash +mayhem doctor --provider-backend vllm +mayhem up --provider --provider-enclave Qwen/Qwen3-Embedding-4B --yes +``` + +The provider's local modality limits may advertise the calibrated batch and +in-flight capacity. They must remain within the signed endpoint limit and the +host's measured memory reserve. + +## Laya typed decisions + +**Selector and source** + +- Model: `convaiinnovations/laya` +- Backend/artifact: native Laya 0.3.5 / BF16 safetensors +- Admin mirror: + `TracNetwork/mayhem-catalog-convaiinnovations-laya@d288cbfe560a0ff904401f57e28820aa140c11e7` +- Upstream pin: + `convaiinnovations/laya@1c5edc17a7acd8701df6fc341c0d179f1c62c982` +- Runtime requirements SHA-256: + `b146de268c7caf04ee58e7c70bde58e7876ee9b280a8de63cf79c68fd06bab14` +- Canary: + [`canary-laya-decision-v1.json`](catalog/canaries/canary-laya-decision-v1.json) + +**Hard requirements and surface** + +- CUDA is mandatory. The backend fails load if it observes a CPU fallback or + does not preload exactly `english`, `multilingual`, and `typed-decisions`. +- Native ceiling is 1,024 input tokens. The English checkpoint internally uses + its shorter documented default where applicable; caller controls remain + bounded by the signed `limits` object. +- Endpoint: Mayhem `POST /v1/decisions`. This model is a structured decision + model and is not exposed as chat completion or media generation. +- `state` accepts a string, JSON object, or conversation array. `questions` + accepts ordered `choice`, `score`, and `noul` decisions. Explicit checkpoint, + language hint, typed-task routing, opt-in automatic routing, email cleaning, + temperature buckets, length controls, and shortlist controls are supported. +- Choice questions accept at most 20 options normally. Larger signed requests + can use shortlist mode, including caller-supplied vectors, within the endpoint + bounds. +- Billing uses exact processed input tokens and one result unit per four bytes + of canonical visible decision JSON. The reference is $0.01 per million input + or result units, with no fixed request or minimum-session charge. The hard + market band remains positive at $0.0025-$0.04 per million units. + +**Measured guidance** + +- Exact three-checkpoint CUDA load: about 13.5-13.7 seconds. +- Peak worker GPU allocation: 5,671 MiB. +- Five-request warm probes on two independent calibration hosts measured + 20.889 ms and 20.444 ms p95. The fuller mixed probes measured 84.531 ms and + 84.237 ms p95. +- One provider process owns one session slot. The launch uses two independent + providers for two aggregate slots and failover without sharing mutable router + state between requests. + +**Start** + +```bash +mayhem doctor --provider-backend laya +mayhem up --provider --provider-enclave convaiinnovations/laya --yes +``` + +The managed provider discovers the signed mirror and builds the pinned Python +runtime. No request may download weights or lazily load a checkpoint. + ## Verification and troubleshooting After startup, require all of the following rather than treating process diff --git a/README.md b/README.md index 2946f1d9..5bd417c7 100644 --- a/README.md +++ b/README.md @@ -872,6 +872,7 @@ behavior; parallel dispatch needs the selected mode's own approval. See | Class | Routes | |-------|--------| | Text generation | `/v1/chat/completions`, `/v1/completions`, `/v1/responses` — tools, JSON mode, streaming, vision input where the catalog says so | +| Exact token counting | `/v1/tokenize`, `/v1/count_tokens` — accepts `messages` or a single `prompt`; set `return_tokens` to include token IDs | | Embedding | `/v1/embeddings` | | Image generation | `/v1/images/generations` | | Video generation | `/v1/videos` | @@ -1340,6 +1341,37 @@ modality health immediately instead of rerunning an expensive functional canary before their first room heartbeat. The Comfy runtime default device is `auto`; explicit `MAYHEM_COMFYUI_DEVICE=cpu` still forces CPU for hosts that need it. +The `0.2.217` source release adds an optional +`MAYHEM_COMFYUI_RESERVE_VRAM_GB` provider setting. It passes a bounded VRAM +reserve to ComfyUI so a workflow provider can offload more model state to system +memory while leaving measured GPU headroom for another local workload. Leave it +unset unless the host has a calibrated coexistence profile. + +The `0.2.218` source release preserves assistant commentary before native +OpenAI-compatible tool calls while streaming the calls as structured deltas; +partial tool envelopes now fail closed instead of appearing as chat text. It +also keeps ComfyUI request journals deletable inside the Windows sandbox. + +The `0.2.219` source release admits signed workflow inventory against the +maximum parts one request can select. Providers still verify every advertised +part, while optional model-part catalogs no longer consume memory admission as +if every choice were loaded together. + +The `0.2.220` source release recovers expired inference reservations from the +canonical ledger even when a gateway's local job record is unavailable. Any +confirmed partial receipt is retained when the expired reservation closes. + +The `0.2.221` source release lets idle workflow engines release retained model +and allocator memory when a provider's runtime floor activates. ComfyUI uses +its supported unload and free-memory control path, restoring cohosted provider +admission without interrupting an active workflow. + +The `0.2.222` source release adds a canonical endpoint-contract fingerprint to +the existing request envelope. Updated providers prefer the canonical value, +while the legacy fingerprint remains present for older peers. Equivalent +contracts now survive JSON object reordering and JavaScript number round trips +without disrupting mixed-version routes. + The `0.2.118` source release documents the current Comfy parts inventory, binds workflow providers to the signed outcome-class definition instead of the local ComfyUI runtime directory, canonicalizes integer-valued workflow JSON diff --git a/catalog/canaries/canary-laya-decision-v1.json b/catalog/canaries/canary-laya-decision-v1.json new file mode 100644 index 00000000..aba93b14 --- /dev/null +++ b/catalog/canaries/canary-laya-decision-v1.json @@ -0,0 +1,272 @@ +{ + "set_id": "canary-laya-decision-v1", + "description": "Pinned Laya decision canaries covering every bundled checkpoint, all question types, multilingual routing, and explicit typed-workflow routing.", + "prompts": [ + { + "id": "english-choice-noul", + "state": { + "subject": "Duplicate invoice charge", + "body": "We were billed twice. Please refund the duplicate today or we will cancel." + }, + "questions": { + "department": { + "type": "choice", + "instructions": "Which department should handle this request?", + "criteria": { + "billing": "invoices, payments, refunds", + "technical": "bugs, outages, system errors", + "sales": "pricing and new contracts", + "other": "everything else" + } + }, + "refund_requested": { + "type": "noul", + "instructions": "Does the user explicitly request a refund?" + } + }, + "checkpoint": "english" + }, + { + "id": "multilingual-choice-score", + "state": { + "body": "Der Kunde wurde zweimal belastet und benötigt heute eine Rückerstattung." + }, + "questions": { + "department": { + "type": "choice", + "instructions": "Which department should handle this request?", + "criteria": { + "billing": "invoices, payments, refunds", + "technical": "bugs, outages, system errors", + "sales": "pricing and new contracts" + } + }, + "urgency": { + "type": "score", + "instructions": "How urgent is this request?", + "criteria": ["not urgent", "soon", "critical deadline or blocking issue"] + } + }, + "checkpoint": "multilingual", + "lang": "de" + }, + { + "id": "typed-all-question-types", + "state": { + "body": "The invoice total differs from the purchase order and payment is due tomorrow." + }, + "questions": { + "disposition": { + "type": "choice", + "instructions": "Choose the processing disposition.", + "criteria": { + "approve": "approve without intervention", + "review": "send for human review", + "reject": "reject the invoice" + } + }, + "urgency": { + "type": "score", + "instructions": "Score the urgency.", + "criteria": ["low", "medium", "high"] + }, + "matches_order": { + "type": "noul", + "instructions": "Does the invoice match the purchase order?" + } + }, + "checkpoint": "typed-decisions" + }, + { + "id": "automatic-non-latin-routing", + "state": { + "body": "मुझसे दो बार शुल्क लिया गया, कृपया पैसे वापस करें।" + }, + "questions": { + "refund_requested": { + "type": "noul", + "instructions": "Does the user request a refund?" + } + } + }, + { + "id": "automatic-typed-workflow-routing", + "state": { + "body": "The invoice total differs from the order and requires review before tomorrow." + }, + "questions": { + "discrepancy_severity": { + "type": "score", + "instructions": "Score the discrepancy severity.", + "criteria": ["none", "minor", "major"] + }, + "disposition": { + "type": "choice", + "instructions": "Choose the processing disposition.", + "criteria": { + "approve": "approve without intervention", + "review": "send for human review", + "reject": "reject the invoice" + } + }, + "duplicate": { + "type": "noul", + "instructions": "Is this invoice a duplicate?" + }, + "matches_order": { + "type": "noul", + "instructions": "Does the invoice match the purchase order?" + }, + "urgency": { + "type": "score", + "instructions": "Score the urgency.", + "criteria": ["low", "medium", "high"] + } + }, + "auto_task_detection": true + }, + { + "id": "email-temperature-limits", + "state": { + "body": "From: customer@example.com\nSent: Monday\n> Previous reply\nPlease cancel the duplicate invoice and refund it today.\n-- \nLong signature block", + "subject": "Duplicate invoice" + }, + "questions": { + "refund_requested": { + "type": "noul", + "instructions": "Does the current email request a refund?" + }, + "urgency": { + "type": "score", + "instructions": "Score the urgency.", + "criteria": ["low", "medium", "high"] + } + }, + "checkpoint": "english", + "email": { + "clean": true, + "max_chars": 3000 + }, + "decision_temperature": { + "noul:2": 1.5, + "score:3-5": 1.25 + }, + "limits": { + "max_len": 512, + "head_max_len": 192 + } + }, + { + "id": "high-cardinality-shortlist", + "state": { + "body": "I cannot sign in because the password reset link has expired." + }, + "questions": { + "intent": { + "type": "choice", + "instructions": "Choose the closest support category.", + "criteria": { + "account_access": "login, password, or authentication problem", + "billing_duplicate": "duplicate invoice or charge", + "billing_refund": "refund request", + "billing_tax": "tax or VAT question", + "cancel_subscription": "cancel a subscription", + "change_plan": "change a subscription plan", + "data_export": "export account data", + "data_import": "import account data", + "delete_account": "delete an account", + "email_delivery": "email was not delivered", + "feature_request": "request a new feature", + "integration_api": "API integration question", + "integration_webhook": "webhook integration question", + "mobile_app": "mobile application problem", + "performance": "slow application performance", + "privacy": "privacy question", + "security": "security concern", + "service_outage": "service is unavailable", + "shipping": "physical shipment question", + "team_members": "manage team members", + "two_factor": "two-factor authentication problem", + "usage_limits": "usage quota or limit", + "verification": "identity or email verification", + "website_bug": "website error", + "other": "none of the listed categories" + } + } + }, + "checkpoint": "english", + "shortlist": { + "k": 20, + "max_length": 512, + "batch_size": 16 + } + }, + { + "id": "supplied-vector-shortlist", + "state": { + "body": "The password reset link expired and I cannot sign in." + }, + "questions": { + "intent": { + "type": "choice", + "instructions": "Choose the closest support category.", + "criteria": { + "account_access": "login, password, or authentication problem", + "billing_duplicate": "duplicate invoice or charge", + "billing_refund": "refund request", + "billing_tax": "tax or VAT question", + "cancel_subscription": "cancel a subscription", + "change_plan": "change a subscription plan", + "data_export": "export account data", + "data_import": "import account data", + "delete_account": "delete an account", + "email_delivery": "email was not delivered", + "feature_request": "request a new feature", + "integration_api": "API integration question", + "integration_webhook": "webhook integration question", + "mobile_app": "mobile application problem", + "performance": "slow application performance", + "privacy": "privacy question", + "security": "security concern", + "service_outage": "service is unavailable", + "team_members": "manage team members", + "two_factor": "two-factor authentication problem", + "other": "none of the listed categories" + } + } + }, + "checkpoint": "english", + "shortlist": { + "k": 20, + "vectors": { + "intent": { + "query": [1, 0], + "options": { + "account_access": [1, 0], + "billing_duplicate": [0, 1], + "billing_refund": [0, 1], + "billing_tax": [0, 1], + "cancel_subscription": [0, 1], + "change_plan": [0, 1], + "data_export": [0, 1], + "data_import": [0, 1], + "delete_account": [0, 1], + "email_delivery": [0, 1], + "feature_request": [0, 1], + "integration_api": [0, 1], + "integration_webhook": [0, 1], + "mobile_app": [0, 1], + "performance": [0, 1], + "privacy": [0, 1], + "security": [0, 1], + "service_outage": [0, 1], + "team_members": [0, 1], + "two_factor": [0, 1], + "other": [0, 1] + } + } + } + } + } + ] +} diff --git a/catalog/canaries/canary-qwen3-embedding-4b-bf16-v1.json b/catalog/canaries/canary-qwen3-embedding-4b-bf16-v1.json new file mode 100644 index 00000000..b98b14e7 --- /dev/null +++ b/catalog/canaries/canary-qwen3-embedding-4b-bf16-v1.json @@ -0,0 +1,34 @@ +{ + "set_id": "canary-qwen3-embedding-4b-bf16-v1", + "description": "Pinned native, 1536-dimensional, and maximum-input Qwen3-Embedding-4B cosine canaries for query and document inputs.", + "prompts": [ + { + "id": "qwen3-embed-query-native", + "input": "Instruct: Given a web search query, retrieve relevant passages that answer the query\nQuery: How does OpenMayhem verify model artifacts?", + "max_tokens": 1 + }, + { + "id": "qwen3-embed-query-1536", + "input": "Instruct: Given a web search query, retrieve relevant passages that answer the query\nQuery: How does OpenMayhem verify model artifacts?", + "dimensions": 1536, + "max_tokens": 1 + }, + { + "id": "qwen3-embed-document-native", + "input": "OpenMayhem binds model artifacts to immutable revisions, verified hashes, calibrated canaries, and signed catalog evidence.", + "max_tokens": 1 + }, + { + "id": "qwen3-embed-document-1536", + "input": "OpenMayhem binds model artifacts to immutable revisions, verified hashes, calibrated canaries, and signed catalog evidence.", + "dimensions": 1536, + "max_tokens": 1 + }, + { + "id": "qwen3-embed-max-1536", + "input": " x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x", + "dimensions": 1536, + "max_tokens": 1 + } + ] +} diff --git a/catalog/drafts/qwen3-embedding-4b/RESEARCH.md b/catalog/drafts/qwen3-embedding-4b/RESEARCH.md new file mode 100644 index 00000000..8c7dd186 --- /dev/null +++ b/catalog/drafts/qwen3-embedding-4b/RESEARCH.md @@ -0,0 +1,158 @@ +# Qwen3-Embedding-4B onboarding evidence + +This tracker follows `CALIBRATION.md` v10. Source review precedes mirroring, +calibration and publication. The selected calibration provider performs +weight-bearing work; the canonical admin/indexer only applies and publishes +signed evidence. + +## Pinned source + +- Repository: `Qwen/Qwen3-Embedding-4B` +- Revision: `5cf2132abc99cad020ac570b19d031efec650f2b` +- License: Apache-2.0 +- Gating: none +- Last source update: 2025-06-20T09:30:56Z +- Artifact: BF16 safetensors, two weight shards, 8,043,548,672 weight bytes +- Architecture: `Qwen3ForCausalLM`, hidden size 2560, 36 layers, 32 attention heads and 8 KV heads +- Popularity snapshot: 2,328,520 recent downloads and 20,559,076 all-time downloads when the pinned source metadata was captured + +The immutable source manifest is in `source-manifest.json`. Raw source evidence is retained under `source/`. + +Primary references: + +- Model repository: https://huggingface.co/Qwen/Qwen3-Embedding-4B +- Official implementation: https://github.com/QwenLM/Qwen3-Embedding +- Pinned vLLM pooling configuration: https://docs.vllm.ai/en/v0.24.0/api/vllm/config/ +- Pinned vLLM pooling API: https://docs.vllm.ai/en/v0.24.0/api/vllm/ + +## Proven model semantics + +- Native embedding size: 2560. +- Matryoshka output dimensions: 32 through 2560 according to the model card; 1536 is required for this launch. +- Advertised context: 32K. The Transformers config contains `max_position_embeddings=40960`, while tokenizer metadata advertises a larger value. The catalog remains at the model card's 32K until the exact production runtime proves a larger supported limit. +- Pooling: last non-padding token. +- Padding: left padding in the official reference implementation. +- Normalization: L2 normalization after optional dimension truncation. +- Similarity: cosine. +- Query instruction: `Instruct: {task_description}\nQuery:{query}`. Documents use no prefix. The default source query prompt is retained in `source/config_sentence_transformers.json`. +- The service never silently adds a generic query instruction to document inputs. Callers choose the task-specific query text. + +## Card-to-pipeline coverage + +| Source requirement | Production implementation | Evidence required before publication | State | +| --- | --- | --- | --- | +| Qwen3 BF16 checkpoint | Pinned two-shard safetensors artifact | Mirror manifest and per-file SHA-256 verification | Source pinned | +| Last-token pooling | vLLM 0.24 pooling runner with `convert=embed` | Exact-runtime comparison against official reference vectors | Passed; minimum cosine 0.999717 | +| Left padding | Pinned tokenizer and vLLM pooling preprocessing | Token IDs and reference-vector comparison | Passed in official-reference comparison | +| L2-normalized embeddings | Model pooler/normalize configuration through vLLM | Vector norm and cosine canaries | Passed; native and 1536 norms are 1.0 | +| Matryoshka dimensions | Request native output, truncate its prefix, then L2-normalize | Native 2560 and exact 1536 vector tests | Passed; vLLM metadata does not declare MRL itself | +| String input | Existing OpenAI-compatible `/v1/embeddings` contract | HTTP/client conformance | Passed in calibration endpoint matrix | +| Array input | Concurrent per-item `AsyncLLM.encode`, stable response ordering | Batch 1, 8 and 32 conformance | Passed; order retained through batch 32 | +| 32K supported input | Catalog context and provider admission | Maximum-length and overflow tests | Passed at 32,767 caller tokens plus one internal token; 32,768 caller tokens rejected | +| Query instructions | Caller-visible documented format | Retrieval comparison with and without proper query instruction | Pending evaluation | +| Multilingual and code retrieval | Same embedding endpoint | Representative Recall@k, MRR and nDCG suite | Pending evaluation | +| Deterministic serving | Seeded exact production backend | Repeatability distribution and `embedding_cosine` tolerance | Passed; native and 1536 canaries retained | +| Prefix reuse | Mandatory vLLM prefix caching | Loaded-runtime evidence and repeat-prefix measurement | Passed; loaded configuration reports prefix caching enabled | + +## Core integration decision + +The public embedding endpoint, batch input schema, dimensions field, base64/float response encoding, routing, receipts and billing already exist. The vLLM engine previously exposed generation only. The generalized change adds: + +- An explicit vLLM task (`generate` or `embedding`) selected from the signed model class. +- Pooling runner and embedding conversion in the pinned managed vLLM runtime. +- Concurrent batched encoding with stable input ordering. +- Dimension, count and finite-vector validation. +- Cancellation of every in-flight item in a batch. +- A parallel-safe embedding handle for independent provider sessions. +- Generation-only KV-capacity checks remain limited to generation; mandatory prefix caching remains enabled for both runners. + +No Qwen model ID or model-specific route is hardcoded into Core. + +## Runtime measurements + +The retained `qwen3-embedding-4b-vllm-benchmark.json` uses vLLM 0.24, BF16, +32,768 model context, eight scheduler sequences, a 32,768-token scheduler +budget, and a 13% unified-memory target. It recorded: + +- 57.21-second cold load. +- 32,767 caller tokens accepted as 32,768 billed/model tokens at 4,462.27 + input tokens/second; one additional caller token was rejected before work. +- Batch 1/8/32 short-input rates of 378.51, 2,358.46, and 2,432.98 input + tokens/second. +- Four concurrent batches of eight at 5,064.32 input tokens/second and eight + concurrent batches of eight at 5,920.53 input tokens/second. +- Sustained p50/p90/p99 latency of 38.4/39.5/41.6 ms for batch 1, + 46.2/79.4/82.1 ms for batch 8, and 169.8/205.6/206.9 ms for batch 32. +- No swap growth: 6,569,984 bytes before, during, and after the run. +- A 0.285 ms caller-visible cancellation acknowledgement. vLLM's active + pooling kernel drained for 7.18 seconds before the recovery request finished; + Core's independent cancellation flag discards that late vector. + +The 12% vLLM target failed safely because a 32K request needs about 4.5 GiB of +KV space and only 3.41 GiB was available. Thirteen percent passed with 4.63 GiB +and 33,680 KV tokens. The launch minimum is therefore 16 GiB full-offload +memory rather than the unproven 12 GiB estimate. + +## Pricing evidence and decision + +Prices were checked against current first-party pages on 2026-09-16: + +| Service | Current public price | Source | +| --- | ---: | --- | +| OpenAI `text-embedding-3-small` | $0.02 / 1M input tokens | https://developers.openai.com/api/docs/models/text-embedding-3-small | +| OpenAI `text-embedding-3-large` | $0.13 / 1M input tokens | https://developers.openai.com/api/docs/models/text-embedding-3-small | +| Voyage `voyage-4-lite` | $0.02 / 1M input tokens | https://docs.voyageai.com/docs/pricing | +| Voyage `voyage-4` | $0.06 / 1M input tokens | https://docs.voyageai.com/docs/pricing | +| Voyage `voyage-4-large` and `voyage-code-4` | $0.12 / 1M input tokens | https://docs.voyageai.com/docs/pricing | +| Google `gemini-embedding-2` text | $0.20 / 1M input tokens | https://ai.google.dev/gemini-api/docs/pricing | +| Cohere Embed 4 dedicated small instance | $4/hour or $2,500/month | https://cohere.com/pricing | + +Measured active board power was normally 30–46 W, with an 82 W observed peak; +idle/load sampling was about 11.5 W. At EUR 0.40/kWh, 46 W costs EUR 0.0184 +per active hour. A conservative shared-hardware allocation assigns 13% of a +$3,999 purchase amortized across three years, or about $0.0198/hour, to this +resident service. Combined allocated compute and power are roughly $0.04/hour +before tax and operator overhead. This is about $0.039 per million tokens at +the measured sustained single-item rate and about $0.0022 per million at the +four-request measurement. These are explicit costing assumptions, not a claim +about the owner's electricity contract or purchase price. + +The reference/start price is $0.06 per million input tokens. Contract v25's +existing 25% to 400% activity band gives a lower bound of $0.015 and upper +bound of $0.24 per million. Output has no price. The existing activity +controller may move the price inside that band; provider-advertised capacity +does not affect it. + +## Acceptance gates + +- Passed: every file was mirrored from the pinned source with the protected + fleet Hugging Face credential, then checked against the retained manifest. +- Passed: exact-runtime native and 1,536-dimensional vectors matched the + official reference with minimum cosine 0.999717. +- Passed locally: four and eight concurrent batches completed with stable item + order. Public concurrent-route proof remains pending publication. +- Passed: cold start, memory, sustained batches, cancellation, recovery, and + maximum-length behavior are retained in the benchmark reports. +- Pending credentialed comparison: run the retained representative suite at + 1,536 dimensions against `text-embedding-3-small` and record Recall@k, MRR, + and nDCG. Publish no match-or-beat claim unless that aggregate proves it. +- Passed: current competitor pricing and explicit operating-cost assumptions + support the $0.06/M input-token starting reference and existing 25% to 400% + activity band. +- Passed locally: tier-1/tier-2 endpoint matrix and `embedding_cosine` evidence. + Signed canonical publication, readback, and paid FIAT/TNK/TAP proofs remain + pending the synchronized release. +- Restored: the pre-existing H3 and ACE provider services returned to accepting + state after clean calibration. Coexistence proof with the persistent embedding + service remains pending its admitted start. + +## Retained evidence hashes + +- Exact-runtime benchmark SHA-256: + `1c271e018273a46549ca9b5f35d9d26b8ffb293b4661cfa8da11744731068d4b` +- Cancellation report SHA-256: + `928a4aeb7f1412e302b34c26d0ee30cc359a3ac260d5653a2dfc264c729099ea` +- Official-reference comparison SHA-256: + `1eef40cec37ba00eae1db319bc93b0a33fa1b8278719fe8d98cc2c10fafac691` +- Final canary report SHA-256: + `8d95b6f473ff29fbe13ae70a32d626f4f107a48b249b66234d36a59dca6e2e41` diff --git a/catalog/drafts/qwen3-embedding-4b/benchmark-vllm.py b/catalog/drafts/qwen3-embedding-4b/benchmark-vllm.py new file mode 100644 index 00000000..327edd80 --- /dev/null +++ b/catalog/drafts/qwen3-embedding-4b/benchmark-vllm.py @@ -0,0 +1,326 @@ +#!/usr/bin/env python3 +"""Bounded Qwen3 embedding calibration benchmark for the pinned vLLM runtime.""" + +import argparse +import asyncio +import json +import math +import os +import statistics +import time +import uuid +from pathlib import Path + +import psutil +from transformers import AutoTokenizer +from vllm import AsyncEngineArgs, PoolingParams +from vllm.v1.engine.async_llm import AsyncLLM + + +def process_tree_rss() -> int: + root = psutil.Process(os.getpid()) + processes = [root, *root.children(recursive=True)] + total = 0 + for process in processes: + try: + total += process.memory_info().rss + except (psutil.NoSuchProcess, psutil.AccessDenied): + pass + return total + + +def memory_snapshot() -> dict: + memory = psutil.virtual_memory() + swap = psutil.swap_memory() + return { + "process_tree_rss_bytes": process_tree_rss(), + "system_available_bytes": memory.available, + "system_used_bytes": memory.used, + "swap_used_bytes": swap.used, + } + + +def normalized_prefix(vector, dimensions: int) -> list[float]: + values = [float(value) for value in vector[:dimensions]] + norm = math.sqrt(sum(value * value for value in values)) + if not math.isfinite(norm) or norm <= 0: + raise RuntimeError("embedding prefix has no finite norm") + return [value / norm for value in values] + + +def vector_from_output(output) -> list[float]: + values = getattr(output.outputs, "embedding", None) + if values is None: + values = getattr(output.outputs, "data", None) + if hasattr(values, "detach"): + values = values.detach().float().cpu().tolist() + return [float(value) for value in values] + + +async def encode_one(engine, text: str, request_id: str): + final = None + async for output in engine.encode( + prompt=text, + pooling_params=PoolingParams(task="embed", dimensions=None), + request_id=request_id, + ): + final = output + if final is None or not final.finished: + raise RuntimeError("embedding ended without a final result") + vector = vector_from_output(final) + return vector, len(final.prompt_token_ids) + + +async def measured_batch(engine, texts: list[str], label: str) -> dict: + started = time.perf_counter() + rows = await asyncio.gather(*[ + encode_one(engine, text, f"{label}-{index}-{uuid.uuid4().hex}") + for index, text in enumerate(texts) + ]) + elapsed = time.perf_counter() - started + tokens = sum(row[1] for row in rows) + vectors = [normalized_prefix(row[0], 1536) for row in rows] + return { + "label": label, + "items": len(rows), + "tokens": tokens, + "elapsed_seconds": elapsed, + "items_per_second": len(rows) / elapsed, + "tokens_per_second": tokens / elapsed, + "native_dimensions": len(rows[0][0]), + "requested_dimensions": len(vectors[0]), + "norm_min": min(math.sqrt(sum(v * v for v in row)) for row in vectors), + "norm_max": max(math.sqrt(sum(v * v for v in row)) for row in vectors), + "process_tree_rss_bytes": process_tree_rss(), + } + + +def percentile(values: list[float], quantile: float) -> float: + ordered = sorted(values) + index = math.ceil(quantile * len(ordered)) - 1 + return ordered[max(0, min(index, len(ordered) - 1))] + + +async def repeated_batches(engine, texts: list[str], label: str, repetitions: int) -> dict: + rows = [] + for repetition in range(repetitions): + rows.append(await measured_batch(engine, texts, f"{label}-{repetition}")) + latencies = [row["elapsed_seconds"] for row in rows] + tokens = sum(row["tokens"] for row in rows) + elapsed = sum(latencies) + return { + "label": label, + "repetitions": repetitions, + "items_per_request": len(texts), + "total_items": repetitions * len(texts), + "total_tokens": tokens, + "elapsed_seconds": elapsed, + "requests_per_second": repetitions / elapsed, + "tokens_per_second": tokens / elapsed, + "latency_p50_seconds": statistics.median(latencies), + "latency_p90_seconds": percentile(latencies, 0.90), + "latency_p99_seconds": percentile(latencies, 0.99), + "memory": memory_snapshot(), + } + + +def sized_text(tokenizer, target_tokens: int, marker: str) -> str: + unit = f" {marker} retrieval passage evidence" + text = unit * max(1, target_tokens // 4) + for _ in range(8): + count = len(tokenizer.encode(text, add_special_tokens=False)) + if target_tokens - 4 <= count <= target_tokens: + return text + if count > target_tokens: + text = text[: max(1, int(len(text) * target_tokens / count * 0.998))] + else: + text += unit * max(1, (target_tokens - count) // 4) + while len(tokenizer.encode(text, add_special_tokens=False)) > target_tokens: + text = text[:-1] + return text + + +def sized_char_text(target_bytes: int, marker: str) -> str: + unit = f" {marker} retrieval evidence" + return (unit * (target_bytes // len(unit) + 1))[:target_bytes] + + +async def main() -> None: + parser = argparse.ArgumentParser() + parser.add_argument("--model", required=True) + parser.add_argument("--output", required=True) + parser.add_argument("--memory-utilization", type=float, default=0.12) + args = parser.parse_args() + + os.environ.setdefault("TOKENIZERS_PARALLELISM", "false") + baseline_memory = memory_snapshot() + tokenizer = AutoTokenizer.from_pretrained(args.model, trust_remote_code=False) + load_started = time.perf_counter() + engine_args = AsyncEngineArgs( + model=args.model, + tokenizer=args.model, + trust_remote_code=False, + max_model_len=32768, + max_num_seqs=8, + max_num_batched_tokens=32768, + tensor_parallel_size=1, + enforce_eager=True, + seed=0, + use_fp64_gumbel=True, + async_scheduling=False, + runner="pooling", + convert="embed", + enable_prefix_caching=True, + dtype="bfloat16", + gpu_memory_utilization=args.memory_utilization, + limit_mm_per_prompt={"image": 1, "audio": 1, "video": 1}, + mm_processor_cache_gb=0, + ) + engine = AsyncLLM.from_engine_args(engine_args) + loaded_seconds = time.perf_counter() - load_started + loaded_memory = memory_snapshot() + results = [] + errors = [] + try: + await measured_batch(engine, ["warm embedding request"], "warmup") + short = "A compact passage about vector search and retrieval quality." + for count in (1, 8, 32): + results.append(await measured_batch( + engine, + [f"{short} Item {index}." for index in range(count)], + f"batch-{count}-short", + )) + text512 = sized_text(tokenizer, 512, "medium") + results.append(await measured_batch( + engine, + [f"{text512} batch item {index}" for index in range(8)], + "batch-8-512-token", + )) + for character_count in (256, 1024, 4096): + text = sized_char_text(character_count, f"chars-{character_count}") + for batch_size in (1, 8, 32): + results.append(await measured_batch( + engine, + [f"{text} item {index}" for index in range(batch_size)], + f"batch-{batch_size}-{character_count}-chars", + )) + + text256 = sized_char_text(256, "concurrent") + for concurrency in (1, 2, 4, 8): + started = time.perf_counter() + concurrent = await asyncio.gather(*[ + measured_batch( + engine, + [f"{text256} request {request} item {index}" for index in range(8)], + f"concurrent-{concurrency}-{request}-batch-8", + ) + for request in range(concurrency) + ]) + elapsed = time.perf_counter() - started + tokens = sum(row["tokens"] for row in concurrent) + items = sum(row["items"] for row in concurrent) + results.append({ + "label": f"concurrent-{concurrency}x-batch-8-256-chars", + "requests": concurrency, + "items": items, + "tokens": tokens, + "elapsed_seconds": elapsed, + "requests_per_second": concurrency / elapsed, + "items_per_second": items / elapsed, + "tokens_per_second": tokens / elapsed, + "memory": memory_snapshot(), + }) + + results.append(await repeated_batches( + engine, + [short], + "sustained-batch-1-short", + 50, + )) + results.append(await repeated_batches( + engine, + [f"{short} sustained item {index}" for index in range(8)], + "sustained-batch-8-short", + 40, + )) + results.append(await repeated_batches( + engine, + [f"{short} sustained item {index}" for index in range(32)], + "sustained-batch-32-short", + 20, + )) + + # vLLM appends one model token, leaving 32,767 caller-supplied tokens in + # the checkpoint's 32,768-token context window. + long_text = " x" * 32767 + results.append(await measured_batch(engine, [long_text], "single-max-input")) + + overflow_text = " x" * 32768 + try: + await measured_batch(engine, [overflow_text], "single-overflow") + errors.append({"label": "overflow", "unexpected": "accepted"}) + except Exception as error: + errors.append({ + "label": "overflow", + "expected_rejection": True, + "type": type(error).__name__, + "message": str(error)[:500], + }) + + blocker_id = f"cancel-blocker-{uuid.uuid4().hex}" + cancel_id = f"cancel-target-{uuid.uuid4().hex}" + blocker_text = " blocker" * 32700 + cancel_text = " target" * 32700 + blocker_task = asyncio.create_task(encode_one(engine, blocker_text, blocker_id)) + cancel_task = asyncio.create_task(encode_one(engine, cancel_text, cancel_id)) + await asyncio.sleep(0.01) + cancel_started = time.perf_counter() + await engine.abort(cancel_id) + try: + await asyncio.wait_for(cancel_task, timeout=20) + errors.append({"label": "cancel", "unexpected": "completed"}) + except BaseException as error: + errors.append({ + "label": "cancel", + "expected_abort": True, + "type": type(error).__name__, + "message": str(error)[:500], + "abort_seconds": time.perf_counter() - cancel_started, + }) + await asyncio.wait_for(blocker_task, timeout=30) + results.append(await measured_batch( + engine, + ["Recovery request after a cancelled long embedding."], + "post-cancel-recovery", + )) + finally: + shutdown = getattr(engine, "shutdown", None) + if callable(shutdown): + shutdown() + + report = { + "schema_version": 1, + "model_path": args.model, + "runtime": "vllm-0.24.0", + "memory_utilization": args.memory_utilization, + "max_model_len": 32768, + "max_num_seqs": 8, + "max_num_batched_tokens": 32768, + "load_seconds": loaded_seconds, + "baseline_memory": baseline_memory, + "loaded_memory": loaded_memory, + "final_memory": memory_snapshot(), + "results": results, + "boundary_results": errors, + } + Path(args.output).write_text(json.dumps(report, indent=2) + "\n") + print(json.dumps({ + "output": args.output, + "load_seconds": loaded_seconds, + "result_count": len(results), + "boundary_results": errors, + })) + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/catalog/drafts/qwen3-embedding-4b/bf16.final.canary.json b/catalog/drafts/qwen3-embedding-4b/bf16.final.canary.json new file mode 100644 index 00000000..4ceddcb9 --- /dev/null +++ b/catalog/drafts/qwen3-embedding-4b/bf16.final.canary.json @@ -0,0 +1,11855 @@ +{ + "model_id": "Qwen/Qwen3-Embedding-4B", + "artifact": "bf16", + "engine": "vllm", + "verification_method": "embedding_cosine", + "artifact_path": "hf://TracNetwork/mayhem-catalog-Qwen-Qwen3-Embedding-4B-BF16@909825755dc39379f3eb31256602da9cafb29c95/model-00001-of-00002.safetensors", + "artifact_binding": { + "artifact_root": "e228d4e36517c331c3ddf034753cf974a7f73d2ac3dd0be4ef145722eccdc26c", + "artifact_root_kind": "blake3_merkle_v1", + "source_kind": "huggingface", + "source_repo": "TracNetwork/mayhem-catalog-Qwen-Qwen3-Embedding-4B-BF16", + "source_revision": "909825755dc39379f3eb31256602da9cafb29c95", + "source_path": "model-00001-of-00002.safetensors", + "source_sha256": "e70bfe3c970523fb7ef4eddffed2254ce3f1e7150c3de2af4342de129dd756f8", + "weights_bytes": 4965826464, + "tokenizer_sha256": "83cdf8c3a34f68862319cb1810ee7b1e2c0a44e0864ae930194ddb76bb7feb8d", + "chat_template_sha256": null, + "min_compute_cap": "12.1", + "artifact_sidecars": { + "sentence_transformers_config": { + "source_kind": "huggingface", + "source_repo": "TracNetwork/mayhem-catalog-Qwen-Qwen3-Embedding-4B-BF16", + "source_revision": "909825755dc39379f3eb31256602da9cafb29c95", + "source_path": "config_sentence_transformers.json", + "artifact_root": "2604cf5d3d4ae70d0a39ef585566408e788e195f470c5e96dcb3e1559816dcc0", + "artifact_root_kind": "blake3_merkle_v1", + "source_sha256": "10667c72ddb772627bf1780cb7f86af8e2ae0032b8c243c731172064105c6961", + "weights_bytes": 215 + }, + "sentence_transformers_modules": { + "source_kind": "huggingface", + "source_repo": "TracNetwork/mayhem-catalog-Qwen-Qwen3-Embedding-4B-BF16", + "source_revision": "909825755dc39379f3eb31256602da9cafb29c95", + "source_path": "modules.json", + "artifact_root": "c74ac8c95ab46522ca38ba2706de4ca7da1406d380ca44a26da19d99bda358dd", + "artifact_root_kind": "blake3_merkle_v1", + "source_sha256": "84e40c8e006c9b1d6c122e02cba9b02458120b5fb0c87b746c41e0207cf642cf", + "weights_bytes": 349 + }, + "sentence_transformers_pooling_config": { + "source_kind": "huggingface", + "source_repo": "TracNetwork/mayhem-catalog-Qwen-Qwen3-Embedding-4B-BF16", + "source_revision": "909825755dc39379f3eb31256602da9cafb29c95", + "source_path": "1_Pooling/config.json", + "artifact_root": "6d8a7ca7a24500117320af3757784db5d674b24b6c78a8a2064cf1ec9a4898ac", + "artifact_root_kind": "blake3_merkle_v1", + "source_sha256": "0f0ed3380602b252fced3fab6d07c76752c32ffca818ccc61afaf17ae8edd96f", + "weights_bytes": 313 + }, + "vllm_config": { + "source_kind": "huggingface", + "source_repo": "TracNetwork/mayhem-catalog-Qwen-Qwen3-Embedding-4B-BF16", + "source_revision": "909825755dc39379f3eb31256602da9cafb29c95", + "source_path": "config.json", + "artifact_root": "301beb973e39d7dc94163445b914217e29c64f55c0033e3e5e46645f75a96e4f", + "artifact_root_kind": "blake3_merkle_v1", + "source_sha256": "78d2861cbbfd80eee05839200c5a3b7ed64c789f6c1cab4fbb84cc4eae33eaf5", + "weights_bytes": 727 + }, + "vllm_generation_config": { + "source_kind": "huggingface", + "source_repo": "TracNetwork/mayhem-catalog-Qwen-Qwen3-Embedding-4B-BF16", + "source_revision": "909825755dc39379f3eb31256602da9cafb29c95", + "source_path": "generation_config.json", + "artifact_root": "e4fa3d00de31cf8a2654f8873a7b983421fa5dc7e9e92ec4ecd284240bda28b2", + "artifact_root_kind": "blake3_merkle_v1", + "source_sha256": "28396d421a2108acce96383f6a7de78008f7f1b17f807958f3c14c51dbfb65fb", + "weights_bytes": 117 + }, + "vllm_merges": { + "source_kind": "huggingface", + "source_repo": "TracNetwork/mayhem-catalog-Qwen-Qwen3-Embedding-4B-BF16", + "source_revision": "909825755dc39379f3eb31256602da9cafb29c95", + "source_path": "merges.txt", + "artifact_root": "4c9e5374d798802ae6491739da886f1c61f2ebe640cf68be8cd9fe856b51ed11", + "artifact_root_kind": "blake3_merkle_v1", + "source_sha256": "8831e4f1a044471340f7c0a83d7bd71306a5b867e95fd870f74d0c5308a904d5", + "weights_bytes": 1671853 + }, + "vllm_model_index": { + "source_kind": "huggingface", + "source_repo": "TracNetwork/mayhem-catalog-Qwen-Qwen3-Embedding-4B-BF16", + "source_revision": "909825755dc39379f3eb31256602da9cafb29c95", + "source_path": "model.safetensors.index.json", + "artifact_root": "15218e7fbe432280e05b130e7d965369bba4f4d61b546c09ce1f8c5c083e27ca", + "artifact_root_kind": "blake3_merkle_v1", + "source_sha256": "9d130c7f24fa1f9a2a7e19fad42c7d6d2d6fea31b180bdf3e8aac1924c26c39a", + "weights_bytes": 30431 + }, + "vllm_tokenizer_config": { + "source_kind": "huggingface", + "source_repo": "TracNetwork/mayhem-catalog-Qwen-Qwen3-Embedding-4B-BF16", + "source_revision": "909825755dc39379f3eb31256602da9cafb29c95", + "source_path": "tokenizer_config.json", + "artifact_root": "f335cfe9a2dc1a8be2028825e513aea6f4ce1a190a2048758d79b7780eaadb59", + "artifact_root_kind": "blake3_merkle_v1", + "source_sha256": "2f58f4bbd7bbce15d683f525954ef3a92cd82f5e06415a9c513859bf8ab72436", + "weights_bytes": 7256 + }, + "vllm_tokenizer_json": { + "source_kind": "huggingface", + "source_repo": "TracNetwork/mayhem-catalog-Qwen-Qwen3-Embedding-4B-BF16", + "source_revision": "909825755dc39379f3eb31256602da9cafb29c95", + "source_path": "tokenizer.json", + "artifact_root": "d0462ea067d57af34425ed175418141c1a347ad44cc6069a01acfb87acaf6ce2", + "artifact_root_kind": "blake3_merkle_v1", + "source_sha256": "83cdf8c3a34f68862319cb1810ee7b1e2c0a44e0864ae930194ddb76bb7feb8d", + "weights_bytes": 11422947 + }, + "vllm_vocab": { + "source_kind": "huggingface", + "source_repo": "TracNetwork/mayhem-catalog-Qwen-Qwen3-Embedding-4B-BF16", + "source_revision": "909825755dc39379f3eb31256602da9cafb29c95", + "source_path": "vocab.json", + "artifact_root": "07e084d5bb703cb11e2e1d14570b12572f794244e2f6b850bfeb278db0053cdf", + "artifact_root_kind": "blake3_merkle_v1", + "source_sha256": "ca10d7e9fb3ed18575dd1e277a2579c16d108e32f27439684afa0e10b1440910", + "weights_bytes": 2776833 + }, + "vllm_weight_shard_00002": { + "source_kind": "huggingface", + "source_repo": "TracNetwork/mayhem-catalog-Qwen-Qwen3-Embedding-4B-BF16", + "source_revision": "909825755dc39379f3eb31256602da9cafb29c95", + "source_path": "model-00002-of-00002.safetensors", + "artifact_root": "20c7a959fb6398c267d3370225629ad7d90f80a77787b9ea7afbeac4d1b36370", + "artifact_root_kind": "blake3_merkle_v1", + "source_sha256": "ed1b87c8e9eb7e535a1a155e4fd00d9f4dba80e58a6db48a4c9f82cede7079c1", + "weights_bytes": 3077765624 + } + } + }, + "runtime_config": { + "ctx_size": 32768, + "seed": 0, + "threads": null, + "gpu_layers": null, + "trt_engine_dir": null, + "trt_require_engine_dir": false, + "trt_tensor_parallel": null, + "trt_kv_cache_dtype": null, + "trt_max_batch_size": null, + "trt_max_num_tokens": null, + "vllm_memory_utilization_pct": 13, + "vllm_memory_utilization_floor_pct": 13, + "vllm_dtype": "bfloat16", + "vllm_kv_cache_dtype": null, + "vllm_max_num_seqs": 8, + "vllm_generation_topology": "shared_worker", + "vllm_max_num_batched_tokens": 32768, + "vllm_enforce_eager": null, + "vllm_linear_backend": null, + "vllm_moe_backend": null, + "vllm_mtp_num_speculative_tokens": null + }, + "canary_set": "canary-qwen3-embedding-4b-bf16-v1", + "canary_set_sha256": "532788ca9859357810501831e0b455a76295a6d583f70f5cbd30bd33cdb8a613", + "prompt_count": 5, + "catalog_fingerprint": "2021ec5c7e63a2537eb359e79f4dc0330f9057dc7853125c3e71ee04df2bc379", + "modality_fingerprints": { + "embedding": "2cb34c1e7652ce1ac18911de95f087a041591ea07134d7600270bf7136fa7279" + }, + "modality_resource_profiles": { + "embedding": { + "unit": "input_token", + "measurement_source": "process_tree_rss; pool=nvidia_unified_memory; NVIDIA unified memory from hwprobe; --memory-reserve override 24.00GiB", + "max_item_bytes": 65533, + "max_item_units": 32768, + "measured_item_bytes": 65533, + "measured_item_units": 32768, + "measured_working_set_bytes": 2826240, + "calibration_baseline_memory_bytes": 3581898752, + "calibration_peak_memory_bytes": 3584724992, + "calibration_f13_budget_bytes": 96129949696, + "default_max_inflight_items": 1, + "default_max_items_per_request": 1 + } + }, + "speciality_calibrations": {}, + "endpoint_calibration": { + "schema_version": 2, + "matrix_fingerprint": "6c6ce2a2c9dc6037b75f2bd06b27c7b24a2b7c997b80f0a6b4c7b6cd21ca3834", + "family_count": 2, + "case_count": 56, + "families": [ + { + "endpoint_family": "hf_feature_extraction", + "contract_fingerprint": "2babf49b4f14f32a12f94ddef9e73cfa6a8570d77bcbb0a7a349352e6d9c1932", + "matrix_fingerprint": "2496813ec9e0d051491d72de17c2d299451d7f2d5b09c8c7ffa766d99a770aef", + "case_count": 22, + "cases": [ + { + "case_id": "hf_feature_extraction-required_present-4d76abac26b13102", + "endpoint_family": "hf_feature_extraction", + "case_kind": "required_present", + "attributes": [ + "inputs" + ], + "expect_accept": true, + "contract_fingerprint": "2babf49b4f14f32a12f94ddef9e73cfa6a8570d77bcbb0a7a349352e6d9c1932", + "request_fingerprint": "4c7201b03d613eb57bda0c18bfd56fd04a523c4dfbc7da25a930bcfc0e464426", + "contract_validation": { + "status": "passed", + "fingerprint": "4c7201b03d613eb57bda0c18bfd56fd04a523c4dfbc7da25a930bcfc0e464426" + }, + "gateway_normalization": { + "status": "passed", + "fingerprint": "4c7201b03d613eb57bda0c18bfd56fd04a523c4dfbc7da25a930bcfc0e464426" + }, + "provider_translation": { + "status": "passed", + "fingerprint": "fc287626a1c8238cd3f9e9c695df1b19029315c4c9d8433c72a78155d1b72e10" + }, + "backend_execution": { + "status": "passed", + "fingerprint": "823a5dbbef763e6111a676949abdfc24b23b0e406671c9cbb654913b768ffbf3" + }, + "backend_proof": { + "kind": "full_inference" + }, + "response_normalization": { + "status": "passed", + "fingerprint": "4e322e890025733c06a40fc308ecd101258476eb26213f55c13a3933f8ac2db5" + }, + "ok": true + }, + { + "case_id": "hf_feature_extraction-required_missing-53efc0b694f4222b", + "endpoint_family": "hf_feature_extraction", + "case_kind": "required_missing", + "attributes": [ + "inputs" + ], + "expect_accept": false, + "contract_fingerprint": "2babf49b4f14f32a12f94ddef9e73cfa6a8570d77bcbb0a7a349352e6d9c1932", + "request_fingerprint": "6e46dd10defc9b56c29a6ec56b508c21f54c08192194e4df25bf36f0c9c3c279", + "contract_validation": { + "status": "rejected_as_expected" + }, + "gateway_normalization": { + "status": "rejected_as_expected" + }, + "provider_translation": { + "status": "blocked_by_expected_rejection" + }, + "backend_execution": { + "status": "blocked_by_expected_rejection" + }, + "response_normalization": { + "status": "blocked_by_expected_rejection" + }, + "ok": true + }, + { + "case_id": "hf_feature_extraction-accepted_value_0-b734bb4e95c09bd1", + "endpoint_family": "hf_feature_extraction", + "case_kind": "accepted_value_0", + "attributes": [ + "inputs" + ], + "expect_accept": true, + "contract_fingerprint": "2babf49b4f14f32a12f94ddef9e73cfa6a8570d77bcbb0a7a349352e6d9c1932", + "request_fingerprint": "4c7201b03d613eb57bda0c18bfd56fd04a523c4dfbc7da25a930bcfc0e464426", + "contract_validation": { + "status": "passed", + "fingerprint": "4c7201b03d613eb57bda0c18bfd56fd04a523c4dfbc7da25a930bcfc0e464426" + }, + "gateway_normalization": { + "status": "passed", + "fingerprint": "4c7201b03d613eb57bda0c18bfd56fd04a523c4dfbc7da25a930bcfc0e464426" + }, + "provider_translation": { + "status": "passed", + "fingerprint": "fc287626a1c8238cd3f9e9c695df1b19029315c4c9d8433c72a78155d1b72e10" + }, + "backend_execution": { + "status": "passed", + "fingerprint": "823a5dbbef763e6111a676949abdfc24b23b0e406671c9cbb654913b768ffbf3" + }, + "backend_proof": { + "kind": "full_inference" + }, + "response_normalization": { + "status": "passed", + "fingerprint": "4e322e890025733c06a40fc308ecd101258476eb26213f55c13a3933f8ac2db5" + }, + "ok": true + }, + { + "case_id": "hf_feature_extraction-accepted_value_1-6e17f0756205c9f3", + "endpoint_family": "hf_feature_extraction", + "case_kind": "accepted_value_1", + "attributes": [ + "inputs" + ], + "expect_accept": true, + "contract_fingerprint": "2babf49b4f14f32a12f94ddef9e73cfa6a8570d77bcbb0a7a349352e6d9c1932", + "request_fingerprint": "b334e14922457c1faa718a80ba06ad4f437e4a002a3dff1bfa85215d59c9b306", + "contract_validation": { + "status": "passed", + "fingerprint": "b334e14922457c1faa718a80ba06ad4f437e4a002a3dff1bfa85215d59c9b306" + }, + "gateway_normalization": { + "status": "passed", + "fingerprint": "b334e14922457c1faa718a80ba06ad4f437e4a002a3dff1bfa85215d59c9b306" + }, + "provider_translation": { + "status": "passed", + "fingerprint": "b334e14922457c1faa718a80ba06ad4f437e4a002a3dff1bfa85215d59c9b306" + }, + "backend_execution": { + "status": "passed", + "fingerprint": "87587f2b85c5d2f9de7055c284f260c6daff180455dbd78a9f2cab34f0c2681e" + }, + "backend_proof": { + "kind": "full_inference" + }, + "response_normalization": { + "status": "passed", + "fingerprint": "7ea457e65ec1673ae109406c606400521bedd876ad4c4e4e8bb297d0af6e920d" + }, + "ok": true + }, + { + "case_id": "hf_feature_extraction-minimum_length_valid-325e3ae80f916991", + "endpoint_family": "hf_feature_extraction", + "case_kind": "minimum_length_valid", + "attributes": [ + "inputs" + ], + "expect_accept": true, + "contract_fingerprint": "2babf49b4f14f32a12f94ddef9e73cfa6a8570d77bcbb0a7a349352e6d9c1932", + "request_fingerprint": "34c0c2cd92aa8b391635820a14bf333d12b5ab299f5d97d7639bc8ba394815c7", + "contract_validation": { + "status": "passed", + "fingerprint": "34c0c2cd92aa8b391635820a14bf333d12b5ab299f5d97d7639bc8ba394815c7" + }, + "gateway_normalization": { + "status": "passed", + "fingerprint": "34c0c2cd92aa8b391635820a14bf333d12b5ab299f5d97d7639bc8ba394815c7" + }, + "provider_translation": { + "status": "passed", + "fingerprint": "4de53a94444e4d8d25d1b88f7796133d42a8c2b1c9a0e9da8c65bea4655a79eb" + }, + "backend_execution": { + "status": "passed", + "fingerprint": "2c42115928077497ebe48448d366b919d0b8d24b1aa6d361dac84b76b854ab01" + }, + "backend_proof": { + "kind": "full_inference" + }, + "response_normalization": { + "status": "passed", + "fingerprint": "49da8da941cdee80048499ac1ec801c5323ea8f08b1702ee1ba77b13807d11b2" + }, + "ok": true + }, + { + "case_id": "hf_feature_extraction-below_minimum_length-6023591978edf15b", + "endpoint_family": "hf_feature_extraction", + "case_kind": "below_minimum_length", + "attributes": [ + "inputs" + ], + "expect_accept": false, + "contract_fingerprint": "2babf49b4f14f32a12f94ddef9e73cfa6a8570d77bcbb0a7a349352e6d9c1932", + "request_fingerprint": "1e511bb46d858ae770a9f109aa6ac74bac2d094b58e30875ebe67d255dc60db6", + "contract_validation": { + "status": "rejected_as_expected" + }, + "gateway_normalization": { + "status": "rejected_as_expected" + }, + "provider_translation": { + "status": "blocked_by_expected_rejection" + }, + "backend_execution": { + "status": "blocked_by_expected_rejection" + }, + "response_normalization": { + "status": "blocked_by_expected_rejection" + }, + "ok": true + }, + { + "case_id": "hf_feature_extraction-minimum_length_valid-781f3164cb76e57c", + "endpoint_family": "hf_feature_extraction", + "case_kind": "minimum_length_valid", + "attributes": [ + "inputs" + ], + "expect_accept": true, + "contract_fingerprint": "2babf49b4f14f32a12f94ddef9e73cfa6a8570d77bcbb0a7a349352e6d9c1932", + "request_fingerprint": "3ecd80076c537dd36f25e5c88be0e999f1aa1d43ef793b97da37f20812f2811e", + "contract_validation": { + "status": "passed", + "fingerprint": "3ecd80076c537dd36f25e5c88be0e999f1aa1d43ef793b97da37f20812f2811e" + }, + "gateway_normalization": { + "status": "passed", + "fingerprint": "3ecd80076c537dd36f25e5c88be0e999f1aa1d43ef793b97da37f20812f2811e" + }, + "provider_translation": { + "status": "passed", + "fingerprint": "3ecd80076c537dd36f25e5c88be0e999f1aa1d43ef793b97da37f20812f2811e" + }, + "backend_execution": { + "status": "passed", + "fingerprint": "bd8725afa5b97d7228192ef555f203a20d0afdbada276e98221d9bf7fc37af29" + }, + "backend_proof": { + "kind": "full_inference" + }, + "response_normalization": { + "status": "passed", + "fingerprint": "ca8b7589d4db733ce8eddc194b47378eed2211d9b641b3f2ddc1b74bcccac123" + }, + "ok": true + }, + { + "case_id": "hf_feature_extraction-below_minimum_length-0fa3d9ae65b10ab6", + "endpoint_family": "hf_feature_extraction", + "case_kind": "below_minimum_length", + "attributes": [ + "inputs" + ], + "expect_accept": false, + "contract_fingerprint": "2babf49b4f14f32a12f94ddef9e73cfa6a8570d77bcbb0a7a349352e6d9c1932", + "request_fingerprint": "7342d76ec17ec76209e37f2350aa14b7340613e9b197068988bf400df734cb5d", + "contract_validation": { + "status": "rejected_as_expected" + }, + "gateway_normalization": { + "status": "rejected_as_expected" + }, + "provider_translation": { + "status": "blocked_by_expected_rejection" + }, + "backend_execution": { + "status": "blocked_by_expected_rejection" + }, + "response_normalization": { + "status": "blocked_by_expected_rejection" + }, + "ok": true + }, + { + "case_id": "hf_feature_extraction-maximum_length_valid-dc42c9f32ab4a36d", + "endpoint_family": "hf_feature_extraction", + "case_kind": "maximum_length_valid", + "attributes": [ + "inputs" + ], + "expect_accept": true, + "contract_fingerprint": "2babf49b4f14f32a12f94ddef9e73cfa6a8570d77bcbb0a7a349352e6d9c1932", + "request_fingerprint": "ce6d07f02595978242c4b29bf1274b036d078990e53992cf237ac0d5d1c2eca5", + "contract_validation": { + "status": "passed", + "fingerprint": "ce6d07f02595978242c4b29bf1274b036d078990e53992cf237ac0d5d1c2eca5" + }, + "gateway_normalization": { + "status": "passed", + "fingerprint": "ce6d07f02595978242c4b29bf1274b036d078990e53992cf237ac0d5d1c2eca5" + }, + "provider_translation": { + "status": "passed", + "fingerprint": "ce6d07f02595978242c4b29bf1274b036d078990e53992cf237ac0d5d1c2eca5" + }, + "backend_execution": { + "status": "passed", + "fingerprint": "a2fc2b5b5ac6f73962b49e0024f51ee5b0596da02ca88df244ff416bb8a084bc" + }, + "backend_proof": { + "kind": "full_inference" + }, + "response_normalization": { + "status": "passed", + "fingerprint": "e81b4ed04a6d7f4adf7cd7b0f8f4a79607150f2b492003a73b9aba3299c9d55a" + }, + "ok": true + }, + { + "case_id": "hf_feature_extraction-above_maximum_length-3c958529917ddfb9", + "endpoint_family": "hf_feature_extraction", + "case_kind": "above_maximum_length", + "attributes": [ + "inputs" + ], + "expect_accept": false, + "contract_fingerprint": "2babf49b4f14f32a12f94ddef9e73cfa6a8570d77bcbb0a7a349352e6d9c1932", + "request_fingerprint": "7a7c1e96e75e4ea1b0f5414d22e66ccd8f7c7865d87f5349c1a85d51e98a6687", + "contract_validation": { + "status": "rejected_as_expected" + }, + "gateway_normalization": { + "status": "rejected_as_expected" + }, + "provider_translation": { + "status": "blocked_by_expected_rejection" + }, + "backend_execution": { + "status": "blocked_by_expected_rejection" + }, + "response_normalization": { + "status": "blocked_by_expected_rejection" + }, + "ok": true + }, + { + "case_id": "hf_feature_extraction-wrong_type-2745fbd1f3243346", + "endpoint_family": "hf_feature_extraction", + "case_kind": "wrong_type", + "attributes": [ + "inputs" + ], + "expect_accept": false, + "contract_fingerprint": "2babf49b4f14f32a12f94ddef9e73cfa6a8570d77bcbb0a7a349352e6d9c1932", + "request_fingerprint": "17075400094ac9475b000d663c605a5db75d7cba81940be204c7456b6af8ce6a", + "contract_validation": { + "status": "rejected_as_expected" + }, + "gateway_normalization": { + "status": "rejected_as_expected" + }, + "provider_translation": { + "status": "blocked_by_expected_rejection" + }, + "backend_execution": { + "status": "blocked_by_expected_rejection" + }, + "response_normalization": { + "status": "blocked_by_expected_rejection" + }, + "ok": true + }, + { + "case_id": "hf_feature_extraction-omitted_optional-d93679b89b3a22a8", + "endpoint_family": "hf_feature_extraction", + "case_kind": "omitted_optional", + "attributes": [ + "dimensions" + ], + "expect_accept": true, + "contract_fingerprint": "2babf49b4f14f32a12f94ddef9e73cfa6a8570d77bcbb0a7a349352e6d9c1932", + "request_fingerprint": "4c7201b03d613eb57bda0c18bfd56fd04a523c4dfbc7da25a930bcfc0e464426", + "contract_validation": { + "status": "passed", + "fingerprint": "4c7201b03d613eb57bda0c18bfd56fd04a523c4dfbc7da25a930bcfc0e464426" + }, + "gateway_normalization": { + "status": "passed", + "fingerprint": "4c7201b03d613eb57bda0c18bfd56fd04a523c4dfbc7da25a930bcfc0e464426" + }, + "provider_translation": { + "status": "passed", + "fingerprint": "fc287626a1c8238cd3f9e9c695df1b19029315c4c9d8433c72a78155d1b72e10" + }, + "backend_execution": { + "status": "passed", + "fingerprint": "823a5dbbef763e6111a676949abdfc24b23b0e406671c9cbb654913b768ffbf3" + }, + "backend_proof": { + "kind": "full_inference" + }, + "response_normalization": { + "status": "passed", + "fingerprint": "4e322e890025733c06a40fc308ecd101258476eb26213f55c13a3933f8ac2db5" + }, + "ok": true + }, + { + "case_id": "hf_feature_extraction-accepted_value_0-5053e2f4006f30ed", + "endpoint_family": "hf_feature_extraction", + "case_kind": "accepted_value_0", + "attributes": [ + "dimensions" + ], + "expect_accept": true, + "contract_fingerprint": "2babf49b4f14f32a12f94ddef9e73cfa6a8570d77bcbb0a7a349352e6d9c1932", + "request_fingerprint": "f262e8229c163a3d1aad1b3d01f979e6234079c0fd6f472eb1c9a577cdb059ed", + "contract_validation": { + "status": "passed", + "fingerprint": "f262e8229c163a3d1aad1b3d01f979e6234079c0fd6f472eb1c9a577cdb059ed" + }, + "gateway_normalization": { + "status": "passed", + "fingerprint": "f262e8229c163a3d1aad1b3d01f979e6234079c0fd6f472eb1c9a577cdb059ed" + }, + "provider_translation": { + "status": "passed", + "fingerprint": "0337e23bcc1ef307877eb027fd14d877163490388417cd3f7c0fd363deee0975" + }, + "backend_execution": { + "status": "passed", + "fingerprint": "b34df3a634c00c3f434094b2c39d8a52673e1f592d2068ac8b5bc8498518e727" + }, + "backend_proof": { + "kind": "full_inference" + }, + "response_normalization": { + "status": "passed", + "fingerprint": "7edcaea6198d4575a619354b213010fe2dede03f1185bb38e7d48927c5f4ff61" + }, + "ok": true + }, + { + "case_id": "hf_feature_extraction-accepted_value_1-bf27083ffb987535", + "endpoint_family": "hf_feature_extraction", + "case_kind": "accepted_value_1", + "attributes": [ + "dimensions" + ], + "expect_accept": true, + "contract_fingerprint": "2babf49b4f14f32a12f94ddef9e73cfa6a8570d77bcbb0a7a349352e6d9c1932", + "request_fingerprint": "c5ad2dc583f985c44c3f25624745c881bfbcf26b5f5fed47832b3e19e8093920", + "contract_validation": { + "status": "passed", + "fingerprint": "c5ad2dc583f985c44c3f25624745c881bfbcf26b5f5fed47832b3e19e8093920" + }, + "gateway_normalization": { + "status": "passed", + "fingerprint": "c5ad2dc583f985c44c3f25624745c881bfbcf26b5f5fed47832b3e19e8093920" + }, + "provider_translation": { + "status": "passed", + "fingerprint": "84fe3f49dad6783290c089ffbf07d9a1b33e509b2ca4a27acf1b9d84d9590780" + }, + "backend_execution": { + "status": "passed", + "fingerprint": "823a5dbbef763e6111a676949abdfc24b23b0e406671c9cbb654913b768ffbf3" + }, + "backend_proof": { + "kind": "full_inference" + }, + "response_normalization": { + "status": "passed", + "fingerprint": "4e322e890025733c06a40fc308ecd101258476eb26213f55c13a3933f8ac2db5" + }, + "ok": true + }, + { + "case_id": "hf_feature_extraction-minimum_valid-40c06214d826ab61", + "endpoint_family": "hf_feature_extraction", + "case_kind": "minimum_valid", + "attributes": [ + "dimensions" + ], + "expect_accept": true, + "contract_fingerprint": "2babf49b4f14f32a12f94ddef9e73cfa6a8570d77bcbb0a7a349352e6d9c1932", + "request_fingerprint": "bc3723d91a904dc22517440e939dfdfb64a4e56005286ca5eb71f490a9b405ad", + "contract_validation": { + "status": "passed", + "fingerprint": "bc3723d91a904dc22517440e939dfdfb64a4e56005286ca5eb71f490a9b405ad" + }, + "gateway_normalization": { + "status": "passed", + "fingerprint": "bc3723d91a904dc22517440e939dfdfb64a4e56005286ca5eb71f490a9b405ad" + }, + "provider_translation": { + "status": "passed", + "fingerprint": "ccbcb828def4e4475db34e743a84439d9f0e2cc69f90f4220e061becd2788087" + }, + "backend_execution": { + "status": "passed", + "fingerprint": "1d3a13df2c4b146126e4098562c4c4fd062518e1af70de24a6cc31880d50ac67" + }, + "backend_proof": { + "kind": "full_inference" + }, + "response_normalization": { + "status": "passed", + "fingerprint": "b9560ecb11e951eb4edccc00de31dbd1dbe26925bf89150efbe4e71c44a98d40" + }, + "ok": true + }, + { + "case_id": "hf_feature_extraction-below_minimum-e8e37a378d91b053", + "endpoint_family": "hf_feature_extraction", + "case_kind": "below_minimum", + "attributes": [ + "dimensions" + ], + "expect_accept": false, + "contract_fingerprint": "2babf49b4f14f32a12f94ddef9e73cfa6a8570d77bcbb0a7a349352e6d9c1932", + "request_fingerprint": "28991276091f3c57cbb97f745f1f4304886c17772c6a62a04f2894d5773fd342", + "contract_validation": { + "status": "rejected_as_expected" + }, + "gateway_normalization": { + "status": "rejected_as_expected" + }, + "provider_translation": { + "status": "blocked_by_expected_rejection" + }, + "backend_execution": { + "status": "blocked_by_expected_rejection" + }, + "response_normalization": { + "status": "blocked_by_expected_rejection" + }, + "ok": true + }, + { + "case_id": "hf_feature_extraction-maximum_valid-737ee5f28c4a830f", + "endpoint_family": "hf_feature_extraction", + "case_kind": "maximum_valid", + "attributes": [ + "dimensions" + ], + "expect_accept": true, + "contract_fingerprint": "2babf49b4f14f32a12f94ddef9e73cfa6a8570d77bcbb0a7a349352e6d9c1932", + "request_fingerprint": "c5ad2dc583f985c44c3f25624745c881bfbcf26b5f5fed47832b3e19e8093920", + "contract_validation": { + "status": "passed", + "fingerprint": "c5ad2dc583f985c44c3f25624745c881bfbcf26b5f5fed47832b3e19e8093920" + }, + "gateway_normalization": { + "status": "passed", + "fingerprint": "c5ad2dc583f985c44c3f25624745c881bfbcf26b5f5fed47832b3e19e8093920" + }, + "provider_translation": { + "status": "passed", + "fingerprint": "84fe3f49dad6783290c089ffbf07d9a1b33e509b2ca4a27acf1b9d84d9590780" + }, + "backend_execution": { + "status": "passed", + "fingerprint": "823a5dbbef763e6111a676949abdfc24b23b0e406671c9cbb654913b768ffbf3" + }, + "backend_proof": { + "kind": "full_inference" + }, + "response_normalization": { + "status": "passed", + "fingerprint": "4e322e890025733c06a40fc308ecd101258476eb26213f55c13a3933f8ac2db5" + }, + "ok": true + }, + { + "case_id": "hf_feature_extraction-above_maximum-c2625b6cc9ec10c1", + "endpoint_family": "hf_feature_extraction", + "case_kind": "above_maximum", + "attributes": [ + "dimensions" + ], + "expect_accept": false, + "contract_fingerprint": "2babf49b4f14f32a12f94ddef9e73cfa6a8570d77bcbb0a7a349352e6d9c1932", + "request_fingerprint": "8899cad759daee8728c65a5c6be9246e0cdf59a509eabe5779a458376a7f316c", + "contract_validation": { + "status": "rejected_as_expected" + }, + "gateway_normalization": { + "status": "rejected_as_expected" + }, + "provider_translation": { + "status": "blocked_by_expected_rejection" + }, + "backend_execution": { + "status": "blocked_by_expected_rejection" + }, + "response_normalization": { + "status": "blocked_by_expected_rejection" + }, + "ok": true + }, + { + "case_id": "hf_feature_extraction-wrong_type-c883f9fbbf215e40", + "endpoint_family": "hf_feature_extraction", + "case_kind": "wrong_type", + "attributes": [ + "dimensions" + ], + "expect_accept": false, + "contract_fingerprint": "2babf49b4f14f32a12f94ddef9e73cfa6a8570d77bcbb0a7a349352e6d9c1932", + "request_fingerprint": "2c6a4afc3f8bf3ec5ad14fb62cfb2a70a7417d3e1f1c04a68611330ecda82e56", + "contract_validation": { + "status": "rejected_as_expected" + }, + "gateway_normalization": { + "status": "rejected_as_expected" + }, + "provider_translation": { + "status": "blocked_by_expected_rejection" + }, + "backend_execution": { + "status": "blocked_by_expected_rejection" + }, + "response_normalization": { + "status": "blocked_by_expected_rejection" + }, + "ok": true + }, + { + "case_id": "hf_feature_extraction-response_attribute-24f7e03a10c4342e", + "endpoint_family": "hf_feature_extraction", + "case_kind": "response_attribute", + "attributes": [ + "embeddings" + ], + "expect_accept": true, + "contract_fingerprint": "2babf49b4f14f32a12f94ddef9e73cfa6a8570d77bcbb0a7a349352e6d9c1932", + "request_fingerprint": "4c7201b03d613eb57bda0c18bfd56fd04a523c4dfbc7da25a930bcfc0e464426", + "contract_validation": { + "status": "passed", + "fingerprint": "4c7201b03d613eb57bda0c18bfd56fd04a523c4dfbc7da25a930bcfc0e464426" + }, + "gateway_normalization": { + "status": "passed", + "fingerprint": "4c7201b03d613eb57bda0c18bfd56fd04a523c4dfbc7da25a930bcfc0e464426" + }, + "provider_translation": { + "status": "passed", + "fingerprint": "fc287626a1c8238cd3f9e9c695df1b19029315c4c9d8433c72a78155d1b72e10" + }, + "backend_execution": { + "status": "passed", + "fingerprint": "823a5dbbef763e6111a676949abdfc24b23b0e406671c9cbb654913b768ffbf3" + }, + "backend_proof": { + "kind": "full_inference" + }, + "response_normalization": { + "status": "passed", + "fingerprint": "4e322e890025733c06a40fc308ecd101258476eb26213f55c13a3933f8ac2db5" + }, + "ok": true + }, + { + "case_id": "hf_feature_extraction-response_attribute-2ad8c84c30acf2e3", + "endpoint_family": "hf_feature_extraction", + "case_kind": "response_attribute", + "attributes": [ + "usage" + ], + "expect_accept": true, + "contract_fingerprint": "2babf49b4f14f32a12f94ddef9e73cfa6a8570d77bcbb0a7a349352e6d9c1932", + "request_fingerprint": "4c7201b03d613eb57bda0c18bfd56fd04a523c4dfbc7da25a930bcfc0e464426", + "contract_validation": { + "status": "passed", + "fingerprint": "4c7201b03d613eb57bda0c18bfd56fd04a523c4dfbc7da25a930bcfc0e464426" + }, + "gateway_normalization": { + "status": "passed", + "fingerprint": "4c7201b03d613eb57bda0c18bfd56fd04a523c4dfbc7da25a930bcfc0e464426" + }, + "provider_translation": { + "status": "passed", + "fingerprint": "fc287626a1c8238cd3f9e9c695df1b19029315c4c9d8433c72a78155d1b72e10" + }, + "backend_execution": { + "status": "passed", + "fingerprint": "823a5dbbef763e6111a676949abdfc24b23b0e406671c9cbb654913b768ffbf3" + }, + "backend_proof": { + "kind": "full_inference" + }, + "response_normalization": { + "status": "passed", + "fingerprint": "4e322e890025733c06a40fc308ecd101258476eb26213f55c13a3933f8ac2db5" + }, + "ok": true + }, + { + "case_id": "hf_feature_extraction-response_attribute-6471ebceab1c1c1b", + "endpoint_family": "hf_feature_extraction", + "case_kind": "response_attribute", + "attributes": [ + "mayhem" + ], + "expect_accept": true, + "contract_fingerprint": "2babf49b4f14f32a12f94ddef9e73cfa6a8570d77bcbb0a7a349352e6d9c1932", + "request_fingerprint": "4c7201b03d613eb57bda0c18bfd56fd04a523c4dfbc7da25a930bcfc0e464426", + "contract_validation": { + "status": "passed", + "fingerprint": "4c7201b03d613eb57bda0c18bfd56fd04a523c4dfbc7da25a930bcfc0e464426" + }, + "gateway_normalization": { + "status": "passed", + "fingerprint": "4c7201b03d613eb57bda0c18bfd56fd04a523c4dfbc7da25a930bcfc0e464426" + }, + "provider_translation": { + "status": "passed", + "fingerprint": "fc287626a1c8238cd3f9e9c695df1b19029315c4c9d8433c72a78155d1b72e10" + }, + "backend_execution": { + "status": "passed", + "fingerprint": "823a5dbbef763e6111a676949abdfc24b23b0e406671c9cbb654913b768ffbf3" + }, + "backend_proof": { + "kind": "full_inference" + }, + "response_normalization": { + "status": "passed", + "fingerprint": "4e322e890025733c06a40fc308ecd101258476eb26213f55c13a3933f8ac2db5" + }, + "ok": true + } + ], + "ok": true + }, + { + "endpoint_family": "openai_embeddings", + "contract_fingerprint": "b9d06534c5bd45cf06efee6f052b8dfae0c79505353860f0d85a0ff393f66701", + "matrix_fingerprint": "d91b646f0da07ef5ed36db2f6b9a1323f05d5d4566dd82cc68e400b52f108b58", + "case_count": 34, + "cases": [ + { + "case_id": "openai_embeddings-required_present-9ade4d2735cc9363", + "endpoint_family": "openai_embeddings", + "case_kind": "required_present", + "attributes": [ + "model" + ], + "expect_accept": true, + "contract_fingerprint": "b9d06534c5bd45cf06efee6f052b8dfae0c79505353860f0d85a0ff393f66701", + "request_fingerprint": "716717af868df286142132f6286649b2f3328361e40b33fb1b250ad4a8709b53", + "contract_validation": { + "status": "passed", + "fingerprint": "716717af868df286142132f6286649b2f3328361e40b33fb1b250ad4a8709b53" + }, + "gateway_normalization": { + "status": "passed", + "fingerprint": "14541778d8b935ae314e8089acf039dbcbcc2d66278eb122bfa484e5b64318fd" + }, + "provider_translation": { + "status": "passed", + "fingerprint": "fc287626a1c8238cd3f9e9c695df1b19029315c4c9d8433c72a78155d1b72e10" + }, + "backend_execution": { + "status": "passed", + "fingerprint": "823a5dbbef763e6111a676949abdfc24b23b0e406671c9cbb654913b768ffbf3" + }, + "backend_proof": { + "kind": "full_inference" + }, + "response_normalization": { + "status": "passed", + "fingerprint": "180989a58d1d9710c56f6e33c872b3868f3a03bb340a716879edffee6b008710" + }, + "ok": true + }, + { + "case_id": "openai_embeddings-required_missing-1d179af5a28a204d", + "endpoint_family": "openai_embeddings", + "case_kind": "required_missing", + "attributes": [ + "model" + ], + "expect_accept": false, + "contract_fingerprint": "b9d06534c5bd45cf06efee6f052b8dfae0c79505353860f0d85a0ff393f66701", + "request_fingerprint": "ac05c62575d141dd71c58dec4ae1f028ac6a620ef50ef0eacb48d844ce0940ed", + "contract_validation": { + "status": "rejected_as_expected" + }, + "gateway_normalization": { + "status": "rejected_as_expected" + }, + "provider_translation": { + "status": "blocked_by_expected_rejection" + }, + "backend_execution": { + "status": "blocked_by_expected_rejection" + }, + "response_normalization": { + "status": "blocked_by_expected_rejection" + }, + "ok": true + }, + { + "case_id": "openai_embeddings-accepted_value_0-84922620195cfc45", + "endpoint_family": "openai_embeddings", + "case_kind": "accepted_value_0", + "attributes": [ + "model" + ], + "expect_accept": true, + "contract_fingerprint": "b9d06534c5bd45cf06efee6f052b8dfae0c79505353860f0d85a0ff393f66701", + "request_fingerprint": "716717af868df286142132f6286649b2f3328361e40b33fb1b250ad4a8709b53", + "contract_validation": { + "status": "passed", + "fingerprint": "716717af868df286142132f6286649b2f3328361e40b33fb1b250ad4a8709b53" + }, + "gateway_normalization": { + "status": "passed", + "fingerprint": "14541778d8b935ae314e8089acf039dbcbcc2d66278eb122bfa484e5b64318fd" + }, + "provider_translation": { + "status": "passed", + "fingerprint": "fc287626a1c8238cd3f9e9c695df1b19029315c4c9d8433c72a78155d1b72e10" + }, + "backend_execution": { + "status": "passed", + "fingerprint": "823a5dbbef763e6111a676949abdfc24b23b0e406671c9cbb654913b768ffbf3" + }, + "backend_proof": { + "kind": "full_inference" + }, + "response_normalization": { + "status": "passed", + "fingerprint": "180989a58d1d9710c56f6e33c872b3868f3a03bb340a716879edffee6b008710" + }, + "ok": true + }, + { + "case_id": "openai_embeddings-wrong_type-024b3fc0e4a6b057", + "endpoint_family": "openai_embeddings", + "case_kind": "wrong_type", + "attributes": [ + "model" + ], + "expect_accept": false, + "contract_fingerprint": "b9d06534c5bd45cf06efee6f052b8dfae0c79505353860f0d85a0ff393f66701", + "request_fingerprint": "dd23ead76608efd489b7dd870e3380f2b7def8c06e7f6671556b9261a27c5089", + "contract_validation": { + "status": "rejected_as_expected" + }, + "gateway_normalization": { + "status": "rejected_as_expected" + }, + "provider_translation": { + "status": "blocked_by_expected_rejection" + }, + "backend_execution": { + "status": "blocked_by_expected_rejection" + }, + "response_normalization": { + "status": "blocked_by_expected_rejection" + }, + "ok": true + }, + { + "case_id": "openai_embeddings-invalid_enum-3b1390f0d0b9675c", + "endpoint_family": "openai_embeddings", + "case_kind": "invalid_enum", + "attributes": [ + "model" + ], + "expect_accept": false, + "contract_fingerprint": "b9d06534c5bd45cf06efee6f052b8dfae0c79505353860f0d85a0ff393f66701", + "request_fingerprint": "2ab4360f91e5227651bf5f9fb42fd6dc3a9acb508a88155635e53d73285c18c0", + "contract_validation": { + "status": "rejected_as_expected" + }, + "gateway_normalization": { + "status": "rejected_as_expected" + }, + "provider_translation": { + "status": "blocked_by_expected_rejection" + }, + "backend_execution": { + "status": "blocked_by_expected_rejection" + }, + "response_normalization": { + "status": "blocked_by_expected_rejection" + }, + "ok": true + }, + { + "case_id": "openai_embeddings-required_present-50aca2ef04fa4653", + "endpoint_family": "openai_embeddings", + "case_kind": "required_present", + "attributes": [ + "input" + ], + "expect_accept": true, + "contract_fingerprint": "b9d06534c5bd45cf06efee6f052b8dfae0c79505353860f0d85a0ff393f66701", + "request_fingerprint": "716717af868df286142132f6286649b2f3328361e40b33fb1b250ad4a8709b53", + "contract_validation": { + "status": "passed", + "fingerprint": "716717af868df286142132f6286649b2f3328361e40b33fb1b250ad4a8709b53" + }, + "gateway_normalization": { + "status": "passed", + "fingerprint": "14541778d8b935ae314e8089acf039dbcbcc2d66278eb122bfa484e5b64318fd" + }, + "provider_translation": { + "status": "passed", + "fingerprint": "fc287626a1c8238cd3f9e9c695df1b19029315c4c9d8433c72a78155d1b72e10" + }, + "backend_execution": { + "status": "passed", + "fingerprint": "823a5dbbef763e6111a676949abdfc24b23b0e406671c9cbb654913b768ffbf3" + }, + "backend_proof": { + "kind": "full_inference" + }, + "response_normalization": { + "status": "passed", + "fingerprint": "180989a58d1d9710c56f6e33c872b3868f3a03bb340a716879edffee6b008710" + }, + "ok": true + }, + { + "case_id": "openai_embeddings-required_missing-0feae2c4b13e3e9f", + "endpoint_family": "openai_embeddings", + "case_kind": "required_missing", + "attributes": [ + "input" + ], + "expect_accept": false, + "contract_fingerprint": "b9d06534c5bd45cf06efee6f052b8dfae0c79505353860f0d85a0ff393f66701", + "request_fingerprint": "b3c36bc8d2e7b8ed80c201769bdb9b4fb8e92640eefdafb103109777b288ec9c", + "contract_validation": { + "status": "rejected_as_expected" + }, + "gateway_normalization": { + "status": "rejected_as_expected" + }, + "provider_translation": { + "status": "blocked_by_expected_rejection" + }, + "backend_execution": { + "status": "blocked_by_expected_rejection" + }, + "response_normalization": { + "status": "blocked_by_expected_rejection" + }, + "ok": true + }, + { + "case_id": "openai_embeddings-accepted_value_0-0e54ad8443d7ab9f", + "endpoint_family": "openai_embeddings", + "case_kind": "accepted_value_0", + "attributes": [ + "input" + ], + "expect_accept": true, + "contract_fingerprint": "b9d06534c5bd45cf06efee6f052b8dfae0c79505353860f0d85a0ff393f66701", + "request_fingerprint": "716717af868df286142132f6286649b2f3328361e40b33fb1b250ad4a8709b53", + "contract_validation": { + "status": "passed", + "fingerprint": "716717af868df286142132f6286649b2f3328361e40b33fb1b250ad4a8709b53" + }, + "gateway_normalization": { + "status": "passed", + "fingerprint": "14541778d8b935ae314e8089acf039dbcbcc2d66278eb122bfa484e5b64318fd" + }, + "provider_translation": { + "status": "passed", + "fingerprint": "fc287626a1c8238cd3f9e9c695df1b19029315c4c9d8433c72a78155d1b72e10" + }, + "backend_execution": { + "status": "passed", + "fingerprint": "823a5dbbef763e6111a676949abdfc24b23b0e406671c9cbb654913b768ffbf3" + }, + "backend_proof": { + "kind": "full_inference" + }, + "response_normalization": { + "status": "passed", + "fingerprint": "180989a58d1d9710c56f6e33c872b3868f3a03bb340a716879edffee6b008710" + }, + "ok": true + }, + { + "case_id": "openai_embeddings-accepted_value_1-f9987dce39207575", + "endpoint_family": "openai_embeddings", + "case_kind": "accepted_value_1", + "attributes": [ + "input" + ], + "expect_accept": true, + "contract_fingerprint": "b9d06534c5bd45cf06efee6f052b8dfae0c79505353860f0d85a0ff393f66701", + "request_fingerprint": "8687bc7d78e34bbb8a799918e6005cbaf420e95b483db63987662917f5e7d6e4", + "contract_validation": { + "status": "passed", + "fingerprint": "8687bc7d78e34bbb8a799918e6005cbaf420e95b483db63987662917f5e7d6e4" + }, + "gateway_normalization": { + "status": "passed", + "fingerprint": "e64567c0efb3189ef41616b1b978b41c3f08de08bd3288c6156de49e3daa0265" + }, + "provider_translation": { + "status": "passed", + "fingerprint": "b334e14922457c1faa718a80ba06ad4f437e4a002a3dff1bfa85215d59c9b306" + }, + "backend_execution": { + "status": "passed", + "fingerprint": "87587f2b85c5d2f9de7055c284f260c6daff180455dbd78a9f2cab34f0c2681e" + }, + "backend_proof": { + "kind": "full_inference" + }, + "response_normalization": { + "status": "passed", + "fingerprint": "3328d5c0220fdfb9589a84686089974d166763bf79294aea4bde40d68d521fc3" + }, + "ok": true + }, + { + "case_id": "openai_embeddings-minimum_length_valid-5cc7b439d3b1d765", + "endpoint_family": "openai_embeddings", + "case_kind": "minimum_length_valid", + "attributes": [ + "input" + ], + "expect_accept": true, + "contract_fingerprint": "b9d06534c5bd45cf06efee6f052b8dfae0c79505353860f0d85a0ff393f66701", + "request_fingerprint": "265d901480e67bd7d305dd1c53bbb6cfc2182a0e625268f4b839a4839f34dc6c", + "contract_validation": { + "status": "passed", + "fingerprint": "265d901480e67bd7d305dd1c53bbb6cfc2182a0e625268f4b839a4839f34dc6c" + }, + "gateway_normalization": { + "status": "passed", + "fingerprint": "e1f0afa3e7bba167eb3364bb23f096cc1b263e241356577ec3378ee4e100ddcf" + }, + "provider_translation": { + "status": "passed", + "fingerprint": "4de53a94444e4d8d25d1b88f7796133d42a8c2b1c9a0e9da8c65bea4655a79eb" + }, + "backend_execution": { + "status": "passed", + "fingerprint": "2c42115928077497ebe48448d366b919d0b8d24b1aa6d361dac84b76b854ab01" + }, + "backend_proof": { + "kind": "full_inference" + }, + "response_normalization": { + "status": "passed", + "fingerprint": "6cbb3f9b537b88e17dacae3bef8ca028422b90d5b22a72fa0501db93de0e7bc3" + }, + "ok": true + }, + { + "case_id": "openai_embeddings-below_minimum_length-a799b372920444b6", + "endpoint_family": "openai_embeddings", + "case_kind": "below_minimum_length", + "attributes": [ + "input" + ], + "expect_accept": false, + "contract_fingerprint": "b9d06534c5bd45cf06efee6f052b8dfae0c79505353860f0d85a0ff393f66701", + "request_fingerprint": "5f2ef12a5104f1fde96e76f12f4f548d1b32dbd841124d08620f52cc44517e8f", + "contract_validation": { + "status": "rejected_as_expected" + }, + "gateway_normalization": { + "status": "rejected_as_expected" + }, + "provider_translation": { + "status": "blocked_by_expected_rejection" + }, + "backend_execution": { + "status": "blocked_by_expected_rejection" + }, + "response_normalization": { + "status": "blocked_by_expected_rejection" + }, + "ok": true + }, + { + "case_id": "openai_embeddings-minimum_length_valid-6324cd2fce201dfd", + "endpoint_family": "openai_embeddings", + "case_kind": "minimum_length_valid", + "attributes": [ + "input" + ], + "expect_accept": true, + "contract_fingerprint": "b9d06534c5bd45cf06efee6f052b8dfae0c79505353860f0d85a0ff393f66701", + "request_fingerprint": "9ebaa12b9c2478c38a292c9bf716495b28843e727229e1790334263fa6d36622", + "contract_validation": { + "status": "passed", + "fingerprint": "9ebaa12b9c2478c38a292c9bf716495b28843e727229e1790334263fa6d36622" + }, + "gateway_normalization": { + "status": "passed", + "fingerprint": "76fd360b44b3b4872d159707693ea64f000cf5561d21dfb24a6e2760df5d05ea" + }, + "provider_translation": { + "status": "passed", + "fingerprint": "3ecd80076c537dd36f25e5c88be0e999f1aa1d43ef793b97da37f20812f2811e" + }, + "backend_execution": { + "status": "passed", + "fingerprint": "bd8725afa5b97d7228192ef555f203a20d0afdbada276e98221d9bf7fc37af29" + }, + "backend_proof": { + "kind": "full_inference" + }, + "response_normalization": { + "status": "passed", + "fingerprint": "db94d2df79132d45ebfa092cb273dd617fb40672e457aa3bc98bf3a3b55890c0" + }, + "ok": true + }, + { + "case_id": "openai_embeddings-below_minimum_length-7771ef45601ba87b", + "endpoint_family": "openai_embeddings", + "case_kind": "below_minimum_length", + "attributes": [ + "input" + ], + "expect_accept": false, + "contract_fingerprint": "b9d06534c5bd45cf06efee6f052b8dfae0c79505353860f0d85a0ff393f66701", + "request_fingerprint": "3375cef67970e094bb91ca9908d106403e85ac40c78cfbf5c936d4fa6171d00f", + "contract_validation": { + "status": "rejected_as_expected" + }, + "gateway_normalization": { + "status": "rejected_as_expected" + }, + "provider_translation": { + "status": "blocked_by_expected_rejection" + }, + "backend_execution": { + "status": "blocked_by_expected_rejection" + }, + "response_normalization": { + "status": "blocked_by_expected_rejection" + }, + "ok": true + }, + { + "case_id": "openai_embeddings-maximum_length_valid-bff51675bbfefe1d", + "endpoint_family": "openai_embeddings", + "case_kind": "maximum_length_valid", + "attributes": [ + "input" + ], + "expect_accept": true, + "contract_fingerprint": "b9d06534c5bd45cf06efee6f052b8dfae0c79505353860f0d85a0ff393f66701", + "request_fingerprint": "cbc00f7b74633318e3fcd36f564206177fc2c406c067c45f33f32a4205096e95", + "contract_validation": { + "status": "passed", + "fingerprint": "cbc00f7b74633318e3fcd36f564206177fc2c406c067c45f33f32a4205096e95" + }, + "gateway_normalization": { + "status": "passed", + "fingerprint": "3e4f5df80724c5131c09c146affeee1f07066f9a1098fb471358cde1cefb35ab" + }, + "provider_translation": { + "status": "passed", + "fingerprint": "ce6d07f02595978242c4b29bf1274b036d078990e53992cf237ac0d5d1c2eca5" + }, + "backend_execution": { + "status": "passed", + "fingerprint": "a2fc2b5b5ac6f73962b49e0024f51ee5b0596da02ca88df244ff416bb8a084bc" + }, + "backend_proof": { + "kind": "full_inference" + }, + "response_normalization": { + "status": "passed", + "fingerprint": "1c64c4496c4feaa17295691aaa4f1cea538829fdc3916b7ecd694bd622f26d8a" + }, + "ok": true + }, + { + "case_id": "openai_embeddings-above_maximum_length-39da210d24e2428f", + "endpoint_family": "openai_embeddings", + "case_kind": "above_maximum_length", + "attributes": [ + "input" + ], + "expect_accept": false, + "contract_fingerprint": "b9d06534c5bd45cf06efee6f052b8dfae0c79505353860f0d85a0ff393f66701", + "request_fingerprint": "15b37d9e04f555f805f0f571534f7314294db9f1c0b1cf1e14e8d7a0d6c9a33f", + "contract_validation": { + "status": "rejected_as_expected" + }, + "gateway_normalization": { + "status": "rejected_as_expected" + }, + "provider_translation": { + "status": "blocked_by_expected_rejection" + }, + "backend_execution": { + "status": "blocked_by_expected_rejection" + }, + "response_normalization": { + "status": "blocked_by_expected_rejection" + }, + "ok": true + }, + { + "case_id": "openai_embeddings-wrong_type-5865901f5212eec9", + "endpoint_family": "openai_embeddings", + "case_kind": "wrong_type", + "attributes": [ + "input" + ], + "expect_accept": false, + "contract_fingerprint": "b9d06534c5bd45cf06efee6f052b8dfae0c79505353860f0d85a0ff393f66701", + "request_fingerprint": "4447eda3ab7da747488d3380cb49faa2455ea3c64382c406bc8cfa5dbcf2a634", + "contract_validation": { + "status": "rejected_as_expected" + }, + "gateway_normalization": { + "status": "rejected_as_expected" + }, + "provider_translation": { + "status": "blocked_by_expected_rejection" + }, + "backend_execution": { + "status": "blocked_by_expected_rejection" + }, + "response_normalization": { + "status": "blocked_by_expected_rejection" + }, + "ok": true + }, + { + "case_id": "openai_embeddings-omitted_default-c1c31ae747bb54be", + "endpoint_family": "openai_embeddings", + "case_kind": "omitted_default", + "attributes": [ + "encoding_format" + ], + "expect_accept": true, + "contract_fingerprint": "b9d06534c5bd45cf06efee6f052b8dfae0c79505353860f0d85a0ff393f66701", + "request_fingerprint": "716717af868df286142132f6286649b2f3328361e40b33fb1b250ad4a8709b53", + "contract_validation": { + "status": "passed", + "fingerprint": "716717af868df286142132f6286649b2f3328361e40b33fb1b250ad4a8709b53" + }, + "gateway_normalization": { + "status": "passed", + "fingerprint": "14541778d8b935ae314e8089acf039dbcbcc2d66278eb122bfa484e5b64318fd" + }, + "provider_translation": { + "status": "passed", + "fingerprint": "fc287626a1c8238cd3f9e9c695df1b19029315c4c9d8433c72a78155d1b72e10" + }, + "backend_execution": { + "status": "passed", + "fingerprint": "823a5dbbef763e6111a676949abdfc24b23b0e406671c9cbb654913b768ffbf3" + }, + "backend_proof": { + "kind": "full_inference" + }, + "response_normalization": { + "status": "passed", + "fingerprint": "180989a58d1d9710c56f6e33c872b3868f3a03bb340a716879edffee6b008710" + }, + "ok": true + }, + { + "case_id": "openai_embeddings-accepted_value_0-0e3b50a240375961", + "endpoint_family": "openai_embeddings", + "case_kind": "accepted_value_0", + "attributes": [ + "encoding_format" + ], + "expect_accept": true, + "contract_fingerprint": "b9d06534c5bd45cf06efee6f052b8dfae0c79505353860f0d85a0ff393f66701", + "request_fingerprint": "14541778d8b935ae314e8089acf039dbcbcc2d66278eb122bfa484e5b64318fd", + "contract_validation": { + "status": "passed", + "fingerprint": "14541778d8b935ae314e8089acf039dbcbcc2d66278eb122bfa484e5b64318fd" + }, + "gateway_normalization": { + "status": "passed", + "fingerprint": "14541778d8b935ae314e8089acf039dbcbcc2d66278eb122bfa484e5b64318fd" + }, + "provider_translation": { + "status": "passed", + "fingerprint": "fc287626a1c8238cd3f9e9c695df1b19029315c4c9d8433c72a78155d1b72e10" + }, + "backend_execution": { + "status": "passed", + "fingerprint": "823a5dbbef763e6111a676949abdfc24b23b0e406671c9cbb654913b768ffbf3" + }, + "backend_proof": { + "kind": "full_inference" + }, + "response_normalization": { + "status": "passed", + "fingerprint": "180989a58d1d9710c56f6e33c872b3868f3a03bb340a716879edffee6b008710" + }, + "ok": true + }, + { + "case_id": "openai_embeddings-accepted_value_1-e635971eabe7970e", + "endpoint_family": "openai_embeddings", + "case_kind": "accepted_value_1", + "attributes": [ + "encoding_format" + ], + "expect_accept": true, + "contract_fingerprint": "b9d06534c5bd45cf06efee6f052b8dfae0c79505353860f0d85a0ff393f66701", + "request_fingerprint": "8c25bea925c596f553bca5c055630183807b79d54c1392f61b06df7ddc71f285", + "contract_validation": { + "status": "passed", + "fingerprint": "8c25bea925c596f553bca5c055630183807b79d54c1392f61b06df7ddc71f285" + }, + "gateway_normalization": { + "status": "passed", + "fingerprint": "8c25bea925c596f553bca5c055630183807b79d54c1392f61b06df7ddc71f285" + }, + "provider_translation": { + "status": "passed", + "fingerprint": "fc287626a1c8238cd3f9e9c695df1b19029315c4c9d8433c72a78155d1b72e10" + }, + "backend_execution": { + "status": "passed", + "fingerprint": "823a5dbbef763e6111a676949abdfc24b23b0e406671c9cbb654913b768ffbf3" + }, + "backend_proof": { + "kind": "full_inference" + }, + "response_normalization": { + "status": "passed", + "fingerprint": "180989a58d1d9710c56f6e33c872b3868f3a03bb340a716879edffee6b008710" + }, + "ok": true + }, + { + "case_id": "openai_embeddings-wrong_type-a34557af58493f22", + "endpoint_family": "openai_embeddings", + "case_kind": "wrong_type", + "attributes": [ + "encoding_format" + ], + "expect_accept": false, + "contract_fingerprint": "b9d06534c5bd45cf06efee6f052b8dfae0c79505353860f0d85a0ff393f66701", + "request_fingerprint": "dbd6a5683d1bd91f51666891427945ff3d4479c58e4c0728cefccbba3ae3f515", + "contract_validation": { + "status": "rejected_as_expected" + }, + "gateway_normalization": { + "status": "rejected_as_expected" + }, + "provider_translation": { + "status": "blocked_by_expected_rejection" + }, + "backend_execution": { + "status": "blocked_by_expected_rejection" + }, + "response_normalization": { + "status": "blocked_by_expected_rejection" + }, + "ok": true + }, + { + "case_id": "openai_embeddings-invalid_enum-cf317adad3a1e1b2", + "endpoint_family": "openai_embeddings", + "case_kind": "invalid_enum", + "attributes": [ + "encoding_format" + ], + "expect_accept": false, + "contract_fingerprint": "b9d06534c5bd45cf06efee6f052b8dfae0c79505353860f0d85a0ff393f66701", + "request_fingerprint": "cd8861edfeef421084542e6aac849c953e25af98f171bbcbc59f15c36e50d5aa", + "contract_validation": { + "status": "rejected_as_expected" + }, + "gateway_normalization": { + "status": "rejected_as_expected" + }, + "provider_translation": { + "status": "blocked_by_expected_rejection" + }, + "backend_execution": { + "status": "blocked_by_expected_rejection" + }, + "response_normalization": { + "status": "blocked_by_expected_rejection" + }, + "ok": true + }, + { + "case_id": "openai_embeddings-omitted_optional-754bb9f09254f53c", + "endpoint_family": "openai_embeddings", + "case_kind": "omitted_optional", + "attributes": [ + "dimensions" + ], + "expect_accept": true, + "contract_fingerprint": "b9d06534c5bd45cf06efee6f052b8dfae0c79505353860f0d85a0ff393f66701", + "request_fingerprint": "716717af868df286142132f6286649b2f3328361e40b33fb1b250ad4a8709b53", + "contract_validation": { + "status": "passed", + "fingerprint": "716717af868df286142132f6286649b2f3328361e40b33fb1b250ad4a8709b53" + }, + "gateway_normalization": { + "status": "passed", + "fingerprint": "14541778d8b935ae314e8089acf039dbcbcc2d66278eb122bfa484e5b64318fd" + }, + "provider_translation": { + "status": "passed", + "fingerprint": "fc287626a1c8238cd3f9e9c695df1b19029315c4c9d8433c72a78155d1b72e10" + }, + "backend_execution": { + "status": "passed", + "fingerprint": "823a5dbbef763e6111a676949abdfc24b23b0e406671c9cbb654913b768ffbf3" + }, + "backend_proof": { + "kind": "full_inference" + }, + "response_normalization": { + "status": "passed", + "fingerprint": "180989a58d1d9710c56f6e33c872b3868f3a03bb340a716879edffee6b008710" + }, + "ok": true + }, + { + "case_id": "openai_embeddings-accepted_value_0-52c4947357cb0f6a", + "endpoint_family": "openai_embeddings", + "case_kind": "accepted_value_0", + "attributes": [ + "dimensions" + ], + "expect_accept": true, + "contract_fingerprint": "b9d06534c5bd45cf06efee6f052b8dfae0c79505353860f0d85a0ff393f66701", + "request_fingerprint": "efd2ae2e588cd600953bc2a526af7b909c31f29cd12e97eaa10a874dfce94730", + "contract_validation": { + "status": "passed", + "fingerprint": "efd2ae2e588cd600953bc2a526af7b909c31f29cd12e97eaa10a874dfce94730" + }, + "gateway_normalization": { + "status": "passed", + "fingerprint": "ce3af7920109b98b28f89aefc8cd7d3c06e9938e389196ade2a0e44a1bcafab3" + }, + "provider_translation": { + "status": "passed", + "fingerprint": "0337e23bcc1ef307877eb027fd14d877163490388417cd3f7c0fd363deee0975" + }, + "backend_execution": { + "status": "passed", + "fingerprint": "b34df3a634c00c3f434094b2c39d8a52673e1f592d2068ac8b5bc8498518e727" + }, + "backend_proof": { + "kind": "full_inference" + }, + "response_normalization": { + "status": "passed", + "fingerprint": "283c493db0476391323e7a29f84b8a466ab7e6623abe5eaa454559cc29078120" + }, + "ok": true + }, + { + "case_id": "openai_embeddings-accepted_value_1-64d07199b8fffe59", + "endpoint_family": "openai_embeddings", + "case_kind": "accepted_value_1", + "attributes": [ + "dimensions" + ], + "expect_accept": true, + "contract_fingerprint": "b9d06534c5bd45cf06efee6f052b8dfae0c79505353860f0d85a0ff393f66701", + "request_fingerprint": "905ee5b531123ba3ce8754a06618d76bed3af057f673b989af5187ed5922eb55", + "contract_validation": { + "status": "passed", + "fingerprint": "905ee5b531123ba3ce8754a06618d76bed3af057f673b989af5187ed5922eb55" + }, + "gateway_normalization": { + "status": "passed", + "fingerprint": "8bdd8a3fb282ad77bda5f95286738d1ea16fa796bbcf4ae1f2512257b1298740" + }, + "provider_translation": { + "status": "passed", + "fingerprint": "84fe3f49dad6783290c089ffbf07d9a1b33e509b2ca4a27acf1b9d84d9590780" + }, + "backend_execution": { + "status": "passed", + "fingerprint": "823a5dbbef763e6111a676949abdfc24b23b0e406671c9cbb654913b768ffbf3" + }, + "backend_proof": { + "kind": "full_inference" + }, + "response_normalization": { + "status": "passed", + "fingerprint": "180989a58d1d9710c56f6e33c872b3868f3a03bb340a716879edffee6b008710" + }, + "ok": true + }, + { + "case_id": "openai_embeddings-minimum_valid-90c1d1f0a5ce9ceb", + "endpoint_family": "openai_embeddings", + "case_kind": "minimum_valid", + "attributes": [ + "dimensions" + ], + "expect_accept": true, + "contract_fingerprint": "b9d06534c5bd45cf06efee6f052b8dfae0c79505353860f0d85a0ff393f66701", + "request_fingerprint": "d5567dd2ac895ebd1198a67fdfdb9bf8a823f4b3d54304baebb1906535daa388", + "contract_validation": { + "status": "passed", + "fingerprint": "d5567dd2ac895ebd1198a67fdfdb9bf8a823f4b3d54304baebb1906535daa388" + }, + "gateway_normalization": { + "status": "passed", + "fingerprint": "89da1cc6b1e1d66316928868a252a9b0bac8401901866c6b084548dcc0ea0b1d" + }, + "provider_translation": { + "status": "passed", + "fingerprint": "ccbcb828def4e4475db34e743a84439d9f0e2cc69f90f4220e061becd2788087" + }, + "backend_execution": { + "status": "passed", + "fingerprint": "1d3a13df2c4b146126e4098562c4c4fd062518e1af70de24a6cc31880d50ac67" + }, + "backend_proof": { + "kind": "full_inference" + }, + "response_normalization": { + "status": "passed", + "fingerprint": "95a23ed1eae4f226d81a8c3b5006fb0006c923f324f139e01480b7260b29b11f" + }, + "ok": true + }, + { + "case_id": "openai_embeddings-below_minimum-49a0030972a81cee", + "endpoint_family": "openai_embeddings", + "case_kind": "below_minimum", + "attributes": [ + "dimensions" + ], + "expect_accept": false, + "contract_fingerprint": "b9d06534c5bd45cf06efee6f052b8dfae0c79505353860f0d85a0ff393f66701", + "request_fingerprint": "98f8a7f97ff7c8cd1d021f737d5fcd0769b9bf636778b28e07e2395f4d535796", + "contract_validation": { + "status": "rejected_as_expected" + }, + "gateway_normalization": { + "status": "rejected_as_expected" + }, + "provider_translation": { + "status": "blocked_by_expected_rejection" + }, + "backend_execution": { + "status": "blocked_by_expected_rejection" + }, + "response_normalization": { + "status": "blocked_by_expected_rejection" + }, + "ok": true + }, + { + "case_id": "openai_embeddings-maximum_valid-1f690a88d718ec5b", + "endpoint_family": "openai_embeddings", + "case_kind": "maximum_valid", + "attributes": [ + "dimensions" + ], + "expect_accept": true, + "contract_fingerprint": "b9d06534c5bd45cf06efee6f052b8dfae0c79505353860f0d85a0ff393f66701", + "request_fingerprint": "905ee5b531123ba3ce8754a06618d76bed3af057f673b989af5187ed5922eb55", + "contract_validation": { + "status": "passed", + "fingerprint": "905ee5b531123ba3ce8754a06618d76bed3af057f673b989af5187ed5922eb55" + }, + "gateway_normalization": { + "status": "passed", + "fingerprint": "8bdd8a3fb282ad77bda5f95286738d1ea16fa796bbcf4ae1f2512257b1298740" + }, + "provider_translation": { + "status": "passed", + "fingerprint": "84fe3f49dad6783290c089ffbf07d9a1b33e509b2ca4a27acf1b9d84d9590780" + }, + "backend_execution": { + "status": "passed", + "fingerprint": "823a5dbbef763e6111a676949abdfc24b23b0e406671c9cbb654913b768ffbf3" + }, + "backend_proof": { + "kind": "full_inference" + }, + "response_normalization": { + "status": "passed", + "fingerprint": "180989a58d1d9710c56f6e33c872b3868f3a03bb340a716879edffee6b008710" + }, + "ok": true + }, + { + "case_id": "openai_embeddings-above_maximum-8abb7073b7cc2f9f", + "endpoint_family": "openai_embeddings", + "case_kind": "above_maximum", + "attributes": [ + "dimensions" + ], + "expect_accept": false, + "contract_fingerprint": "b9d06534c5bd45cf06efee6f052b8dfae0c79505353860f0d85a0ff393f66701", + "request_fingerprint": "34381e15e2603e1cabbe5f148e6ae7abf1d65c0a1345d454cac3fe916ac3aa76", + "contract_validation": { + "status": "rejected_as_expected" + }, + "gateway_normalization": { + "status": "rejected_as_expected" + }, + "provider_translation": { + "status": "blocked_by_expected_rejection" + }, + "backend_execution": { + "status": "blocked_by_expected_rejection" + }, + "response_normalization": { + "status": "blocked_by_expected_rejection" + }, + "ok": true + }, + { + "case_id": "openai_embeddings-wrong_type-b97fe9ed73bfd695", + "endpoint_family": "openai_embeddings", + "case_kind": "wrong_type", + "attributes": [ + "dimensions" + ], + "expect_accept": false, + "contract_fingerprint": "b9d06534c5bd45cf06efee6f052b8dfae0c79505353860f0d85a0ff393f66701", + "request_fingerprint": "08a5e10e1072c7c7e9c670092cf81460efc862e5643485b48ee53648c81c8eac", + "contract_validation": { + "status": "rejected_as_expected" + }, + "gateway_normalization": { + "status": "rejected_as_expected" + }, + "provider_translation": { + "status": "blocked_by_expected_rejection" + }, + "backend_execution": { + "status": "blocked_by_expected_rejection" + }, + "response_normalization": { + "status": "blocked_by_expected_rejection" + }, + "ok": true + }, + { + "case_id": "openai_embeddings-response_attribute-74f482379add26f1", + "endpoint_family": "openai_embeddings", + "case_kind": "response_attribute", + "attributes": [ + "object" + ], + "expect_accept": true, + "contract_fingerprint": "b9d06534c5bd45cf06efee6f052b8dfae0c79505353860f0d85a0ff393f66701", + "request_fingerprint": "716717af868df286142132f6286649b2f3328361e40b33fb1b250ad4a8709b53", + "contract_validation": { + "status": "passed", + "fingerprint": "716717af868df286142132f6286649b2f3328361e40b33fb1b250ad4a8709b53" + }, + "gateway_normalization": { + "status": "passed", + "fingerprint": "14541778d8b935ae314e8089acf039dbcbcc2d66278eb122bfa484e5b64318fd" + }, + "provider_translation": { + "status": "passed", + "fingerprint": "fc287626a1c8238cd3f9e9c695df1b19029315c4c9d8433c72a78155d1b72e10" + }, + "backend_execution": { + "status": "passed", + "fingerprint": "823a5dbbef763e6111a676949abdfc24b23b0e406671c9cbb654913b768ffbf3" + }, + "backend_proof": { + "kind": "full_inference" + }, + "response_normalization": { + "status": "passed", + "fingerprint": "180989a58d1d9710c56f6e33c872b3868f3a03bb340a716879edffee6b008710" + }, + "ok": true + }, + { + "case_id": "openai_embeddings-response_attribute-fe72c4ba315b1fa6", + "endpoint_family": "openai_embeddings", + "case_kind": "response_attribute", + "attributes": [ + "data" + ], + "expect_accept": true, + "contract_fingerprint": "b9d06534c5bd45cf06efee6f052b8dfae0c79505353860f0d85a0ff393f66701", + "request_fingerprint": "716717af868df286142132f6286649b2f3328361e40b33fb1b250ad4a8709b53", + "contract_validation": { + "status": "passed", + "fingerprint": "716717af868df286142132f6286649b2f3328361e40b33fb1b250ad4a8709b53" + }, + "gateway_normalization": { + "status": "passed", + "fingerprint": "14541778d8b935ae314e8089acf039dbcbcc2d66278eb122bfa484e5b64318fd" + }, + "provider_translation": { + "status": "passed", + "fingerprint": "fc287626a1c8238cd3f9e9c695df1b19029315c4c9d8433c72a78155d1b72e10" + }, + "backend_execution": { + "status": "passed", + "fingerprint": "823a5dbbef763e6111a676949abdfc24b23b0e406671c9cbb654913b768ffbf3" + }, + "backend_proof": { + "kind": "full_inference" + }, + "response_normalization": { + "status": "passed", + "fingerprint": "180989a58d1d9710c56f6e33c872b3868f3a03bb340a716879edffee6b008710" + }, + "ok": true + }, + { + "case_id": "openai_embeddings-response_attribute-c0fd6de8ae61a9bd", + "endpoint_family": "openai_embeddings", + "case_kind": "response_attribute", + "attributes": [ + "model" + ], + "expect_accept": true, + "contract_fingerprint": "b9d06534c5bd45cf06efee6f052b8dfae0c79505353860f0d85a0ff393f66701", + "request_fingerprint": "716717af868df286142132f6286649b2f3328361e40b33fb1b250ad4a8709b53", + "contract_validation": { + "status": "passed", + "fingerprint": "716717af868df286142132f6286649b2f3328361e40b33fb1b250ad4a8709b53" + }, + "gateway_normalization": { + "status": "passed", + "fingerprint": "14541778d8b935ae314e8089acf039dbcbcc2d66278eb122bfa484e5b64318fd" + }, + "provider_translation": { + "status": "passed", + "fingerprint": "fc287626a1c8238cd3f9e9c695df1b19029315c4c9d8433c72a78155d1b72e10" + }, + "backend_execution": { + "status": "passed", + "fingerprint": "823a5dbbef763e6111a676949abdfc24b23b0e406671c9cbb654913b768ffbf3" + }, + "backend_proof": { + "kind": "full_inference" + }, + "response_normalization": { + "status": "passed", + "fingerprint": "180989a58d1d9710c56f6e33c872b3868f3a03bb340a716879edffee6b008710" + }, + "ok": true + }, + { + "case_id": "openai_embeddings-response_attribute-0213b4877f241fff", + "endpoint_family": "openai_embeddings", + "case_kind": "response_attribute", + "attributes": [ + "usage" + ], + "expect_accept": true, + "contract_fingerprint": "b9d06534c5bd45cf06efee6f052b8dfae0c79505353860f0d85a0ff393f66701", + "request_fingerprint": "716717af868df286142132f6286649b2f3328361e40b33fb1b250ad4a8709b53", + "contract_validation": { + "status": "passed", + "fingerprint": "716717af868df286142132f6286649b2f3328361e40b33fb1b250ad4a8709b53" + }, + "gateway_normalization": { + "status": "passed", + "fingerprint": "14541778d8b935ae314e8089acf039dbcbcc2d66278eb122bfa484e5b64318fd" + }, + "provider_translation": { + "status": "passed", + "fingerprint": "fc287626a1c8238cd3f9e9c695df1b19029315c4c9d8433c72a78155d1b72e10" + }, + "backend_execution": { + "status": "passed", + "fingerprint": "823a5dbbef763e6111a676949abdfc24b23b0e406671c9cbb654913b768ffbf3" + }, + "backend_proof": { + "kind": "full_inference" + }, + "response_normalization": { + "status": "passed", + "fingerprint": "180989a58d1d9710c56f6e33c872b3868f3a03bb340a716879edffee6b008710" + }, + "ok": true + }, + { + "case_id": "openai_embeddings-response_attribute-2a87081ebd50b981", + "endpoint_family": "openai_embeddings", + "case_kind": "response_attribute", + "attributes": [ + "mayhem" + ], + "expect_accept": true, + "contract_fingerprint": "b9d06534c5bd45cf06efee6f052b8dfae0c79505353860f0d85a0ff393f66701", + "request_fingerprint": "716717af868df286142132f6286649b2f3328361e40b33fb1b250ad4a8709b53", + "contract_validation": { + "status": "passed", + "fingerprint": "716717af868df286142132f6286649b2f3328361e40b33fb1b250ad4a8709b53" + }, + "gateway_normalization": { + "status": "passed", + "fingerprint": "14541778d8b935ae314e8089acf039dbcbcc2d66278eb122bfa484e5b64318fd" + }, + "provider_translation": { + "status": "passed", + "fingerprint": "fc287626a1c8238cd3f9e9c695df1b19029315c4c9d8433c72a78155d1b72e10" + }, + "backend_execution": { + "status": "passed", + "fingerprint": "823a5dbbef763e6111a676949abdfc24b23b0e406671c9cbb654913b768ffbf3" + }, + "backend_proof": { + "kind": "full_inference" + }, + "response_normalization": { + "status": "passed", + "fingerprint": "180989a58d1d9710c56f6e33c872b3868f3a03bb340a716879edffee6b008710" + }, + "ok": true + } + ], + "ok": true + } + ], + "ok": true + }, + "existing_catalog_fingerprint": null, + "matches_existing_catalog": null, + "prompts": [ + { + "prompt_id": "qwen3-embed-query-native", + "max_tokens": 1, + "prompt_tokens": 29, + "completion_tokens": 0, + "reasoning_tokens": 0, + "token_count": 0, + "token_ids": [], + "token_prefix": [], + "reproducibility_runs": 1, + "fingerprint": "f70c41c3ad8e77cd141573cd7f78262252bca3f4159dbbc8b472a387dc88fc3b", + "embedding_vector": [ + -0.00029763737, + 0.0011475175, + -0.04062212, + 0.015376735, + -0.0006383066, + 0.06471999, + 0.031212477, + 0.0058523393, + -0.0039015596, + 0.018130777, + 0.028228931, + -0.01239319, + 0.001384193, + -0.031900987, + 0.0045613823, + -0.04383517, + 0.0, + 0.023638861, + 0.0014630848, + -0.0006598226, + -0.012450566, + 0.007917871, + 0.022376591, + 0.03993361, + -0.019737301, + -0.013311204, + -0.02352411, + 0.010040779, + -0.0034712404, + -0.0029261697, + -0.010270282, + -0.010844041, + -0.014343969, + -0.023983115, + 0.0037007441, + -0.014401345, + 0.012565317, + -0.00025281246, + -0.008663758, + 0.025819145, + -0.0043605664, + 0.013598083, + 4.325603e-05, + 0.013655459, + 0.029605953, + 0.0325895, + 0.0004070101, + -0.04176964, + -0.03534354, + -0.0073441123, + -0.00751624, + 0.020655315, + 0.032359995, + -0.015835742, + 0.08583431, + 0.0148029765, + -0.010786665, + -0.0002832934, + 0.006598226, + 0.02478638, + 0.010212906, + 0.027999427, + -0.0057949633, + -0.025130633, + -0.005163829, + -0.018130777, + -0.0021659394, + 0.0053646443, + -0.0015563207, + 0.0014559128, + 0.04773673, + 0.01044241, + 0.007458864, + -0.008721133, + 0.03144198, + -0.026278151, + -0.01767177, + 0.023294605, + 0.024901131, + 0.0025962584, + 0.026392903, + -0.008778509, + -0.008319502, + 0.04176964, + -0.0130817, + 0.017327515, + 0.03465503, + 0.0017571362, + -0.0019220918, + -0.009753899, + 0.00042494008, + -0.0062826583, + 0.021688081, + 0.0114178, + -0.0049056374, + 0.009581772, + -0.0156062385, + -0.029376449, + -0.016065245, + -0.0022089712, + 0.0007315424, + 0.0012694412, + 0.03740907, + 0.0049056374, + 0.015950494, + -0.02742567, + -0.0130817, + 0.013483331, + -0.0025532264, + 0.013770211, + 0.010614537, + 0.03006496, + 0.029835455, + 0.0044179424, + 0.010098155, + -0.010499786, + 0.0019077479, + 0.039015595, + 0.009926027, + 0.038327087, + -0.0010973136, + 0.012507941, + 0.004618758, + -0.009811275, + 0.014401345, + -0.045441695, + -0.0058523393, + 0.00057734473, + 0.021229073, + 0.0075736158, + 0.01836028, + -0.016179997, + -0.008262126, + 0.0010686257, + -0.03465503, + -0.0043892544, + -0.009926027, + -0.010901417, + -0.030523967, + 0.00089649804, + -0.0146882245, + -0.026392903, + 0.012680069, + -0.022376591, + -0.005106453, + -0.043376163, + 0.00084270816, + -0.036950063, + 0.011819431, + -0.00375812, + 0.00052714086, + 0.0015132887, + 0.0075736158, + -0.004131063, + 0.00033708327, + 0.035114035, + -0.006110531, + -0.02352411, + 0.023409357, + -0.0007423004, + -0.011589927, + 0.0156062385, + 0.03786808, + 0.025704393, + -0.020999571, + 0.013253828, + -0.0015419767, + 0.025130633, + -0.0009682179, + 0.0, + -0.0032273931, + -0.004274503, + 0.007057233, + -0.031212477, + 0.015376735, + 0.016524252, + -0.0030122334, + 0.01503248, + -0.015950494, + 0.004303191, + -0.0048195734, + -0.014516097, + -0.0063113463, + -0.0057662753, + -0.0017929961, + 0.03465503, + 0.004274503, + -0.015376735, + 0.0078031193, + -0.013139076, + 0.005536772, + 0.009753899, + -0.0067416653, + 0.017212763, + -0.005221205, + 0.0039589354, + -0.019393045, + -0.028228931, + 0.022376591, + 0.004762198, + 0.014401345, + 0.0031269852, + -0.0156062385, + -0.029261697, + 0.023294605, + -0.022606095, + 0.009581772, + -0.004274503, + 0.0151472315, + -0.012737445, + 0.005995779, + -0.008089999, + -0.00946702, + -0.058064386, + 0.024097867, + 0.029835455, + -0.017786521, + -0.0148029765, + 0.01836028, + -0.0011475175, + -0.004245815, + -0.0027396982, + -0.014516097, + 0.017557018, + -0.018475031, + -0.012335814, + -0.00034425527, + -0.0026249464, + 0.0058523393, + -0.006971169, + 0.032359995, + 0.012221062, + -0.008032623, + 0.0062539703, + -0.011991558, + -0.013425955, + -0.03213049, + 0.00668429, + 0.013483331, + -0.027655173, + -0.010958793, + -0.008204751, + -0.014343969, + 0.011876807, + -0.00067416654, + -0.027769923, + 0.010499786, + 0.0045613823, + -0.020081557, + -0.0015706646, + -0.0078031193, + 0.0020655314, + -0.00045721402, + -0.014630849, + -0.025360137, + -0.017212763, + -0.01836028, + -0.051408786, + 0.0031843612, + 0.0078031193, + -0.012450566, + 0.026048647, + -0.011475176, + 0.0024958507, + -0.01698326, + -0.011704679, + 0.007860495, + -0.0071719843, + -0.013999714, + -0.019393045, + 0.0156062385, + -0.009868651, + -0.009811275, + 0.0050203893, + 0.03488453, + -0.02547489, + -0.025015881, + -0.008147375, + 0.0043892544, + -0.004188439, + -0.005680212, + -0.009008013, + 0.0068851053, + -0.009753899, + 0.0, + 0.023179853, + 0.04773673, + -0.007860495, + -0.006598226, + 0.0015491487, + 0.0057662753, + -0.00030839533, + 0.0039876234, + 0.0024958507, + -0.03213049, + -0.017557018, + 0.007917871, + -0.025819145, + 0.019278293, + 0.00086063816, + -0.032819003, + -0.011360424, + -0.0053359563, + 0.032819003, + 0.004790886, + 0.032819003, + 0.018704535, + 0.008721133, + 0.0068277293, + 0.00029225837, + -0.016753756, + -0.00849163, + 0.024327371, + 0.0012694412, + 0.0030409214, + 0.013770211, + 0.007975247, + -0.01698326, + -0.0017786522, + -0.018016025, + -0.0154914865, + -0.033278007, + 0.04521219, + -0.009926027, + 0.014458721, + 0.019852053, + 0.03144198, + 0.0228356, + 0.029146945, + 0.011073544, + 0.0022089712, + 0.023065101, + -0.049343254, + -0.011360424, + -0.012450566, + -0.0057089, + 0.0067990413, + 0.035573043, + 0.008262126, + -0.012278438, + -0.0058810273, + -0.0068277293, + -0.014229218, + -0.027310917, + -0.04176964, + -0.0071432968, + -0.00849163, + 0.009524396, + 0.016409501, + -0.0057089, + -0.02811418, + -0.010844041, + -0.07986722, + -0.011532551, + 0.006598226, + -0.0012335813, + -0.0074014883, + 0.025130633, + -0.018704535, + 0.011991558, + -0.0076883673, + -0.0072580483, + -0.005536772, + 0.024901131, + 0.0456712, + -0.026622407, + -0.011245672, + 0.018016025, + 0.0325895, + -0.017442266, + 0.04062212, + 0.019048791, + 0.048195735, + -0.030294463, + -0.005192517, + -0.00946702, + 0.029146945, + -0.021343825, + -0.013827587, + -0.0035859921, + 0.026048647, + 0.027081413, + 0.0024384747, + 0.0015276327, + -0.019278293, + 0.008606382, + -0.024901131, + -0.038097583, + 0.014458721, + 0.011704679, + 0.009983403, + 0.035802547, + 0.0068851053, + -0.026966661, + 0.039704107, + 0.02157333, + 0.012852197, + 0.009926027, + 0.0152619835, + -0.015835742, + -0.0075736158, + -0.0076309917, + -0.03075347, + -0.0015850086, + 0.013024324, + -0.0040163114, + 0.0071719843, + 0.030982973, + 0.0017284483, + 0.022606095, + 0.046818715, + -6.589261e-05, + 0.012507941, + -0.026278151, + -0.008950637, + -0.02088482, + 0.01113092, + -0.010270282, + 0.02019631, + 0.011819431, + -0.000469765, + -0.02157333, + -0.0029118257, + -0.0022233152, + 0.022376591, + 0.0068564173, + 0.02157333, + 0.0050203893, + -0.03878609, + 0.0021372514, + -0.027655173, + -0.023868365, + -0.027196165, + 0.033278007, + -0.0039302474, + 0.024097867, + 0.026278151, + -0.007085921, + -0.019966805, + -0.06012992, + -0.03075347, + -0.036950063, + 0.0049056374, + 0.0148029765, + 0.0054507083, + -0.00668429, + 0.000114751754, + 0.01572099, + 0.023983115, + 0.03006496, + -0.021114323, + 0.029146945, + -0.015950494, + -0.0025101947, + -0.039474603, + 5.7824127e-05, + -0.0076883673, + 0.01113092, + -0.009753899, + 0.028687937, + -0.0523268, + 0.00017481712, + -0.0063687223, + 0.016065245, + -0.04658921, + -0.03350751, + -0.0030552654, + 0.019966805, + -0.027540421, + 0.034425527, + 0.007917871, + 0.01629475, + 0.0006383066, + -0.0020655314, + -0.0007458864, + 0.012221062, + 0.019278293, + -0.013712835, + 0.009524396, + -0.016868507, + -0.02685191, + -0.018819287, + 0.04314666, + 0.01767177, + 0.023294605, + 0.014516097, + -0.012909573, + 0.041081127, + 0.0071432968, + 0.045900702, + 0.006024467, + 0.021802833, + -0.014516097, + -0.044294175, + -0.01698326, + -0.008204751, + 0.021114323, + -0.0033995206, + 0.01824553, + 0.033048503, + 0.00011744125, + -0.016868507, + 0.011934183, + -0.0040163114, + -0.001391365, + -0.016409501, + 0.053933322, + -0.00062037667, + -0.009352268, + 0.0026966662, + -0.007860495, + 0.036261555, + -0.015950494, + -0.0146882245, + -0.01629475, + -0.0114178, + -0.012278438, + -0.023409357, + 0.020081557, + -0.03396652, + -0.032819003, + -0.024901131, + 0.0012264093, + -0.017901273, + -0.0004948669, + 0.019507797, + -0.002811418, + 0.023983115, + -0.013196452, + 0.008204751, + -0.00017929962, + 0.025015881, + -0.00751624, + 0.0029548577, + 0.01824553, + -0.020425811, + -0.00751624, + -0.019966805, + -0.027655173, + 0.0007996763, + -0.006196595, + 0.0013053012, + -0.0011833775, + 0.021114323, + 0.0015491487, + -0.024556875, + -0.039015595, + 0.013196452, + 0.007114609, + 0.03465503, + 0.005737588, + -0.009122765, + -0.0067129773, + -0.016753756, + -0.0114178, + 0.03465503, + 0.03488453, + 0.014114466, + 0.018704535, + -0.016065245, + 0.01767177, + 0.004331879, + 0.03144198, + 0.025360137, + 0.037638575, + -0.0048195734, + -0.010499786, + 0.035802547, + 0.012565317, + -0.0019794677, + 0.027540421, + 0.0045613823, + 0.01824553, + -0.0009682179, + 0.022376591, + 0.048884246, + 0.0036433681, + 0.012507941, + 0.00085705216, + -0.010499786, + 0.00654085, + 0.0078031193, + -0.008376878, + -0.02088482, + 0.007975247, + -0.01405709, + -0.006598226, + 0.016524252, + -0.009524396, + 0.0114178, + -0.03534354, + -0.012048934, + -0.0021229074, + 0.015376735, + -0.0026823222, + -0.004704822, + 0.046359707, + -0.01767177, + 0.0027253542, + 0.0261634, + 0.035573043, + 0.002366755, + -0.030523967, + -0.005737588, + -0.01503248, + -0.007975247, + 0.022950351, + 0.035573043, + -0.016868507, + 0.05163829, + 0.039704107, + 0.011991558, + 0.028917441, + -0.008606382, + 0.03878609, + -0.009352268, + 0.0062539703, + -0.016179997, + -0.008663758, + 0.0039589354, + -0.013598083, + -0.044294175, + -0.010844041, + 0.001434397, + 0.014458721, + -0.013770211, + -0.03603205, + -0.008663758, + 0.020425811, + 0.0076309917, + 0.0030982974, + 0.00076023035, + -0.0072293603, + -0.007917871, + 0.0024671627, + -0.026278151, + -0.011360424, + 0.030982973, + 0.0063400343, + -0.0030265774, + 0.0007925043, + -0.00035859924, + 0.025015881, + 0.012221062, + 0.013598083, + -0.0045040064, + -0.03534354, + 0.015376735, + 0.044294175, + 0.00918014, + -0.0015132887, + 0.019163543, + -0.01767177, + 0.0154914865, + -0.02088482, + 0.009409644, + 0.013827587, + -0.027769923, + 0.03672056, + -0.034425527, + 0.03465503, + -0.030523967, + 0.008721133, + -0.02547489, + -0.01767177, + 0.007917871, + -0.011245672, + -0.022147087, + 0.016065245, + -0.039474603, + -0.03350751, + 0.026966661, + 0.024671627, + 0.008950637, + 0.0152619835, + 0.0009682179, + 0.0044753184, + -0.005163829, + -0.017557018, + 0.025589641, + -0.0156062385, + 0.016179997, + 0.012048934, + 0.008089999, + 0.0039589354, + -0.019966805, + 0.01572099, + -0.013884962, + 0.001864716, + 0.015950494, + -0.016065245, + -0.030523967, + 0.02685191, + 0.010958793, + 0.039704107, + 0.009868651, + 0.0130817, + -0.022950351, + -0.008893261, + -0.010270282, + -0.041081127, + 0.003313457, + 0.012565317, + -0.019852053, + -0.0261634, + -0.014458721, + -0.020081557, + -0.017098011, + 0.01629475, + -0.029835455, + -0.03144198, + 0.028343683, + -0.028573187, + -0.036950063, + -0.029376449, + -0.012680069, + -0.055310346, + -0.014516097, + -0.05347432, + -0.0022663472, + -0.0015778366, + -0.022491343, + -0.019278293, + -0.0062826583, + -0.00375812, + 0.010327658, + 0.023753613, + -0.009983403, + 0.029835455, + 0.03465503, + -0.0005665868, + 0.004704822, + -0.03465503, + 0.008147375, + -0.010844041, + -0.0059670913, + -0.00668429, + -0.031900987, + 0.031212477, + -0.019852053, + 0.02880269, + 0.020081557, + -0.022950351, + -0.00026177743, + 0.030523967, + -0.016065245, + 0.0043605664, + -0.027769923, + 0.02478638, + -0.033048503, + 0.027999427, + 0.0048195734, + 0.004159751, + 0.0035716484, + -0.009122765, + 0.023409357, + 0.01503248, + 0.030294463, + 0.003313457, + -0.024097867, + -0.03144198, + -0.017327515, + -0.023868365, + -0.0073154243, + -0.013942338, + -0.012565317, + 0.01962255, + -0.019278293, + 0.0053933323, + 0.024327371, + -0.019048791, + -0.008434254, + 0.0019794677, + -0.00083912216, + -0.0016280405, + 0.009237516, + -0.023983115, + -0.017557018, + 0.039015595, + 0.003270425, + 0.0043892544, + -0.010729289, + 0.023409357, + 0.031900987, + -0.039474603, + 0.0012550973, + -0.02157333, + -0.012737445, + 0.016524252, + -0.0076883673, + -0.010844041, + 0.01836028, + -0.035114035, + -0.013827587, + -0.013483331, + -0.029605953, + 0.011016169, + -0.012450566, + -0.016065245, + 0.007114609, + -0.014573473, + -0.008778509, + -0.0114178, + 0.02019631, + -0.013024324, + 0.0043892544, + 0.0064260983, + -0.015950494, + -0.009237516, + 0.0076309917, + 0.007917871, + -0.024556875, + -0.036950063, + 0.010729289, + -0.020540563, + 0.00946702, + 0.025819145, + -0.02742567, + -0.007860495, + -0.013827587, + 0.005651524, + 0.015376735, + 0.01698326, + 0.020770067, + -0.01893404, + -0.003729432, + 0.014343969, + -0.02478638, + 0.008089999, + 0.008204751, + 0.013540707, + 0.0228356, + -0.03465503, + -0.008835885, + 0.004303191, + 0.0026106024, + 0.025360137, + 0.0076309917, + -0.013024324, + 0.021917585, + 0.026622407, + 0.0074014883, + -0.008434254, + -0.033737015, + 0.0142865935, + -0.0068851053, + -0.007975247, + 0.0014774288, + -0.005163829, + 0.033048503, + -0.0151472315, + 0.020540563, + -0.0024097867, + 0.03465503, + 0.030294463, + 0.03396652, + 0.00063113467, + 0.0014487408, + 0.007057233, + -0.013253828, + 0.027769923, + -0.011991558, + -0.0156062385, + -0.008204751, + 0.0032273931, + 0.0154914865, + -0.04773673, + 0.00751624, + -0.009065389, + -0.036261555, + 0.017901273, + -0.012966948, + 0.044982687, + 0.029835455, + 0.03006496, + -0.005594148, + -0.03534354, + -0.0054507083, + -0.013139076, + 0.0038728716, + 0.0018934039, + 0.0071719843, + -0.0005630008, + 0.024671627, + 0.004331879, + 0.021917585, + 0.0076883673, + 0.028687937, + -0.0053072684, + 0.038097583, + -0.03534354, + 0.029261697, + -0.008032623, + -0.0152619835, + -0.017327515, + -0.014573473, + -0.019966805, + -0.036261555, + 0.019852053, + -0.032819003, + 0.025589641, + 0.008950637, + -0.011188296, + 0.002395443, + 0.01405709, + 0.0072293603, + -0.025130633, + 0.02811418, + 0.014917728, + -0.01836028, + -0.027540421, + 0.020540563, + -0.014401345, + 0.013598083, + -0.008319502, + -0.014343969, + -0.03740907, + -0.015376735, + -0.0152619835, + 0.020425811, + 0.0029261697, + -0.0029548577, + -0.02031106, + 0.011762055, + 0.020540563, + -0.0010614537, + -0.03465503, + -0.021229073, + 0.009926027, + 0.0151472315, + 0.019048791, + 0.028343683, + -0.0130817, + 0.019278293, + -0.028687937, + 0.047277723, + -0.0035859921, + -0.02019631, + 0.027540421, + 0.0049630133, + -0.031212477, + -0.0049056374, + 0.012507941, + 0.057375874, + -0.017098011, + 0.011188296, + 0.0036720561, + -0.0014630848, + -0.013884962, + -0.0062826583, + 0.011589927, + -0.009065389, + -0.0146882245, + -0.01044241, + -0.011647303, + -0.05049077, + 0.009696523, + 0.0058523393, + 0.012622693, + -0.007917871, + 0.02088482, + 0.013253828, + -0.0025819144, + -0.00034784124, + -0.021114323, + 0.0068564173, + -0.005077765, + 0.00285445, + 0.0017427922, + 0.020655315, + -0.008262126, + -0.011073544, + 0.008893261, + -0.017786521, + 0.014917728, + -0.016753756, + -0.026278151, + 0.0043892544, + 0.012450566, + 0.018130777, + -0.02685191, + 0.0, + -0.00849163, + -0.023868365, + -0.013425955, + 0.0072293603, + 0.030294463, + -0.025704393, + -0.021343825, + -0.0036433681, + -0.0033851766, + 0.034196023, + -0.01836028, + 0.049802262, + 0.0015491487, + -0.0152619835, + 0.026278151, + 0.001427225, + 0.028458435, + -0.0130817, + -0.0018288561, + 0.017901273, + -0.001405709, + -0.009294892, + 0.010614537, + -0.014229218, + 0.019966805, + 0.008204751, + 0.002323723, + 0.00048410895, + 0.008663758, + -0.012335814, + -0.001420053, + -0.02421262, + -0.027999427, + -0.0063687223, + -0.008032623, + 0.03075347, + 0.011762055, + 0.013139076, + 0.008835885, + 0.010614537, + -0.021802833, + -0.008376878, + 0.013311204, + -0.010901417, + 0.0029548577, + 0.030294463, + -0.02352411, + 0.0156062385, + 0.01572099, + -0.014343969, + -0.008721133, + 0.011016169, + -0.0067416653, + -0.01336858, + 0.020655315, + 0.024442123, + -0.019507797, + -0.041540135, + -0.006081843, + -0.018704535, + -0.016524252, + 0.023638861, + -0.007458864, + 0.0154914865, + -0.027655173, + 0.0075736158, + -0.004303191, + 0.013311204, + 0.00668429, + 0.004274503, + 0.00085705216, + 0.010729289, + -0.04452368, + -0.02031106, + -0.005651524, + 0.004647446, + -0.0069137933, + 0.004618758, + -0.0228356, + -0.02157333, + 0.0063113463, + -0.004303191, + 0.0038154959, + 0.014630849, + -0.0006992685, + -0.006024467, + -0.00086422416, + 0.017786521, + -0.03786808, + 0.02031106, + -0.016409501, + -0.010040779, + 0.00067775254, + -0.0151472315, + -0.006999857, + 0.024097867, + 0.016753756, + -0.035802547, + -0.011245672, + -0.02226184, + -0.0068277293, + -0.028343683, + 0.023065101, + -0.022491343, + 0.03144198, + -0.014917728, + -0.03006496, + 0.0010757977, + 0.002768386, + 0.018589783, + 0.00654085, + 0.010499786, + 0.0010829697, + -0.03649106, + -0.0075736158, + 0.008319502, + -0.03534354, + -0.03006496, + -0.009237516, + -0.009524396, + -0.02088482, + -0.011819431, + -0.010671913, + -0.014458721, + -0.022606095, + -0.0016567284, + 0.0054793963, + 0.002395443, + -0.042228647, + 0.009753899, + -0.006024467, + -0.004217127, + 0.0148029765, + 0.0017571362, + 0.0130817, + 0.0074014883, + -0.01824553, + -0.008721133, + -0.0041023754, + 0.020425811, + -0.03144198, + 0.021917585, + 0.040163115, + -0.020999571, + 0.013139076, + 0.0074014883, + -0.008319502, + 0.0024241307, + -0.0059670913, + 0.0130817, + -0.0114178, + -0.03144198, + -0.004676134, + 0.024442123, + -0.0012981292, + 0.02742567, + 0.00066699454, + 0.010385034, + -0.01698326, + -0.021688081, + 0.0020081557, + -0.027884675, + 0.011876807, + 0.002768386, + -0.017212763, + -0.008778509, + 0.026278151, + 0.0053646443, + 0.006598226, + 0.03534354, + -0.0011546895, + -0.0001846786, + -0.0156062385, + -0.023983115, + 0.021688081, + 0.008778509, + 0.0148029765, + -0.013139076, + -0.019737301, + -0.014458721, + 0.004331879, + 0.031900987, + 0.021343825, + 0.012794821, + 0.021343825, + -0.019737301, + -0.0013196452, + 0.022950351, + -0.011073544, + -0.0035573044, + -0.016524252, + 0.033278007, + 0.0015348047, + -0.011475176, + 0.019163543, + -0.012450566, + -0.008434254, + -0.004131063, + -0.010786665, + -0.019966805, + 0.04773673, + -0.014171842, + -0.01698326, + 0.052097294, + -0.01015553, + 0.017557018, + 0.009811275, + 0.0016065246, + 0.0142865935, + -0.02226184, + 0.011589927, + -0.029835455, + 0.012794821, + -0.015376735, + 0.011073544, + 0.012737445, + -0.0067703533, + -0.007028545, + 0.023868365, + -0.026392903, + -0.0148029765, + 0.0027396982, + 0.01210631, + 0.015376735, + -0.013942338, + 0.01893404, + 0.023753613, + 0.013598083, + -0.0076309917, + 0.013942338, + -0.029032193, + 0.017327515, + -0.03075347, + -0.00024384748, + -0.008663758, + -0.017212763, + -0.01824553, + -0.027769923, + 0.02880269, + 0.0151472315, + -0.0003621852, + -0.014229218, + -0.029261697, + -0.028228931, + 0.0076309917, + 0.0261634, + 0.0156062385, + -0.027540421, + 0.030982973, + 0.0068851053, + 0.016868507, + -0.03649106, + -0.013196452, + 0.009753899, + -0.008549006, + 0.031671483, + -0.00946702, + -0.035114035, + -0.026048647, + 0.032819003, + 0.018016025, + 0.033278007, + 0.023409357, + -0.016639004, + 0.0044753184, + 0.02088482, + -0.019737301, + -0.008835885, + -0.0034999284, + 0.017098011, + -0.0228356, + -0.03786808, + 0.03465503, + -0.0016710724, + 0.022720847, + 0.019966805, + 0.029835455, + 0.001412881, + -0.030294463, + -0.027884675, + -0.0053933323, + -0.038097583, + -0.003327801, + -0.011589927, + 0.02088482, + -0.00654085, + 0.0, + -0.005651524, + 0.022720847, + -0.019278293, + 0.027655173, + 0.009008013, + -0.02421262, + 0.012680069, + -0.007975247, + -0.009237516, + -0.0044753184, + -0.017442266, + -0.0020511877, + -0.031671483, + -0.015376735, + -0.034425527, + 0.018589783, + 0.0012837852, + -3.025681e-05, + 0.019163543, + -0.030982973, + -0.00668429, + 0.0043892544, + 0.02811418, + -0.011991558, + -0.0071719843, + 0.006024467, + 0.012852197, + -0.001420053, + -0.012565317, + 0.025589641, + 0.008204751, + -0.00946702, + 0.031900987, + 0.00556546, + -0.00023846849, + -0.013253828, + -0.03006496, + 0.012507941, + -0.005651524, + 0.018819287, + -0.012794821, + -0.01405709, + -0.016409501, + 0.029835455, + -0.0071719843, + -0.0017714802, + -0.022032337, + 0.0025388824, + -0.008147375, + -0.039704107, + -0.013884962, + -0.015376735, + 0.0006024467, + -0.0039015596, + 0.031900987, + 0.023983115, + 0.03534354, + 0.025130633, + -0.019852053, + -0.01698326, + -0.0025962584, + 0.015950494, + -0.0017284483, + 0.009352268, + 0.017327515, + -0.015376735, + -0.028228931, + 0.009008013, + 0.025130633, + 0.030294463, + -0.015376735, + -0.014573473, + -0.009352268, + -0.022147087, + -0.032359995, + 0.00023667549, + 0.061047934, + -0.012622693, + 0.0028831377, + 0.018589783, + -0.02673716, + 0.03350751, + 0.02880269, + 0.011360424, + -0.025589641, + 0.012909573, + 0.0025388824, + -0.0034568966, + -0.010327658, + 0.027081413, + 0.012622693, + 0.02088482, + 0.0, + 0.01962255, + 0.061047934, + 0.00048410895, + 0.0024958507, + -0.0006024467, + 0.011647303, + 0.0043892544, + 0.0064547863, + -0.01405709, + -0.011532551, + 0.008262126, + 0.005278581, + 0.010901417, + -0.016868507, + 0.0004105961, + 0.010327658, + 0.02031106, + -0.0011546895, + -0.0072867363, + -0.042458147, + 0.0049630133, + 0.0156062385, + 0.017098011, + 0.0154914865, + -0.010098155, + 0.025015881, + -0.002825762, + 0.0152619835, + -0.003729432, + 0.009352268, + -0.009524396, + -0.017442266, + -0.0067703533, + -0.012335814, + 0.005995779, + 0.025704393, + 0.031212477, + -0.014401345, + -0.016179997, + 0.015376735, + 0.0019364358, + 0.024097867, + 0.019507797, + 0.024901131, + 0.008089999, + -0.016868507, + -0.0068277293, + -0.010671913, + -0.021688081, + 0.025360137, + -0.010786665, + 0.018819287, + 0.00019722957, + -0.0078031193, + 0.019393045, + -0.027540421, + -0.004188439, + -0.01962255, + 0.005622836, + -0.027999427, + -0.016065245, + -0.013540707, + 0.0037868079, + -0.0053933323, + 0.012278438, + -0.00023129651, + 0.009753899, + -0.032819003, + -0.004159751, + 0.0078031193, + 0.0012981292, + -0.009696523, + -0.019163543, + 0.00033170427, + -0.0017714802, + -0.018819287, + 0.04062212, + -0.00654085, + 0.012335814, + -0.018704535, + 0.013999714, + -0.004188439, + 0.0050203893, + 0.01572099, + -0.0011762055, + -0.014458721, + -0.009524396, + 0.013540707, + -0.022950351, + 0.00059527473, + -0.021229073, + -0.0037868079, + -0.005192517, + 0.02421262, + -0.005249893, + 0.008893261, + 0.018589783, + -0.010040779, + -0.0039589354, + 0.019507797, + -0.00187906, + 0.007028545, + 0.019852053, + 0.007028545, + -0.025704393, + 0.012163686, + -0.009811275, + 0.0018001681, + 0.0012048933, + 0.0228356, + 0.01210631, + -0.00946702, + -0.034196023, + 0.0054793963, + 0.0068277293, + -0.027196165, + 0.0228356, + -0.009983403, + -0.0010399377, + 0.036261555, + 0.009868651, + -0.019163543, + -0.013311204, + 0.012622693, + -0.023294605, + 0.03075347, + 0.00085705216, + 0.02088482, + -0.0027110102, + 0.010901417, + 0.03878609, + 0.0063974103, + 0.017901273, + 0.018016025, + -0.01572099, + -0.029146945, + -0.013425955, + -0.010270282, + 0.024671627, + 0.015376735, + 0.011303048, + -0.0072006723, + 0.037638575, + 0.027310917, + 0.013540707, + 0.00918014, + 0.025589641, + -0.018819287, + -0.032359995, + -0.012852197, + 0.022491343, + -0.0029261697, + 0.013540707, + 0.0031413292, + -0.006598226, + -0.010901417, + -0.023753613, + 0.0021659394, + 0.010557162, + -0.020540563, + -0.019163543, + 0.009008013, + 0.016179997, + 0.01015553, + -0.008835885, + 0.033048503, + -0.009926027, + -0.013196452, + 0.0146882245, + 0.03144198, + -0.01962255, + 0.0076309917, + -0.015376735, + -0.019507797, + 0.035573043, + -0.008663758, + 0.011073544, + -0.0022089712, + -0.0044753184, + 0.02088482, + 0.012565317, + -0.008606382, + 0.00012550973, + -0.0019077479, + 0.044294175, + 0.016524252, + 0.012565317, + 0.044753183, + 0.008376878, + -0.011934183, + -0.0037007441, + 0.014630849, + -0.029835455, + 0.023179853, + 0.0035573044, + 0.012335814, + 0.020425811, + -0.023753613, + -0.02226184, + 0.04521219, + -0.0456712, + 0.03488453, + -0.0045326944, + 0.027196165, + -0.030523967, + -0.019163543, + 0.008893261, + -0.023179853, + 0.0024384747, + -0.00918014, + -0.0026249464, + 0.010212906, + -0.008319502, + 0.0063687223, + 0.00083912216, + 0.004217127, + -0.013712835, + 0.018475031, + -0.0063113463, + 0.017442266, + 0.009926027, + -0.0050490773, + 0.019737301, + 0.00059886073, + -0.016753756, + -0.03075347, + -0.058752898, + 0.018704535, + -0.011245672, + -0.0152619835, + 0.007028545, + -0.0058523393, + 0.0017427922, + -0.014171842, + -0.004274503, + 0.028228931, + 0.03006496, + 0.026048647, + -0.01893404, + 0.008778509, + -0.009811275, + 0.025704393, + -0.00015509417, + -0.0018001681, + 0.016524252, + -0.03144198, + 0.017327515, + -0.0069137933, + 0.00025460546, + -0.011589927, + -0.009926027, + -0.023753613, + -0.0325895, + 0.0063687223, + -0.0325895, + 0.03488453, + 0.00033170427, + 0.039015595, + -0.0011905495, + 0.036261555, + -0.004331879, + -0.016524252, + 0.02811418, + 0.024901131, + -0.015835742, + -0.0114178, + 0.018589783, + 0.006971169, + 0.020540563, + -0.012163686, + 0.0022233152, + -0.013311204, + -0.002825762, + -0.010098155, + 0.0010327657, + 0.0054507083, + 0.029376449, + 0.029605953, + -0.0044179424, + -0.0077457433, + -0.0015634926, + -0.024901131, + 0.012335814, + -0.010098155, + 0.0012120653, + 0.01572099, + 0.019852053, + -0.02019631, + -0.024442123, + 0.0031700172, + -0.00023846849, + 0.000118337746, + 0.010557162, + 0.008950637, + 0.0037868079, + 0.012335814, + 0.018704535, + -0.03786808, + -0.01836028, + -0.008204751, + 0.01893404, + -0.00041956108, + 0.014573473, + 0.0040736874, + 0.0456712, + -0.0076883673, + 0.0021802832, + -0.009811275, + 0.00059168873, + -0.02811418, + 0.015376735, + -0.033278007, + -0.019737301, + 0.017098011, + -0.011475176, + -0.0076309917, + -0.02421262, + 0.0031843612, + -0.0078031193, + -0.025819145, + 0.00023846849, + 0.031671483, + 0.027196165, + -0.03603205, + -0.018704535, + -0.017557018, + -0.019852053, + 0.0011690335, + 0.042228647, + 0.017212763, + 0.0130817, + 0.03396652, + 0.025130633, + -0.007975247, + 0.0072006723, + 0.011762055, + 0.0059097153, + 0.00048410895, + -0.017557018, + 0.016868507, + -0.0040736874, + -0.007860495, + -0.0039015596, + -0.007458864, + 0.002381099, + 0.0073441123, + -0.020999571, + 0.000469765, + -0.011360424, + 0.0021802832, + 0.0049630133, + -0.024901131, + -0.04704822, + -0.00946702, + -0.0072580483, + 0.027196165, + 0.01503248, + -0.012335814, + 0.0012694412, + 0.02031106, + 0.000119234246, + -0.011188296, + 0.02031106, + 0.037638575, + -0.007860495, + -0.0020081557, + 0.01210631, + 0.0029692017, + 0.014516097, + 0.008549006, + -0.0063113463, + -0.008319502, + 0.029261697, + -0.007085921, + 0.005192517, + 0.020540563, + -0.008434254, + 0.028573187, + -0.016639004, + 0.017557018, + -0.036950063, + -0.0073154243, + -0.012507941, + 0.01962255, + 0.00556546, + -0.011303048, + 0.00849163, + 0.0010112498, + 0.027196165, + 0.031212477, + -0.0026679782, + -0.009237516, + -0.027540421, + 0.007860495, + -0.019507797, + 0.012852197, + 0.009639147, + 0.003729432, + 0.013540707, + -0.029032193, + -0.039704107, + 0.021688081, + -0.027081413, + -0.010844041, + -0.027310917, + 0.019852053, + -0.03144198, + -0.006655602, + -0.019163543, + 0.0130817, + -0.013196452, + 0.03649106, + -0.025015881, + 0.028343683, + 0.039704107, + 0.023294605, + -0.00012013074, + -0.0456712, + -0.0014487408, + -0.026622407, + -0.02478638, + -0.012622693, + -0.0011977215, + 0.004676134, + 0.027999427, + 0.011360424, + 0.019048791, + 0.008376878, + 0.0039302474, + 0.011934183, + 0.028458435, + -0.0007925043, + -0.019737301, + -0.0025819144, + 0.010270282, + -0.039474603, + 0.025589641, + 0.006167907, + -0.038556587, + -0.03488453, + 0.0067416653, + 0.011360424, + 0.03213049, + 0.009639147, + -0.007458864, + 0.0025245387, + -0.017327515, + 0.023638861, + 0.0033995206, + 0.012565317, + 0.014458721, + -0.015835742, + 0.00093953, + 0.021229073, + -0.010901417, + 0.03144198, + -0.0069137933, + -0.0024815067, + 0.008089999, + 0.0016639004, + -0.02352411, + -0.011188296, + -0.00039087315, + 0.031671483, + -0.0392451, + 0.020999571, + -0.017212763, + 0.010098155, + -0.011819431, + 0.010327658, + -0.012794821, + -0.01629475, + -0.022376591, + 0.012794821, + -0.00030122334, + -0.01893404, + 0.017557018, + -0.016753756, + 0.022491343, + -0.004159751, + -0.012909573, + 0.009696523, + 0.01044241, + -0.00047335098, + 0.041540135, + -0.027196165, + 0.011073544, + 0.017327515, + -0.0019651237, + -0.026622407, + 0.023065101, + -0.024327371, + -0.037638575, + -0.0012981292, + 0.010270282, + -0.010270282, + -0.031900987, + 0.0148029765, + 0.0067990413, + -0.03878609, + 0.016524252, + -0.022032337, + 0.029605953, + -0.02019631, + 0.0067703533, + -0.0148029765, + 0.028917441, + -0.015835742, + -0.0031700172, + 0.019852053, + 0.010327658, + -0.023868365, + 0.018704535, + 0.0021085634, + 0.034196023, + 0.0074014883, + -0.0151472315, + -0.00751624, + 0.003241737, + 0.02421262, + 0.035573043, + -0.014343969, + -0.0016997603, + -0.0035573044, + 0.0010829697, + 0.013540707, + 0.019966805, + -0.008089999, + -0.002395443, + -0.039474603, + 0.007975247, + 0.019966805, + -0.014229218, + 0.023294605, + -0.023638861, + 0.050031763, + 0.010385034, + 0.026278151, + 0.012680069, + -0.011647303, + -0.01629475, + 0.012507941, + -0.0017714802, + 0.01824553, + -0.044064675, + 0.0068851053, + 0.0013483331, + 0.013598083, + -0.009008013, + 0.027540421, + -0.030982973, + 0.024901131, + 0.0026823222, + 0.024442123, + -0.016065245, + 0.015835742, + -0.035802547, + -0.0031269852, + -0.018589783, + 0.0007279564, + 0.0053933323, + 0.010729289, + -0.01210631, + -0.00067775254, + 0.010270282, + -0.011934183, + -0.038556587, + 0.017901273, + 0.014343969, + 0.0021946272, + -0.011303048, + -0.0053072684, + -0.022950351, + -0.0151472315, + -0.03350751, + -0.01836028, + -0.01336858, + -0.041999143, + -0.0057662753, + 0.014401345, + 0.0057949633, + 0.0050203893, + -0.020770067, + -0.0073441123, + -0.0030122334, + 0.012335814, + 0.029835455, + 0.017557018, + 0.03649106, + -0.019278293, + -0.0044466304, + 0.038327087, + 0.024442123, + -0.009581772, + -0.035802547, + -0.011934183, + -0.023294605, + 0.03534354, + 0.0152619835, + 0.005192517, + -0.012909573, + -0.006999857, + 0.010212906, + 0.018704535, + 0.006196595, + -0.0038441836, + -0.006626914, + -0.016065245, + 0.0036720561, + -0.016524252, + -0.009294892, + 0.022950351, + -0.01044241, + 0.0007817463, + -0.009524396, + 0.0031269852, + 0.013770211, + -0.025933895, + -0.012163686, + -0.027540421, + 0.006081843, + 0.041999143, + 0.025130633, + -0.006053155, + -0.004790886, + 0.014171842, + -0.020081557, + -0.006598226, + -0.028687937, + 0.0032273931, + -0.03786808, + -0.0005594148, + 0.0078031193, + -0.017442266, + 0.03396652, + 0.034425527, + -0.0022520032, + -0.00084629416, + 0.019966805, + -0.016868507, + 0.029605953, + 0.050261267, + -0.02811418, + -0.023179853, + -0.013425955, + 4.66179e-05, + -0.014516097, + 0.03488453, + -0.0148029765, + 0.0039876234, + 0.06793304, + -0.03213049, + -0.012622693, + -0.0022376592, + 0.003299113, + -0.0033851766, + -0.033278007, + 0.051408786, + 0.0053933323, + -0.014229218, + -0.021917585, + 0.017442266, + 0.007917871, + 0.008663758, + 0.021114323, + 0.0007889183, + -0.0040736874, + 0.022950351, + -0.014917728, + -0.011991558, + -0.010098155, + 0.006139219, + 8.920156e-05, + -0.013253828, + -0.0049343253, + -0.005106453, + 9.05463e-05, + 0.027884675, + 0.051179282, + 0.009122765, + 0.0034425526, + 0.018130777, + 0.01572099, + 0.01824553, + -0.0392451, + -0.009868651, + -0.0151472315, + -0.002366755, + -0.011704679, + 0.009237516, + -0.033737015, + 0.031900987, + -0.005680212, + 0.013483331, + 0.022147087, + -0.0071719843, + -0.0049056374, + 0.01962255, + -0.03740907, + 0.053244814, + 0.0021085634, + -0.026278151, + -0.012909573, + -0.0063400343, + -0.010958793, + -0.011876807, + 0.009352268, + -0.025015881, + -0.011819431, + -0.0069137933, + -0.016753756, + 0.00017840312, + -0.014917728, + 0.00058810273, + 0.0130817, + -0.0059670913, + -0.027081413, + -0.02088482, + -0.0021659394, + -0.021917585, + -0.0045326944, + 0.040163115, + 0.0028974817, + 0.0007171985, + 0.04911375, + -0.020540563, + -0.026392903, + -0.018589783, + -0.010958793, + -0.0007028545, + 0.02019631, + -0.016868507, + 0.007458864, + -0.018704535, + -0.0148029765, + 0.0076883673, + 0.006512162, + -0.01698326, + 0.016753756, + -0.0010399377, + 0.017212763, + 0.011762055, + 0.010901417, + -0.0058810273, + 0.02019631, + -0.0030265774, + 0.009409644, + -0.013024324, + -0.029605953, + -0.01698326, + -0.013770211, + -0.022147087, + -0.0039876234, + -0.023983115, + -0.018475031, + -0.017901273, + -0.007028545, + -0.005192517, + 0.04131063, + -0.004762198, + 0.021688081, + 0.013139076, + 0.0016710724, + -0.0036433681, + -0.0019364358, + -0.014229218, + 0.01698326, + -0.05347432, + -0.0009753899, + -0.0045613823, + 0.016868507, + 0.007028545, + 0.012450566, + -0.029146945, + 0.061965946, + 0.008606382, + -0.016753756, + -0.018704535, + -0.0033851766, + 0.011016169, + 0.0142865935, + 0.004647446, + 0.02478638, + 0.0025101947, + 0.039015595, + 0.037638575, + 0.030523967, + -0.038097583, + 0.04452368, + -0.00017571362, + -0.025704393, + 0.01824553, + -0.047277723, + -0.016753756, + 0.010958793, + -0.041999143, + 0.016524252, + 0.008663758, + 0.038556587, + 0.0015706646, + -0.0019507798, + -0.025933895, + 0.010901417, + -0.0053646443, + 0.031671483, + -0.056457862, + 0.004647446, + -0.0074014883, + 0.016524252, + 0.009753899, + 0.0068851053, + -0.016753756, + 0.014516097, + 0.01698326, + 0.008835885, + 0.029835455, + 0.024556875, + -0.0068851053, + -0.020655315, + 0.026507655, + -0.023179853, + 0.0058523393, + -0.011016169, + -0.019966805, + 0.00654085, + -0.010040779, + 0.010557162, + -0.003729432, + -0.01767177, + 0.00751624, + 0.023179853, + -0.0456712, + -0.0031126414, + 0.016753756, + 0.0025101947, + -0.019737301, + 0.013253828, + 0.008434254, + 0.0054793963, + 0.01336858, + -0.020999571, + 0.01572099, + -2.9584437e-05, + 0.0030122334, + 0.015835742, + -0.005249893, + 0.026622407, + -0.026392903, + -0.016753756, + 0.017786521, + -0.007114609, + 0.0026679782, + 0.013712835, + 0.00849163, + -0.0068851053, + 0.0057089, + -0.022491343, + -0.012335814, + 0.018589783, + 0.0018001681, + -0.0018073401, + 0.00030122334, + -0.0053646443, + 0.008262126, + 0.003327801, + -0.022147087, + -0.016868507, + 0.0325895, + 0.016065245, + -0.0029548577, + -0.006081843, + 0.008089999, + 0.021802833, + 0.0146882245, + -0.044753183, + -0.001398537, + 0.027310917, + 0.018475031, + 0.0062539703, + 0.010844041, + -0.017327515, + -0.02880269, + 0.002309379, + 0.019278293, + 0.005737588, + 0.02226184, + 0.016524252, + 0.021114323, + -0.0063687223, + -0.00024026148, + -0.0071719843, + 0.030294463, + -0.028917441, + -0.013483331, + -0.01698326, + 0.010212906, + 0.008549006, + 0.00556546, + 0.006999857, + -0.006196595, + 0.005135141, + 0.023065101, + -0.0014630848, + 0.027769923, + 0.009581772, + -0.01836028, + -0.016524252, + -0.0049343253, + -0.033278007, + 0.026278151, + 0.010385034, + -0.024901131, + -0.007458864, + 0.028228931, + -0.01893404, + 0.0156062385, + 0.014171842, + -0.01962255, + 0.010844041, + -0.0044753184, + 0.010671913, + 0.01962255, + -0.020425811, + 0.0040163114, + 0.005536772, + 0.0073154243, + 0.009294892, + 0.018475031, + -0.026278151, + 0.023868365, + -0.0053359563, + 0.025933895, + 0.005163829, + -0.0154914865, + 0.012278438, + 0.0035142724, + -0.024556875, + 0.033048503, + 0.009639147, + 0.01113092, + -0.01836028, + -0.00060961867, + 0.033048503, + 0.013196452, + 0.008778509, + 0.013942338, + -0.007975247, + 0.006024467, + 0.014401345, + -0.013942338, + 0.0044179424, + -0.027999427, + -0.039474603, + -0.0148029765, + -0.029605953, + -0.000932358, + 0.01893404, + -0.02880269, + -0.014171842, + 0.017327515, + -0.016639004, + 0.01824553, + 0.00375812, + -0.0148029765, + 0.009352268, + 0.0038728716, + -0.005163829, + -0.032359995, + 0.025589641, + -0.006110531, + 0.010729289, + 0.032359995, + -0.014573473, + 0.00057375873, + -0.022032337, + -0.028917441, + 0.029376449, + -0.035573043, + -0.006512162, + 0.0012622693, + 0.027884675, + -0.0040736874, + 0.012794821, + 0.004303191, + -0.017557018, + -0.0075736158, + 0.012048934, + -0.00014433618, + 0.015835742, + -0.009237516, + -0.03603205, + -0.00076023035, + -0.0067703533, + -0.0014487408, + 0.017557018, + 0.038327087, + -0.01962255, + -0.022950351, + -0.013139076, + 0.017098011, + 0.043605667, + -0.021802833, + 0.014343969, + -0.0013411611, + 0.019966805, + 0.01893404, + -0.01893404, + 0.02685191, + -0.016639004, + 6.1410115e-05, + 0.0392451, + -0.0005056249, + -0.005278581, + 0.010901417, + 0.031212477, + -0.0152619835, + 0.0148029765, + 0.022720847, + -0.0073154243, + -0.00654085, + -0.0036003361, + 0.0006562366, + -0.0058236513, + -0.0325895, + 0.018704535, + -0.016065245, + -0.0074014883, + 0.018016025, + 0.013598083, + 0.012565317, + -0.012966948, + -0.00946702, + 0.06931006, + -0.0156062385, + -0.03878609, + -0.017327515, + 0.011475176, + 0.02673716, + 0.0151472315, + 0.009409644, + 0.008549006, + -0.025704393, + -0.020655315, + -0.0030839534, + -0.0114178, + 0.014114466, + 0.010671913, + 0.03144198, + 0.0011905495, + 0.0021802832, + -0.013425955, + -0.020540563, + -0.013540707, + 0.006598226, + 0.010671913, + 0.018819287, + 0.0058810273, + 0.013024324, + 0.0014917728, + -0.00459007, + -0.010499786, + 0.06150694, + -0.0148029765, + -0.020425811, + -0.016639004, + 0.0048195734, + -0.017212763, + -0.025360137, + -0.0062539703, + 0.011704679, + 0.0156062385, + -0.0154914865, + -0.007028545, + -0.03144198, + -0.014401345, + 0.030982973, + -0.006569538, + -0.037179567, + 0.028228931, + 0.027310917, + 0.011303048, + 0.035573043, + -0.012335814, + 0.0038154959, + -0.026507655, + 0.022606095, + -0.024097867, + 0.02547489, + -0.00751624, + -0.020081557, + 0.0076883673, + -0.019737301, + -0.0036433681, + -0.03213049, + 0.017212763, + -0.008893261, + -0.0009682179, + -0.0004088031, + -0.00048410895, + 0.018704535, + -0.0010255938, + 0.01824553, + -0.013425955, + -0.008606382, + 0.01113092, + -0.029146945, + -0.0021372514, + 0.0076883673, + -0.0026249464, + 0.019393045, + -0.0014702568, + 0.026507655, + -0.017327515, + 0.023179853, + 0.024327371, + 0.025245385, + 0.007975247, + 0.03993361, + -0.003729432, + 0.023983115, + -0.0050203893, + -0.012966948, + -0.036261555, + -0.010614537, + -0.00024205448, + -0.02031106, + 0.02421262, + 0.0009682179, + -0.019507797, + -0.01044241, + 0.0027396982, + -0.01405709, + 0.013655459, + -0.018819287, + -0.017327515, + 0.012680069, + -0.021917585, + -0.027655173, + 0.00556546, + 0.024556875, + -0.03144198, + 0.0024815067, + -0.0053933323, + -0.00059886073, + -0.0020368437, + -0.028458435, + -0.0146882245, + -0.002768386, + -0.029146945, + -0.009294892, + 0.03488453, + 0.03993361, + -0.0054793963, + 0.00946702, + 0.004790886, + -0.022606095, + -0.0151472315, + 0.013253828, + -0.028458435, + -0.011647303, + 0.010212906, + -0.03534354, + -0.03534354, + 0.05967091, + -0.046130203, + -0.037179567, + -0.0049343253, + -0.051179282, + 0.02157333, + 0.010040779, + -0.03534354, + -0.00278273, + -0.031671483, + 0.01113092, + 0.016639004, + 0.022950351, + 0.018589783, + 0.006598226, + -0.0151472315, + 0.015376735, + 0.009352268, + 0.019852053, + -0.0077457433, + -0.0049917013, + -0.019966805, + -0.0057949633, + -0.0154914865, + -0.032819003, + -0.018589783, + 0.0067129773, + 0.004303191, + -0.009983403, + 0.002825762, + -0.0057949633, + -0.0016423844, + -0.039704107, + -0.018819287, + 0.0044179424 + ], + "resource_items": { + "embedding": { + "unit": "input_token", + "item_count": 1, + "item_bytes": 135, + "item_units": 29 + } + }, + "calibration_baseline_memory_bytes": 3511644160, + "calibration_peak_memory_bytes": 3581628416, + "output_text": null + }, + { + "prompt_id": "qwen3-embed-query-1536", + "max_tokens": 1, + "prompt_tokens": 29, + "completion_tokens": 0, + "reasoning_tokens": 0, + "token_count": 0, + "token_ids": [], + "token_prefix": [], + "reproducibility_runs": 1, + "fingerprint": "d8ebc6212383651c3cc8426483c9a4575203067461f99af1c5361691e01a2bcc", + "embedding_vector": [ + -0.00038522322, + 0.0014851978, + -0.052576005, + 0.019901652, + -0.00082614133, + 0.083765164, + 0.040397383, + 0.0075745094, + -0.005049673, + 0.023466129, + 0.03653587, + -0.016040139, + 0.0017915199, + -0.041288503, + 0.005903662, + -0.056734562, + 0.0, + 0.030595077, + 0.0018936273, + -0.00085398887, + -0.016114399, + 0.010247866, + 0.028961359, + 0.05168489, + -0.025545405, + -0.017228296, + -0.030446557, + 0.012995482, + -0.0044927234, + -0.0037872547, + -0.013292521, + -0.014035121, + -0.018564973, + -0.031040635, + 0.0047897636, + -0.018639233, + 0.016262917, + -0.00032720767, + -0.011213245, + 0.033416953, + -0.005643752, + 0.017599596, + 5.5984998e-05, + 0.017673856, + 0.03831811, + 0.04217962, + 0.00052678114, + -0.054061204, + -0.045744095, + -0.009505267, + -0.009728046, + 0.026733562, + 0.041882582, + -0.020495731, + 0.111092806, + 0.019159054, + -0.013960861, + -0.00036665826, + 0.008539888, + 0.032080278, + 0.013218261, + 0.03623883, + -0.0075002494, + -0.032525834, + -0.0066833906, + -0.023466129, + -0.0028033112, + 0.0069433004, + -0.0020142999, + 0.0018843448, + 0.061784234, + 0.013515301, + 0.009653786, + -0.011287504, + 0.040694423, + -0.034011032, + -0.022872047, + 0.030149518, + 0.032228798, + 0.0033602603, + 0.034159552, + -0.011361764, + -0.010767684, + 0.054061204, + -0.016931256, + 0.02242649, + 0.04485298, + 0.0022742094, + -0.0024877065, + -0.012624182, + 0.00054998737, + -0.008131458, + 0.028070241, + 0.01477772, + -0.006349221, + 0.012401403, + -0.020198692, + -0.03802107, + -0.020792771, + -0.002859006, + 0.00094681367, + 0.0016430002, + 0.048417453, + 0.006349221, + 0.020644251, + -0.03549623, + -0.016931256, + 0.017451076, + -0.0033045653, + 0.017822376, + 0.01373808, + 0.038912185, + 0.038615145, + 0.005718012, + 0.013069742, + -0.013589561, + 0.0024691417, + 0.050496727, + 0.012846963, + 0.04960561, + -0.0014202205, + 0.016188657, + 0.0059779217, + -0.012698442, + 0.018639233, + -0.05881384, + -0.0075745094, + 0.0007472402, + 0.02747616, + 0.009802306, + 0.023763165, + -0.020941291, + -0.010693424, + 0.0013830906, + -0.04485298, + -0.005680882, + -0.012846963, + -0.014109381, + -0.039506268, + 0.0011603108, + -0.019010535, + -0.034159552, + 0.016411437, + -0.028961359, + -0.0066091307, + -0.056140482, + 0.0010906922, + -0.047823373, + 0.015297539, + -0.004864023, + 0.0006822628, + 0.0019586047, + 0.009802306, + -0.005346712, + 0.0004362769, + 0.045447055, + -0.007908679, + -0.030446557, + 0.030298037, + -0.0009607374, + -0.015000499, + 0.020198692, + 0.049011532, + 0.033268433, + -0.027179122, + 0.017154036, + -0.0019957346, + 0.032525834, + -0.0012531357, + 0.0, + -0.004177119, + -0.0055323625, + 0.009133968, + -0.040397383, + 0.019901652, + 0.021386849, + -0.0038986444, + 0.019456092, + -0.020644251, + 0.0055694925, + -0.006237831, + -0.018787753, + -0.008168588, + -0.0074631195, + -0.0023206216, + 0.04485298, + 0.0055323625, + -0.019901652, + 0.010099346, + -0.017005516, + 0.0071660797, + 0.012624182, + -0.008725538, + 0.02227797, + -0.0067576505, + 0.005123933, + -0.025099844, + -0.03653587, + 0.028961359, + 0.0061635715, + 0.018639233, + 0.004047164, + -0.020198692, + -0.03787255, + 0.030149518, + -0.0292584, + 0.012401403, + -0.0055323625, + 0.019604612, + -0.016485697, + 0.0077601587, + -0.0104706455, + -0.012252883, + -0.07515101, + 0.031189155, + 0.038615145, + -0.023020567, + -0.019159054, + 0.023763165, + -0.0014851978, + -0.0054952325, + -0.0035459101, + -0.018787753, + 0.022723528, + -0.023911687, + -0.015965877, + -0.0004455594, + -0.0033973902, + 0.0075745094, + -0.009022578, + 0.041882582, + 0.015817357, + -0.010396386, + 0.008094328, + -0.0155203175, + -0.017376816, + -0.041585542, + 0.008651278, + 0.017451076, + -0.03579327, + -0.0141836405, + -0.010619165, + -0.018564973, + 0.015371799, + -0.0008725538, + -0.035941787, + 0.013589561, + 0.005903662, + -0.025990965, + -0.0020328646, + -0.010099346, + 0.002673356, + -0.0005917586, + -0.018936275, + -0.032822873, + -0.02227797, + -0.023763165, + -0.066536866, + 0.004121424, + 0.010099346, + -0.016114399, + 0.033713993, + -0.01485198, + 0.0032303056, + -0.02198093, + -0.015149019, + 0.010173606, + -0.0092824865, + -0.018119415, + -0.025099844, + 0.020198692, + -0.012772703, + -0.012698442, + 0.006497741, + 0.045150016, + -0.032971393, + -0.032377314, + -0.010544905, + 0.005680882, + -0.005420972, + -0.00735173, + -0.011658804, + 0.008911188, + -0.012624182, + 0.0, + 0.030000998, + 0.061784234, + -0.010173606, + -0.008539888, + 0.0020050174, + 0.0074631195, + -0.00039914696, + 0.005161063, + 0.0032303056, + -0.041585542, + -0.022723528, + 0.010247866, + -0.033416953, + 0.024951324, + 0.0011138985, + -0.04247666, + -0.01470346, + -0.0069061704, + 0.04247666, + 0.0062007015, + 0.04247666, + 0.024208726, + 0.011287504, + 0.008836928, + 0.00037826135, + -0.021683889, + -0.010990465, + 0.031486195, + 0.0016430002, + 0.0039357743, + 0.017822376, + 0.010322126, + -0.02198093, + -0.0023020569, + -0.023317607, + -0.020050172, + -0.043070737, + 0.0585168, + -0.012846963, + 0.018713493, + 0.025693925, + 0.040694423, + 0.02955544, + 0.037724026, + 0.014332159, + 0.002859006, + 0.029852478, + -0.06386351, + -0.01470346, + -0.016114399, + -0.00738886, + 0.008799798, + 0.046041135, + 0.010693424, + -0.015891617, + -0.0076116393, + -0.008836928, + -0.018416455, + -0.03534771, + -0.054061204, + -0.0092453575, + -0.010990465, + 0.012327143, + 0.02123833, + -0.00738886, + -0.03638735, + -0.014035121, + -0.10336978, + -0.014926239, + 0.008539888, + -0.0015965878, + -0.009579527, + 0.032525834, + -0.024208726, + 0.0155203175, + -0.009950826, + -0.009393876, + -0.0071660797, + 0.032228798, + 0.05911088, + -0.034456592, + -0.014554939, + 0.023317607, + 0.04217962, + -0.022575008, + 0.052576005, + 0.024654286, + 0.06237831, + -0.039209224, + -0.0067205206, + -0.012252883, + 0.037724026, + -0.027624682, + -0.017896635, + -0.0046412433, + 0.033713993, + 0.03505067, + 0.0031560455, + 0.0019771697, + -0.024951324, + 0.011138985, + -0.032228798, + -0.049308572, + 0.018713493, + 0.015149019, + 0.0129212225, + 0.046338174, + 0.008911188, + -0.03490215, + 0.05138785, + 0.027921721, + 0.016634217, + 0.012846963, + 0.019753134, + -0.020495731, + -0.009802306, + -0.009876567, + -0.039803304, + -0.0020514296, + 0.016856996, + -0.005198193, + 0.0092824865, + 0.040100344, + 0.0022370794, + 0.0292584, + 0.060596075, + -8.528285e-05, + 0.016188657, + -0.034011032, + -0.011584544, + -0.027030602, + 0.014406419, + -0.013292521, + 0.026139485, + 0.015297539, + -0.0006080029, + -0.027921721, + -0.0037686897, + -0.002877571, + 0.028961359, + 0.008874058, + 0.027921721, + 0.006497741, + -0.050199687, + 0.0027661812, + -0.03579327, + -0.030892117, + -0.03519919, + 0.043070737, + -0.005086803, + 0.031189155, + 0.034011032, + -0.009171098, + -0.025842445, + -0.07782437, + -0.039803304, + -0.047823373, + 0.006349221, + 0.019159054, + 0.0070546903, + -0.008651278, + 0.0001485198, + 0.020347212, + 0.031040635, + 0.038912185, + -0.027327644, + 0.037724026, + -0.020644251, + -0.0032488706, + -0.05109081, + 7.4840056e-05, + -0.009950826, + 0.014406419, + -0.012624182, + 0.037129946, + -0.067725025, + 0.00022626061, + -0.008242848, + 0.020792771, + -0.060299035, + -0.043367777, + -0.0039543393, + 0.025842445, + -0.03564475, + 0.04455594, + 0.010247866, + 0.02108981, + 0.00082614133, + -0.002673356, + -0.00096537865, + 0.015817357, + 0.024951324, + -0.017748116, + 0.012327143, + -0.02183241, + -0.03475363, + -0.024357246, + 0.055843443, + 0.022872047, + 0.030149518, + 0.018787753, + -0.016708476, + 0.053170085, + 0.0092453575, + 0.05940792, + 0.0077972887, + 0.028218761, + -0.018787753, + -0.057328638, + -0.02198093, + -0.010619165, + 0.027327644, + -0.004399899, + 0.023614649, + 0.042773698, + 0.00015200072, + -0.02183241, + 0.0154460585, + -0.005198193, + -0.0018008024, + -0.02123833, + 0.0698043, + -0.00080293516, + -0.012104363, + 0.0034902152, + -0.010173606, + 0.046932258, + -0.020644251, + -0.019010535, + -0.02108981, + -0.01477772, + -0.015891617, + -0.030298037, + 0.025990965, + -0.04396186, + -0.04247666, + -0.032228798, + 0.0015873052, + -0.023169087, + -0.0006404916, + 0.025248364, + -0.0036387348, + 0.031040635, + -0.017079776, + 0.010619165, + -0.00023206219, + 0.032377314, + -0.009728046, + 0.0038243847, + 0.023614649, + -0.026436523, + -0.009728046, + -0.025842445, + -0.03579327, + 0.0010349973, + -0.008020069, + 0.0016894126, + -0.0015316104, + 0.027327644, + 0.0020050174, + -0.031783234, + -0.050496727, + 0.017079776, + 0.0092082275, + 0.04485298, + 0.00742599, + -0.011807324, + -0.008688408, + -0.021683889, + -0.01477772, + 0.04485298, + 0.045150016, + 0.018267935, + 0.024208726, + -0.020792771, + 0.022872047, + 0.0056066224, + 0.040694423, + 0.032822873, + 0.048714492, + -0.006237831, + -0.013589561, + 0.046338174, + 0.016262917, + -0.0025619664, + 0.03564475, + 0.005903662, + 0.023614649, + -0.0012531357, + 0.028961359, + 0.06326943, + 0.004715503, + 0.016188657, + 0.0011092572, + -0.013589561, + 0.008465628, + 0.010099346, + -0.010841944, + -0.027030602, + 0.010322126, + -0.018193675, + -0.008539888, + 0.021386849, + -0.012327143, + 0.01477772, + -0.045744095, + -0.015594577, + -0.0027476163, + 0.019901652, + -0.0034716502, + -0.0060893116, + 0.060001995, + -0.022872047, + 0.0035273451, + 0.033862513, + 0.046041135, + 0.0030632208, + -0.039506268, + -0.00742599, + -0.019456092, + -0.010322126, + 0.02970396, + 0.046041135, + -0.02183241, + 0.066833906, + 0.05138785, + 0.0155203175, + 0.037426986, + -0.011138985, + 0.050199687, + -0.012104363, + 0.008094328, + -0.020941291, + -0.011213245, + 0.005123933, + -0.017599596, + -0.057328638, + -0.014035121, + 0.0018564975, + 0.018713493, + -0.017822376, + -0.046635214, + -0.011213245, + 0.026436523, + 0.009876567, + 0.0040100347, + 0.0009839436, + -0.009356746, + -0.010247866, + 0.0031931757, + -0.034011032, + -0.01470346, + 0.040100344, + 0.008205718, + -0.0039172093, + 0.0010257148, + -0.00046412437, + 0.032377314, + 0.015817357, + 0.017599596, + -0.005829402, + -0.045744095, + 0.019901652, + 0.057328638, + 0.011881582, + -0.0019586047, + 0.024802806, + -0.022872047, + 0.020050172, + -0.027030602, + 0.012178623, + 0.017896635, + -0.035941787, + 0.04752633, + -0.04455594, + 0.04485298, + -0.039506268, + 0.011287504, + -0.032971393, + -0.022872047, + 0.010247866, + -0.014554939, + -0.028664319, + 0.020792771, + -0.05109081, + -0.043367777, + 0.03490215, + 0.031931754, + 0.011584544, + 0.019753134, + 0.0012531357, + 0.005792272, + -0.0066833906, + -0.022723528, + 0.033119913, + -0.020198692, + 0.020941291, + 0.015594577, + 0.0104706455, + 0.005123933, + -0.025842445, + 0.020347212, + -0.017970894, + 0.0024134465, + 0.020644251, + -0.020792771, + -0.039506268, + 0.03475363, + 0.0141836405, + 0.05138785, + 0.012772703, + 0.016931256, + -0.02970396, + -0.011510284, + -0.013292521, + -0.053170085, + 0.004288509, + 0.016262917, + -0.025693925, + -0.033862513, + -0.018713493, + -0.025990965, + -0.02212945, + 0.02108981, + -0.038615145, + -0.040694423, + 0.03668439, + -0.03698143, + -0.047823373, + -0.03802107, + -0.016411437, + -0.07158654, + -0.018787753, + -0.069210224, + -0.002933266, + -0.002042147, + -0.029109878, + -0.024951324, + -0.008131458, + -0.004864023, + 0.013366781, + 0.030743597, + -0.0129212225, + 0.038615145, + 0.04485298, + -0.0007333165, + 0.0060893116, + -0.04485298, + 0.010544905, + -0.014035121, + -0.0077230292, + -0.008651278, + -0.041288503, + 0.040397383, + -0.025693925, + 0.037278466, + 0.025990965, + -0.02970396, + -0.00033881076, + 0.039506268, + -0.020792771, + 0.005643752, + -0.035941787, + 0.032080278, + -0.042773698, + 0.03623883, + 0.006237831, + 0.005383842, + 0.0046226787, + -0.011807324, + 0.030298037, + 0.019456092, + 0.039209224, + 0.004288509, + -0.031189155, + -0.040694423, + -0.02242649, + -0.030892117, + -0.009468137, + -0.018045155, + -0.016262917, + 0.025396883, + -0.024951324, + 0.0069804303, + 0.031486195, + -0.024654286, + -0.010916205, + 0.0025619664, + -0.001086051, + -0.0021071245, + 0.011955843, + -0.031040635, + -0.022723528, + 0.050496727, + 0.004232814, + 0.005680882, + -0.013886601, + 0.030298037, + 0.041288503, + -0.05109081, + 0.0016244353, + -0.027921721, + -0.016485697, + 0.021386849, + -0.009950826, + -0.014035121, + 0.023763165, + -0.045447055, + -0.017896635, + -0.017451076, + -0.03831811, + 0.0142579, + -0.016114399, + -0.020792771, + 0.0092082275, + -0.018862013, + -0.011361764, + -0.01477772, + 0.026139485, + -0.016856996, + 0.005680882, + 0.008317108, + -0.020644251, + -0.011955843, + 0.009876567, + 0.010247866, + -0.031783234, + -0.047823373, + 0.013886601, + -0.026585042, + 0.012252883, + 0.033416953, + -0.03549623, + -0.010173606, + -0.017896635, + 0.0073146, + 0.019901652, + 0.02198093, + 0.026882082, + -0.024505766, + -0.004826893, + 0.018564973, + -0.032080278, + 0.0104706455, + 0.010619165, + 0.017525336, + 0.02955544, + -0.04485298, + -0.011436024, + 0.0055694925, + 0.0033788253, + 0.032822873, + 0.009876567, + -0.016856996, + 0.028367281, + 0.034456592, + 0.009579527, + -0.010916205, + -0.04366482, + 0.018490715, + -0.008911188, + -0.010322126, + 0.0019121923, + -0.0066833906, + 0.042773698, + -0.019604612, + 0.026585042, + -0.0031189155, + 0.04485298, + 0.039209224, + 0.04396186, + 0.0008168589, + 0.0018750624, + 0.009133968, + -0.017154036, + 0.035941787, + -0.0155203175, + -0.020198692, + -0.010619165, + 0.004177119, + 0.020050172, + -0.061784234, + 0.009728046, + -0.011733064, + -0.046932258, + 0.023169087, + -0.016782736, + 0.058219757, + 0.038615145, + 0.038912185, + -0.0072403396, + -0.045744095, + -0.0070546903, + -0.017005516, + 0.005012543, + 0.0024505765, + 0.0092824865, + -0.0007286752, + 0.031931754, + 0.0056066224, + 0.028367281, + 0.009950826, + 0.037129946, + -0.00686904, + 0.049308572, + -0.045744095, + 0.03787255, + -0.010396386, + -0.019753134, + -0.02242649, + -0.018862013, + -0.025842445, + -0.046932258, + 0.025693925, + -0.04247666, + 0.033119913, + 0.011584544, + -0.014480679, + 0.0031003507, + 0.018193675, + 0.009356746, + -0.032525834, + 0.03638735, + 0.019307572, + -0.023763165, + -0.03564475, + 0.026585042, + -0.018639233, + 0.017599596, + -0.010767684, + -0.018564973, + -0.048417453, + -0.019901652, + -0.019753134, + 0.026436523, + 0.0037872547, + -0.0038243847, + -0.026288003, + 0.015223279, + 0.026585042, + -0.0013738081, + -0.04485298, + -0.02747616, + 0.012846963, + 0.019604612, + 0.024654286, + 0.03668439, + -0.016931256, + 0.024951324, + -0.037129946, + 0.061190154, + -0.0046412433, + -0.026139485, + 0.03564475, + 0.0064234813, + -0.040397383, + -0.006349221, + 0.016188657, + 0.07425989, + -0.02212945, + 0.014480679, + 0.0047526336, + -0.0018936273, + -0.017970894, + -0.008131458, + 0.015000499, + -0.011733064, + -0.019010535, + -0.013515301, + -0.015074759, + -0.06534871, + 0.012549922, + 0.0075745094, + 0.016337177, + -0.010247866, + 0.027030602, + 0.017154036, + -0.0033416953, + -0.0004502006, + -0.027327644, + 0.008874058, + -0.0065720007, + 0.00369443, + 0.0022556444, + 0.026733562, + -0.010693424, + -0.014332159, + 0.011510284, + -0.023020567, + 0.019307572, + -0.021683889, + -0.034011032, + 0.005680882, + 0.016114399, + 0.023466129, + -0.03475363, + 0.0, + -0.010990465, + -0.030892117, + -0.017376816, + 0.009356746, + 0.039209224, + -0.033268433, + -0.027624682, + -0.004715503, + -0.004381334, + 0.0442589, + -0.023763165, + 0.064457595, + 0.0020050174, + -0.019753134, + 0.034011032, + 0.001847215, + 0.03683291, + -0.016931256, + -0.0023670343, + 0.023169087, + -0.0018193674, + -0.012030103, + 0.01373808, + -0.018416455, + 0.025842445, + 0.010619165, + 0.0030075258, + 0.00062656787, + 0.011213245, + -0.015965877, + -0.0018379325, + -0.031337675, + -0.03623883, + -0.008242848, + -0.010396386, + 0.039803304, + 0.015223279, + 0.017005516, + 0.011436024, + 0.01373808, + -0.028218761, + -0.010841944, + 0.017228296, + -0.014109381, + 0.0038243847, + 0.039209224, + -0.030446557, + 0.020198692, + 0.020347212, + -0.018564973, + -0.011287504, + 0.0142579, + -0.008725538, + -0.017302556, + 0.026733562, + 0.031634714, + -0.025248364, + -0.053764164, + -0.007871549, + -0.024208726, + -0.021386849, + 0.030595077, + -0.009653786, + 0.020050172, + -0.03579327, + 0.009802306, + -0.0055694925, + 0.017228296, + 0.008651278, + 0.0055323625, + 0.0011092572, + 0.013886601, + -0.057625677, + -0.026288003, + -0.0073146, + 0.0060150516, + -0.008948318, + 0.0059779217, + -0.02955544, + -0.027921721, + 0.008168588, + -0.0055694925, + 0.0049382835, + 0.018936275, + -0.00090504246, + -0.0077972887, + -0.0011185397, + 0.023020567, + -0.049011532, + 0.026288003, + -0.02123833, + -0.012995482, + 0.00087719504, + -0.019604612, + -0.009059708, + 0.031189155, + 0.021683889, + -0.046338174, + -0.014554939, + -0.028812839, + -0.008836928, + -0.03668439, + 0.029852478, + -0.029109878, + 0.040694423, + -0.019307572, + -0.038912185, + 0.0013923731, + 0.0035830399, + 0.024060206, + 0.008465628, + 0.013589561, + 0.0014016556, + -0.047229297, + -0.009802306, + 0.010767684, + -0.045744095, + -0.038912185, + -0.011955843, + -0.012327143, + -0.027030602, + -0.015297539, + -0.013812341, + -0.018713493, + -0.0292584, + -0.0021442545, + 0.0070918202, + 0.0031003507, + -0.054655287, + 0.012624182, + -0.0077972887, + -0.0054581026, + 0.019159054, + 0.0022742094, + 0.016931256, + 0.009579527, + -0.023614649, + -0.011287504, + -0.0053095827, + 0.026436523, + -0.040694423, + 0.028367281, + 0.05198193, + -0.027179122, + 0.017005516, + 0.009579527, + -0.010767684, + 0.0031374805, + -0.0077230292, + 0.016931256, + -0.01477772, + -0.040694423, + -0.0060521816, + 0.031634714, + -0.0016801301, + 0.03549623, + 0.0008632713, + 0.013441041, + -0.02198093, + -0.028070241, + 0.0025990964, + -0.03609031, + 0.015371799, + 0.0035830399, + -0.02227797, + -0.011361764, + 0.034011032, + 0.0069433004, + 0.008539888, + 0.045744095, + -0.0014944804, + -0.00023902404, + -0.020198692, + -0.031040635, + 0.028070241, + 0.011361764, + 0.019159054, + -0.017005516, + -0.025545405, + -0.018713493, + 0.0056066224, + 0.041288503, + 0.027624682, + 0.016559957, + 0.027624682, + -0.025545405, + -0.0017079777, + 0.02970396, + -0.014332159, + -0.0046041138, + -0.021386849, + 0.043070737, + 0.0019864521, + -0.01485198, + 0.024802806, + -0.016114399, + -0.010916205, + -0.005346712, + -0.013960861, + -0.025842445, + 0.061784234, + -0.018342195, + -0.02198093, + 0.067427985, + -0.013144001, + 0.022723528, + 0.012698442, + 0.002079277, + 0.018490715, + -0.028812839, + 0.015000499, + -0.038615145, + 0.016559957, + -0.019901652, + 0.014332159, + 0.016485697, + -0.008762668, + -0.009096838, + 0.030892117, + -0.034159552, + -0.019159054, + 0.0035459101, + 0.015668837, + 0.019901652, + -0.018045155, + 0.024505766, + 0.030743597, + 0.017599596, + -0.009876567, + 0.018045155, + -0.037575506, + 0.02242649, + -0.039803304, + -0.00031560456, + -0.011213245, + -0.02227797, + -0.023614649, + -0.035941787, + 0.037278466, + 0.019604612, + -0.0004687656, + -0.018416455, + -0.03787255, + -0.03653587, + 0.009876567, + 0.033862513, + 0.020198692, + -0.03564475, + 0.040100344, + 0.008911188, + 0.02183241, + -0.047229297, + -0.017079776, + 0.012624182, + -0.011064725, + 0.040991463, + -0.012252883, + -0.045447055, + -0.033713993, + 0.04247666, + 0.023317607, + 0.043070737, + 0.030298037, + -0.021535369, + 0.005792272, + 0.027030602, + -0.025545405, + -0.011436024, + -0.004529854, + 0.02212945, + -0.02955544, + -0.049011532, + 0.04485298, + -0.0021628195, + 0.02940692, + 0.025842445, + 0.038615145, + 0.00182865, + -0.039209224, + -0.03609031, + -0.0069804303, + -0.049308572, + -0.004307074, + -0.015000499, + 0.027030602, + -0.008465628, + 0.0, + -0.0073146, + 0.02940692, + -0.024951324, + 0.03579327, + 0.011658804, + -0.031337675, + 0.016411437, + -0.010322126, + -0.011955843, + -0.005792272, + -0.022575008, + -0.0026547913, + -0.040991463, + -0.019901652, + -0.04455594, + 0.024060206, + 0.0016615652, + -3.916049e-05, + 0.024802806, + -0.040100344, + -0.008651278, + 0.005680882, + 0.03638735, + -0.0155203175, + -0.0092824865, + 0.0077972887, + 0.016634217, + -0.0018379325, + -0.016262917, + 0.033119913, + 0.010619165, + -0.012252883, + 0.041288503, + 0.0072032097, + -0.00030864272, + -0.017154036, + -0.038912185, + 0.016188657, + -0.0073146, + 0.024357246, + -0.016559957, + -0.018193675, + -0.02123833, + 0.038615145, + -0.0092824865, + -0.0022927744, + -0.0285158, + 0.0032860003, + -0.010544905, + -0.05138785, + -0.017970894, + -0.019901652, + 0.00077972887, + -0.005049673, + 0.041288503, + 0.031040635, + 0.045744095, + 0.032525834, + -0.025693925, + -0.02198093, + -0.0033602603, + 0.020644251, + -0.0022370794, + 0.012104363, + 0.02242649, + -0.019901652, + -0.03653587, + 0.011658804, + 0.032525834, + 0.039209224, + -0.019901652, + -0.018862013, + -0.012104363, + -0.028664319, + -0.041882582, + 0.00030632206, + 0.079012536, + -0.016337177, + 0.0037315597, + 0.024060206, + -0.034605112, + 0.043367777, + 0.037278466, + 0.01470346, + -0.033119913, + 0.016708476, + 0.0032860003, + -0.004474159, + -0.013366781, + 0.03505067, + 0.016337177, + 0.027030602, + 0.0, + 0.025396883, + 0.079012536, + 0.00062656787, + 0.0032303056, + -0.00077972887, + 0.015074759, + 0.005680882, + 0.008354238, + -0.018193675, + -0.014926239, + 0.010693424, + 0.006831911, + 0.014109381, + -0.02183241, + 0.0005314224, + 0.013366781, + 0.026288003, + -0.0014944804, + -0.009431006, + -0.05495232, + 0.0064234813, + 0.020198692, + 0.02212945, + 0.020050172, + -0.013069742, + 0.032377314, + -0.0036573, + 0.019753134, + -0.004826893, + 0.012104363, + -0.012327143, + -0.022575008, + -0.008762668, + -0.015965877, + 0.0077601587, + 0.033268433, + 0.040397383, + -0.018639233, + -0.020941291, + 0.019901652, + 0.0025062715, + 0.031189155, + 0.025248364, + 0.032228798, + 0.0104706455, + -0.02183241, + -0.008836928, + -0.013812341, + -0.028070241, + 0.032822873, + -0.013960861, + 0.024357246, + 0.0002552684, + -0.010099346, + 0.025099844, + -0.03564475, + -0.005420972, + -0.025396883, + 0.0072774696, + -0.03623883, + -0.020792771, + -0.017525336, + 0.004901153, + -0.0069804303, + 0.015891617, + -0.00029936022, + 0.012624182, + -0.04247666, + -0.005383842, + 0.010099346, + 0.0016801301, + -0.012549922, + -0.024802806, + 0.000429315, + -0.0022927744, + -0.024357246, + 0.052576005, + -0.008465628, + 0.015965877, + -0.024208726, + 0.018119415, + -0.005420972, + 0.006497741, + 0.020347212, + -0.0015223279, + -0.018713493, + -0.012327143, + 0.017525336, + -0.02970396, + 0.00077044644, + -0.02747616, + -0.004901153, + -0.0067205206, + 0.031337675, + -0.0067947805, + 0.011510284, + 0.024060206, + -0.012995482, + -0.005123933, + 0.025248364, + -0.0024320115, + 0.009096838, + 0.025693925, + 0.009096838, + -0.033268433, + 0.015743097, + -0.012698442, + 0.0023299041, + 0.0015594577, + 0.02955544, + 0.015668837, + -0.012252883, + -0.0442589, + 0.0070918202, + 0.008836928, + -0.03519919, + 0.02955544, + -0.0129212225, + -0.0013459605, + 0.046932258, + 0.012772703, + -0.024802806, + -0.017228296, + 0.016337177, + -0.030149518, + 0.039803304, + 0.0011092572, + 0.027030602, + -0.0035087802, + 0.014109381, + 0.050199687, + 0.008279978, + 0.023169087, + 0.023317607, + -0.020347212, + -0.037724026, + -0.017376816, + -0.013292521, + 0.031931754, + 0.019901652, + 0.0146292, + -0.0093196165, + 0.048714492, + 0.03534771, + 0.017525336, + 0.011881582, + 0.033119913, + -0.024357246, + -0.041882582, + -0.016634217, + 0.029109878, + -0.0037872547, + 0.017525336, + 0.004065729, + -0.008539888, + -0.014109381, + -0.030743597, + 0.0028033112, + 0.013663822, + -0.026585042, + -0.024802806, + 0.011658804, + 0.020941291, + 0.013144001, + -0.011436024, + 0.042773698, + -0.012846963, + -0.017079776, + 0.019010535, + 0.040694423, + -0.025396883, + 0.009876567, + -0.019901652, + -0.025248364 + ], + "resource_items": { + "embedding": { + "unit": "input_token", + "item_count": 1, + "item_bytes": 135, + "item_units": 29 + } + }, + "calibration_baseline_memory_bytes": 3581825024, + "calibration_peak_memory_bytes": 3581874176, + "output_text": null + }, + { + "prompt_id": "qwen3-embed-document-native", + "max_tokens": 1, + "prompt_tokens": 23, + "completion_tokens": 0, + "reasoning_tokens": 0, + "token_count": 0, + "token_ids": [], + "token_prefix": [], + "reproducibility_runs": 1, + "fingerprint": "0294d92ebf46bbb222d1c2f998efcfe24ae24a9ab156f1e496157183d1b5dda5", + "embedding_vector": [ + -0.0002605405, + -0.029180536, + -0.0058187377, + -0.0024751348, + -0.00089018, + 0.07040383, + 0.008221501, + 0.0384442, + 0.024317114, + -0.012911229, + 0.00521081, + -0.024548704, + -0.0005500299, + -0.03265441, + -0.04052852, + -0.049097408, + -0.03450714, + -0.012737536, + -0.027327804, + -0.004226546, + -0.018990507, + 0.021422219, + 0.028138373, + 0.042381253, + -0.042844437, + 0.019106302, + -0.022348585, + -0.014648166, + 0.0356651, + 0.0042554946, + 0.0013606003, + -0.03728624, + 0.005645044, + -0.01314282, + 0.0044002393, + -0.022695972, + 0.013490208, + -0.0038791585, + -0.02906474, + 0.033812366, + 0.001657327, + 0.0069187977, + 0.008858377, + 0.0034883479, + 0.0, + 0.009032071, + 0.0011869067, + -0.061603352, + -0.03450714, + -0.010016334, + 0.003285705, + 0.021885403, + 0.0356651, + -0.024896093, + 0.023622338, + -0.007874113, + 0.018179936, + 0.0022869667, + -0.0017514111, + -0.024085522, + -0.0015849547, + 0.007758317, + -0.00025330327, + -0.028138373, + 0.0020119515, + -0.018064141, + -0.0023738134, + -0.012448046, + 0.0059055844, + -8.775149e-05, + 0.047013085, + 0.0028080477, + 0.0047765756, + 0.018295733, + 0.0058766357, + -0.04052852, + -0.009437356, + 0.034738734, + -0.0038502095, + 0.013084923, + 0.011926965, + 0.0021856453, + -0.009495254, + 0.018990507, + 0.011926965, + -0.0017586483, + 0.026980417, + 0.007237236, + -0.016095612, + -0.0047765756, + -0.0072082873, + 0.027906783, + 0.03149645, + 0.005587146, + -0.00087208697, + 0.009147867, + -0.015053451, + -0.019916873, + -0.0016428526, + -0.0022580177, + 0.020959036, + 0.0036909904, + -0.0014546844, + -0.009958437, + -0.011058496, + -0.038907383, + -0.0144165745, + 0.01783255, + 0.0070345933, + 0.015979817, + 0.00631087, + 0.0141849825, + 0.024896093, + 0.008221501, + -0.014937655, + -0.0075846235, + -0.004747627, + 0.042149663, + 0.012679637, + 0.057434704, + -0.002605405, + 0.015053451, + -0.0014329727, + -0.016674591, + 0.009784743, + -0.04029693, + 0.0006585885, + -0.006716155, + 0.025359275, + 0.022116993, + 0.020380057, + -0.015864022, + 0.013953391, + -0.0049792184, + -0.010074233, + 0.0025185582, + 0.006831951, + -0.016558796, + -0.025590867, + 0.016327204, + -0.020148465, + -0.012795433, + 0.007989909, + -0.024548704, + -0.01725357, + -0.034970324, + 0.0012954653, + -0.019801078, + -0.004747627, + -0.00045232725, + 0.009553151, + 0.01343231, + 0.021306423, + 0.0044291886, + 0.014474472, + 0.030570084, + -0.007874113, + 0.0143007785, + 0.0075267255, + 0.0016211409, + 0.0014836334, + 0.005645044, + 0.03867579, + 0.012100658, + -0.014011289, + 0.007874113, + -0.020148465, + 0.02987531, + -0.008395194, + -0.0026198796, + -0.012795433, + -0.00712144, + 0.0064266655, + -0.01262174, + 0.004023903, + 0.007642521, + -0.01621141, + 0.012737536, + -0.011000599, + 0.028138373, + -0.0059924317, + -0.011926965, + -0.04029693, + -0.01424288, + 0.0054134526, + 0.02628564, + 0.0036620414, + -0.012911229, + 0.012216454, + -0.0047765756, + -0.010711109, + 0.011232191, + -0.010247926, + 0.01621141, + -0.015169247, + 0.009668947, + -0.019685281, + -0.001411261, + 0.011290088, + 0.0074688275, + 0.010711109, + 0.006252972, + -0.012158557, + -0.02524348, + 0.027790986, + -0.018990507, + 0.008510989, + -0.0014402099, + 0.014821859, + -0.018874712, + 0.0058766357, + -0.022116993, + -0.015285042, + -0.029180536, + 0.0074688275, + 0.028833149, + -0.016790388, + -0.016558796, + 0.035201915, + 0.016095612, + -0.008395194, + -0.0045739333, + -0.0192221, + 0.010363722, + -0.028948944, + 0.0010493993, + -0.005529248, + -0.0035751946, + 0.0, + -0.017948346, + 0.02026426, + 0.005529248, + 0.0071503893, + 0.002605405, + -0.013316514, + -0.010942701, + -0.021074831, + 0.0059924317, + 0.01621141, + -0.025822459, + -0.022116993, + -0.014127085, + -0.0022001197, + 0.023506543, + 0.017485162, + -0.022580177, + -0.0024461858, + -0.008047806, + -0.00712144, + -0.006108227, + -0.011753271, + 0.0034015009, + 0.0009770269, + -0.016790388, + -0.02385393, + -0.007758317, + -0.018179936, + -0.028138373, + 0.0035751946, + 0.009726846, + -0.013374412, + -0.007295134, + -0.0027790987, + -0.0038212605, + -0.017369367, + -0.016095612, + 0.006368768, + 0.0023882878, + 0.00018364486, + -0.030570084, + 0.078741126, + -0.016674591, + 0.012042761, + 0.0032133327, + 0.027790986, + -0.014821859, + -0.01013213, + 0.003285705, + 0.012737536, + 0.015516634, + -0.007642521, + 0.0009227476, + 0.009205764, + -0.013663902, + 0.013200719, + 0.01233225, + 0.045623537, + -0.016558796, + -0.007874113, + 0.015979817, + 0.01563243, + -0.029412128, + 0.012390148, + -0.007353032, + -0.00631087, + -0.0143007785, + 0.021074831, + -0.011579578, + 0.005152912, + 0.0018165462, + -0.0141849825, + -0.0068608997, + -0.0044291886, + 0.04631831, + 0.016327204, + 0.0019106303, + 0.00961105, + 0.007932011, + -0.019337894, + -0.0022145943, + -0.004284444, + -0.007932011, + -0.00045051795, + -0.002113273, + 0.003618618, + -0.001657327, + 0.018295733, + -0.008858377, + -0.0054713506, + -0.022116993, + -0.00094807794, + -0.024317114, + 0.038212605, + -0.0074688275, + 0.034970324, + 0.020148465, + 0.025590867, + 0.011521679, + 0.030570084, + 0.026169846, + -0.0070056445, + 0.026401438, + -0.06114017, + -0.024085522, + -0.007758317, + -0.003618618, + 0.020727444, + 0.049329, + 0.00961105, + -0.00073819805, + -0.009263662, + -0.008221501, + -0.037054647, + 0.032191224, + -0.054887198, + -0.012853331, + -0.020148465, + 0.006831951, + -0.009958437, + -0.0053845034, + -0.028022578, + -0.025127683, + -0.080130674, + -0.021769606, + 0.040065337, + 0.020148465, + -0.009032071, + 0.014590268, + 0.01864312, + 0.0074688275, + -0.008916275, + -0.003285705, + -0.0021566963, + 0.012911229, + 0.008742581, + -0.019685281, + -0.026980417, + 0.026169846, + 0.03126486, + -0.025590867, + 0.021538014, + -0.031728044, + 0.038212605, + -0.0246645, + -0.024780296, + -0.007816215, + 0.02084324, + -0.014648166, + -0.017021978, + 0.011695374, + 0.029527923, + 0.033349182, + -0.015979817, + 0.014763962, + -0.020380057, + 0.010826905, + -0.03126486, + -0.010016334, + -0.011926965, + 0.0055002994, + 0.018990507, + 0.018758915, + 0.011290088, + -0.009437356, + 0.019453691, + -0.014821859, + 0.016443001, + 0.017137775, + 0.045160353, + -0.017137775, + -0.017021978, + 0.002142222, + -0.05233969, + -0.011463782, + 0.011521679, + -0.014937655, + 0.0015198196, + 0.020032668, + 0.013548106, + -0.005268708, + 0.035896692, + -0.022811769, + 0.02026426, + -0.05071855, + -0.026517233, + 0.002142222, + 0.030338494, + -0.016790388, + -0.02327495, + 0.005702942, + 0.010016334, + -0.028369965, + -0.010711109, + 0.0005138438, + 0.02524348, + 0.016558796, + -0.017948346, + 0.009263662, + -0.055581972, + 0.0023882878, + -0.011521679, + -0.0026922517, + -0.016558796, + 0.021769606, + -0.015748225, + 0.0246645, + 0.0058766357, + -0.02026426, + -0.025127683, + -0.024896093, + -0.013374412, + -0.042381253, + -0.0070635425, + 0.027212007, + -0.0032278073, + 0.017600957, + 0.0034015009, + 0.026517233, + 0.029527923, + 0.004863423, + -0.024317114, + -0.008974173, + -0.026980417, + -0.020148465, + -0.048171043, + -0.011521679, + 0.011926965, + -0.009958437, + -0.027096212, + 0.020380057, + -0.11347986, + -0.008858377, + 0.025938254, + -0.01725357, + -0.028948944, + -0.026748825, + 0.0064266655, + 0.035201915, + 0.010884803, + 0.010595313, + -0.0029672668, + 0.0058766357, + 0.00081057043, + 0.005847687, + 0.0027067263, + 0.011058496, + 0.02987531, + -0.014474472, + 0.029527923, + -0.00063325814, + -0.0043133926, + -0.032191224, + 0.02825417, + 0.021306423, + 0.033349182, + 0.029412128, + -0.009263662, + 0.042844437, + 0.0055581974, + 0.047244675, + -0.0069477465, + -0.00011534345, + -0.005268708, + -0.013779697, + -0.014763962, + -0.0020119515, + 0.019106302, + -0.007758317, + 0.015516634, + 0.019916873, + 0.0009227476, + -0.0136060035, + 0.019106302, + -0.017021978, + 0.0007092491, + -0.020148465, + 0.05257128, + -0.010074233, + -0.022116993, + 0.012216454, + 0.008279398, + 0.028369965, + -0.022116993, + -0.046781495, + -0.00631087, + -0.0035317712, + -0.011058496, + -0.020380057, + 0.0014908706, + -0.028022578, + -0.023506543, + -0.0014040238, + 0.03728624, + -0.03265441, + -0.010942701, + 0.011637475, + -0.012911229, + 0.016327204, + 0.003155435, + 0.009842641, + -0.021074831, + 0.0030106902, + -0.013490208, + -0.0035317712, + 0.013548106, + -0.01424288, + 0.0043133926, + -0.015285042, + 0.013548106, + -0.0038791585, + -0.011405884, + -0.012042761, + -0.0019540538, + 0.016790388, + 0.013779697, + -0.021538014, + -0.030338494, + -0.005181861, + -0.036823057, + 0.045623537, + 0.005587146, + -0.009726846, + -0.016327204, + -0.026401438, + -0.01343231, + 0.012737536, + 0.047939453, + 0.0010132131, + 0.020611648, + -0.0044870866, + 0.0027356753, + 0.031033268, + 0.010479518, + 0.018064141, + 0.028717352, + 0.0013895493, + -0.0141849825, + 0.029643718, + -0.009495254, + -0.027906783, + 0.017948346, + 0.007642521, + -0.00020716588, + 0.0058187377, + 0.014011289, + 0.003647567, + -0.018064141, + 0.007758317, + -0.010711109, + 0.0010059758, + -0.00020264261, + 0.011232191, + -0.0058766357, + 0.0054713506, + -0.023390748, + -0.020380057, + -3.23414e-05, + 0.006774053, + 0.018411528, + -0.0062819207, + -0.022116993, + 0.0031120116, + 0.006195074, + 0.004023903, + -0.014474472, + -0.016095612, + 0.008105705, + -0.042844437, + 0.004226546, + 0.011521679, + 0.024317114, + 0.0052976566, + -0.045623537, + 0.020611648, + -0.0007418167, + -0.0032278073, + 0.0003636711, + 0.036359873, + -0.03242282, + 0.045391943, + -0.005239759, + 0.02628564, + 0.04145489, + -0.005326606, + 0.039833747, + -0.027559394, + -0.0054134526, + -0.016095612, + -0.021190627, + 0.024432909, + -0.034738734, + -0.018874712, + 0.009205764, + -0.025938254, + 0.011174292, + -0.036591467, + -0.036823057, + -0.004226546, + 0.022695972, + -0.004023903, + -0.027096212, + -0.01453237, + 0.014011289, + -0.017716754, + 0.011869067, + -0.010190029, + -0.01783255, + 0.0011434833, + -0.021885403, + -0.015864022, + 0.023622338, + 0.011290088, + 0.0356651, + 0.0050950143, + 0.012448046, + -0.017021978, + -0.042612847, + -0.023506543, + 0.028138373, + 0.020611648, + -0.0029527922, + 0.026633028, + -0.031728044, + 0.0060213804, + -0.0068898485, + -0.012505944, + 0.028369965, + -0.0016645642, + 0.0356651, + -0.004342342, + 0.014937655, + -0.020959036, + 0.0062819207, + 0.00932156, + -0.026169846, + 0.017485162, + -0.010074233, + -0.015285042, + 0.0356651, + -0.023043359, + -0.022811769, + 0.007816215, + -0.018990507, + -0.005326606, + 0.010479518, + -0.0006368768, + 0.0029383178, + -0.0137217995, + -0.010826905, + 0.021190627, + 0.021422219, + 0.025938254, + 0.015516634, + 0.02026426, + -0.010537416, + -0.013663902, + -0.0028948945, + -0.018295733, + 0.012448046, + 0.025127683, + -0.004342342, + -0.036823057, + 0.012969127, + 0.037749425, + 0.011347986, + 0.014648166, + 0.011869067, + -5.2696127e-05, + -0.0028369965, + -0.028138373, + -0.054192424, + 0.003068588, + 0.024548704, + -0.0063398187, + 0.0010204503, + -0.003314654, + -0.0043133926, + -0.010479518, + 0.016327204, + -0.013374412, + -0.017948346, + 0.007237236, + -0.029643718, + -0.05975062, + -0.034738734, + -0.002301441, + -0.051876508, + -0.03265441, + -0.033117592, + -0.010595313, + 0.0022290687, + -0.010363722, + -0.02987531, + -0.0023159154, + -0.001071111, + -0.0008395194, + 0.010653212, + -0.0007418167, + 0.044233985, + 0.01262174, + 0.018064141, + 0.005731891, + -0.040065337, + 0.013374412, + -0.004631831, + 0.012505944, + -0.010074233, + -0.016327204, + 0.013316514, + -0.011290088, + 0.022695972, + 0.012679637, + 0.0074109295, + 0.0059345337, + 0.021306423, + -0.003343603, + 0.00016736108, + -0.020380057, + 0.029296331, + -0.015979817, + 0.005702942, + -0.0009082731, + 0.02084324, + 0.027559394, + -0.004226546, + 0.052108098, + 0.0035317712, + 0.045160353, + 0.0074109295, + -0.024085522, + -0.043770805, + -0.018411528, + 0.003097537, + -0.011405884, + -0.03867579, + -0.016906183, + 0.0027501497, + -0.04770786, + -0.008337296, + 0.02165381, + -0.019569486, + 0.02848576, + -0.0010204503, + -0.009842641, + 0.033812366, + -0.017369367, + -0.019685281, + -0.0012303301, + 0.054887198, + 0.029180536, + 0.016558796, + 0.0053555546, + 0.018527323, + 0.043076027, + -0.034738734, + 0.0012013812, + -0.036128283, + -0.026517233, + 0.00020354726, + -0.011869067, + 0.007758317, + 0.023390748, + -0.028138373, + -0.025706662, + -0.005529248, + -0.030338494, + 0.02084324, + -0.026169846, + -0.030801676, + -0.006629308, + -0.015053451, + -0.014474472, + -0.014937655, + 0.014937655, + 0.004168648, + 0.009263662, + 0.014358676, + 0.017137775, + 0.013779697, + 0.030338494, + 0.023043359, + -0.049097408, + -0.044233985, + -0.0025185582, + -0.0012665163, + -0.004342342, + 0.0001592192, + -0.011174292, + 0.009147867, + -0.01424288, + 0.01424288, + 0.0033291285, + 0.012911229, + 0.014648166, + -0.010479518, + -0.02385393, + 0.022580177, + -0.02223279, + -0.002663303, + 0.037054647, + 0.00521081, + -0.0027790987, + 0.00020535657, + 0.0033001797, + 0.0009010359, + -0.00018183555, + 0.012042761, + -0.017369367, + -0.01621141, + -0.012042761, + 0.025590867, + 0.013258616, + 0.026633028, + -0.02686462, + -0.009147867, + 0.011290088, + -0.011174292, + 0.025011888, + 0.006774053, + 0.011000599, + -0.020380057, + 0.056276746, + 0.015169247, + 0.04446558, + 0.030106902, + 0.036591467, + -0.0024172368, + -0.0022145943, + 0.005702942, + -0.0024172368, + 0.020148465, + -0.013953391, + -0.035896692, + -0.0031120116, + 0.0, + 0.0055581974, + -0.028833149, + -0.014821859, + 0.009784743, + -0.03450714, + 0.012390148, + -0.01262174, + 0.034738734, + 0.019916873, + 0.011579578, + -0.01783255, + -0.012737536, + 0.008742581, + -0.004863423, + 0.0041107503, + 0.015400838, + 0.003184384, + 0.015053451, + 0.025590867, + -0.0074688275, + 0.018295733, + 0.013027024, + 0.033580776, + 0.008279398, + 0.023622338, + -0.035201915, + 0.036591467, + -0.006252972, + 0.0049502696, + 0.011984863, + 0.004139699, + 0.0039081075, + -0.03728624, + 0.012563841, + -0.03867579, + 0.027559394, + 0.005673993, + -0.014127085, + 0.0063398187, + 0.039138973, + -0.015053451, + -0.01783255, + 0.035896692, + 0.0, + -0.0192221, + -0.03867579, + 0.015400838, + -0.022001198, + 0.004023903, + -0.027443599, + -0.04052852, + -0.029412128, + 0.010884803, + -0.041223295, + 0.0144165745, + -0.0047765756, + 0.0018961559, + -0.027790986, + 0.0011941439, + 0.026633028, + -0.023738135, + -0.04145489, + -0.011521679, + -0.0075846235, + 0.007642521, + 0.02165381, + 0.009668947, + -0.014706064, + 0.029180536, + -0.027906783, + 0.051644918, + 0.016674591, + -0.016327204, + 0.00021711708, + -0.008510989, + -0.022348585, + 0.010711109, + 0.0052976566, + 0.055581972, + -0.02686462, + 0.015748225, + 0.029643718, + 0.007700419, + -0.0019540538, + 0.009379458, + 0.012448046, + -0.021306423, + -0.015400838, + 0.015864022, + -0.014648166, + -0.025475072, + 0.007874113, + 0.011000599, + 0.01013213, + 0.00046680172, + -0.0021566963, + 0.029180536, + 0.0047765756, + 0.016558796, + 0.0029383178, + -0.004544984, + 0.0143007785, + 0.001809309, + 0.015979817, + 0.036823057, + -0.010711109, + -0.0030396392, + 0.007758317, + -0.015748225, + 0.016327204, + -0.034738734, + -0.018874712, + 0.009668947, + 0.005673993, + 0.00712144, + -0.015400838, + 0.0016428526, + 0.007874113, + -0.016790388, + -0.020380057, + 0.008684684, + 0.021885403, + -0.015516634, + 0.0065424615, + -0.016790388, + -0.0024027624, + 0.030801676, + -0.023506543, + 0.023043359, + 0.02026426, + 0.009784743, + 0.027790986, + 0.015285042, + 0.021074831, + -0.018874712, + -0.017137775, + 0.030106902, + 0.0017441739, + -0.016674591, + -0.0027501497, + -0.034970324, + 0.0029672668, + -0.0025619816, + 0.023969727, + 0.011695374, + -0.006195074, + -0.007989909, + -0.010942701, + -0.01725357, + 0.0029093688, + -0.014011289, + -0.0017514111, + 0.038907383, + -0.010653212, + 0.024432909, + 0.016443001, + 0.0137217995, + -0.028138373, + -0.011000599, + 0.009900539, + -0.0138375955, + 0.009032071, + 0.02686462, + -0.015864022, + 0.005587146, + -0.0069477465, + -0.012853331, + -0.011579578, + 0.019801078, + 0.011405884, + -0.015169247, + 0.028022578, + 0.018295733, + 0.0054424014, + -0.010305824, + -0.03728624, + -0.0143007785, + -0.024317114, + 0.006224023, + -0.0064845635, + 0.0037054648, + -0.027327804, + 0.021074831, + -0.019453691, + 0.018527323, + 0.0060792784, + 0.0138375955, + -0.008858377, + -0.008684684, + -0.04168648, + -0.010942701, + 0.0054713506, + 0.010305824, + -0.0011796695, + 0.007874113, + -0.007989909, + -0.03126486, + 0.016095612, + -0.010942701, + -0.011926965, + -0.0031988584, + -0.017137775, + 0.011926965, + -0.0059924317, + 0.010595313, + -0.019569486, + -0.013258616, + -0.03149645, + -0.016906183, + 0.011984863, + -0.02628564, + 0.003589669, + 0.0048344736, + -0.012042761, + -0.030106902, + -0.0356651, + -0.029296331, + -0.015979817, + -0.039370563, + 0.02686462, + -0.024548704, + 0.016674591, + -0.0044002393, + -0.025822459, + 0.017716754, + -0.0035462456, + 0.002142222, + 0.00031120115, + 0.012853331, + 0.027443599, + -0.02524348, + 0.0011724322, + 0.002301441, + 0.0064556147, + -0.02628564, + -0.002113273, + -0.008974173, + -0.020148465, + -0.0039660055, + 0.0011434833, + -0.030106902, + -0.018179936, + -0.0023882878, + -0.01563243, + 0.013953391, + -0.050023776, + 0.020032668, + -0.0029527922, + 0.008395194, + 0.031728044, + 0.031959634, + -0.008684684, + 0.010305824, + -0.02165381, + -0.013779697, + 0.011405884, + 0.009263662, + -0.036591467, + 0.012158557, + 0.035896692, + -0.023506543, + 0.0069766957, + -0.00015831453, + -0.012100658, + 0.008626785, + -0.007874113, + 0.029412128, + 0.009147867, + -0.008163602, + 0.01725357, + 0.0016645642, + 0.006195074, + 0.010826905, + 0.0141849825, + 0.007237236, + -0.024548704, + -0.026169846, + -0.019453691, + -0.007324083, + 0.008800479, + 0.02906474, + -0.0050950143, + 0.005673993, + 0.024085522, + 0.021538014, + -0.027559394, + 0.04469717, + -0.0070056445, + -0.023969727, + -0.010884803, + -0.018990507, + 0.022580177, + 0.02987531, + -0.0075846235, + 0.0070345933, + -0.03149645, + -0.0028080477, + 0.012563841, + 0.013895493, + -0.0035317712, + -0.013490208, + 0.00961105, + -0.015979817, + -0.008974173, + 0.005673993, + 0.00073096086, + -0.030801676, + -0.019685281, + 0.0356651, + 0.0009625524, + 0.009726846, + 0.020148465, + -0.030106902, + 0.03149645, + -0.008337296, + -0.0054134526, + -0.0137217995, + 0.04145489, + -0.020148465, + -0.0064266655, + 0.026980417, + -0.001628378, + 0.017021978, + 0.005587146, + -0.010479518, + 0.011984863, + -0.020495852, + -0.0038502095, + -0.026633028, + 0.012563841, + -0.0074688275, + -0.0014619217, + -0.009842641, + -0.031959634, + -0.01262174, + -0.005587146, + -0.02906474, + -0.007324083, + -0.017948346, + 2.6687308e-05, + 0.025590867, + 0.001838258, + -0.0057608397, + 0.008568888, + 0.047939453, + -0.0036620414, + 0.015053451, + -0.021422219, + 0.030801676, + -0.004747627, + -0.013953391, + 0.0044291886, + 0.0074109295, + 0.013084923, + -0.016674591, + -0.004805525, + 0.015979817, + 0.0060213804, + -0.012911229, + -0.015516634, + -0.023043359, + -0.005731891, + 0.025011888, + 0.014706064, + -0.020959036, + 0.017485162, + 0.020495852, + 0.0032422817, + -0.010826905, + -0.017485162, + -0.006687206, + -0.026980417, + 0.019453691, + -0.0009625524, + -0.023043359, + -0.015285042, + 0.023622338, + 0.027096212, + 0.034043957, + 0.045391943, + 0.011232191, + -0.0075846235, + 0.010247926, + -0.015979817, + -0.0075846235, + -0.022464382, + 0.01864312, + -0.043076027, + -0.028601557, + 0.03242282, + 0.011811169, + 0.045391943, + 0.006195074, + 0.025127683, + 0.027790986, + -0.025822459, + -0.04330762, + -0.020959036, + -0.051413324, + 0.006050329, + -0.020611648, + 0.020380057, + 0.00010584458, + 0.0055581974, + -0.017369367, + 0.016327204, + -0.0137217995, + 0.0140691865, + 0.011811169, + -0.01725357, + 0.017369367, + -0.0008033332, + -0.04747627, + 0.013490208, + -0.007324083, + -0.014358676, + -0.0044002393, + -0.008163602, + -0.0356651, + 0.0064266655, + -0.015169247, + -0.012274352, + 0.021769606, + -0.01314282, + -0.01453237, + 0.0034159755, + 0.013258616, + -0.05048696, + -0.005008167, + -0.007381981, + -0.0008503752, + 0.024780296, + -0.017485162, + 0.014648166, + 0.008395194, + 0.0036620414, + 0.0029962158, + 0.013663902, + -0.012969127, + -0.0014402099, + -0.033117592, + 0.004544984, + -0.012448046, + -0.01042162, + -0.0032133327, + 0.0032278073, + -0.015979817, + 0.02686462, + 0.010942701, + 0.018527323, + -0.022580177, + -0.008974173, + 0.008395194, + -0.010942701, + -0.013895493, + -0.009726846, + -0.014648166, + 0.0069187977, + 0.030338494, + 0.022001198, + 0.026517233, + 0.020148465, + -0.008974173, + -0.03149645, + -0.0014040238, + 0.010653212, + -0.014648166, + -0.0036620414, + 0.019685281, + -0.00066944433, + -0.02385393, + -0.0033001797, + 0.028717352, + 0.031033268, + -0.017485162, + -0.005731891, + -0.022116993, + -0.02987531, + -0.03126486, + -0.0037344138, + 0.038907383, + -0.044928763, + 0.0017296994, + 0.024432909, + -0.021306423, + 0.028369965, + 0.01621141, + 0.01783255, + -0.0020264261, + 0.01725357, + 0.02524348, + 0.015053451, + -0.0016500899, + 0.014127085, + 0.006658257, + 0.012795433, + -0.01314282, + 0.024780296, + 0.03242282, + 0.023390748, + -0.0016718016, + 0.0019974771, + 0.0019251048, + 0.011116395, + 0.021769606, + -0.0140691865, + 0.006050329, + 0.010595313, + -0.014648166, + 0.02385393, + -0.017021978, + -0.003618618, + -0.0018454952, + 0.0032278073, + -0.017485162, + 0.0074688275, + -0.03450714, + 0.013895493, + -0.005326606, + 0.016906183, + 0.00010584458, + -0.042844437, + 0.033349182, + 0.02165381, + 0.0070924913, + -0.021190627, + 0.0023882878, + -0.026633028, + 0.006513512, + 0.011174292, + -0.020032668, + 0.0045739333, + -0.007932011, + -0.02165381, + -0.0048344736, + -0.009958437, + 0.003589669, + -0.038212605, + 0.011811169, + 0.03427555, + 0.0045160353, + 0.015516634, + -0.008337296, + -0.0068030017, + -0.009147867, + -0.008684684, + 0.029296331, + -0.006831951, + -0.0047765756, + 0.010247926, + -0.005239759, + 0.021885403, + -0.020380057, + 0.00027863358, + -0.03242282, + -0.0015560058, + -0.025590867, + -0.009900539, + -0.0024172368, + -0.03149645, + -0.022695972, + 0.012390148, + 0.010942701, + -0.007237236, + -0.034738734, + -0.003097537, + 0.002663303, + 0.012737536, + -0.0070345933, + -0.012042761, + 0.009842641, + -0.010884803, + 0.0, + 0.054424014, + -0.011637475, + -0.008221501, + -0.011347986, + -0.017137775, + -0.022927564, + -0.00015831453, + -0.002084324, + -0.018411528, + -0.008337296, + -0.011811169, + 0.0017948345, + -0.023506543, + -0.0068898485, + 0.0028804198, + -0.013663902, + -0.012274352, + 0.009784743, + -0.0019251048, + -0.011405884, + 0.0140691865, + -0.030570084, + 0.009437356, + 0.01783255, + -0.003184384, + 0.013663902, + 0.02524348, + -0.012737536, + -0.021538014, + 0.013548106, + -0.013490208, + -0.0049792184, + -0.02026426, + 0.004718678, + 0.011463782, + 0.0021856453, + -0.00466078, + -0.0016645642, + 0.03126486, + -0.011695374, + 0.0027067263, + -0.03427555, + 0.0004812762, + 0.028369965, + 0.0057608397, + 0.012274352, + -0.026980417, + -0.008858377, + -0.026633028, + -0.0020553751, + 0.007700419, + 0.025706662, + -2.8157372e-05, + 0.0006368768, + 0.04330762, + 0.00031120115, + 0.02987531, + 0.0060792784, + 0.004226546, + -0.010826905, + -0.012679637, + -0.025359275, + 0.030338494, + 0.002663303, + 0.0053845034, + -0.0013533632, + 0.027212007, + 0.008800479, + 0.01233225, + 0.005152912, + 0.011579578, + -0.027212007, + -0.027212007, + -0.020032668, + 0.023622338, + -0.0006766816, + 2.6348063e-05, + -0.00093360344, + -0.006368768, + -0.007989909, + -0.021885403, + -0.009553151, + -0.004081801, + -0.034738734, + 0.0030106902, + 0.016095612, + 0.012563841, + -0.00030396393, + 0.01262174, + 0.031033268, + -0.009784743, + 0.011290088, + 0.028601557, + 0.0024317114, + -0.0136060035, + 0.009553151, + 0.008221501, + -0.026517233, + 0.0141849825, + 0.017485162, + -0.007324083, + -0.005181861, + 0.018295733, + 0.008163602, + 0.010942701, + 0.007642521, + 0.011579578, + -0.00073096086, + 0.047244675, + 0.006137176, + 0.01783255, + 0.022695972, + -0.011000599, + -0.0144165745, + 0.01563243, + 0.020032668, + -0.028138373, + 0.0140691865, + 0.00631087, + 0.013084923, + 0.008800479, + 0.0024317114, + -0.010826905, + 0.046086717, + -0.053960834, + 0.031728044, + -0.006513512, + 0.015285042, + -0.0054134526, + 0.011984863, + 0.0015125823, + -0.025938254, + -0.030338494, + 0.007353032, + 0.017716754, + 0.012100658, + 0.0021856453, + 0.007266185, + -0.0062819207, + -0.02628564, + -0.00631087, + 0.007758317, + 0.008684684, + 0.005587146, + -0.00047765757, + -0.0014402099, + 0.032886002, + 0.00020987984, + -0.0059924317, + 0.0021277473, + -0.029643718, + 0.0030251646, + 0.008684684, + -0.0038791585, + 0.02848576, + -0.0041107503, + -0.002764624, + -0.0010566365, + 0.010884803, + 0.02987531, + 0.038212605, + 0.012274352, + -0.026517233, + 0.0016139037, + -0.0031409604, + 0.0068030017, + -0.010942701, + -0.017137775, + 0.027790986, + -0.007642521, + 0.004023903, + -0.00011760509, + -0.011984863, + -0.010826905, + -0.010942701, + -0.016790388, + -0.009089968, + 0.0026777773, + -0.034738734, + 0.024201317, + 0.01233225, + 0.019106302, + 0.014706064, + 0.011174292, + -0.008163602, + -0.004602882, + 0.036359873, + 0.016906183, + -0.00466078, + -0.013490208, + 0.0043712906, + -0.009147867, + 0.025011888, + -0.01621141, + -0.00064773264, + -0.0029817412, + -0.028369965, + -0.0075846235, + -0.0018020718, + 0.025359275, + 0.020611648, + 0.020495852, + -0.019337894, + -0.011984863, + 0.0034304499, + -0.042149663, + 0.006108227, + -0.039138973, + -0.018064141, + 0.005152912, + 0.02385393, + -0.0246645, + 0.010595313, + -0.00021349847, + -0.008337296, + -0.016327204, + 0.029527923, + 0.008510989, + 0.0058187377, + -0.000843138, + 0.013200719, + -0.018179936, + -0.014648166, + -0.0050371164, + 0.047939453, + 0.0141849825, + 0.0037054648, + -0.0016790387, + 0.02385393, + 0.014706064, + -0.013779697, + 0.015053451, + -0.014937655, + -0.047244675, + 0.011521679, + -0.030801676, + -0.01621141, + -0.04052852, + -0.008395194, + 0.00042699694, + -0.03126486, + -0.011637475, + -0.0068898485, + -0.019685281, + -0.0014619217, + 0.020148465, + 0.027675191, + -0.036591467, + 0.017021978, + 0.0075267255, + -0.011174292, + 0.0038791585, + 0.008626785, + 0.013200719, + 0.00015198196, + 0.04770786, + 0.006687206, + 0.0070635425, + 0.016327204, + -0.004544984, + -0.0034449243, + 0.00010539225, + -0.01725357, + 0.012100658, + 0.007381981, + -0.009263662, + 0.027212007, + 0.01233225, + -0.020032668, + -0.004342342, + -0.0054713506, + 0.01864312, + -0.00961105, + 0.0035172966, + -0.005731891, + -0.029643718, + -0.031959634, + 0.018064141, + -0.0024896092, + 0.0044581373, + 0.011405884, + 5.2696127e-05, + -0.004342342, + 0.018295733, + -0.0002089752, + -0.02987531, + 0.02165381, + -0.009668947, + 0.0068898485, + 0.006137176, + 0.021306423, + 0.020495852, + 0.014706064, + 0.007237236, + 0.010247926, + -0.008858377, + 0.033117592, + -0.025822459, + -0.007381981, + 0.0025619816, + -0.014763962, + 0.00034738734, + -0.0068898485, + 0.010479518, + -0.014590268, + -0.0075267255, + -0.022695972, + 0.029296331, + 0.0012665163, + 0.0005825975, + 0.009726846, + -0.010479518, + 0.010884803, + 0.047013085, + -0.0023882878, + -0.014937655, + -0.025706662, + 0.006224023, + 0.002576456, + 0.028022578, + -0.023969727, + 0.008163602, + -0.005066065, + -0.02165381, + -0.016906183, + 0.007266185, + -0.018179936, + -0.022348585, + -0.0072082873, + 0.015285042, + -0.03149645, + -0.0064266655, + -0.02084324, + 0.0053555546, + 0.013200719, + 0.017137775, + -0.0015125823, + 0.027790986, + 0.012795433, + 0.039602157, + 0.0044002393, + -0.039602157, + -0.0031409604, + -0.008105705, + -0.0136060035, + -0.009263662, + 0.011000599, + 0.002576456, + 0.0064845635, + 0.0033001797, + 0.015053451, + -0.0016935132, + -0.0049792184, + 0.014474472, + -0.011463782, + -0.025822459, + -0.010884803, + 0.03265441, + 0.010537416, + -0.017948346, + 0.0058187377, + 0.015400838, + -0.024548704, + -0.037749425, + -0.0062819207, + 0.021306423, + 0.011811169, + 0.015979817, + -0.004718678, + 0.0017224621, + 0.0006368768, + 0.019685281, + 0.02628564, + 0.017716754, + 0.0038212605, + -0.009089968, + -0.0013316515, + 0.00932156, + -0.013200719, + 0.025011888, + -0.0036620414, + 0.003126486, + 0.010537416, + -0.00932156, + -0.044928763, + -0.0059634824, + -0.012563841, + 0.007874113, + -0.037054647, + 0.019801078, + -0.012737536, + 0.003184384, + -0.038212605, + 0.010595313, + -0.017485162, + -0.037054647, + -0.010363722, + 0.008395194, + 0.029527923, + -0.048865817, + 0.011232191, + 0.008279398, + 0.03427555, + 0.02906474, + -0.0246645, + 0.008395194, + -0.0039081075, + -0.0010276876, + 0.001838258, + -0.020727444, + -0.000644114, + 0.008568888, + -0.0002587312, + -0.042844437, + 0.02084324, + -0.007816215, + -0.014474472, + -0.00020626123, + -0.0025040836, + 0.0018672069, + -0.020727444, + 0.0034883479, + -0.008742581, + -0.037981015, + 0.018758915, + -0.023043359, + 0.017485162, + 0.0033001797, + 0.012216454, + -0.020611648, + 0.015169247, + -0.0136060035, + 0.003068588, + -0.0038502095, + 0.0036909904, + -0.024432909, + 0.019337894, + -0.0022145943, + 0.03450714, + 0.017137775, + 0.0019106303, + 0.022580177, + 0.0068030017, + 0.030570084, + 0.038212605, + 0.01725357, + 0.029180536, + 0.015516634, + -0.0017369366, + 0.013490208, + 0.01783255, + 0.0044002393, + -0.020727444, + -0.024896093, + 0.011058496, + 0.0045739333, + -0.0070635425, + 0.026401438, + -0.008742581, + 0.047939453, + 0.015053451, + 0.03149645, + -0.007758317, + -0.02223279, + -0.012505944, + 0.0024896092, + 0.013027024, + 0.019337894, + -0.03242282, + -0.001317177, + -0.011232191, + 0.011926965, + -0.010884803, + 0.017369367, + -0.015748225, + 0.01864312, + -0.012448046, + 0.04168648, + -0.006513512, + 0.019337894, + -0.007816215, + -0.034738734, + 0.012563841, + -0.006368768, + 0.0022869667, + 0.0025330326, + 0.008858377, + 0.02686462, + 0.022001198, + -0.0023159154, + -0.04052852, + 0.007353032, + 0.014590268, + -0.016443001, + 0.00065135123, + -0.0045739333, + -0.020959036, + 0.002113273, + 0.0035317712, + -0.012274352, + 0.0011217716, + -0.020959036, + -0.012911229, + 0.01563243, + 0.01013213, + 0.0011869067, + -0.009958437, + -0.005645044, + -0.0011941439, + 0.02987531, + -0.0005283182, + 0.015169247, + 0.009032071, + -0.034043957, + -0.013953391, + 0.044233985, + 0.008279398, + -0.0031409604, + -0.0140691865, + -0.025590867, + -0.012679637, + 0.016095612, + -0.00073819805, + 0.0025475072, + -0.058592662, + 0.003256756, + -0.010537416, + 0.014011289, + 0.037054647, + -0.022348585, + 0.008568888, + -0.013895493, + -0.0048923716, + -0.023043359, + -0.011405884, + 0.0032278073, + -0.0050950143, + 0.019685281, + -0.057203114, + 0.007324083, + -0.0006224023, + -0.029296331, + -0.01042162, + -0.007381981, + 0.010190029, + 0.020032668, + 0.014011289, + -0.011753271, + 0.024085522, + -0.015516634, + -0.0030830626, + 0.017716754, + -0.016095612, + 0.0074109295, + -0.040991705, + -0.011058496, + 0.029180536, + -0.023390748, + 0.022811769, + 0.038907383, + -0.01563243, + 0.0012665163, + 0.01783255, + -0.0075846235, + 0.049097408, + 0.038212605, + -0.008279398, + -0.044002395, + -0.018179936, + 0.012853331, + -0.007237236, + -0.0024461858, + -0.014474472, + -0.005587146, + 0.04747627, + -0.029643718, + -0.041918073, + 0.0017948345, + 0.009263662, + -0.0044581373, + -0.012911229, + 0.023506543, + 0.0020409005, + -0.016906183, + -0.022348585, + 0.016095612, + 0.005673993, + 0.0019540538, + 0.021306423, + -0.0059924317, + 0.008626785, + 0.020611648, + -0.008047806, + 0.0048923716, + -0.009379458, + 0.020148465, + -0.003097537, + 0.0025330326, + -0.022580177, + 0.011174292, + 0.00015831453, + 0.013374412, + 0.016095612, + 0.02385393, + 0.0070924913, + 0.019685281, + 0.018990507, + 0.012448046, + -0.008047806, + -0.015053451, + -0.019453691, + -0.0024461858, + 0.006368768, + 0.008684684, + -0.01563243, + 0.010826905, + -0.04747627, + -0.011290088, + 0.034738734, + -0.0074109295, + -0.006658257, + 0.018411528, + -0.02385393, + 0.04446558, + -0.0057608397, + -0.051644918, + 0.0028369965, + -0.0021277473, + 0.006513512, + 0.0036620414, + 0.03126486, + -0.0020119515, + -0.0066003595, + 0.0138375955, + -0.029180536, + 0.021190627, + -0.011058496, + -0.008800479, + 0.02385393, + -0.028601557, + -0.025475072, + -0.015979817, + -0.018411528, + -0.012042761, + 0.014937655, + 0.024201317, + -0.023622338, + -0.007642521, + 0.039370563, + 0.004081801, + -0.037054647, + -0.028138373, + -0.012679637, + 0.01864312, + 0.02987531, + -0.003343603, + 0.031033268, + -0.008453092, + 0.010537416, + 0.0027356753, + -0.0069477465, + -0.013084923, + 0.02385393, + -0.011637475, + 0.006831951, + 0.0038791585, + 0.0019540538, + -0.0018165462, + 0.0136060035, + -0.00096978963, + -0.016095612, + -0.0002605405, + -0.013084923, + -0.017600957, + -0.022348585, + -0.036591467, + -0.005152912, + -0.012795433, + 0.0031120116, + 0.00010855854, + 0.013548106, + 0.016558796, + 0.012042761, + -0.016906183, + -0.0037054648, + 0.027906783, + 0.02327495, + 0.011058496, + -0.01621141, + -0.0013461258, + 0.0011434833, + -0.04052852, + -0.0015704802, + -0.038212605, + 0.011695374, + 0.009032071, + 0.031728044, + -0.029643718, + 0.039602157, + 0.027096212, + 0.0058766357, + -0.0020409005, + -0.00015831453, + 0.008337296, + 0.029180536, + 0.017369367, + 0.015516634, + 0.01314282, + 0.054887198, + 0.0072082873, + 0.020495852, + -0.012042761, + 0.036591467, + 0.020495852, + -0.036359873, + 0.0049213204, + -0.018758915, + -0.05349765, + 0.015053451, + -0.014011289, + -0.029527923, + 0.0035172966, + 0.025127683, + -0.032191224, + 0.001042162, + -0.024548704, + -0.010826905, + -0.006368768, + -0.018295733, + -0.054887198, + 0.029180536, + -0.027675191, + 0.022927564, + 0.0002107845, + 0.0002107845, + 0.012563841, + 0.002171171, + -0.029412128, + -0.021306423, + 0.016327204, + 0.028833149, + 0.0013678377, + -0.012216454, + 0.012042761, + -0.050023776, + -0.01783255, + -0.0140691865, + -0.023043359, + 0.020032668, + -0.010537416, + 0.013548106, + 0.029412128, + -0.0015487685, + 0.013027024, + 0.044233985, + -0.032191224, + -0.0054713506, + 0.028369965, + 0.0015125823, + 0.011058496, + -0.00091189175, + 0.019569486, + -0.004226546, + 0.002576456, + -0.024432909, + 0.0017369366, + 0.004284444, + -0.02686462, + 0.013027024, + -0.010190029, + 0.02026426, + 0.016674591, + -0.017021978, + 0.0012158557, + -0.018064141, + 0.0064556147, + 0.021306423, + 0.028717352, + 0.026054049, + -0.0019395793, + -0.021190627, + -0.002793573, + 0.040991705, + -0.012505944, + 0.0045739333, + -0.0053845034, + 0.0136060035, + 0.016327204, + 0.0020698495, + 0.00021349847, + 0.004747627, + 0.018179936, + 0.015053451, + -0.0031120116, + 0.023159156, + 0.0054424014, + -0.000767147, + 0.02223279, + -0.038212605, + -0.012042761, + 0.016906183, + 0.018527323, + -0.019685281, + 0.0055002994, + -0.037749425, + -0.006195074, + -0.009495254, + 0.021885403, + -0.0020698495, + 0.0018020718, + 0.0074688275, + 0.01013213, + -0.008163602, + -0.010190029, + 0.020032668, + 0.010942701, + -0.015169247, + -0.008800479, + -0.028369965, + -0.0031120116, + 0.029180536, + 0.03149645, + -0.0075267255, + -0.016790388, + 0.030106902, + 0.031033268, + -0.0041107503, + 0.013779697, + 0.0017369366, + -0.02026426, + -0.018179936, + 0.008684684, + -0.027906783, + 0.020148465, + -0.007816215, + -0.0059634824, + 0.013490208, + 0.04330762, + -0.012737536, + 0.0058187377, + 0.017021978, + -0.010653212, + 0.016790388, + 0.0022145943, + 0.0058766357, + 0.008279398, + -0.014648166, + -0.021422219, + -0.00020716588, + -0.008974173, + -0.019916873, + 0.019801078, + -0.018990507, + 0.0356651, + 0.010653212, + 0.036591467, + 0.021190627, + 0.0013316515, + 0.007642521, + 0.0045739333, + -0.028833149, + 0.022695972, + 0.014706064, + 0.0006006906, + 0.006687206, + -0.018874712, + 0.021074831, + 0.010190029, + -0.0048344736, + 0.006658257, + -0.018758915, + -0.009495254, + 0.022464382, + -0.0048923716, + -0.011926965, + -0.041918073, + -0.02026426, + -0.01343231, + -0.021306423, + 0.0246645, + 0.04353921, + -0.024432909, + -0.020727444, + 0.0037633628, + -0.0026922517, + 0.011926965, + 0.0137217995, + -0.02987531, + 0.01042162, + 0.017948346, + -0.0050371164, + -0.00466078, + -0.011058496, + -0.007758317, + 0.008163602, + 0.020959036, + -0.017716754, + 0.0002840615, + -0.0029093688, + -0.028369965, + 0.026517233, + -0.018874712, + -0.0002659684, + 0.0026777773, + 0.029296331, + -0.0035462456, + -0.016906183, + 0.02084324, + 0.0014329727, + 0.015864022, + 0.00035643388, + -0.009495254, + 0.028369965, + -0.025359275, + -0.019453691, + 0.0071503893, + -0.009900539, + -0.031959634, + 0.0044002393, + 0.01343231, + -0.029643718, + -0.013258616, + -0.005702942, + 0.012390148, + 0.015053451, + -0.0005608858, + 0.009437356, + -0.018990507, + 0.0015053451, + 0.027675191, + -0.01563243, + 0.018527323, + -0.0048923716, + -0.015979817, + 0.0045160353, + 0.028601557, + -0.010769007, + -0.008800479, + 0.025011888, + -0.014706064, + 0.024085522, + -0.0014908706, + -0.009958437, + -0.0070056445, + 0.011637475, + 0.01314282, + -0.029296331, + -0.017485162, + -0.0021277473, + -0.010595313, + 0.00010720156, + 0.010711109, + 0.018179936, + 0.03242282, + -0.007381981, + -0.009842641, + 0.06484564, + -0.015169247, + -0.021769606, + -0.030106902, + 0.010537416, + 0.020727444, + 0.033580776, + 0.019337894, + -0.0056160954, + -0.036591467, + -0.018411528, + -0.008105705, + -0.019337894, + 0.024201317, + 0.008568888, + 0.014821859, + -0.0003998573, + 0.001288228, + -0.014937655, + -0.044928763, + 0.0030251646, + -0.002605405, + 0.012853331, + 0.024317114, + -0.011753271, + -0.010479518, + -0.002663303, + 0.0005174624, + -0.0035462456, + 0.036823057, + -0.031728044, + -0.040991705, + -0.012505944, + -0.021769606, + 0.02327495, + -0.020959036, + -0.015748225, + 0.019106302, + -0.0020553751, + -0.028138373, + 0.009437356, + -0.04168648, + 0.0068608997, + 0.01864312, + -0.026054049, + -0.010479518, + 0.034970324, + 0.033117592, + 0.010363722, + 0.03265441, + -0.014011289, + 0.0060792784, + -0.013374412, + 0.016906183, + -0.012042761, + 0.04330762, + -0.014474472, + -0.02165381, + -0.0008178077, + -0.003256756, + -0.008221501, + -0.027096212, + 7.780029e-05, + -0.0047765756, + -0.006629308, + 0.0, + 0.005326606, + -0.007237236, + 0.005702942, + 0.029412128, + -0.005702942, + 0.008221501, + 0.0040528523, + -0.011290088, + -0.013779697, + 0.01621141, + -0.009205764, + 0.006716155, + -0.010305824, + 0.03126486, + 0.00085761247, + 0.0023882878, + 0.02165381, + 0.010942701, + 0.010595313, + 0.04168648, + 0.006368768, + 0.018295733, + -0.004168648, + -0.001657327, + -0.019916873, + 0.010305824, + -0.008221501, + -0.028138373, + 0.0010855854, + 0.012969127, + 0.0049502696, + 0.012505944, + 0.005326606, + -0.0140691865, + 0.024201317, + 0.0005283182, + -0.030338494, + 0.021074831, + 0.005268708, + 0.0058766357, + 0.013084923, + 0.007932011, + -0.024317114, + 0.0055002994, + 0.005066065, + 0.011232191, + 0.0074688275, + -0.02327495, + -0.028833149, + -0.0075267255, + -0.014648166, + -0.01453237, + 0.02524348, + 0.039138973, + -0.001288228, + 0.018990507, + 0.011926965, + -0.011174292, + -0.012042761, + -0.008684684, + -0.012737536, + -0.005702942, + 0.011579578, + -0.024201317, + -0.029180536, + 0.035896692, + -0.033117592, + -0.024085522, + -0.005529248, + -0.022580177, + 0.008279398, + -0.01262174, + -0.021538014, + 0.0057608397, + -0.034970324, + -0.011869067, + 0.022811769, + 0.022464382, + -0.0008684683, + -0.0059924317, + -0.005268708, + 0.018295733, + -0.00932156, + 0.020495852, + -0.0032133327, + -0.00657141, + -0.025011888, + 0.0038502095, + 0.0013750748, + -0.031033268, + -0.009263662, + 0.0012954653, + 0.0059924317, + 0.014937655, + 0.013490208, + 0.012911229, + -0.017485162, + -0.03543351, + -0.015400838, + 0.0010132131 + ], + "resource_items": { + "embedding": { + "unit": "input_token", + "item_count": 1, + "item_bytes": 123, + "item_units": 23 + } + }, + "calibration_baseline_memory_bytes": 3581874176, + "calibration_peak_memory_bytes": 3581890560, + "output_text": null + }, + { + "prompt_id": "qwen3-embed-document-1536", + "max_tokens": 1, + "prompt_tokens": 23, + "completion_tokens": 0, + "reasoning_tokens": 0, + "token_count": 0, + "token_ids": [], + "token_prefix": [], + "reproducibility_runs": 1, + "fingerprint": "5974f902768342c58e96941028a0c07da735176ab6fef7f85e0f0abd75f65889", + "embedding_vector": [ + -0.0003288714, + -0.0368336, + -0.0073447945, + -0.0031242785, + -0.001123644, + 0.08886836, + 0.010377721, + 0.048526805, + 0.030694665, + -0.016297406, + 0.006577428, + -0.030986995, + -0.00069428404, + -0.04121855, + -0.051157773, + -0.06197399, + -0.04355719, + -0.01607816, + -0.03449496, + -0.005335025, + -0.023971071, + 0.027040537, + 0.03551811, + 0.053496413, + -0.054081075, + 0.024117235, + -0.028209858, + -0.01848988, + 0.04501884, + 0.005371566, + 0.0017174395, + -0.047065154, + 0.007125547, + -0.016589735, + 0.0055542723, + -0.028648352, + 0.017028231, + -0.00489653, + -0.03668743, + 0.0426802, + 0.0020919875, + 0.008733363, + 0.011181628, + 0.004403223, + 0.0, + 0.011400876, + 0.001498192, + -0.07775982, + -0.04355719, + -0.012643278, + 0.0041474337, + 0.0276252, + 0.04501884, + -0.03142549, + 0.029817674, + -0.009939224, + 0.022947915, + 0.0028867603, + -0.0022107468, + -0.030402334, + -0.0020006343, + 0.00979306, + -0.0003197361, + -0.03551811, + 0.002539618, + -0.022801751, + -0.002996384, + -0.015712745, + 0.0074544186, + -0.00011076572, + 0.059343018, + 0.0035445031, + 0.006029309, + 0.023094082, + 0.007417877, + -0.051157773, + -0.011912454, + 0.043849524, + -0.0048599886, + 0.016516654, + 0.015055002, + 0.0027588657, + -0.011985536, + 0.023971071, + 0.015055002, + -0.0022198819, + 0.034056462, + 0.009135317, + -0.020316944, + -0.006029309, + -0.009098776, + 0.035225783, + 0.039756898, + 0.0070524644, + -0.0011008057, + 0.011547041, + -0.01900146, + -0.025140392, + -0.0020737168, + -0.002850219, + 0.02645588, + 0.0046590115, + -0.0018361986, + -0.012570196, + -0.013958764, + -0.049111467, + -0.018197551, + 0.02250942, + 0.008879527, + 0.02017078, + 0.007965997, + 0.01790522, + 0.03142549, + 0.010377721, + -0.018855294, + -0.009573813, + -0.005992768, + 0.053204086, + 0.016005075, + 0.072497874, + -0.003288714, + 0.01900146, + -0.0018087927, + -0.02104777, + 0.0123509485, + -0.050865445, + 0.00083131384, + -0.008477574, + 0.03201015, + 0.027917529, + 0.025725054, + -0.020024616, + 0.017612891, + -0.006285098, + -0.012716361, + 0.0031790903, + 0.00862374, + -0.020901605, + -0.03230248, + 0.020609275, + -0.025432723, + -0.01615124, + 0.01008539, + -0.030986995, + -0.021778595, + -0.04414185, + 0.0016352218, + -0.024994228, + -0.005992768, + -0.0005709573, + 0.012058618, + 0.016955148, + 0.026894374, + 0.005590814, + 0.018270634, + 0.038587578, + -0.009939224, + 0.018051386, + 0.00950073, + 0.002046311, + 0.00187274, + 0.007125547, + 0.048819132, + 0.01527425, + -0.017685974, + 0.009939224, + -0.025432723, + 0.03771059, + -0.010596968, + -0.003306985, + -0.01615124, + -0.008989152, + 0.008112161, + -0.015931994, + 0.005079236, + 0.009646894, + -0.02046311, + 0.01607816, + -0.0138856815, + 0.03551811, + -0.0075640427, + -0.015055002, + -0.050865445, + -0.017978303, + 0.006833217, + 0.03317947, + 0.00462247, + -0.016297406, + 0.0154204145, + -0.006029309, + -0.013520269, + 0.0141780125, + -0.012935609, + 0.02046311, + -0.019147625, + 0.012204783, + -0.02484806, + -0.0017813868, + 0.014251094, + 0.009427647, + 0.013520269, + 0.007892914, + -0.015347333, + -0.031863987, + 0.035079617, + -0.023971071, + 0.010743132, + -0.001817928, + 0.018709129, + -0.023824908, + 0.007417877, + -0.027917529, + -0.019293789, + -0.0368336, + 0.009427647, + 0.036395103, + -0.021193936, + -0.020901605, + 0.04443418, + 0.020316944, + -0.010596968, + -0.0057735206, + -0.024263402, + 0.013081774, + -0.03654127, + 0.001324621, + -0.006979382, + -0.0045128465, + 0.0, + -0.022655588, + 0.025578886, + 0.006979382, + 0.009025693, + 0.003288714, + -0.016808983, + -0.0138126, + -0.026602043, + 0.0075640427, + 0.02046311, + -0.03259481, + -0.027917529, + -0.01783214, + -0.0027771362, + 0.029671509, + 0.022070926, + -0.028502189, + -0.0030877371, + -0.010158472, + -0.008989152, + -0.0077102073, + -0.014835754, + 0.0042935987, + 0.0012332678, + -0.021193936, + -0.030110003, + -0.00979306, + -0.022947915, + -0.03551811, + 0.0045128465, + 0.012277867, + -0.016882066, + -0.0092084, + -0.0035079618, + -0.004823447, + -0.021924762, + -0.020316944, + 0.00803908, + 0.0030146544, + 0.00023180866, + -0.038587578, + 0.09939224, + -0.02104777, + 0.015201167, + 0.0040560807, + 0.035079617, + -0.018709129, + -0.012789443, + 0.0041474337, + 0.01607816, + 0.01958612, + -0.009646894, + 0.0011647529, + 0.011620123, + -0.01724748, + 0.01666282, + 0.01556658, + 0.05758904, + -0.020901605, + -0.009939224, + 0.02017078, + 0.019732285, + -0.03712593, + 0.015639663, + -0.009281483, + -0.007965997, + -0.018051386, + 0.026602043, + -0.014616507, + 0.006504345, + 0.0022929644, + -0.01790522, + -0.00866028, + -0.005590814, + 0.05846603, + 0.020609275, + 0.0024117236, + 0.012131701, + 0.010012308, + -0.024409566, + -0.002795407, + -0.005408108, + -0.010012308, + -0.0005686735, + -0.0026675125, + 0.0045676585, + -0.0020919875, + 0.023094082, + -0.011181628, + -0.0069063, + -0.027917529, + -0.0011967266, + -0.030694665, + 0.04823447, + -0.009427647, + 0.04414185, + 0.025432723, + 0.03230248, + 0.014543424, + 0.038587578, + 0.033033308, + -0.008842987, + 0.03332564, + -0.077175155, + -0.030402334, + -0.00979306, + -0.0045676585, + 0.026163548, + 0.06226632, + 0.012131701, + -0.0009318023, + -0.011693205, + -0.010377721, + -0.04677282, + 0.040633887, + -0.06928224, + -0.016224323, + -0.025432723, + 0.00862374, + -0.012570196, + -0.0067966757, + -0.035371948, + -0.03171782, + -0.10114622, + -0.027479032, + 0.05057311, + 0.025432723, + -0.011400876, + 0.0184168, + 0.023532577, + 0.009427647, + -0.01125471, + -0.0041474337, + -0.0027223243, + 0.016297406, + 0.011035463, + -0.02484806, + -0.034056462, + 0.033033308, + 0.03946457, + -0.03230248, + 0.027186703, + -0.040049233, + 0.04823447, + -0.03113316, + -0.031279325, + -0.009866143, + 0.026309712, + -0.01848988, + -0.021486264, + 0.014762673, + 0.037272092, + 0.04209554, + -0.02017078, + 0.018636046, + -0.025725054, + 0.013666434, + -0.03946457, + -0.012643278, + -0.015055002, + 0.0069428408, + 0.023971071, + 0.02367874, + 0.014251094, + -0.011912454, + 0.024555733, + -0.018709129, + 0.020755442, + 0.021632431, + 0.057004377, + -0.021632431, + -0.021486264, + 0.002704054, + -0.066066615, + -0.014470342, + 0.014543424, + -0.018855294, + 0.0019184166, + 0.025286555, + 0.017101314, + -0.0066505107, + 0.045311175, + -0.02879452, + 0.025578886, + -0.0640203, + -0.0334718, + 0.002704054, + 0.03829525, + -0.021193936, + -0.029379178, + 0.00719863, + 0.012643278, + -0.03581044, + -0.013520269, + 0.00064860756, + 0.031863987, + 0.020901605, + -0.022655588, + 0.011693205, + -0.070159234, + 0.0030146544, + -0.014543424, + -0.0033983379, + -0.020901605, + 0.027479032, + -0.019878449, + 0.03113316, + 0.007417877, + -0.025578886, + -0.03171782, + -0.03142549, + -0.016882066, + -0.053496413, + -0.00891607, + 0.03434879, + -0.0040743514, + 0.02221709, + 0.0042935987, + 0.0334718, + 0.037272092, + 0.0061389334, + -0.030694665, + -0.011327794, + -0.034056462, + -0.025432723, + -0.06080467, + -0.014543424, + 0.015055002, + -0.012570196, + -0.034202628, + 0.025725054, + -0.14324176, + -0.011181628, + 0.032740977, + -0.021778595, + -0.03654127, + -0.03376413, + 0.008112161, + 0.04443418, + 0.013739516, + 0.013374103, + -0.00374548, + 0.007417877, + 0.0010231555, + 0.0073813363, + 0.0034166086, + 0.013958764, + 0.03771059, + -0.018270634, + 0.037272092, + -0.0007993402, + -0.0054446487, + -0.040633887, + 0.03566428, + 0.026894374, + 0.04209554, + 0.03712593, + -0.011693205, + 0.054081075, + 0.0070159235, + 0.05963535, + -0.008769904, + -0.00014559411, + -0.0066505107, + -0.017393643, + -0.018636046, + -0.002539618, + 0.024117235, + -0.00979306, + 0.01958612, + 0.025140392, + 0.0011647529, + -0.017174395, + 0.024117235, + -0.021486264, + 0.000895261, + -0.025432723, + 0.06635894, + -0.012716361, + -0.027917529, + 0.0154204145, + 0.010450803, + 0.03581044, + -0.027917529, + -0.05905069, + -0.007965997, + -0.004458035, + -0.013958764, + -0.025725054, + 0.0018818752, + -0.035371948, + -0.029671509, + -0.0017722516, + 0.047065154, + -0.04121855, + -0.0138126, + 0.014689589, + -0.016297406, + 0.020609275, + 0.0039829984, + 0.01242403, + -0.026602043, + 0.0038002918, + -0.017028231, + -0.004458035, + 0.017101314, + -0.017978303, + 0.0054446487, + -0.019293789, + 0.017101314, + -0.00489653, + -0.01439726, + -0.015201167, + -0.0024665357, + 0.021193936, + 0.017393643, + -0.027186703, + -0.03829525, + -0.006540887, + -0.04648049, + 0.05758904, + 0.0070524644, + -0.012277867, + -0.020609275, + -0.03332564, + -0.016955148, + 0.01607816, + 0.06051234, + 0.0012789444, + 0.02601738, + -0.005663897, + 0.00345315, + 0.03917224, + 0.01322794, + 0.022801751, + 0.036248937, + 0.0017539809, + -0.01790522, + 0.037418257, + -0.011985536, + -0.035225783, + 0.022655588, + 0.009646894, + -0.00026149844, + 0.0073447945, + 0.017685974, + 0.0046042, + -0.022801751, + 0.00979306, + -0.013520269, + 0.001269809, + -0.00025578888, + 0.0141780125, + -0.007417877, + 0.0069063, + -0.029525345, + -0.025725054, + -4.082345e-05, + 0.008550657, + 0.023240246, + -0.007929455, + -0.027917529, + 0.0039281864, + 0.007819831, + 0.005079236, + -0.018270634, + -0.020316944, + 0.010231555, + -0.054081075, + 0.005335025, + 0.014543424, + 0.030694665, + 0.0066870516, + -0.05758904, + 0.02601738, + -0.00093637, + -0.0040743514, + 0.00045904965, + 0.04589583, + -0.04092622, + 0.057296705, + -0.00661397, + 0.03317947, + 0.052327096, + -0.0067235935, + 0.050280783, + -0.034787286, + -0.006833217, + -0.020316944, + -0.026748206, + 0.030840829, + -0.043849524, + -0.023824908, + 0.011620123, + -0.032740977, + 0.014104929, + -0.046188165, + -0.04648049, + -0.005335025, + 0.028648352, + -0.005079236, + -0.034202628, + -0.018343715, + 0.017685974, + -0.022363257, + 0.01498192, + -0.012862527, + -0.02250942, + 0.0014433801, + -0.0276252, + -0.020024616, + 0.029817674, + 0.014251094, + 0.04501884, + 0.0064312634, + 0.015712745, + -0.021486264, + -0.053788748, + -0.029671509, + 0.03551811, + 0.02601738, + -0.0037272093, + 0.033617966, + -0.040049233, + 0.0076005836, + -0.0086968215, + -0.015785828, + 0.03581044, + -0.0021011229, + 0.04501884, + -0.0054811905, + 0.018855294, + -0.02645588, + 0.007929455, + 0.011766288, + -0.033033308, + 0.022070926, + -0.012716361, + -0.019293789, + 0.04501884, + -0.029086849, + -0.02879452, + 0.009866143, + -0.023971071, + -0.0067235935, + 0.01322794, + -0.0008039079, + 0.0037089386, + -0.01732056, + -0.013666434, + 0.026748206, + 0.027040537, + 0.032740977, + 0.01958612, + 0.025578886, + -0.013301021, + -0.01724748, + -0.0036541268, + -0.023094082, + 0.015712745, + 0.03171782, + -0.0054811905, + -0.04648049, + 0.016370488, + 0.047649816, + 0.014324176, + 0.01848988, + 0.01498192, + -6.651653e-05, + -0.003581044, + -0.03551811, + -0.068405256, + 0.0038733743, + 0.030986995, + -0.008002537, + 0.0012880797, + -0.004183975, + -0.0054446487, + -0.01322794, + 0.020609275, + -0.016882066, + -0.022655588, + 0.009135317, + -0.037418257, + -0.07542118, + -0.043849524, + -0.0029050307, + -0.06548195, + -0.04121855, + -0.04180321, + -0.013374103, + 0.0028136775, + -0.013081774, + -0.03771059, + -0.0029233012, + -0.001352027, + -0.0010596968, + 0.013447187, + -0.00093637, + 0.055835057, + 0.015931994, + 0.022801751, + 0.007235171, + -0.05057311, + 0.016882066, + -0.0058466024, + 0.015785828, + -0.012716361, + -0.020609275, + 0.016808983, + -0.014251094, + 0.028648352, + 0.016005075, + 0.009354564, + 0.00749096, + 0.026894374, + -0.0042205164, + 0.0002112542, + -0.025725054, + 0.03697976, + -0.02017078, + 0.00719863, + -0.0011464822, + 0.026309712, + 0.034787286, + -0.005335025, + 0.06577428, + 0.004458035, + 0.057004377, + 0.009354564, + -0.030402334, + -0.0552504, + -0.023240246, + 0.0039099157, + -0.01439726, + -0.048819132, + -0.0213401, + 0.0034714204, + -0.060220007, + -0.010523885, + 0.027332868, + -0.024701897, + 0.035956606, + -0.0012880797, + -0.01242403, + 0.0426802, + -0.021924762, + -0.02484806, + -0.0015530038, + 0.06928224, + 0.0368336, + 0.020901605, + 0.0067601344, + 0.02338641, + 0.054373406, + -0.043849524, + 0.0015164627, + -0.045603503, + -0.0334718, + 0.00025693077, + -0.01498192, + 0.00979306, + 0.029525345, + -0.03551811, + -0.032448646, + -0.006979382, + -0.03829525, + 0.026309712, + -0.033033308, + -0.03887991, + -0.00836795, + -0.01900146, + -0.018270634, + -0.018855294, + 0.018855294, + 0.0052619423, + 0.011693205, + 0.018124469, + 0.021632431, + 0.017393643, + 0.03829525, + 0.029086849, + -0.06197399, + -0.055835057, + -0.0031790903, + -0.0015986804, + -0.0054811905, + 0.00020097698, + -0.014104929, + 0.011547041, + -0.017978303, + 0.017978303, + 0.0042022457, + 0.016297406, + 0.01848988, + -0.01322794, + -0.030110003, + 0.028502189, + -0.028063694, + -0.0033617967, + 0.04677282, + 0.006577428, + -0.0035079618, + 0.0002592146, + 0.004165705, + 0.001137347, + -0.00022952483, + 0.015201167, + -0.021924762, + -0.02046311, + -0.015201167, + 0.03230248, + 0.0167359, + 0.033617966, + -0.033910297, + -0.011547041, + 0.014251094, + -0.014104929, + 0.031571656, + 0.008550657, + 0.0138856815, + -0.025725054, + 0.07103622, + 0.019147625, + 0.056127388, + 0.03800292, + 0.046188165, + -0.0030511958, + -0.002795407, + 0.00719863, + -0.0030511958, + 0.025432723, + -0.017612891, + -0.045311175, + -0.0039281864, + 0.0, + 0.0070159235, + -0.036395103, + -0.018709129, + 0.0123509485, + -0.04355719, + 0.015639663, + -0.015931994, + 0.043849524, + 0.025140392, + 0.014616507, + -0.02250942, + -0.01607816, + 0.011035463, + -0.0061389334, + 0.0051888605, + 0.019439954, + 0.00401954, + 0.01900146, + 0.03230248, + -0.009427647, + 0.023094082, + 0.01644357, + 0.042387873, + 0.010450803, + 0.029817674, + -0.04443418, + 0.046188165, + -0.007892914, + 0.006248557, + 0.015128085, + 0.0052254014, + 0.0049330713, + -0.047065154, + 0.01585891, + -0.048819132, + 0.034787286, + 0.007162088, + -0.01783214, + 0.008002537, + 0.049403794, + -0.01900146, + -0.02250942, + 0.045311175, + 0.0, + -0.024263402, + -0.048819132, + 0.019439954, + -0.027771363, + 0.005079236, + -0.03464112, + -0.051157773, + -0.03712593, + 0.013739516, + -0.05203476, + 0.018197551, + -0.006029309, + 0.0023934531, + -0.035079617, + 0.0015073272, + 0.033617966, + -0.02996384, + -0.052327096, + -0.014543424, + -0.009573813, + 0.009646894, + 0.027332868, + 0.012204783, + -0.018562965, + 0.0368336, + -0.035225783, + 0.06518962, + 0.02104777, + -0.020609275, + 0.0002740595, + -0.010743132, + -0.028209858, + 0.013520269, + 0.0066870516, + 0.070159234, + -0.033910297, + 0.019878449, + 0.037418257, + 0.009719977, + -0.0024665357, + 0.01183937, + 0.015712745, + -0.026894374, + -0.019439954, + 0.020024616, + -0.01848988, + -0.03215632, + 0.009939224, + 0.0138856815, + 0.012789443, + 0.00058922794, + -0.0027223243, + 0.0368336, + 0.006029309, + 0.020901605, + 0.0037089386, + -0.0057369787, + 0.018051386, + 0.0022838293, + 0.02017078, + 0.04648049, + -0.013520269, + -0.0038368332, + 0.00979306, + -0.019878449, + 0.020609275, + -0.043849524, + -0.023824908, + 0.012204783, + 0.007162088, + 0.008989152, + -0.019439954, + 0.0020737168, + 0.009939224, + -0.021193936, + -0.025725054, + 0.010962381, + 0.0276252, + -0.01958612, + 0.008258327, + -0.021193936, + -0.0030329253, + 0.03887991, + -0.029671509, + 0.029086849, + 0.025578886, + 0.0123509485, + 0.035079617, + 0.019293789, + 0.026602043, + -0.023824908, + -0.021632431, + 0.03800292, + 0.0022016114, + -0.02104777, + -0.0034714204, + -0.04414185, + 0.00374548, + -0.0032339022, + 0.03025617, + 0.014762673, + -0.007819831, + -0.01008539, + -0.0138126, + -0.021778595, + 0.0036723972, + -0.017685974, + -0.0022107468, + 0.049111467, + -0.013447187, + 0.030840829, + 0.020755442, + 0.01732056, + -0.03551811, + -0.0138856815, + 0.012497114, + -0.017466726, + 0.011400876, + 0.033910297, + -0.020024616, + 0.0070524644, + -0.008769904, + -0.016224323, + -0.014616507, + 0.024994228, + 0.01439726, + -0.019147625, + 0.035371948, + 0.023094082, + 0.006869758, + -0.01300869, + -0.047065154, + -0.018051386, + -0.030694665, + 0.007856373, + -0.008185244, + 0.004677282, + -0.03449496, + 0.026602043, + -0.024555733, + 0.02338641, + 0.0076736663, + 0.017466726, + -0.011181628, + -0.010962381, + -0.052619424, + -0.0138126, + 0.0069063, + 0.01300869, + -0.0014890567, + 0.009939224, + -0.01008539, + -0.03946457, + 0.020316944, + -0.0138126, + -0.015055002, + -0.00403781, + -0.021632431, + 0.015055002, + -0.0075640427, + 0.013374103, + -0.024701897, + -0.0167359, + -0.039756898, + -0.0213401, + 0.015128085, + -0.03317947, + 0.004531117, + 0.0061023915, + -0.015201167, + -0.03800292, + -0.04501884, + -0.03697976, + -0.02017078, + -0.04969612, + 0.033910297, + -0.030986995, + 0.02104777, + -0.0055542723, + -0.03259481, + 0.022363257, + -0.004476305, + 0.002704054, + 0.00039281862, + 0.016224323, + 0.03464112, + -0.031863987, + 0.0014799213, + 0.0029050307, + 0.008148703, + -0.03317947, + -0.0026675125, + -0.011327794, + -0.025432723, + -0.005006154, + 0.0014433801, + -0.03800292, + -0.022947915, + -0.0030146544, + -0.019732285, + 0.017612891, + -0.06314331, + 0.025286555, + -0.0037272093, + 0.010596968, + 0.040049233, + 0.04034156, + -0.010962381, + 0.01300869, + -0.027332868, + -0.017393643, + 0.01439726, + 0.011693205, + -0.046188165, + 0.015347333, + 0.045311175, + -0.029671509, + 0.008806446, + -0.00019983505, + -0.01527425, + 0.010889297, + -0.009939224, + 0.03712593, + 0.011547041, + -0.010304637, + 0.021778595, + 0.0021011229, + 0.007819831, + 0.013666434, + 0.01790522, + 0.009135317, + -0.030986995, + -0.033033308, + -0.024555733, + -0.00924494, + 0.011108545, + 0.03668743, + -0.0064312634, + 0.007162088, + 0.030402334, + 0.027186703, + -0.034787286, + 0.056419715, + -0.008842987, + -0.03025617, + -0.013739516, + -0.023971071, + 0.028502189, + 0.03771059, + -0.009573813, + 0.008879527, + -0.039756898, + -0.0035445031, + 0.01585891, + 0.017539809, + -0.004458035, + -0.017028231, + 0.012131701, + -0.02017078, + -0.011327794, + 0.007162088, + 0.00092266704, + -0.03887991, + -0.02484806, + 0.04501884, + 0.0012149971, + 0.012277867, + 0.025432723, + -0.03800292, + 0.039756898, + -0.010523885, + -0.006833217, + -0.01732056, + 0.052327096, + -0.025432723, + -0.008112161, + 0.034056462, + -0.0020554462, + 0.021486264, + 0.0070524644, + -0.01322794, + 0.015128085, + -0.025871217, + -0.0048599886, + -0.033617966, + 0.01585891, + -0.009427647, + -0.0018453341, + -0.01242403, + -0.04034156, + -0.015931994, + -0.0070524644, + -0.03668743, + -0.00924494, + -0.022655588, + 3.3686483e-05, + 0.03230248, + 0.0023203706, + -0.007271712, + 0.010816216, + 0.06051234, + -0.00462247, + 0.01900146, + -0.027040537, + 0.03887991, + -0.005992768, + -0.017612891, + 0.005590814, + 0.009354564, + 0.016516654, + -0.02104777, + -0.0060658506, + 0.02017078, + 0.0076005836, + -0.016297406, + -0.01958612, + -0.029086849, + -0.007235171, + 0.031571656, + 0.018562965, + -0.02645588, + 0.022070926, + 0.025871217, + 0.004092622, + -0.013666434, + -0.022070926, + -0.008441033, + -0.034056462, + 0.024555733, + -0.0012149971, + -0.029086849, + -0.019293789, + 0.029817674, + 0.034202628, + 0.042972527, + 0.057296705, + 0.0141780125, + -0.009573813, + 0.012935609, + -0.02017078, + -0.009573813, + -0.028356025, + 0.023532577, + -0.054373406, + -0.03610277, + 0.04092622, + 0.014908837, + 0.057296705, + 0.007819831, + 0.03171782, + 0.035079617, + -0.03259481, + -0.054665737, + -0.02645588, + -0.06489729, + 0.007637125, + -0.02601738, + 0.025725054, + 0.00013360401, + 0.0070159235, + -0.021924762, + 0.020609275, + -0.01732056, + 0.017759055, + 0.014908837, + -0.021778595, + 0.021924762, + -0.0010140202, + -0.05992768, + 0.017028231, + -0.00924494, + -0.018124469, + -0.0055542723, + -0.010304637, + -0.04501884, + 0.008112161, + -0.019147625, + -0.015493497, + 0.027479032, + -0.016589735, + -0.018343715, + 0.00431187, + 0.0167359, + -0.063727975, + -0.006321639, + -0.009318023, + -0.0010733997, + 0.031279325, + -0.022070926, + 0.01848988, + 0.010596968, + 0.00462247, + 0.0037820213, + 0.01724748, + -0.016370488, + -0.001817928, + -0.04180321, + 0.0057369787, + -0.015712745, + -0.013154856, + -0.0040560807, + 0.0040743514, + -0.02017078, + 0.033910297, + 0.0138126, + 0.02338641, + -0.028502189, + -0.011327794, + 0.010596968, + -0.0138126, + -0.017539809, + -0.012277867, + -0.01848988, + 0.008733363, + 0.03829525, + 0.027771363, + 0.0334718, + 0.025432723, + -0.011327794, + -0.039756898, + -0.0017722516, + 0.013447187, + -0.01848988, + -0.00462247, + 0.02484806, + -0.0008450168, + -0.030110003, + -0.004165705, + 0.036248937, + 0.03917224, + -0.022070926, + -0.007235171, + -0.027917529, + -0.03771059, + -0.03946457, + -0.0047138236, + 0.049111467, + -0.05671205, + 0.0021833407, + 0.030840829, + -0.026894374, + 0.03581044, + 0.02046311, + 0.02250942, + -0.0025578889, + 0.021778595, + 0.031863987, + 0.01900146, + -0.0020828524, + 0.01783214, + 0.0084044915, + 0.01615124, + -0.016589735, + 0.031279325, + 0.04092622, + 0.029525345, + -0.0021102582, + 0.0025213475, + 0.0024299943, + 0.014031847, + 0.027479032, + -0.017759055, + 0.007637125, + 0.013374103, + -0.01848988, + 0.030110003, + -0.021486264, + -0.0045676585, + -0.0023295057, + 0.0040743514, + -0.022070926, + 0.009427647, + -0.04355719, + 0.017539809, + -0.0067235935, + 0.0213401, + 0.00013360401, + -0.054081075, + 0.04209554, + 0.027332868, + 0.00895261, + -0.026748206, + 0.0030146544, + -0.033617966, + 0.008221785, + 0.014104929, + -0.025286555, + 0.0057735206, + -0.010012308, + -0.027332868, + -0.0061023915, + -0.012570196, + 0.004531117, + -0.04823447, + 0.014908837, + 0.043264862, + 0.005700438, + 0.01958612, + -0.010523885, + -0.008587197, + -0.011547041, + -0.010962381, + 0.03697976, + -0.00862374, + -0.006029309, + 0.012935609, + -0.00661397, + 0.0276252, + -0.025725054, + 0.0003517097, + -0.04092622, + -0.0019640932, + -0.03230248, + -0.012497114, + -0.0030511958, + -0.039756898, + -0.028648352, + 0.015639663, + 0.0138126, + -0.009135317, + -0.043849524, + -0.0039099157, + 0.0033617967, + 0.01607816, + -0.008879527, + -0.015201167, + 0.01242403, + -0.013739516, + 0.0, + 0.06869758, + -0.014689589, + -0.010377721, + -0.014324176, + -0.021632431, + -0.028940683, + -0.00019983505, + -0.0026309711, + -0.023240246, + -0.010523885, + -0.014908837, + 0.0022655586, + -0.029671509, + -0.0086968215, + 0.003635856, + -0.01724748, + -0.015493497, + 0.0123509485, + -0.0024299943, + -0.01439726, + 0.017759055, + -0.038587578, + 0.011912454, + 0.02250942, + -0.00401954, + 0.01724748, + 0.031863987, + -0.01607816, + -0.027186703, + 0.017101314, + -0.017028231, + -0.006285098, + -0.025578886, + 0.005956227, + 0.014470342, + 0.0027588657, + -0.005883144, + -0.0021011229, + 0.03946457, + -0.014762673, + 0.0034166086, + -0.043264862, + 0.0006074986, + 0.03581044, + 0.007271712, + 0.015493497, + -0.034056462, + -0.011181628, + -0.033617966, + -0.0025944302, + 0.009719977, + 0.032448646, + -3.5542093e-05, + 0.0008039079, + 0.054665737, + 0.00039281862, + 0.03771059, + 0.0076736663, + 0.005335025, + -0.013666434, + -0.016005075, + -0.03201015, + 0.03829525, + 0.0033617967, + 0.0067966757, + -0.0017083043, + 0.03434879, + 0.011108545, + 0.01556658, + 0.006504345, + 0.014616507, + -0.03434879, + -0.03434879, + -0.025286555, + 0.029817674, + -0.00085415214, + 3.3258264e-05, + -0.0011784559, + -0.00803908, + -0.01008539, + -0.0276252, + -0.012058618, + -0.0051523186, + -0.043849524, + 0.0038002918, + 0.020316944, + 0.01585891, + -0.00038368333, + 0.015931994, + 0.03917224, + -0.0123509485, + 0.014251094, + 0.03610277, + 0.0030694667, + -0.017174395, + 0.012058618, + 0.010377721, + -0.0334718 + ], + "resource_items": { + "embedding": { + "unit": "input_token", + "item_count": 1, + "item_bytes": 123, + "item_units": 23 + } + }, + "calibration_baseline_memory_bytes": 3581890560, + "calibration_peak_memory_bytes": 3581898752, + "output_text": null + }, + { + "prompt_id": "qwen3-embed-max-1536", + "max_tokens": 1, + "prompt_tokens": 32768, + "completion_tokens": 0, + "reasoning_tokens": 0, + "token_count": 0, + "token_ids": [], + "token_prefix": [], + "reproducibility_runs": 1, + "fingerprint": "a23dc2726fbbe5fb7656f4123255996823662671a4e348e9aee63d2598e853a6", + "embedding_vector": [ + 0.0003033418, + 0.022268858, + 0.060525615, + -0.0013293509, + 0.0009903219, + 0.051104173, + 0.058527127, + -0.01805776, + 0.030120058, + 0.019984871, + 0.02198336, + -0.03797126, + -0.0042467853, + -0.04510871, + 0.027836073, + 0.0006959018, + 0.031547546, + -0.051675167, + 0.028978065, + -0.012133673, + -0.02155511, + 0.011205803, + -0.00107954, + -0.03740026, + -0.0930724, + -0.023839097, + -0.031404797, + -0.07365853, + 0.045679707, + 0.0036936325, + 0.0014899436, + -0.03268954, + 0.07251654, + -0.017486764, + 0.005031905, + -0.03497353, + -0.014060785, + -0.008600633, + -0.019556625, + -0.03911325, + 0.015488276, + -0.025837583, + 0.011348553, + -0.00112861, + -0.012704669, + -0.049962178, + 0.00255164, + 0.038542252, + 0.004264629, + -0.033974282, + 0.0005330786, + -0.0055672145, + 0.022554355, + -0.03882775, + -0.049105685, + -0.047678195, + 0.03268954, + -0.0038542252, + -0.03454528, + -0.0465362, + -0.01870013, + -0.022697106, + -0.028549818, + 0.00081188546, + 0.0023732036, + -0.03468803, + 0.03882775, + -0.09649838, + 0.029120812, + 0.04168273, + -0.04882019, + 0.0026230146, + -0.043681223, + 0.006423709, + 0.0013382727, + -0.027550573, + -0.0077084503, + 0.036829267, + 0.033260535, + 0.02954906, + -0.012347796, + -0.0020252527, + 0.007351578, + 0.07537152, + 0.049105685, + -0.01084893, + 0.005174654, + 0.0039612874, + -0.012276421, + 0.00745864, + 0.01727264, + 0.0465362, + 0.0055672145, + -0.025694836, + -0.00032564634, + 0.01855738, + -0.013061542, + -0.012276421, + -0.02341085, + 0.024981089, + -0.008529258, + -0.0044252216, + -0.02683683, + -0.00084311183, + -0.032546792, + -0.025837583, + -0.03939875, + -0.0035508836, + 0.0, + 0.0018557381, + -0.023696348, + -0.0056029013, + 0.025694836, + 0.022554355, + -0.009564189, + -0.0041040364, + 0.007244516, + 0.10734732, + -0.004353847, + 0.01727264, + -0.0031047927, + 0.03597277, + 0.0067805815, + 0.052531663, + 0.00225722, + 0.009849687, + -0.019556625, + 0.0017219108, + -0.011348553, + 0.012918792, + 0.0033367597, + 0.006851956, + -0.03939875, + -0.0009011036, + -0.02341085, + -0.022554355, + -0.009492815, + 0.0053174035, + 0.0035687273, + -0.026123084, + -0.0037650072, + -0.011277177, + 0.026408581, + 0.017700886, + -0.016558893, + -0.052817162, + -0.012276421, + -0.061953105, + 0.00070928456, + 0.0014721, + 0.027550573, + -0.0027657636, + -0.002587327, + 0.02526659, + 0.0022661418, + 0.017700886, + -0.017986385, + 0.01855738, + 0.012918792, + 0.038542252, + -0.01084893, + 0.025552087, + -0.02341085, + 0.00274792, + 0.00073605, + -0.02041312, + -0.0035865707, + -0.0104920585, + -0.02341085, + -0.04339572, + -0.021412363, + -0.031547546, + 0.012633295, + -0.024981089, + 0.00011654125, + 0.0028371383, + -0.014988653, + -0.032404043, + 0.003283229, + -0.027122326, + -0.0077084503, + -0.0009189473, + 0.03468803, + -0.009921062, + 0.024124594, + -0.0028014507, + -0.014132159, + 0.023125352, + 0.012633295, + 0.01634477, + -0.05367366, + -0.012419171, + 0.014417658, + -0.01584515, + -0.00019404954, + -0.001712989, + -0.02954906, + -0.0010527745, + 0.019556625, + 0.023696348, + -0.0007494327, + 0.02169786, + 0.0012401327, + 0.019128378, + 0.0027122325, + -0.018271882, + 0.00070036267, + -0.014417658, + -0.0039434433, + -0.025837583, + -0.0008921818, + -0.033117786, + -0.020984117, + -0.055672146, + -0.008315134, + 0.026694078, + -0.019699374, + -0.015488276, + 0.04482321, + 0.0041397233, + 0.00471072, + 0.025837583, + -0.013846662, + 0.015631026, + -0.046250705, + 0.009706938, + -0.01727264, + -0.020698616, + 0.01884288, + -0.014988653, + 0.015702398, + 0.020698616, + -0.019128378, + -0.009064567, + -0.0057456507, + 0.030833801, + 0.01398941, + -0.01398941, + 0.013132916, + 0.0014007253, + -0.006173898, + -0.022839854, + 0.006423709, + 0.013846662, + 0.00314048, + -0.033974282, + -0.0047820946, + -0.026551329, + -0.018200507, + -0.01256192, + -0.001659458, + -0.0052460292, + 0.030405555, + -0.020555869, + -0.016202021, + 0.011990923, + -0.017558139, + -0.055386648, + 0.005174654, + 0.010563433, + 0.013918037, + 0.03768576, + -0.012276421, + -0.007851199, + 0.013632538, + -0.019556625, + 0.023839097, + 0.011419927, + -0.008779069, + -0.006816269, + -0.026123084, + -0.03283229, + -0.01634477, + -0.011277177, + -0.0012401327, + 0.0062452722, + -0.008850443, + -0.00087433815, + 0.030262807, + 0.053959157, + 0.007244516, + 0.0039255996, + -0.0316903, + 0.005781338, + 0.041111737, + -0.0042467853, + 0.06737757, + 0.00040594273, + -0.01006381, + -0.009100254, + 0.00255164, + -0.038256757, + -0.0026765454, + -0.012704669, + -0.026123084, + 0.03126205, + 0.022839854, + -0.012776043, + 0.012062297, + -0.00372932, + 0.004960531, + 0.01006381, + -0.014060785, + 0.014203534, + 0.015559651, + 0.00030557226, + 0.00902888, + -0.013632538, + -0.03797126, + -0.024981089, + 0.016987141, + -0.036829267, + 0.014346283, + -0.005924087, + -0.012347796, + -0.03939875, + 0.0019271126, + 0.020555869, + -0.02812157, + -0.027693324, + -0.007993949, + -0.057099637, + -0.0465362, + -0.014703156, + -0.0005933009, + -0.020841366, + 0.019413875, + -0.00510328, + 0.004460909, + 0.016844392, + -0.02812157, + 0.03340329, + -0.003425978, + -0.0007137454, + -0.021126866, + -0.008315134, + -0.006637832, + 0.03497353, + 0.021126866, + 0.015416901, + 0.00372932, + -0.013489789, + 0.028978065, + 0.016987141, + -0.04082624, + 0.0038542252, + 0.02797882, + -0.0023375163, + 0.00067805813, + -0.0062095854, + 0.00431816, + -0.013561163, + -0.016701644, + 0.0114913015, + -0.019699374, + 0.010706182, + -0.004371691, + 0.0014096472, + 0.0782265, + 5.046403e-05, + -0.0030512619, + 0.008600633, + -0.014631782, + 0.0046036583, + 0.0038899127, + -0.028549818, + -0.007208829, + 0.049962178, + -0.03768576, + -0.039684247, + -0.010135186, + -0.025980335, + -0.011134429, + 0.0070303925, + -0.0066735195, + -0.007637076, + 0.045679707, + -0.017201265, + 0.0059597744, + 0.015987897, + 0.01084893, + 0.02969181, + 0.00078512, + 0.027836073, + -0.02512384, + 0.017486764, + -0.005138967, + 0.032261293, + 0.006423709, + 0.008457883, + -0.003782851, + -0.029120812, + 0.011705425, + 0.028835315, + -0.042253733, + 0.054815646, + 0.0049248436, + -0.0124905445, + 0.032404043, + 0.012133673, + 0.03611552, + -0.026408581, + -0.03425978, + -0.011063054, + -0.008315134, + -0.019842124, + 0.00628096, + 0.01884288, + -0.006423709, + -0.039969742, + -0.009778312, + -0.00047285637, + 0.0060668364, + -0.031547546, + -0.01791501, + 0.013918037, + 0.011919549, + -0.060525615, + 0.018414633, + -0.0056029013, + -0.011205803, + -0.01870013, + 0.012133673, + -0.012776043, + 0.04025524, + 0.03425978, + -0.014132159, + 0.042253733, + 0.030120058, + -0.017843636, + 0.014489032, + -0.015060029, + 0.011990923, + 0.06109661, + -0.0032118545, + -0.01163405, + 0.008779069, + 0.0049248436, + -0.034830775, + -0.020841366, + -0.04482321, + 0.016416145, + -0.009992436, + 0.002203689, + -0.014560406, + -0.026979579, + 0.019699374, + -0.011277177, + 0.04482321, + -0.014203534, + -0.027550573, + 0.006994705, + -0.012847418, + 0.03939875, + -0.041968234, + 0.001811129, + 0.0132042905, + -0.009564189, + -0.012419171, + -0.007851199, + -0.13989411, + -0.030120058, + 0.014988653, + -0.045394212, + -0.0047464073, + -0.036829267, + -0.01712989, + 0.009564189, + 0.013918037, + 0.018414633, + -0.03568727, + -0.02969181, + -0.01727264, + -0.021412363, + -0.0069590183, + 0.03454528, + 0.006459396, + 0.006637832, + 0.010920306, + -0.00215908, + 0.06880506, + 0.0031226361, + 0.011562676, + 0.02341085, + -0.0088861305, + 0.011848174, + 0.010706182, + 0.029120812, + -0.024410095, + 0.012633295, + 0.024410095, + 0.055672146, + -0.013632538, + -0.052817162, + 0.0007048236, + -0.023981847, + 0.032118544, + -0.023125352, + 0.01256192, + -0.0059954617, + 0.0064950837, + -0.001659458, + 0.017986385, + -0.03611552, + 0.0015256309, + 0.02198336, + 0.02341085, + 0.0026587017, + -0.00372932, + 0.039969742, + 0.0071374546, + -0.0026587017, + -0.0124905445, + -0.026694078, + 0.029406313, + -0.024410095, + -0.010777555, + -0.011705425, + 0.009849687, + -0.01256192, + -0.042824727, + 0.009564189, + 0.01898563, + -0.020555869, + -0.041968234, + 0.03411703, + 0.054815646, + -0.045394212, + -0.02155511, + 0.017772261, + -0.0056742765, + -0.011205803, + -0.010920306, + 0.031119302, + -0.02341085, + 0.02983456, + 0.03768576, + 0.030120058, + -0.007387265, + -0.0024624218, + -0.013347039, + -0.0008520336, + -0.0029620435, + -0.061382107, + 0.011419927, + -0.033831533, + -0.02797882, + -0.009778312, + -0.016701644, + 0.04510871, + -0.00942144, + 0.009564189, + 0.04824919, + -0.013703912, + -0.0022750634, + 0.03797126, + 0.025837583, + 0.03611552, + -0.012347796, + 0.027407823, + -0.022126108, + 0.00927869, + 0.017629512, + 0.038256757, + 0.025552087, + 0.006173898, + 0.006173898, + -0.003782851, + -0.051104173, + 0.0022393763, + -0.015488276, + 0.013061542, + -0.0232681, + 0.026123084, + 0.002498109, + -0.026408581, + -0.02041312, + -0.016773017, + -0.01006381, + -0.0017219108, + 0.007280203, + 0.056528635, + 0.014560406, + -0.030120058, + -0.01084893, + 0.0071731415, + 0.00628096, + -0.01870013, + 0.0010483136, + 0.011705425, + -0.031404797, + 0.030262807, + -0.02041312, + 0.017201265, + 0.0053887777, + -0.049962178, + 0.014631782, + -0.025694836, + -0.04082624, + -0.009564189, + 0.049962178, + -0.015631026, + 0.019271126, + 0.0010483136, + -0.035116278, + -0.011063054, + -0.021269614, + 0.02983456, + -0.02812157, + -0.011348553, + -0.014845905, + 0.00927869, + -0.017986385, + 0.041111737, + 0.026694078, + -0.02341085, + -0.0066735195, + 0.0058527123, + 0.0034616655, + -0.023696348, + -0.042253733, + -0.0465362, + -0.022126108, + -0.028549818, + 0.0077798255, + -0.0468217, + -0.022268858, + 0.025409337, + -0.027122326, + -0.025552087, + 0.03297504, + -0.0316903, + 0.055386648, + 0.01648752, + 0.009350065, + 0.00628096, + -0.044537716, + 0.019842124, + 0.003675789, + -0.002203689, + 0.01855738, + -0.01099168, + 0.03939875, + -0.015916524, + 0.076513514, + -0.04311022, + -0.02954906, + -0.01870013, + -0.026551329, + 0.016059272, + 0.018129135, + 0.03711476, + -0.009992436, + -0.0004260168, + 0.0038006944, + 0.02512384, + 0.02041312, + 0.0033367597, + 0.023839097, + -0.01712989, + -0.028407069, + 0.009100254, + 0.018271882, + 0.010135186, + -0.01727264, + -0.009492815, + -0.032118544, + -0.017415388, + 0.04082624, + 0.008957505, + -0.02812157, + -0.0132042905, + -0.0104920585, + -0.009849687, + 0.03268954, + -0.05795613, + -0.011063054, + 0.007958262, + 0.011205803, + -0.022697106, + 0.0316903, + -0.029406313, + 0.0059954617, + -0.03268954, + -0.004371691, + -0.009350065, + -0.04853469, + -0.022268858, + -0.013132916, + 0.023839097, + 0.01634477, + -0.0029620435, + 0.00082080724, + 0.052817162, + 0.02155511, + -0.0029620435, + -0.0031047927, + 0.020555869, + -0.017986385, + -0.03597277, + -0.010420683, + 0.024552843, + 0.013418415, + 0.026694078, + 0.01020656, + 0.058241624, + 0.036401015, + 0.009849687, + 0.0117768, + 0.025980335, + 0.0015880836, + -0.04311022, + -0.03583002, + -0.030262807, + -0.019556625, + -0.0020966271, + -0.01791501, + -0.028407069, + -0.02969181, + -0.01020656, + -0.0062452722, + 0.01884288, + -0.04853469, + -0.019128378, + 0.017986385, + 0.005174654, + 0.017344015, + 0.039684247, + -0.015131404, + 0.03425978, + 0.023981847, + -0.04882019, + 0.019699374, + 0.022411605, + -0.01020656, + 0.030262807, + 0.010277934, + 0.008779069, + -0.002890669, + 0.009992436, + -0.0022750634, + -0.012776043, + -3.6523692e-05, + 0.003979131, + -0.04824919, + 0.02812157, + 0.017201265, + 0.005460153, + 0.013632538, + 0.011063054, + 0.010135186, + 0.019984871, + 0.011277177, + 0.0035508836, + 0.011848174, + 0.012990167, + 0.0232681, + -0.007886887, + 0.02954906, + 0.019699374, + -0.03568727, + -0.009100254, + 0.0040505053, + -0.015916524, + 0.00927869, + 0.0014364127, + 0.014417658, + -0.016987141, + 0.0010260091, + 0.047963694, + -0.014060785, + 0.06709207, + -0.013061542, + -0.008101011, + 0.040540744, + -0.003283229, + -0.015773773, + -0.017629512, + 0.058527127, + 0.005067593, + 0.042824727, + 0.010349308, + 0.021269614, + -0.0059597744, + -0.026551329, + -0.033117786, + 0.009350065, + -0.012704669, + -0.035116278, + -0.042253733, + 0.0, + 0.039969742, + -0.038542252, + -0.01256192, + -0.005424465, + -0.009992436, + 0.0040861927, + 0.014346283, + -0.038256757, + 0.0066735195, + -0.0117768, + -0.032261293, + -0.042253733, + 0.034830775, + -0.010563433, + 0.025552087, + 0.0062095854, + 0.0056742765, + 0.045965206, + 0.009992436, + -0.0020163308, + -0.03583002, + -0.049105685, + -0.0008609554, + -0.010277934, + -0.0028192943, + 0.0026051707, + -0.013918037, + 0.009921062, + 0.024695592, + 0.002007409, + 0.058527127, + -0.03425978, + -0.007744138, + 0.039684247, + -0.016630268, + 0.01855738, + -0.014132159, + 0.0027836072, + -0.020270372, + -0.0046036583, + -0.00372932, + -0.0036401015, + 0.010420683, + -0.019556625, + -0.013347039, + 0.0009813999, + -0.05909812, + -0.033688784, + 0.0076727634, + -0.0017219108, + 7.109573e-05, + 0.03183305, + -0.017558139, + 0.016844392, + 0.024838341, + 0.004996218, + -0.01634477, + -0.01805776, + 0.020984117, + -0.026694078, + 0.084507465, + 0.03568727, + 0.02041312, + 0.017201265, + 0.02012762, + 0.028692566, + 0.0007271281, + 0.0058884, + -0.0033010726, + -0.024695592, + -0.004175411, + -0.045394212, + 0.010349308, + -0.01855738, + 0.017558139, + -0.0036579454, + -0.0019271126, + -0.0057456507, + -0.041968234, + 0.0132042905, + 0.047678195, + 0.035259023, + 0.028692566, + 0.022697106, + -0.008386509, + 0.03597277, + -0.022554355, + 0.015274152, + -0.02683683, + 0.019413875, + -0.0015345527, + 0.082223475, + 0.02969181, + -0.029406313, + 0.011990923, + 0.011205803, + 0.01791501, + -0.022126108, + -0.0008029636, + -0.0232681, + 0.0023732036, + 0.019413875, + 0.02812157, + 0.0235536, + -0.009350065, + -0.017843636, + -0.00510328, + 0.016773017, + -0.0232681, + 0.02954906, + 0.007387265, + 0.010634807, + 0.019984871, + 0.011277177, + 0.010920306, + 0.024838341, + -0.015773773, + 0.030120058, + 0.005781338, + -0.018200507, + -0.03440253, + 0.0017219108, + 0.012847418, + -0.06652107, + -0.007315891, + -0.022126108, + 0.038542252, + 0.0015167091, + 0.004960531, + 0.0067092073, + -0.00023865863, + -0.0235536, + -0.016844392, + 0.041968234, + -0.024695592, + -0.016130647, + -0.024124594, + -0.041397233, + 0.0021323145, + -0.04824919, + 0.014203534, + -0.016059272, + -0.023981847, + -0.019842124, + -0.031119302, + 0.008136698, + 0.00927869, + 0.019699374, + 0.00942144, + -0.0056385887, + -0.013775286, + 0.027550573, + 0.09764038, + -0.021840611, + 0.03797126, + 0.023696348, + 0.01491728, + -0.005531527, + 0.033974282, + -0.0056385887, + -0.0013471944, + 0.006566458, + 0.009706938, + 0.022982603, + -0.0232681, + -0.008208073, + 0.017629512, + 0.0235536, + -0.009350065, + 0.010135186, + 0.001811129, + -0.04082624, + -0.015202777, + 0.012633295, + -0.0028014507, + -0.0062452722, + -0.01727264, + 0.01898563, + -0.016773017, + 0.017558139, + -0.0058527123, + 3.6523692e-05, + -0.009706938, + 0.02041312, + 0.010634807, + -0.031975795, + 0.04082624, + 0.031404797, + 0.030833801, + -0.030691054, + -0.018414633, + 0.013846662, + -0.022411605, + -0.01099168, + -0.024267346, + 0.01256192, + -0.008743382, + -0.0063523343, + -0.032404043, + 0.014988653, + 0.0062452722, + 0.0039612874, + 0.0042289416, + 0.016130647, + 0.006566458, + -0.018129135, + -0.014132159, + 0.009778312, + -0.02512384, + -0.02512384, + 0.041968234, + 0.04824919, + -0.026694078, + 0.011848174, + -0.030691054, + 0.041968234, + -0.031119302, + 0.010777555, + -0.042539228, + -0.029977307, + -0.007815513, + -0.01870013, + -0.0010840009, + -0.035116278, + -0.0056385887, + 0.013632538, + 0.007922575, + -0.03468803, + 0.009849687, + -0.0030334182, + 0.010135186, + 0.020698616, + -0.01884288, + -0.00065575365, + -0.014132159, + 0.015274152, + -0.012276421, + 0.012347796, + 0.017201265, + -0.011990923, + 0.012276421, + 0.019556625, + 0.007886887, + 0.001811129, + -0.013561163, + 0.007244516, + -0.017201265, + 0.0044430653, + 0.005460153, + -0.027265076, + -0.019984871, + 0.0074943267, + -0.016558893, + 0.0077084503, + 0.016915767, + 0.016701644, + 0.00014163386, + -0.051675167, + 0.020270372, + 0.035401773, + 0.028692566, + -0.0010929228, + -0.0023732036, + -0.03497353, + 0.0059597744, + -0.00048846955, + -0.011848174, + 0.01163405, + 0.0033010726, + 0.0074943267, + 0.016416145, + -0.032404043, + -0.02198336, + -0.019699374, + -0.042253733, + 0.010135186, + -0.009992436, + 0.0004014818, + 0.049105685, + -0.024267346, + 0.024552843, + -0.03283229, + -0.01020656, + 0.007351578, + -0.008707694, + -0.0104920585, + -0.02797882, + -0.0012668982, + -0.058812626, + -0.0114913015, + 0.0132042905, + -0.01163405, + -0.032546792, + -0.03583002, + -0.02683683, + 0.040540744, + 0.033546034, + -0.00824376, + -0.0071374546, + 0.052531663, + 0.0066021453, + -0.030262807, + -0.026265832, + 0.05624314, + -0.058812626, + -0.0009903219, + -0.0122050475, + 0.006887643, + -0.003979131, + 0.012990167, + 0.007565702, + -0.0058527123, + -0.030548304, + -0.027407823, + -0.025837583, + 0.004550127, + 0.031975795, + 0.033117786, + 0.03882775, + 0.011277177, + 0.01648752, + 0.026123084, + 0.015631026, + 0.016844392, + -0.01898563, + -0.024838341, + 0.0124905445, + -0.023839097, + -0.022554355, + -0.0035865707, + 0.019699374, + 0.02041312, + -0.009635563, + 0.0063166474, + -0.02526659, + 0.02683683, + 0.024981089, + -0.058527127, + 0.020841366, + -0.03625827, + 0.03939875, + 0.009921062, + -0.02526659, + 0.05909812, + 0.022839854, + 0.014845905, + 0.0044430653, + 0.009100254, + 0.017201265, + 0.005138967, + -0.013275664, + -0.012062297, + 0.010634807, + 0.0027300764, + 0.046250705, + -0.017486764, + 0.03711476, + -0.019842124, + 0.024267346, + -0.038256757, + -0.008172385, + -0.012776043, + 0.021126866, + 0.021126866, + 0.01884288, + 0.010135186, + 0.027407823, + -0.0010170873, + 0.053102657, + 0.012062297, + 0.011277177, + -0.0235536, + -0.0042824727, + -0.019413875, + 0.032546792, + -0.05224617, + -0.021126866, + -0.03882775, + -0.0132042905, + -0.009100254, + -0.011419927, + 0.033260535, + 0.010277934, + 0.027122326, + 0.031975795, + 0.050818674, + 0.0076013887, + 0.008564945, + -0.04339572, + 0.02012762, + 0.022126108, + 0.019842124, + -0.04025524, + -0.024981089, + 0.019699374, + 0.022697106, + -0.006423709, + 0.035259023, + 0.007208829, + 0.038256757, + -0.049105685, + -0.0016416145, + -0.007744138, + 0.010277934, + 0.028835315, + 0.011848174, + -0.027265076, + -0.015131404, + -0.011205803, + -0.029263563, + -0.027122326, + 0.012276421, + -0.022982603, + -0.0021323145, + 0.0114913015, + 0.017058516, + 0.0040326617, + 0.013703912, + 0.042539228, + 0.0015167091, + 0.0052103414, + -0.0043003163, + 0.06109661, + -0.015131404, + -0.020698616, + 0.038542252, + 0.00072266726, + 0.00510328, + -0.005924087, + -0.05738513, + 0.012347796, + 0.02797882, + -0.01870013, + -0.012633295, + 0.012419171, + 0.040540744, + 0.01870013, + -0.0042289416, + -0.0075300145, + -0.005031905, + 0.017986385, + 0.0031761671, + 0.03268954, + -0.08108149, + 0.024838341, + -0.006923331, + 0.011419927, + 0.0030155743, + 0.057099637, + -0.009992436, + 0.035401773, + -0.020698616, + 0.02812157, + 0.060525615, + -0.0011598363, + -0.029120812, + 0.03411703, + -0.01648752, + -0.02526659, + -0.03454528, + -0.035259023, + -0.005138967, + -0.0015167091, + 0.0024267344, + 0.0002498109, + -0.001222289, + -0.012847418, + 0.00038363817, + 0.026265832, + 0.012918792, + -0.028835315, + 0.012990167, + -0.0026408583, + 0.024124594, + 0.0070660794, + 0.013489789, + 0.016630268, + -0.00824376, + -0.0026765454, + 0.03126205, + 0.003818538, + -0.03797126, + 0.023696348, + -0.008814756, + 0.03740026, + -0.027265076, + -0.049962178, + 0.028978065, + -0.03283229, + 0.0031761671, + 0.019556625, + 0.014631782, + -0.0059597744, + -0.043681223, + -0.02012762, + -0.026265832, + 0.06709207, + 0.020555869, + -0.03882775, + 0.019271126, + -0.011919549, + -0.026123084, + -0.0044787526, + -0.0052817166, + -0.00510328, + 0.005353091, + -0.0235536, + -0.03611552, + -0.024695592, + -0.009207317, + -0.027265076, + 0.0026943889, + 0.0023018292, + 0.011848174, + -0.03440253, + 0.042253733, + -0.00471072, + -0.0471072, + 0.00043270818, + 0.039684247, + -0.050818674, + -0.0035330397, + 0.009064567, + 0.040540744, + -0.036543764, + -0.028692566, + -0.03768576, + 0.0015345527, + -0.01648752, + -0.008208073, + -0.016059272, + 0.015202777, + 0.022982603, + 0.052531663, + -0.033831533, + -0.013061542, + -0.027407823, + -0.01727264, + 0.003086949, + 0.01584515, + 0.035544522, + -0.022268858, + 0.008993193, + -0.026979579, + 0.014203534, + 0.027122326, + 0.027265076, + 0.043681223, + -0.06909056, + -0.009135941, + -0.028835315, + 0.022697106, + 0.017201265, + 0.03597277, + -0.00863632, + -0.032404043, + 0.015488276, + 0.036543764, + -0.016630268, + -0.006173898, + 0.013703912, + 0.030691054, + -0.0034438216, + 0.024124594, + 0.022126108, + 0.043681223, + -0.0014096472, + -0.024552843, + 0.023125352, + 0.024838341, + -0.01648752, + -0.023981847, + 0.01791501, + 0.013132916, + 0.010349308, + 0.0048177815, + 0.0077798255, + -0.03283229, + 0.04025524, + 0.015274152, + -0.0010483136, + -0.008315134, + -0.02041312, + 0.01584515, + 0.021126866, + -0.006459396, + 0.008600633, + -0.020698616, + -0.006816269, + -0.00051300455, + -0.039684247, + -0.0029442, + -0.033546034, + 0.03797126, + 0.023839097, + -0.030120058, + 0.049105685, + 0.011919549, + 0.00032787683, + 0.016558893, + -0.0045322836, + -0.029977307, + 0.04853469, + 0.042824727, + 0.0026943889, + 0.015773773, + -0.040540744, + -0.01870013, + 0.03882775, + 0.03740026, + 0.0052460292, + -0.0232681, + -0.0076727634, + 0.01884288, + -0.026551329, + 0.03882775, + -0.025409337, + -0.0028371383, + -0.018129135, + 0.032118544, + 0.058527127, + -0.011990923, + -0.039684247, + -0.0053174035, + -0.035116278, + 0.032118544, + -0.02012762, + -0.04168273, + -0.025409337, + 0.053388156, + -0.017843636, + -0.00412188, + -0.053959157, + -0.0042289416, + -0.0064950837, + 0.024124594, + -0.017843636, + 0.0114913015, + -0.010634807, + 0.006851956, + -7.137454e-05, + 0.009564189, + -0.01099168, + 0.01898563, + 0.047678195, + 0.041111737, + -0.03340329, + 0.03768576, + 0.00225722, + -0.027836073, + -0.01855738, + -0.010777555, + -0.05367366, + -0.02797882, + 0.012347796, + -0.002587327, + -0.0008074245, + -0.012918792, + 0.041968234, + -0.016773017, + -0.008529258, + 0.031404797, + 0.01898563, + -0.01163405, + 0.01163405, + 0.01884288, + -0.0031940108, + 0.022126108, + -0.05224617, + 0.0004148645, + 0.011562676, + -0.017700886, + 0.025409337, + 0.036543764, + -0.026123084, + 0.0040505053, + -0.020270372, + -0.0063166474, + 0.008743382, + -0.01884288, + -0.0052817166, + -0.049962178, + 0.0044430653, + 0.021840611, + -0.010563433, + 0.060525615, + -0.04168273, + 0.025552087, + -0.04482321, + 0.011277177, + 0.009849687, + -0.011063054, + 0.009992436, + -0.041397233, + 0.0041397233, + -0.0053887777, + -0.04482321, + -0.005138967, + -0.004853469, + 0.018271882, + 0.007387265, + 0.021269614, + -0.013489789, + 0.06395159, + 0.01884288, + -0.026551329, + 0.02341085, + -0.023981847, + -0.0117768, + 0.030548304, + -0.017986385, + -0.008386509, + -0.01884288, + 0.016773017, + -0.02155511, + 0.011348553, + -0.020698616, + 0.016630268, + -0.006816269, + 0.00062006636, + 0.019128378, + -0.002444578, + 0.038256757, + -0.025409337, + 0.024552843, + -0.0132042905, + 0.011134429, + -0.0010706182, + 0.022268858, + 0.00039925135, + -0.011919549, + 0.0016059272, + -0.0031761671, + -0.004157567, + -0.027122326, + -0.02797882, + 0.032261293, + 0.014988653, + 0.06452259, + 0.011919549, + -0.015631026, + 0.027550573, + -0.0114913015, + -0.03340329, + -0.00030780272 + ], + "resource_items": { + "embedding": { + "unit": "input_token", + "item_count": 1, + "item_bytes": 65533, + "item_units": 32768 + } + }, + "calibration_baseline_memory_bytes": 3581898752, + "calibration_peak_memory_bytes": 3584724992, + "output_text": null + } + ] +} diff --git a/catalog/drafts/qwen3-embedding-4b/cancellation-probe.py b/catalog/drafts/qwen3-embedding-4b/cancellation-probe.py new file mode 100644 index 00000000..17b75a71 --- /dev/null +++ b/catalog/drafts/qwen3-embedding-4b/cancellation-probe.py @@ -0,0 +1,108 @@ +#!/usr/bin/env python3 +"""Focused vLLM pooling cancellation and post-abort recovery proof.""" + +import argparse +import asyncio +import json +import os +import time +import uuid +from pathlib import Path + +from vllm import AsyncEngineArgs, PoolingParams +from vllm.v1.engine.async_llm import AsyncLLM + + +async def encode( + engine, + text: str, + request_id: str, + cancelled: asyncio.Event | None = None, +) -> int: + final = None + async for output in engine.encode( + prompt=text, + pooling_params=PoolingParams(task="embed", dimensions=None), + request_id=request_id, + ): + if cancelled is not None and cancelled.is_set(): + raise asyncio.CancelledError("caller cancelled embedding request") + final = output + if final is None or not final.finished: + raise RuntimeError("embedding ended without a final result") + return len(final.prompt_token_ids) + + +async def main() -> None: + parser = argparse.ArgumentParser() + parser.add_argument("--model", required=True) + parser.add_argument("--output", required=True) + args = parser.parse_args() + os.environ.setdefault("TOKENIZERS_PARALLELISM", "false") + + engine = AsyncLLM.from_engine_args(AsyncEngineArgs( + model=args.model, + tokenizer=args.model, + trust_remote_code=False, + max_model_len=32768, + max_num_seqs=8, + max_num_batched_tokens=32768, + tensor_parallel_size=1, + enforce_eager=True, + seed=0, + use_fp64_gumbel=True, + async_scheduling=False, + runner="pooling", + convert="embed", + enable_prefix_caching=True, + dtype="bfloat16", + gpu_memory_utilization=0.13, + limit_mm_per_prompt={"image": 1, "audio": 1, "video": 1}, + mm_processor_cache_gb=0, + )) + request_id = f"cancel-{uuid.uuid4().hex}" + cancelled = asyncio.Event() + task = asyncio.create_task( + encode(engine, " cancel" * 32700, request_id, cancelled) + ) + await asyncio.sleep(0.5) + done_before_abort = task.done() + started = time.perf_counter() + cancelled.set() + await engine.abort(request_id) + outcome = {"aborted": False, "done_before_abort": done_before_abort} + try: + await asyncio.wait_for(task, timeout=5) + outcome["unexpected"] = "completed" + except BaseException as error: + outcome.update({ + "aborted": True, + "type": type(error).__name__, + "message": str(error)[:500], + }) + outcome["abort_seconds"] = time.perf_counter() - started + + recovery_started = time.perf_counter() + recovery_tokens = await asyncio.wait_for( + encode(engine, "Recovery after cancellation.", f"recovery-{uuid.uuid4().hex}"), + timeout=10, + ) + outcome["recovery_seconds"] = time.perf_counter() - recovery_started + outcome["recovery_tokens"] = recovery_tokens + outcome["passed"] = ( + outcome["aborted"] + and not outcome["done_before_abort"] + and outcome["abort_seconds"] < 5 + and recovery_tokens > 0 + ) + Path(args.output).write_text(json.dumps(outcome, indent=2) + "\n") + shutdown = getattr(engine, "shutdown", None) + if callable(shutdown): + shutdown() + print(json.dumps(outcome)) + if not outcome["passed"]: + raise SystemExit(1) + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/catalog/drafts/qwen3-embedding-4b/compare-openai.py b/catalog/drafts/qwen3-embedding-4b/compare-openai.py new file mode 100644 index 00000000..4d6ff9d4 --- /dev/null +++ b/catalog/drafts/qwen3-embedding-4b/compare-openai.py @@ -0,0 +1,153 @@ +#!/usr/bin/env python3 +"""Compare Qwen3 Embedding 4B and text-embedding-3-small at 1536 dimensions.""" + +import argparse +import hashlib +import json +import math +import os +import urllib.request +from pathlib import Path + + +QUERY_INSTRUCTION = ( + "Instruct: Given a user-memory or retrieval query, retrieve relevant passages " + "that answer the query\nQuery:" +) + + +def post_embeddings(url: str, key: str, model: str, inputs: list[str]) -> tuple[list[list[float]], dict]: + body = json.dumps({ + "model": model, + "input": inputs, + "dimensions": 1536, + "encoding_format": "float", + }).encode() + request = urllib.request.Request( + url, + data=body, + headers={"Authorization": f"Bearer {key}", "Content-Type": "application/json"}, + method="POST", + ) + with urllib.request.urlopen(request, timeout=180) as response: + payload = json.load(response) + rows = sorted(payload["data"], key=lambda row: row["index"]) + vectors = [[float(value) for value in row["embedding"]] for row in rows] + if len(vectors) != len(inputs) or any(len(vector) != 1536 for vector in vectors): + raise RuntimeError("embedding response has the wrong item count or dimensions") + return vectors, payload.get("usage") or {} + + +def cosine(left: list[float], right: list[float]) -> float: + dot = sum(a * b for a, b in zip(left, right)) + ln = math.sqrt(sum(value * value for value in left)) + rn = math.sqrt(sum(value * value for value in right)) + return dot / (ln * rn) + + +def metrics(queries: list[dict], query_vectors: list[list[float]], documents: list[dict], document_vectors: list[list[float]]) -> dict: + recalls = {1: 0.0, 3: 0.0, 5: 0.0} + reciprocal_ranks = [] + ndcgs = [] + per_query = [] + for query, vector in zip(queries, query_vectors): + ranked = sorted( + zip(documents, document_vectors), + key=lambda row: cosine(vector, row[1]), + reverse=True, + ) + ids = [document["id"] for document, _ in ranked] + relevant = set(query["relevant"]) + ranks = [index + 1 for index, item_id in enumerate(ids) if item_id in relevant] + for k in recalls: + recalls[k] += len(relevant.intersection(ids[:k])) / len(relevant) + reciprocal_ranks.append(1.0 / min(ranks) if ranks else 0.0) + dcg = sum(1.0 / math.log2(rank + 1) for rank in ranks if rank <= 10) + ideal = sum(1.0 / math.log2(rank + 1) for rank in range(1, min(len(relevant), 10) + 1)) + ndcgs.append(dcg / ideal if ideal else 0.0) + per_query.append({"id": query["id"], "first_relevant_rank": min(ranks) if ranks else None}) + count = len(queries) + return { + "recall_at_1": recalls[1] / count, + "recall_at_3": recalls[3] / count, + "recall_at_5": recalls[5] / count, + "mrr_at_10": sum(value if value >= 0.1 else 0.0 for value in reciprocal_ranks) / count, + "ndcg_at_10": sum(ndcgs) / count, + "per_query": per_query, + } + + +def main() -> None: + parser = argparse.ArgumentParser() + parser.add_argument("--suite", required=True) + parser.add_argument("--output", required=True) + parser.add_argument("--qwen-url", required=True) + parser.add_argument("--qwen-model", default="Qwen/Qwen3-Embedding-4B") + parser.add_argument("--openai-url", default="https://api.openai.com/v1/embeddings") + parser.add_argument("--openai-model", default="text-embedding-3-small") + args = parser.parse_args() + qwen_key = os.environ.get("QWEN_API_KEY") + openai_key = os.environ.get("OPENAI_API_KEY") + if not qwen_key or not openai_key: + raise SystemExit("QWEN_API_KEY and OPENAI_API_KEY must be set") + + suite_path = Path(args.suite) + suite_bytes = suite_path.read_bytes() + suite = json.loads(suite_bytes) + documents = suite["documents"] + queries = suite["queries"] + document_texts = [row["text"] for row in documents] + query_texts = [row["text"] for row in queries] + + qwen_documents, qwen_doc_usage = post_embeddings( + args.qwen_url, qwen_key, args.qwen_model, document_texts + ) + qwen_plain_queries, qwen_plain_usage = post_embeddings( + args.qwen_url, qwen_key, args.qwen_model, query_texts + ) + qwen_instructed_queries, qwen_instructed_usage = post_embeddings( + args.qwen_url, + qwen_key, + args.qwen_model, + [QUERY_INSTRUCTION + text for text in query_texts], + ) + openai_documents, openai_doc_usage = post_embeddings( + args.openai_url, openai_key, args.openai_model, document_texts + ) + openai_queries, openai_query_usage = post_embeddings( + args.openai_url, openai_key, args.openai_model, query_texts + ) + + qwen_plain = metrics(queries, qwen_plain_queries, documents, qwen_documents) + qwen_instructed = metrics(queries, qwen_instructed_queries, documents, qwen_documents) + openai = metrics(queries, openai_queries, documents, openai_documents) + aggregate_keys = ("recall_at_1", "recall_at_3", "recall_at_5", "mrr_at_10", "ndcg_at_10") + report = { + "schema_version": 1, + "suite_sha256": hashlib.sha256(suite_bytes).hexdigest(), + "dimensions": 1536, + "query_instruction": QUERY_INSTRUCTION, + "models": { + "qwen_plain": {"model": args.qwen_model, "metrics": qwen_plain, "usage": {"documents": qwen_doc_usage, "queries": qwen_plain_usage}}, + "qwen_instructed": {"model": args.qwen_model, "metrics": qwen_instructed, "usage": {"documents": qwen_doc_usage, "queries": qwen_instructed_usage}}, + "openai": {"model": args.openai_model, "metrics": openai, "usage": {"documents": openai_doc_usage, "queries": openai_query_usage}}, + }, + "aggregate_delta_qwen_instructed_minus_openai": { + key: qwen_instructed[key] - openai[key] for key in aggregate_keys + }, + } + report["qwen_matches_or_beats_openai_aggregate"] = ( + sum(qwen_instructed[key] for key in aggregate_keys) + >= sum(openai[key] for key in aggregate_keys) + ) + Path(args.output).write_text(json.dumps(report, indent=2) + "\n") + print(json.dumps({ + "qwen_instructed": {key: qwen_instructed[key] for key in aggregate_keys}, + "qwen_plain": {key: qwen_plain[key] for key in aggregate_keys}, + "openai": {key: openai[key] for key in aggregate_keys}, + "qwen_matches_or_beats_openai_aggregate": report["qwen_matches_or_beats_openai_aggregate"], + })) + + +if __name__ == "__main__": + main() diff --git a/catalog/drafts/qwen3-embedding-4b/downloaded-snapshot.sha256-and-sizes.txt b/catalog/drafts/qwen3-embedding-4b/downloaded-snapshot.sha256-and-sizes.txt new file mode 100644 index 00000000..add34cce --- /dev/null +++ b/catalog/drafts/qwen3-embedding-4b/downloaded-snapshot.sha256-and-sizes.txt @@ -0,0 +1,28 @@ +34448b82c17d60fec9b65b1f093c115ddbaadc04beb1b0140b6bfed2e012a930 ./.gitattributes +0f0ed3380602b252fced3fab6d07c76752c32ffca818ccc61afaf17ae8edd96f ./1_Pooling/config.json +3c8dafc1e6529491fa8918cd96c62b90229b317fd83f2794cea20cda100d6122 ./README.md +78d2861cbbfd80eee05839200c5a3b7ed64c789f6c1cab4fbb84cc4eae33eaf5 ./config.json +10667c72ddb772627bf1780cb7f86af8e2ae0032b8c243c731172064105c6961 ./config_sentence_transformers.json +28396d421a2108acce96383f6a7de78008f7f1b17f807958f3c14c51dbfb65fb ./generation_config.json +8831e4f1a044471340f7c0a83d7bd71306a5b867e95fd870f74d0c5308a904d5 ./merges.txt +e70bfe3c970523fb7ef4eddffed2254ce3f1e7150c3de2af4342de129dd756f8 ./model-00001-of-00002.safetensors +ed1b87c8e9eb7e535a1a155e4fd00d9f4dba80e58a6db48a4c9f82cede7079c1 ./model-00002-of-00002.safetensors +9d130c7f24fa1f9a2a7e19fad42c7d6d2d6fea31b180bdf3e8aac1924c26c39a ./model.safetensors.index.json +84e40c8e006c9b1d6c122e02cba9b02458120b5fb0c87b746c41e0207cf642cf ./modules.json +83cdf8c3a34f68862319cb1810ee7b1e2c0a44e0864ae930194ddb76bb7feb8d ./tokenizer.json +2f58f4bbd7bbce15d683f525954ef3a92cd82f5e06415a9c513859bf8ab72436 ./tokenizer_config.json +ca10d7e9fb3ed18575dd1e277a2579c16d108e32f27439684afa0e10b1440910 ./vocab.json +.gitattributes 1570 +1_Pooling/config.json 313 +README.md 17276 +config.json 727 +config_sentence_transformers.json 215 +generation_config.json 117 +merges.txt 1671853 +model-00001-of-00002.safetensors 4965826464 +model-00002-of-00002.safetensors 3077765624 +model.safetensors.index.json 30431 +modules.json 349 +tokenizer.json 11422947 +tokenizer_config.json 7256 +vocab.json 2776833 diff --git a/catalog/drafts/qwen3-embedding-4b/execution-proof.json b/catalog/drafts/qwen3-embedding-4b/execution-proof.json new file mode 100644 index 00000000..51ff3c41 --- /dev/null +++ b/catalog/drafts/qwen3-embedding-4b/execution-proof.json @@ -0,0 +1,45 @@ +{ + "schema_version": 1, + "model_id": "Qwen/Qwen3-Embedding-4B", + "artifact_root": "e228d4e36517c331c3ddf034753cf974a7f73d2ac3dd0be4ef145722eccdc26c", + "runtime": "vllm-0.24.0", + "platform": "linux-nvidia-gb10", + "task": "embedding", + "independent_dispatch": true, + "request_modalities": [ + [ + "embedding" + ] + ], + "scheduler": { + "max_num_seqs": 8, + "max_num_batched_tokens": 32768, + "memory_utilization": 0.13 + }, + "proven_capacity": { + "concurrent_requests": 8, + "items_per_concurrent_request": 32, + "max_inflight_items": 256, + "max_items_per_request": 128, + "max_sized_concurrent_requests": 2, + "max_input_tokens_including_internal_token": 32768 + }, + "evidence": { + "performance_benchmark": { + "path": "qwen3-embedding-4b-vllm-benchmark.json", + "sha256": "1c271e018273a46549ca9b5f35d9d26b8ffb293b4661cfa8da11744731068d4b" + }, + "cancellation_recovery": { + "path": "qwen3-embedding-4b-cancellation.json", + "sha256": "928a4aeb7f1412e302b34c26d0ee30cc359a3ac260d5653a2dfc264c729099ea" + }, + "reference_equivalence": { + "path": "qwen3-embedding-4b-reference-compare.json", + "sha256": "1eef40cec37ba00eae1db319bc93b0a33fa1b8278719fe8d98cc2c10fafac691" + }, + "large_batch_benchmark": { + "path": "qwen3-embedding-4b-large-batch-benchmark.json", + "sha256": "db4d1ae1bda0aaf9650d7e5fe595b141f7d842021b8b5475f5fe2a03de015e8d" + } + } +} diff --git a/catalog/drafts/qwen3-embedding-4b/mirrored-revision.txt b/catalog/drafts/qwen3-embedding-4b/mirrored-revision.txt new file mode 100644 index 00000000..83e2cf27 --- /dev/null +++ b/catalog/drafts/qwen3-embedding-4b/mirrored-revision.txt @@ -0,0 +1 @@ +909825755dc39379f3eb31256602da9cafb29c95 diff --git a/catalog/drafts/qwen3-embedding-4b/qwen3-embedding-4b-cancellation.json b/catalog/drafts/qwen3-embedding-4b/qwen3-embedding-4b-cancellation.json new file mode 100644 index 00000000..75f4ffa6 --- /dev/null +++ b/catalog/drafts/qwen3-embedding-4b/qwen3-embedding-4b-cancellation.json @@ -0,0 +1,10 @@ +{ + "aborted": true, + "done_before_abort": false, + "type": "CancelledError", + "message": "caller cancelled embedding request", + "abort_seconds": 0.0002848817966878414, + "recovery_seconds": 7.183610714040697, + "recovery_tokens": 6, + "passed": true +} diff --git a/catalog/drafts/qwen3-embedding-4b/qwen3-embedding-4b-large-batch-benchmark.json b/catalog/drafts/qwen3-embedding-4b/qwen3-embedding-4b-large-batch-benchmark.json new file mode 100644 index 00000000..b40ab3b1 --- /dev/null +++ b/catalog/drafts/qwen3-embedding-4b/qwen3-embedding-4b-large-batch-benchmark.json @@ -0,0 +1,215 @@ +{ + "artifact_root": "e228d4e36517c331c3ddf034753cf974a7f73d2ac3dd0be4ef145722eccdc26c", + "baseline_memory": { + "process_tree_rss_bytes": 896282624, + "swap_used_bytes": 137527296, + "system_available_bytes": 103230713856, + "system_used_bytes": 25289302016 + }, + "engine": { + "dtype": "bfloat16", + "max_model_len": 32768, + "max_num_batched_tokens": 32768, + "max_num_seqs": 8, + "memory_utilization": 0.13, + "runner": "pooling" + }, + "error": null, + "final_memory": { + "process_tree_rss_bytes": 1282727936, + "swap_used_bytes": 137527296, + "system_available_bytes": 102961094656, + "system_used_bytes": 25558921216 + }, + "load_seconds": 54.16936773201451, + "model_id": "Qwen/Qwen3-Embedding-4B", + "results": [ + { + "characters_per_item": 256, + "elapsed_seconds": 0.21863502683117986, + "items": 32, + "items_per_second": 146.36264126474558, + "label": "batch-32-short", + "memory": { + "process_tree_rss_bytes": 3488243712, + "swap_used_bytes": 137527296, + "system_available_bytes": 84238307328, + "system_used_bytes": 44281708544 + }, + "native_dimensions": 2560, + "prefix_norm_max": 0.7856993008427331, + "prefix_norm_min": 0.7792221555829805, + "tokens": 1910 + }, + { + "characters_per_item": 256, + "elapsed_seconds": 0.3710502199828625, + "items": 64, + "items_per_second": 172.48339053122226, + "label": "batch-64-short", + "memory": { + "process_tree_rss_bytes": 3488673792, + "swap_used_bytes": 137527296, + "system_available_bytes": 84237283328, + "system_used_bytes": 44282732544 + }, + "native_dimensions": 2560, + "prefix_norm_max": 0.7843741288268352, + "prefix_norm_min": 0.7788104116055219, + "tokens": 3830 + }, + { + "characters_per_item": 256, + "elapsed_seconds": 0.6391891683451831, + "items": 128, + "items_per_second": 200.25370631887148, + "label": "batch-128-short", + "memory": { + "process_tree_rss_bytes": 3489374208, + "swap_used_bytes": 137527296, + "system_available_bytes": 84234555392, + "system_used_bytes": 44285460480 + }, + "native_dimensions": 2560, + "prefix_norm_max": 0.7847229961029868, + "prefix_norm_min": 0.7682898861122237, + "tokens": 8594 + }, + { + "characters_per_item": 256, + "elapsed_seconds": 1.221637852024287, + "items": 256, + "items_per_second": 209.55473799031446, + "label": "batch-256-short", + "memory": { + "process_tree_rss_bytes": 3490811904, + "swap_used_bytes": 137527296, + "system_available_bytes": 84229816320, + "system_used_bytes": 44290199552 + }, + "native_dimensions": 2560, + "prefix_norm_max": 0.784149551808552, + "prefix_norm_min": 0.7739120455896952, + "tokens": 17298 + }, + { + "characters_per_item": 1024, + "elapsed_seconds": 0.7169509520754218, + "items": 128, + "items_per_second": 178.5338308422173, + "label": "batch-128-medium", + "memory": { + "process_tree_rss_bytes": 3491168256, + "swap_used_bytes": 137527296, + "system_available_bytes": 84231159808, + "system_used_bytes": 44288856064 + }, + "native_dimensions": 2560, + "prefix_norm_max": 0.7786052924707149, + "prefix_norm_min": 0.7705326067971371, + "tokens": 30226 + }, + { + "children": [ + { + "characters_per_item": 256, + "elapsed_seconds": 0.675924182869494, + "items": 128, + "items_per_second": 189.37035135598038, + "label": "concurrent-2x128-short-request-0", + "memory": { + "process_tree_rss_bytes": 3491291136, + "swap_used_bytes": 137527296, + "system_available_bytes": 84229103616, + "system_used_bytes": 44290912256 + }, + "native_dimensions": 2560, + "prefix_norm_max": 0.7813606755954394, + "prefix_norm_min": 0.7727673270565892, + "tokens": 8978 + }, + { + "characters_per_item": 256, + "elapsed_seconds": 1.297165031079203, + "items": 128, + "items_per_second": 98.6767272730963, + "label": "concurrent-2x128-short-request-1", + "memory": { + "process_tree_rss_bytes": 3491291136, + "swap_used_bytes": 137527296, + "system_available_bytes": 84231680000, + "system_used_bytes": 44288335872 + }, + "native_dimensions": 2560, + "prefix_norm_max": 0.7816625150801263, + "prefix_norm_min": 0.7767387438664163, + "tokens": 8978 + } + ], + "elapsed_seconds": 1.3031410882249475, + "items_per_request": 128, + "items_per_second": 196.4484139999808, + "label": "concurrent-2x128-short", + "memory": { + "process_tree_rss_bytes": 3491291136, + "swap_used_bytes": 137527296, + "system_available_bytes": 84231680000, + "system_used_bytes": 44288335872 + }, + "requests": 2, + "total_items": 256 + }, + { + "children": [ + { + "characters_per_item": 256, + "elapsed_seconds": 1.3670537709258497, + "items": 256, + "items_per_second": 187.26403119214663, + "label": "headroom-2x256-short-request-0", + "memory": { + "process_tree_rss_bytes": 3493797888, + "swap_used_bytes": 137527296, + "system_available_bytes": 84228239360, + "system_used_bytes": 44291776512 + }, + "native_dimensions": 2560, + "prefix_norm_max": 0.7929500773948939, + "prefix_norm_min": 0.7867744059672586, + "tokens": 19858 + }, + { + "characters_per_item": 256, + "elapsed_seconds": 2.6903738491237164, + "items": 256, + "items_per_second": 95.15406198412981, + "label": "headroom-2x256-short-request-1", + "memory": { + "process_tree_rss_bytes": 3493797888, + "swap_used_bytes": 137527296, + "system_available_bytes": 84222025728, + "system_used_bytes": 44297990144 + }, + "native_dimensions": 2560, + "prefix_norm_max": 0.7911254723700878, + "prefix_norm_min": 0.7859656944333793, + "tokens": 19858 + } + ], + "elapsed_seconds": 2.699317823164165, + "items_per_request": 256, + "items_per_second": 189.677553197433, + "label": "headroom-2x256-short", + "memory": { + "process_tree_rss_bytes": 3493797888, + "swap_used_bytes": 137527296, + "system_available_bytes": 84222025728, + "system_used_bytes": 44297990144 + }, + "requests": 2, + "total_items": 512 + } + ], + "runtime": "vllm-0.24.0", + "schema_version": 1 +} diff --git a/catalog/drafts/qwen3-embedding-4b/qwen3-embedding-4b-reference-compare.json b/catalog/drafts/qwen3-embedding-4b/qwen3-embedding-4b-reference-compare.json new file mode 100644 index 00000000..6724f0bd --- /dev/null +++ b/catalog/drafts/qwen3-embedding-4b/qwen3-embedding-4b-reference-compare.json @@ -0,0 +1,40 @@ +{ + "schema_version": 1, + "model_path": "hf://TracNetwork/mayhem-catalog-Qwen-Qwen3-Embedding-4B-BF16@909825755dc39379f3eb31256602da9cafb29c95", + "reference_runtime": "transformers-official-last-token-pooling", + "dtype": "bfloat16", + "attention": "sdpa", + "load_seconds": 46.523810502141714, + "inference_seconds": 0.7429673876613379, + "cases": [ + { + "id": "qwen3-embed-query-native", + "dimensions": 2560, + "cosine": 0.9997303381428202, + "reference_norm": 1.0019597312973452, + "vllm_norm": 0.9999999713036141 + }, + { + "id": "qwen3-embed-query-1536", + "dimensions": 1536, + "cosine": 0.9997171903088767, + "reference_norm": 1.0, + "vllm_norm": 0.9999999971255501 + }, + { + "id": "qwen3-embed-document-native", + "dimensions": 2560, + "cosine": 0.9997516154802264, + "reference_norm": 1.0009139912045055, + "vllm_norm": 1.0000000445473054 + }, + { + "id": "qwen3-embed-document-1536", + "dimensions": 1536, + "cosine": 0.9997535800177928, + "reference_norm": 1.0, + "vllm_norm": 0.9999999985817098 + } + ], + "minimum_cosine": 0.9997171903088767 +} diff --git a/catalog/drafts/qwen3-embedding-4b/qwen3-embedding-4b-vllm-benchmark.json b/catalog/drafts/qwen3-embedding-4b/qwen3-embedding-4b-vllm-benchmark.json new file mode 100644 index 00000000..3330c252 --- /dev/null +++ b/catalog/drafts/qwen3-embedding-4b/qwen3-embedding-4b-vllm-benchmark.json @@ -0,0 +1,358 @@ +{ + "schema_version": 1, + "model_path": "hf://TracNetwork/mayhem-catalog-Qwen-Qwen3-Embedding-4B-BF16@909825755dc39379f3eb31256602da9cafb29c95", + "runtime": "vllm-0.24.0", + "memory_utilization": 0.13, + "max_model_len": 32768, + "max_num_seqs": 8, + "max_num_batched_tokens": 32768, + "load_seconds": 57.20902464585379, + "baseline_memory": { + "process_tree_rss_bytes": 900386816, + "system_available_bytes": 121376788480, + "system_used_bytes": 7143227392, + "swap_used_bytes": 6569984 + }, + "loaded_memory": { + "process_tree_rss_bytes": 3558404096, + "system_available_bytes": 102346375168, + "system_used_bytes": 26173640704, + "swap_used_bytes": 6569984 + }, + "final_memory": { + "process_tree_rss_bytes": 1371574272, + "system_available_bytes": 120905363456, + "system_used_bytes": 7614652416, + "swap_used_bytes": 6569984 + }, + "results": [ + { + "label": "batch-1-short", + "items": 1, + "tokens": 15, + "elapsed_seconds": 0.03962881816551089, + "items_per_second": 25.234161559486115, + "tokens_per_second": 378.51242339229174, + "native_dimensions": 2560, + "requested_dimensions": 1536, + "norm_min": 1.0, + "norm_max": 1.0, + "process_tree_rss_bytes": 3652653056 + }, + { + "label": "batch-8-short", + "items": 8, + "tokens": 120, + "elapsed_seconds": 0.050880649127066135, + "items_per_second": 157.2306984531841, + "tokens_per_second": 2358.460476797761, + "native_dimensions": 2560, + "requested_dimensions": 1536, + "norm_min": 0.9999999999999999, + "norm_max": 1.0, + "process_tree_rss_bytes": 3653804032 + }, + { + "label": "batch-32-short", + "items": 32, + "tokens": 502, + "elapsed_seconds": 0.20633112080395222, + "items_per_second": 155.09051603710887, + "tokens_per_second": 2432.9824703321456, + "native_dimensions": 2560, + "requested_dimensions": 1536, + "norm_min": 0.9999999999999999, + "norm_max": 1.0, + "process_tree_rss_bytes": 3657347072 + }, + { + "label": "batch-8-512-token", + "items": 8, + "tokens": 4136, + "elapsed_seconds": 0.12206439208239317, + "items_per_second": 65.53917865416491, + "tokens_per_second": 33883.755364203265, + "native_dimensions": 2560, + "requested_dimensions": 1536, + "norm_min": 0.9999999999999999, + "norm_max": 1.0, + "process_tree_rss_bytes": 3657539584 + }, + { + "label": "batch-1-256-chars", + "items": 1, + "tokens": 68, + "elapsed_seconds": 0.04476333688944578, + "items_per_second": 22.339710787641888, + "tokens_per_second": 1519.1003335596483, + "native_dimensions": 2560, + "requested_dimensions": 1536, + "norm_min": 1.0, + "norm_max": 1.0, + "process_tree_rss_bytes": 3657543680 + }, + { + "label": "batch-8-256-chars", + "items": 8, + "tokens": 544, + "elapsed_seconds": 0.0762604852207005, + "items_per_second": 104.90360737736879, + "tokens_per_second": 7133.445301661077, + "native_dimensions": 2560, + "requested_dimensions": 1536, + "norm_min": 0.9999999999999999, + "norm_max": 1.0, + "process_tree_rss_bytes": 3657555968 + }, + { + "label": "batch-32-256-chars", + "items": 32, + "tokens": 2198, + "elapsed_seconds": 0.19174370309337974, + "items_per_second": 166.8894440012766, + "tokens_per_second": 11463.218684837684, + "native_dimensions": 2560, + "requested_dimensions": 1536, + "norm_min": 0.9999999999999999, + "norm_max": 1.0, + "process_tree_rss_bytes": 3658002432 + }, + { + "label": "batch-1-1024-chars", + "items": 1, + "tokens": 277, + "elapsed_seconds": 0.054048505146056414, + "items_per_second": 18.501899308735346, + "tokens_per_second": 5125.026108519691, + "native_dimensions": 2560, + "requested_dimensions": 1536, + "norm_min": 1.0, + "norm_max": 1.0, + "process_tree_rss_bytes": 3658002432 + }, + { + "label": "batch-8-1024-chars", + "items": 8, + "tokens": 2216, + "elapsed_seconds": 0.08084416389465332, + "items_per_second": 98.95581343910818, + "tokens_per_second": 27410.760322632967, + "native_dimensions": 2560, + "requested_dimensions": 1536, + "norm_min": 0.9999999999999999, + "norm_max": 1.0, + "process_tree_rss_bytes": 3658006528 + }, + { + "label": "batch-32-1024-chars", + "items": 32, + "tokens": 8886, + "elapsed_seconds": 0.20209483290091157, + "items_per_second": 158.34150502843292, + "tokens_per_second": 43969.456677582966, + "native_dimensions": 2560, + "requested_dimensions": 1536, + "norm_min": 0.9999999999999999, + "norm_max": 1.0, + "process_tree_rss_bytes": 3658194944 + }, + { + "label": "batch-1-4096-chars", + "items": 1, + "tokens": 1099, + "elapsed_seconds": 0.12830515997484326, + "items_per_second": 7.793918811964145, + "tokens_per_second": 8565.516774348596, + "native_dimensions": 2560, + "requested_dimensions": 1536, + "norm_min": 0.9999999999999999, + "norm_max": 0.9999999999999999, + "process_tree_rss_bytes": 3658194944 + }, + { + "label": "batch-8-4096-chars", + "items": 8, + "tokens": 8792, + "elapsed_seconds": 0.08892752882093191, + "items_per_second": 89.96089406812513, + "tokens_per_second": 98867.02258086951, + "native_dimensions": 2560, + "requested_dimensions": 1536, + "norm_min": 0.9999999999999999, + "norm_max": 1.0, + "process_tree_rss_bytes": 3658207232 + }, + { + "label": "batch-32-4096-chars", + "items": 32, + "tokens": 35190, + "elapsed_seconds": 0.2250598119571805, + "items_per_second": 142.1844252055461, + "tokens_per_second": 156358.43509322396, + "native_dimensions": 2560, + "requested_dimensions": 1536, + "norm_min": 0.9999999999999999, + "norm_max": 1.0, + "process_tree_rss_bytes": 3659075584 + }, + { + "label": "concurrent-1x-batch-8-256-chars", + "requests": 1, + "items": 8, + "tokens": 264, + "elapsed_seconds": 0.08938903827220201, + "requests_per_second": 11.187054020593234, + "items_per_second": 89.49643216474587, + "tokens_per_second": 2953.3822614366136, + "memory": { + "process_tree_rss_bytes": 3659079680, + "system_available_bytes": 102151188480, + "system_used_bytes": 26368827392, + "swap_used_bytes": 6569984 + } + }, + { + "label": "concurrent-2x-batch-8-256-chars", + "requests": 2, + "items": 16, + "tokens": 528, + "elapsed_seconds": 0.18036476289853454, + "requests_per_second": 11.088640418777997, + "items_per_second": 88.70912335022398, + "tokens_per_second": 2927.401070557391, + "memory": { + "process_tree_rss_bytes": 3716472832, + "system_available_bytes": 102095929344, + "system_used_bytes": 26424086528, + "swap_used_bytes": 6569984 + } + }, + { + "label": "concurrent-4x-batch-8-256-chars", + "requests": 4, + "items": 32, + "tokens": 1056, + "elapsed_seconds": 0.2085175858810544, + "requests_per_second": 19.18303428988353, + "items_per_second": 153.46427431906824, + "tokens_per_second": 5064.321052529252, + "memory": { + "process_tree_rss_bytes": 3716517888, + "system_available_bytes": 102095421440, + "system_used_bytes": 26424594432, + "swap_used_bytes": 6569984 + } + }, + { + "label": "concurrent-8x-batch-8-256-chars", + "requests": 8, + "items": 64, + "tokens": 2112, + "elapsed_seconds": 0.3567245681770146, + "requests_per_second": 22.426265846736477, + "items_per_second": 179.41012677389182, + "tokens_per_second": 5920.53418353843, + "memory": { + "process_tree_rss_bytes": 3716538368, + "system_available_bytes": 102094913536, + "system_used_bytes": 26425102336, + "swap_used_bytes": 6569984 + } + }, + { + "label": "sustained-batch-1-short", + "repetitions": 50, + "items_per_request": 1, + "total_items": 50, + "total_tokens": 550, + "elapsed_seconds": 1.9200295670889318, + "requests_per_second": 26.041265643532718, + "tokens_per_second": 286.45392207885993, + "latency_p50_seconds": 0.038372226525098085, + "latency_p90_seconds": 0.03953313594684005, + "latency_p99_seconds": 0.04163725394755602, + "memory": { + "process_tree_rss_bytes": 3716538368, + "system_available_bytes": 102098534400, + "system_used_bytes": 26421481472, + "swap_used_bytes": 6569984 + } + }, + { + "label": "sustained-batch-8-short", + "repetitions": 40, + "items_per_request": 8, + "total_items": 320, + "total_tokens": 4800, + "elapsed_seconds": 2.1035634013824165, + "requests_per_second": 19.015352698051725, + "tokens_per_second": 2281.8423237662073, + "latency_p50_seconds": 0.046183115337044, + "latency_p90_seconds": 0.07935190293937922, + "latency_p99_seconds": 0.08208994893357158, + "memory": { + "process_tree_rss_bytes": 3716558848, + "system_available_bytes": 102098104320, + "system_used_bytes": 26421911552, + "swap_used_bytes": 6569984 + } + }, + { + "label": "sustained-batch-32-short", + "repetitions": 20, + "items_per_request": 32, + "total_items": 640, + "total_tokens": 10040, + "elapsed_seconds": 3.5666005169041455, + "requests_per_second": 5.607580637418921, + "tokens_per_second": 2815.0054799842983, + "latency_p50_seconds": 0.16978783207014203, + "latency_p90_seconds": 0.2056286809965968, + "latency_p99_seconds": 0.20694764517247677, + "memory": { + "process_tree_rss_bytes": 3716620288, + "system_available_bytes": 102096232448, + "system_used_bytes": 26423783424, + "swap_used_bytes": 6569984 + } + }, + { + "label": "single-max-input", + "items": 1, + "tokens": 32768, + "elapsed_seconds": 7.3433410678990185, + "items_per_second": 0.13617779574088434, + "tokens_per_second": 4462.274010837298, + "native_dimensions": 2560, + "requested_dimensions": 1536, + "norm_min": 1.0, + "norm_max": 1.0, + "process_tree_rss_bytes": 3721003008 + }, + { + "label": "post-cancel-recovery", + "items": 1, + "tokens": 10, + "elapsed_seconds": 0.039507505018264055, + "items_per_second": 25.311646471669288, + "tokens_per_second": 253.11646471669286, + "native_dimensions": 2560, + "requested_dimensions": 1536, + "norm_min": 1.0, + "norm_max": 1.0, + "process_tree_rss_bytes": 3725426688 + } + ], + "boundary_results": [ + { + "label": "overflow", + "expected_rejection": true, + "type": "VLLMValidationError", + "message": "This model's maximum context length is 32768 tokens. However, you requested 0 output tokens and your prompt contains at least 32769 input tokens, for a total of at least 32769 tokens. Please reduce the length of the input prompt or the number of requested output tokens. (parameter=input_tokens, value=32769)" + }, + { + "label": "cancel", + "unexpected": "completed" + } + ] +} diff --git a/catalog/drafts/qwen3-embedding-4b/reference-compare.py b/catalog/drafts/qwen3-embedding-4b/reference-compare.py new file mode 100644 index 00000000..0abfc90a --- /dev/null +++ b/catalog/drafts/qwen3-embedding-4b/reference-compare.py @@ -0,0 +1,104 @@ +#!/usr/bin/env python3 +"""Compare pinned Qwen reference pooling with retained vLLM canary vectors.""" + +import argparse +import json +import math +import time +from pathlib import Path + +import torch +import torch.nn.functional as F +from transformers import AutoModel, AutoTokenizer + + +def last_token_pool(last_hidden_states, attention_mask): + left_padding = attention_mask[:, -1].sum() == attention_mask.shape[0] + if left_padding: + return last_hidden_states[:, -1] + sequence_lengths = attention_mask.sum(dim=1) - 1 + batch_size = last_hidden_states.shape[0] + return last_hidden_states[torch.arange(batch_size), sequence_lengths] + + +def cosine(left, right): + dot = sum(a * b for a, b in zip(left, right)) + ln = math.sqrt(sum(a * a for a in left)) + rn = math.sqrt(sum(b * b for b in right)) + return dot / (ln * rn) + + +def truncate_normalize(vector, dimensions): + values = vector[:dimensions] + norm = math.sqrt(sum(value * value for value in values)) + return [value / norm for value in values] + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument("--model", required=True) + parser.add_argument("--canary", required=True) + parser.add_argument("--output", required=True) + args = parser.parse_args() + + canary = json.loads(Path(args.canary).read_text()) + vectors = {row["prompt_id"]: row["embedding_vector"] for row in canary["prompts"]} + query = "Instruct: Given a web search query, retrieve relevant passages that answer the query\nQuery: How does OpenMayhem verify model artifacts?" + document = "OpenMayhem binds model artifacts to immutable revisions, verified hashes, calibrated canaries, and signed catalog evidence." + + started = time.perf_counter() + tokenizer = AutoTokenizer.from_pretrained(args.model, trust_remote_code=False, padding_side="left") + model = AutoModel.from_pretrained( + args.model, + trust_remote_code=False, + torch_dtype=torch.bfloat16, + attn_implementation="sdpa", + ).to("cuda") + model.eval() + load_seconds = time.perf_counter() - started + + encoded = tokenizer( + [query, document], + padding=True, + truncation=True, + max_length=32768, + return_tensors="pt", + ).to("cuda") + inference_started = time.perf_counter() + with torch.inference_mode(): + outputs = model(**encoded) + pooled = last_token_pool(outputs.last_hidden_state, encoded["attention_mask"]) + embeddings = F.normalize(pooled, p=2, dim=1).float().cpu().tolist() + inference_seconds = time.perf_counter() - inference_started + + cases = [] + for index, kind in enumerate(("query", "document")): + native = embeddings[index] + reduced = truncate_normalize(native, 1536) + for suffix, observed in (("native", native), ("1536", reduced)): + expected = vectors[f"qwen3-embed-{kind}-{suffix}"] + cases.append({ + "id": f"qwen3-embed-{kind}-{suffix}", + "dimensions": len(observed), + "cosine": cosine(expected, observed), + "reference_norm": math.sqrt(sum(value * value for value in observed)), + "vllm_norm": math.sqrt(sum(value * value for value in expected)), + }) + + report = { + "schema_version": 1, + "model_path": args.model, + "reference_runtime": "transformers-official-last-token-pooling", + "dtype": "bfloat16", + "attention": "sdpa", + "load_seconds": load_seconds, + "inference_seconds": inference_seconds, + "cases": cases, + "minimum_cosine": min(case["cosine"] for case in cases), + } + Path(args.output).write_text(json.dumps(report, indent=2) + "\n") + print(json.dumps(report)) + + +if __name__ == "__main__": + main() diff --git a/catalog/drafts/qwen3-embedding-4b/retrieval-suite.json b/catalog/drafts/qwen3-embedding-4b/retrieval-suite.json new file mode 100644 index 00000000..baa77d88 --- /dev/null +++ b/catalog/drafts/qwen3-embedding-4b/retrieval-suite.json @@ -0,0 +1,80 @@ +{ + "schema_version": 1, + "description": "Synthetic representative memory, multilingual, technical, and code retrieval suite. It contains no customer data.", + "documents": [ + {"id":"d01","text":"Car insurance renewal is due on 18 October. Compare the annual quote before renewing."}, + {"id":"d02","text":"The home contents insurance policy renews automatically in March."}, + {"id":"d03","text":"Sam prefers vegetarian meals and has a severe peanut allergy."}, + {"id":"d04","text":"Alex likes Thai food and is not allergic to peanuts."}, + {"id":"d05","text":"For long flights, reserve a window seat near the front of the cabin."}, + {"id":"d06","text":"For train journeys, choose a quiet-zone aisle seat when available."}, + {"id":"d07","text":"Meeting with Nora is Thursday at 15:30 in the Berlin office."}, + {"id":"d08","text":"Lunch with Noah moved to Friday at 12:30."}, + {"id":"d09","text":"Request the asthma prescription refill from the clinic before 4 November."}, + {"id":"d10","text":"The dental cleaning does not require a prescription."}, + {"id":"d11","text":"The memory database stores raw text beside vectors. When changing embedding models, re-embed every retained chunk into a fresh aligned vector index."}, + {"id":"d12","text":"Vector dimensions may be truncated only when the embedding model documents Matryoshka representation learning."}, + {"id":"d13","text":"For slow case-insensitive email lookup in PostgreSQL, create an index on lower(email) and query with the same expression."}, + {"id":"d14","text":"A PostgreSQL full table scan can also result from stale table statistics; run ANALYZE after bulk imports."}, + {"id":"d15","text":"In Rust, pass a CancellationToken into the async task, select on token.cancelled(), and release resources before returning."}, + {"id":"d16","text":"A Tokio semaphore limits concurrent tasks but does not itself propagate cancellation."}, + {"id":"d17","text":"For HTTP 429 responses in JavaScript, honor Retry-After and retry with capped exponential backoff plus jitter."}, + {"id":"d18","text":"Retrying every HTTP 400 response is unsafe because most client errors require changing the request."}, + {"id":"d19","text":"Normalize a Python embedding by dividing each component by sqrt(sum(x*x for x in vector)); reject a zero or non-finite norm."}, + {"id":"d20","text":"Standardizing tabular features subtracts the mean and divides by standard deviation; that is different from L2 vector normalization."}, + {"id":"d21","text":"A Docker healthcheck can call curl --fail against the local readiness endpoint and set interval, timeout, retries, and start_period."}, + {"id":"d22","text":"Docker restart policies restart exited containers but do not prove the application is ready."}, + {"id":"d23","text":"The production TLS certificate expires on 7 December; renew it at least two weeks earlier."}, + {"id":"d24","text":"The staging SSH host key rotates in December, independently of the TLS certificate."}, + {"id":"d25","text":"Die Stromrechnung ist spätestens am 22. September fällig und wird nicht automatisch eingezogen."}, + {"id":"d26","text":"Die Gasrechnung wurde bereits am 3. September per Lastschrift bezahlt."}, + {"id":"d27","text":"La cita con el dentista es el martes a las 09:15. Hay que llegar diez minutos antes."}, + {"id":"d28","text":"La revisión del coche es el martes a las 11:00 en otro barrio."}, + {"id":"d29","text":"Camille est allergique aux noix et aux noisettes, mais elle peut manger des graines de tournesol."}, + {"id":"d30","text":"Camille préfère les desserts aux noisettes lorsqu'ils sont disponibles."}, + {"id":"d31","text":"東京発京都行きの次の新幹線は14時12分、18番線から出発する。"}, + {"id":"d32","text":"京都発大阪行きの快速列車は14時10分、4番線から出発する。"}, + {"id":"d33","text":"减少 GPU 显存占用可以降低批大小、缩短上下文,并使用量化权重。"}, + {"id":"d34","text":"提高 GPU 吞吐量通常可以增大批大小,但这会增加显存占用。"}, + {"id":"d35","text":"fn validate_dimensions(n: usize) -> Result<()> { if n == 0 || n > 2560 { bail!(\"dimensions out of range\") } Ok(()) }"}, + {"id":"d36","text":"fn validate_batch(n: usize) -> Result<()> { if n > 32 { bail!(\"batch too large\") } Ok(()) }"}, + {"id":"d37","text":"To diagnose PostgreSQL deadlocks, inspect the deadlock log for the two lock orders and make all transactions acquire rows in a consistent order."}, + {"id":"d38","text":"A connection pool timeout means no connection became available; it is not necessarily a database deadlock."}, + {"id":"d39","text":"Use git revert to create an inverse commit while preserving the existing branch history."}, + {"id":"d40","text":"Use git reset --soft HEAD~1 to remove the latest local commit while keeping its changes staged."}, + {"id":"d41","text":"Restart the home Wi-Fi router every Sunday at 04:00 only if the connectivity probe has failed three times."}, + {"id":"d42","text":"The office access point installs firmware on the first Monday of each month."}, + {"id":"d43","text":"Book note: recommend The Dispossessed for thoughtful social science fiction with political themes."}, + {"id":"d44","text":"Book note: avoid recommending long fantasy series when the reader asks for a short standalone novel."}, + {"id":"d45","text":"Take the evening medication with food at 20:00; do not double the next dose after a missed dose."}, + {"id":"d46","text":"The morning vitamin can be taken without food between 07:00 and 09:00."}, + {"id":"d47","text":"Invoice ACME-204 is due 30 days after 6 September and should be paid by bank transfer."}, + {"id":"d48","text":"Invoice ACME-203 was paid by card on 2 September and requires no follow-up."} + ], + "queries": [ + {"id":"q01","text":"When do I need to renew the car insurance?","relevant":["d01"]}, + {"id":"q02","text":"What food restriction should I remember for Sam?","relevant":["d03"]}, + {"id":"q03","text":"Which seat should I reserve for the long flight?","relevant":["d05"]}, + {"id":"q04","text":"When and where am I meeting Nora?","relevant":["d07"]}, + {"id":"q05","text":"What medical refill must be requested before November?","relevant":["d09"]}, + {"id":"q06","text":"What must happen to saved memories when switching embedding models?","relevant":["d11"]}, + {"id":"q07","text":"How can I speed up case-insensitive PostgreSQL email searches?","relevant":["d13"]}, + {"id":"q08","text":"How should a Tokio task react to cancellation and clean up?","relevant":["d15"]}, + {"id":"q09","text":"What is the safe retry strategy for HTTP status 429 in JavaScript?","relevant":["d17"]}, + {"id":"q10","text":"How do I L2-normalize an embedding in Python?","relevant":["d19"]}, + {"id":"q11","text":"How should a container prove its local service is ready?","relevant":["d21"]}, + {"id":"q12","text":"When should the production TLS certificate be renewed?","relevant":["d23"]}, + {"id":"q13","text":"Wann ist die Stromrechnung fällig?","relevant":["d25"]}, + {"id":"q14","text":"¿A qué hora es la cita con el dentista?","relevant":["d27"]}, + {"id":"q15","text":"À quels aliments Camille est-elle allergique ?","relevant":["d29"]}, + {"id":"q16","text":"次の東京発京都行きの新幹線は何時に何番線から出ますか?","relevant":["d31"]}, + {"id":"q17","text":"如何减少 GPU 显存占用?","relevant":["d33"]}, + {"id":"q18","text":"Find the function that rejects embedding dimensions above 2560.","relevant":["d35"]}, + {"id":"q19","text":"How do I fix transactions that deadlock because they lock rows in opposite order?","relevant":["d37"]}, + {"id":"q20","text":"Which Git command undoes a published commit without rewriting history?","relevant":["d39"]}, + {"id":"q21","text":"What is the conditional schedule for restarting the home router?","relevant":["d41"]}, + {"id":"q22","text":"Which social science-fiction book did I want to recommend?","relevant":["d43"]}, + {"id":"q23","text":"What should happen after missing the evening medication dose?","relevant":["d45"]}, + {"id":"q24","text":"When and how should invoice ACME-204 be paid?","relevant":["d47"]} + ] +} diff --git a/catalog/drafts/qwen3-embedding-4b/source-manifest.json b/catalog/drafts/qwen3-embedding-4b/source-manifest.json new file mode 100644 index 00000000..b3106a04 --- /dev/null +++ b/catalog/drafts/qwen3-embedding-4b/source-manifest.json @@ -0,0 +1,37 @@ +{ + "schema_version": 1, + "source": "huggingface", + "repository": "Qwen/Qwen3-Embedding-4B", + "revision": "5cf2132abc99cad020ac570b19d031efec650f2b", + "license": "apache-2.0", + "gated": false, + "artifact_format": "safetensors", + "weight_bytes": 8043548672, + "files": [ + { + "path": "model-00001-of-00002.safetensors", + "bytes": 4965826464, + "sha256": "e70bfe3c970523fb7ef4eddffed2254ce3f1e7150c3de2af4342de129dd756f8" + }, + { + "path": "model-00002-of-00002.safetensors", + "bytes": 3077765624, + "sha256": "ed1b87c8e9eb7e535a1a155e4fd00d9f4dba80e58a6db48a4c9f82cede7079c1" + }, + { + "path": "tokenizer.json", + "bytes": 11422947, + "sha256": "83cdf8c3a34f68862319cb1810ee7b1e2c0a44e0864ae930194ddb76bb7feb8d" + } + ], + "source_evidence": { + "model_api": "source/model-api.json", + "tree": "source/tree.json", + "config": "source/config.json", + "sentence_transformers_config": "source/config_sentence_transformers.json", + "modules": "source/modules.json", + "pooling": "source/1_Pooling/config.json", + "model_card": "source/README.md" + }, + "download_status": "source_verified_not_mirrored" +} diff --git a/catalog/drafts/qwen3-embedding-4b/source/1_Pooling/config.json b/catalog/drafts/qwen3-embedding-4b/source/1_Pooling/config.json new file mode 100644 index 00000000..81de5602 --- /dev/null +++ b/catalog/drafts/qwen3-embedding-4b/source/1_Pooling/config.json @@ -0,0 +1,10 @@ +{ + "word_embedding_dimension": 2560, + "pooling_mode_cls_token": false, + "pooling_mode_mean_tokens": false, + "pooling_mode_max_tokens": false, + "pooling_mode_mean_sqrt_len_tokens": false, + "pooling_mode_weightedmean_tokens": false, + "pooling_mode_lasttoken": true, + "include_prompt": true +} \ No newline at end of file diff --git a/catalog/drafts/qwen3-embedding-4b/source/config.json b/catalog/drafts/qwen3-embedding-4b/source/config.json new file mode 100644 index 00000000..8b4b87fc --- /dev/null +++ b/catalog/drafts/qwen3-embedding-4b/source/config.json @@ -0,0 +1,30 @@ +{ + "architectures": [ + "Qwen3ForCausalLM" + ], + "attention_bias": false, + "attention_dropout": 0.0, + "bos_token_id": 151643, + "eos_token_id": 151645, + "head_dim": 128, + "hidden_act": "silu", + "hidden_size": 2560, + "initializer_range": 0.02, + "intermediate_size": 9728, + "max_position_embeddings": 40960, + "max_window_layers": 36, + "model_type": "qwen3", + "num_attention_heads": 32, + "num_hidden_layers": 36, + "num_key_value_heads": 8, + "rms_norm_eps": 1e-06, + "rope_scaling": null, + "rope_theta": 1000000, + "sliding_window": null, + "tie_word_embeddings": true, + "torch_dtype": "bfloat16", + "transformers_version": "4.51.2", + "use_cache": true, + "use_sliding_window": false, + "vocab_size": 151665 +} diff --git a/catalog/drafts/qwen3-embedding-4b/source/config_sentence_transformers.json b/catalog/drafts/qwen3-embedding-4b/source/config_sentence_transformers.json new file mode 100644 index 00000000..76aef3ad --- /dev/null +++ b/catalog/drafts/qwen3-embedding-4b/source/config_sentence_transformers.json @@ -0,0 +1,8 @@ +{ + "prompts": { + "query": "Instruct: Given a web search query, retrieve relevant passages that answer the query\nQuery:", + "document": "" + }, + "default_prompt_name": null, + "similarity_fn_name": "cosine" +} \ No newline at end of file diff --git a/catalog/drafts/qwen3-embedding-4b/source/model-api.json b/catalog/drafts/qwen3-embedding-4b/source/model-api.json new file mode 100644 index 00000000..3f43711a --- /dev/null +++ b/catalog/drafts/qwen3-embedding-4b/source/model-api.json @@ -0,0 +1 @@ +{"_id":"683f07454f6e7115e88614de","id":"Qwen/Qwen3-Embedding-4B","downloads":2328520,"downloadsAllTime":20559076,"lastModified":"2025-06-20T09:30:56.000Z","sha":"5cf2132abc99cad020ac570b19d031efec650f2b","tags":["sentence-transformers","safetensors","qwen3","text-generation","transformers","sentence-similarity","feature-extraction","text-embeddings-inference","arxiv:2506.05176","base_model:Qwen/Qwen3-4B-Base","base_model:finetune:Qwen/Qwen3-4B-Base","license:apache-2.0","endpoints_compatible","region:us"],"siblings":[{"rfilename":".gitattributes"},{"rfilename":"1_Pooling/config.json"},{"rfilename":"README.md"},{"rfilename":"config.json"},{"rfilename":"config_sentence_transformers.json"},{"rfilename":"generation_config.json"},{"rfilename":"merges.txt"},{"rfilename":"model-00001-of-00002.safetensors"},{"rfilename":"model-00002-of-00002.safetensors"},{"rfilename":"model.safetensors.index.json"},{"rfilename":"modules.json"},{"rfilename":"tokenizer.json"},{"rfilename":"tokenizer_config.json"},{"rfilename":"vocab.json"}]} \ No newline at end of file diff --git a/catalog/drafts/qwen3-embedding-4b/source/model.safetensors.index.json b/catalog/drafts/qwen3-embedding-4b/source/model.safetensors.index.json new file mode 100644 index 00000000..3d736ef2 --- /dev/null +++ b/catalog/drafts/qwen3-embedding-4b/source/model.safetensors.index.json @@ -0,0 +1,405 @@ +{ + "metadata": { + "total_size": 8043548672 + }, + "weight_map": { + "embed_tokens.weight": "model-00001-of-00002.safetensors", + "layers.0.input_layernorm.weight": "model-00001-of-00002.safetensors", + "layers.0.mlp.down_proj.weight": "model-00001-of-00002.safetensors", + "layers.0.mlp.gate_proj.weight": "model-00001-of-00002.safetensors", + "layers.0.mlp.up_proj.weight": "model-00001-of-00002.safetensors", + "layers.0.post_attention_layernorm.weight": "model-00001-of-00002.safetensors", + "layers.0.self_attn.k_norm.weight": "model-00001-of-00002.safetensors", + "layers.0.self_attn.k_proj.weight": "model-00001-of-00002.safetensors", + "layers.0.self_attn.o_proj.weight": "model-00001-of-00002.safetensors", + "layers.0.self_attn.q_norm.weight": "model-00001-of-00002.safetensors", + "layers.0.self_attn.q_proj.weight": "model-00001-of-00002.safetensors", + "layers.0.self_attn.v_proj.weight": "model-00001-of-00002.safetensors", + "layers.1.input_layernorm.weight": "model-00001-of-00002.safetensors", + "layers.1.mlp.down_proj.weight": "model-00001-of-00002.safetensors", + "layers.1.mlp.gate_proj.weight": "model-00001-of-00002.safetensors", + "layers.1.mlp.up_proj.weight": "model-00001-of-00002.safetensors", + "layers.1.post_attention_layernorm.weight": "model-00001-of-00002.safetensors", + "layers.1.self_attn.k_norm.weight": "model-00001-of-00002.safetensors", + "layers.1.self_attn.k_proj.weight": "model-00001-of-00002.safetensors", + "layers.1.self_attn.o_proj.weight": "model-00001-of-00002.safetensors", + "layers.1.self_attn.q_norm.weight": "model-00001-of-00002.safetensors", + "layers.1.self_attn.q_proj.weight": "model-00001-of-00002.safetensors", + "layers.1.self_attn.v_proj.weight": "model-00001-of-00002.safetensors", + "layers.10.input_layernorm.weight": "model-00001-of-00002.safetensors", + "layers.10.mlp.down_proj.weight": "model-00001-of-00002.safetensors", + "layers.10.mlp.gate_proj.weight": "model-00001-of-00002.safetensors", + "layers.10.mlp.up_proj.weight": "model-00001-of-00002.safetensors", + "layers.10.post_attention_layernorm.weight": "model-00001-of-00002.safetensors", + "layers.10.self_attn.k_norm.weight": "model-00001-of-00002.safetensors", + "layers.10.self_attn.k_proj.weight": "model-00001-of-00002.safetensors", + "layers.10.self_attn.o_proj.weight": "model-00001-of-00002.safetensors", + "layers.10.self_attn.q_norm.weight": "model-00001-of-00002.safetensors", + "layers.10.self_attn.q_proj.weight": "model-00001-of-00002.safetensors", + "layers.10.self_attn.v_proj.weight": "model-00001-of-00002.safetensors", + "layers.11.input_layernorm.weight": "model-00001-of-00002.safetensors", + "layers.11.mlp.down_proj.weight": "model-00001-of-00002.safetensors", + "layers.11.mlp.gate_proj.weight": "model-00001-of-00002.safetensors", + "layers.11.mlp.up_proj.weight": "model-00001-of-00002.safetensors", + "layers.11.post_attention_layernorm.weight": "model-00001-of-00002.safetensors", + "layers.11.self_attn.k_norm.weight": "model-00001-of-00002.safetensors", + "layers.11.self_attn.k_proj.weight": "model-00001-of-00002.safetensors", + "layers.11.self_attn.o_proj.weight": "model-00001-of-00002.safetensors", + "layers.11.self_attn.q_norm.weight": "model-00001-of-00002.safetensors", + "layers.11.self_attn.q_proj.weight": "model-00001-of-00002.safetensors", + "layers.11.self_attn.v_proj.weight": "model-00001-of-00002.safetensors", + "layers.12.input_layernorm.weight": "model-00001-of-00002.safetensors", + "layers.12.mlp.down_proj.weight": "model-00001-of-00002.safetensors", + "layers.12.mlp.gate_proj.weight": "model-00001-of-00002.safetensors", + "layers.12.mlp.up_proj.weight": "model-00001-of-00002.safetensors", + "layers.12.post_attention_layernorm.weight": "model-00001-of-00002.safetensors", + "layers.12.self_attn.k_norm.weight": "model-00001-of-00002.safetensors", + "layers.12.self_attn.k_proj.weight": "model-00001-of-00002.safetensors", + "layers.12.self_attn.o_proj.weight": "model-00001-of-00002.safetensors", + "layers.12.self_attn.q_norm.weight": "model-00001-of-00002.safetensors", + "layers.12.self_attn.q_proj.weight": "model-00001-of-00002.safetensors", + "layers.12.self_attn.v_proj.weight": "model-00001-of-00002.safetensors", + "layers.13.input_layernorm.weight": "model-00001-of-00002.safetensors", + "layers.13.mlp.down_proj.weight": "model-00001-of-00002.safetensors", + "layers.13.mlp.gate_proj.weight": "model-00001-of-00002.safetensors", + "layers.13.mlp.up_proj.weight": "model-00001-of-00002.safetensors", + "layers.13.post_attention_layernorm.weight": "model-00001-of-00002.safetensors", + "layers.13.self_attn.k_norm.weight": "model-00001-of-00002.safetensors", + "layers.13.self_attn.k_proj.weight": "model-00001-of-00002.safetensors", + "layers.13.self_attn.o_proj.weight": "model-00001-of-00002.safetensors", + "layers.13.self_attn.q_norm.weight": "model-00001-of-00002.safetensors", + "layers.13.self_attn.q_proj.weight": "model-00001-of-00002.safetensors", + "layers.13.self_attn.v_proj.weight": "model-00001-of-00002.safetensors", + "layers.14.input_layernorm.weight": "model-00001-of-00002.safetensors", + "layers.14.mlp.down_proj.weight": "model-00001-of-00002.safetensors", + "layers.14.mlp.gate_proj.weight": "model-00001-of-00002.safetensors", + "layers.14.mlp.up_proj.weight": "model-00001-of-00002.safetensors", + "layers.14.post_attention_layernorm.weight": "model-00001-of-00002.safetensors", + "layers.14.self_attn.k_norm.weight": "model-00001-of-00002.safetensors", + "layers.14.self_attn.k_proj.weight": "model-00001-of-00002.safetensors", + "layers.14.self_attn.o_proj.weight": "model-00001-of-00002.safetensors", + "layers.14.self_attn.q_norm.weight": "model-00001-of-00002.safetensors", + "layers.14.self_attn.q_proj.weight": "model-00001-of-00002.safetensors", + "layers.14.self_attn.v_proj.weight": "model-00001-of-00002.safetensors", + "layers.15.input_layernorm.weight": "model-00001-of-00002.safetensors", + "layers.15.mlp.down_proj.weight": "model-00001-of-00002.safetensors", + "layers.15.mlp.gate_proj.weight": "model-00001-of-00002.safetensors", + "layers.15.mlp.up_proj.weight": "model-00001-of-00002.safetensors", + "layers.15.post_attention_layernorm.weight": "model-00001-of-00002.safetensors", + "layers.15.self_attn.k_norm.weight": "model-00001-of-00002.safetensors", + "layers.15.self_attn.k_proj.weight": "model-00001-of-00002.safetensors", + "layers.15.self_attn.o_proj.weight": "model-00001-of-00002.safetensors", + "layers.15.self_attn.q_norm.weight": "model-00001-of-00002.safetensors", + "layers.15.self_attn.q_proj.weight": "model-00001-of-00002.safetensors", + "layers.15.self_attn.v_proj.weight": "model-00001-of-00002.safetensors", + "layers.16.input_layernorm.weight": "model-00001-of-00002.safetensors", + "layers.16.mlp.down_proj.weight": "model-00001-of-00002.safetensors", + "layers.16.mlp.gate_proj.weight": "model-00001-of-00002.safetensors", + "layers.16.mlp.up_proj.weight": "model-00001-of-00002.safetensors", + "layers.16.post_attention_layernorm.weight": "model-00001-of-00002.safetensors", + "layers.16.self_attn.k_norm.weight": "model-00001-of-00002.safetensors", + "layers.16.self_attn.k_proj.weight": "model-00001-of-00002.safetensors", + "layers.16.self_attn.o_proj.weight": "model-00001-of-00002.safetensors", + "layers.16.self_attn.q_norm.weight": "model-00001-of-00002.safetensors", + "layers.16.self_attn.q_proj.weight": "model-00001-of-00002.safetensors", + "layers.16.self_attn.v_proj.weight": "model-00001-of-00002.safetensors", + "layers.17.input_layernorm.weight": "model-00001-of-00002.safetensors", + "layers.17.mlp.down_proj.weight": "model-00001-of-00002.safetensors", + "layers.17.mlp.gate_proj.weight": "model-00001-of-00002.safetensors", + "layers.17.mlp.up_proj.weight": "model-00001-of-00002.safetensors", + "layers.17.post_attention_layernorm.weight": "model-00001-of-00002.safetensors", + "layers.17.self_attn.k_norm.weight": "model-00001-of-00002.safetensors", + "layers.17.self_attn.k_proj.weight": "model-00001-of-00002.safetensors", + "layers.17.self_attn.o_proj.weight": "model-00001-of-00002.safetensors", + "layers.17.self_attn.q_norm.weight": "model-00001-of-00002.safetensors", + "layers.17.self_attn.q_proj.weight": "model-00001-of-00002.safetensors", + "layers.17.self_attn.v_proj.weight": "model-00001-of-00002.safetensors", + "layers.18.input_layernorm.weight": "model-00001-of-00002.safetensors", + "layers.18.mlp.down_proj.weight": "model-00001-of-00002.safetensors", + "layers.18.mlp.gate_proj.weight": "model-00001-of-00002.safetensors", + "layers.18.mlp.up_proj.weight": "model-00001-of-00002.safetensors", + "layers.18.post_attention_layernorm.weight": "model-00001-of-00002.safetensors", + "layers.18.self_attn.k_norm.weight": "model-00001-of-00002.safetensors", + "layers.18.self_attn.k_proj.weight": "model-00001-of-00002.safetensors", + "layers.18.self_attn.o_proj.weight": "model-00001-of-00002.safetensors", + "layers.18.self_attn.q_norm.weight": "model-00001-of-00002.safetensors", + "layers.18.self_attn.q_proj.weight": "model-00001-of-00002.safetensors", + "layers.18.self_attn.v_proj.weight": "model-00001-of-00002.safetensors", + "layers.19.input_layernorm.weight": "model-00001-of-00002.safetensors", + "layers.19.mlp.down_proj.weight": "model-00001-of-00002.safetensors", + "layers.19.mlp.gate_proj.weight": "model-00001-of-00002.safetensors", + "layers.19.mlp.up_proj.weight": "model-00001-of-00002.safetensors", + "layers.19.post_attention_layernorm.weight": "model-00001-of-00002.safetensors", + "layers.19.self_attn.k_norm.weight": "model-00001-of-00002.safetensors", + "layers.19.self_attn.k_proj.weight": "model-00001-of-00002.safetensors", + "layers.19.self_attn.o_proj.weight": "model-00001-of-00002.safetensors", + "layers.19.self_attn.q_norm.weight": "model-00001-of-00002.safetensors", + "layers.19.self_attn.q_proj.weight": "model-00001-of-00002.safetensors", + "layers.19.self_attn.v_proj.weight": "model-00001-of-00002.safetensors", + "layers.2.input_layernorm.weight": "model-00001-of-00002.safetensors", + "layers.2.mlp.down_proj.weight": "model-00001-of-00002.safetensors", + "layers.2.mlp.gate_proj.weight": "model-00001-of-00002.safetensors", + "layers.2.mlp.up_proj.weight": "model-00001-of-00002.safetensors", + "layers.2.post_attention_layernorm.weight": "model-00001-of-00002.safetensors", + "layers.2.self_attn.k_norm.weight": "model-00001-of-00002.safetensors", + "layers.2.self_attn.k_proj.weight": "model-00001-of-00002.safetensors", + "layers.2.self_attn.o_proj.weight": "model-00001-of-00002.safetensors", + "layers.2.self_attn.q_norm.weight": "model-00001-of-00002.safetensors", + "layers.2.self_attn.q_proj.weight": "model-00001-of-00002.safetensors", + "layers.2.self_attn.v_proj.weight": "model-00001-of-00002.safetensors", + "layers.20.input_layernorm.weight": "model-00002-of-00002.safetensors", + "layers.20.mlp.down_proj.weight": "model-00002-of-00002.safetensors", + "layers.20.mlp.gate_proj.weight": "model-00001-of-00002.safetensors", + "layers.20.mlp.up_proj.weight": "model-00001-of-00002.safetensors", + "layers.20.post_attention_layernorm.weight": "model-00002-of-00002.safetensors", + "layers.20.self_attn.k_norm.weight": "model-00001-of-00002.safetensors", + "layers.20.self_attn.k_proj.weight": "model-00001-of-00002.safetensors", + "layers.20.self_attn.o_proj.weight": "model-00001-of-00002.safetensors", + "layers.20.self_attn.q_norm.weight": "model-00001-of-00002.safetensors", + "layers.20.self_attn.q_proj.weight": "model-00001-of-00002.safetensors", + "layers.20.self_attn.v_proj.weight": "model-00001-of-00002.safetensors", + "layers.21.input_layernorm.weight": "model-00002-of-00002.safetensors", + "layers.21.mlp.down_proj.weight": "model-00002-of-00002.safetensors", + "layers.21.mlp.gate_proj.weight": "model-00002-of-00002.safetensors", + "layers.21.mlp.up_proj.weight": "model-00002-of-00002.safetensors", + "layers.21.post_attention_layernorm.weight": "model-00002-of-00002.safetensors", + "layers.21.self_attn.k_norm.weight": "model-00002-of-00002.safetensors", + "layers.21.self_attn.k_proj.weight": "model-00002-of-00002.safetensors", + "layers.21.self_attn.o_proj.weight": "model-00002-of-00002.safetensors", + "layers.21.self_attn.q_norm.weight": "model-00002-of-00002.safetensors", + "layers.21.self_attn.q_proj.weight": "model-00002-of-00002.safetensors", + "layers.21.self_attn.v_proj.weight": "model-00002-of-00002.safetensors", + "layers.22.input_layernorm.weight": "model-00002-of-00002.safetensors", + "layers.22.mlp.down_proj.weight": "model-00002-of-00002.safetensors", + "layers.22.mlp.gate_proj.weight": "model-00002-of-00002.safetensors", + "layers.22.mlp.up_proj.weight": "model-00002-of-00002.safetensors", + "layers.22.post_attention_layernorm.weight": "model-00002-of-00002.safetensors", + "layers.22.self_attn.k_norm.weight": "model-00002-of-00002.safetensors", + "layers.22.self_attn.k_proj.weight": "model-00002-of-00002.safetensors", + "layers.22.self_attn.o_proj.weight": "model-00002-of-00002.safetensors", + "layers.22.self_attn.q_norm.weight": "model-00002-of-00002.safetensors", + "layers.22.self_attn.q_proj.weight": "model-00002-of-00002.safetensors", + "layers.22.self_attn.v_proj.weight": "model-00002-of-00002.safetensors", + "layers.23.input_layernorm.weight": "model-00002-of-00002.safetensors", + "layers.23.mlp.down_proj.weight": "model-00002-of-00002.safetensors", + "layers.23.mlp.gate_proj.weight": "model-00002-of-00002.safetensors", + "layers.23.mlp.up_proj.weight": "model-00002-of-00002.safetensors", + "layers.23.post_attention_layernorm.weight": "model-00002-of-00002.safetensors", + "layers.23.self_attn.k_norm.weight": "model-00002-of-00002.safetensors", + "layers.23.self_attn.k_proj.weight": "model-00002-of-00002.safetensors", + "layers.23.self_attn.o_proj.weight": "model-00002-of-00002.safetensors", + "layers.23.self_attn.q_norm.weight": "model-00002-of-00002.safetensors", + "layers.23.self_attn.q_proj.weight": "model-00002-of-00002.safetensors", + "layers.23.self_attn.v_proj.weight": "model-00002-of-00002.safetensors", + "layers.24.input_layernorm.weight": "model-00002-of-00002.safetensors", + "layers.24.mlp.down_proj.weight": "model-00002-of-00002.safetensors", + "layers.24.mlp.gate_proj.weight": "model-00002-of-00002.safetensors", + "layers.24.mlp.up_proj.weight": "model-00002-of-00002.safetensors", + "layers.24.post_attention_layernorm.weight": "model-00002-of-00002.safetensors", + "layers.24.self_attn.k_norm.weight": "model-00002-of-00002.safetensors", + "layers.24.self_attn.k_proj.weight": "model-00002-of-00002.safetensors", + "layers.24.self_attn.o_proj.weight": "model-00002-of-00002.safetensors", + "layers.24.self_attn.q_norm.weight": "model-00002-of-00002.safetensors", + "layers.24.self_attn.q_proj.weight": "model-00002-of-00002.safetensors", + "layers.24.self_attn.v_proj.weight": "model-00002-of-00002.safetensors", + "layers.25.input_layernorm.weight": "model-00002-of-00002.safetensors", + "layers.25.mlp.down_proj.weight": "model-00002-of-00002.safetensors", + "layers.25.mlp.gate_proj.weight": "model-00002-of-00002.safetensors", + "layers.25.mlp.up_proj.weight": "model-00002-of-00002.safetensors", + "layers.25.post_attention_layernorm.weight": "model-00002-of-00002.safetensors", + "layers.25.self_attn.k_norm.weight": "model-00002-of-00002.safetensors", + "layers.25.self_attn.k_proj.weight": "model-00002-of-00002.safetensors", + "layers.25.self_attn.o_proj.weight": "model-00002-of-00002.safetensors", + "layers.25.self_attn.q_norm.weight": "model-00002-of-00002.safetensors", + "layers.25.self_attn.q_proj.weight": "model-00002-of-00002.safetensors", + "layers.25.self_attn.v_proj.weight": "model-00002-of-00002.safetensors", + "layers.26.input_layernorm.weight": "model-00002-of-00002.safetensors", + "layers.26.mlp.down_proj.weight": "model-00002-of-00002.safetensors", + "layers.26.mlp.gate_proj.weight": "model-00002-of-00002.safetensors", + "layers.26.mlp.up_proj.weight": "model-00002-of-00002.safetensors", + "layers.26.post_attention_layernorm.weight": "model-00002-of-00002.safetensors", + "layers.26.self_attn.k_norm.weight": "model-00002-of-00002.safetensors", + "layers.26.self_attn.k_proj.weight": "model-00002-of-00002.safetensors", + "layers.26.self_attn.o_proj.weight": "model-00002-of-00002.safetensors", + "layers.26.self_attn.q_norm.weight": "model-00002-of-00002.safetensors", + "layers.26.self_attn.q_proj.weight": "model-00002-of-00002.safetensors", + "layers.26.self_attn.v_proj.weight": "model-00002-of-00002.safetensors", + "layers.27.input_layernorm.weight": "model-00002-of-00002.safetensors", + "layers.27.mlp.down_proj.weight": "model-00002-of-00002.safetensors", + "layers.27.mlp.gate_proj.weight": "model-00002-of-00002.safetensors", + "layers.27.mlp.up_proj.weight": "model-00002-of-00002.safetensors", + "layers.27.post_attention_layernorm.weight": "model-00002-of-00002.safetensors", + "layers.27.self_attn.k_norm.weight": "model-00002-of-00002.safetensors", + "layers.27.self_attn.k_proj.weight": "model-00002-of-00002.safetensors", + "layers.27.self_attn.o_proj.weight": "model-00002-of-00002.safetensors", + "layers.27.self_attn.q_norm.weight": "model-00002-of-00002.safetensors", + "layers.27.self_attn.q_proj.weight": "model-00002-of-00002.safetensors", + "layers.27.self_attn.v_proj.weight": "model-00002-of-00002.safetensors", + "layers.28.input_layernorm.weight": "model-00002-of-00002.safetensors", + "layers.28.mlp.down_proj.weight": "model-00002-of-00002.safetensors", + "layers.28.mlp.gate_proj.weight": "model-00002-of-00002.safetensors", + "layers.28.mlp.up_proj.weight": "model-00002-of-00002.safetensors", + "layers.28.post_attention_layernorm.weight": "model-00002-of-00002.safetensors", + "layers.28.self_attn.k_norm.weight": "model-00002-of-00002.safetensors", + "layers.28.self_attn.k_proj.weight": "model-00002-of-00002.safetensors", + "layers.28.self_attn.o_proj.weight": "model-00002-of-00002.safetensors", + "layers.28.self_attn.q_norm.weight": "model-00002-of-00002.safetensors", + "layers.28.self_attn.q_proj.weight": "model-00002-of-00002.safetensors", + "layers.28.self_attn.v_proj.weight": "model-00002-of-00002.safetensors", + "layers.29.input_layernorm.weight": "model-00002-of-00002.safetensors", + "layers.29.mlp.down_proj.weight": "model-00002-of-00002.safetensors", + "layers.29.mlp.gate_proj.weight": "model-00002-of-00002.safetensors", + "layers.29.mlp.up_proj.weight": "model-00002-of-00002.safetensors", + "layers.29.post_attention_layernorm.weight": "model-00002-of-00002.safetensors", + "layers.29.self_attn.k_norm.weight": "model-00002-of-00002.safetensors", + "layers.29.self_attn.k_proj.weight": "model-00002-of-00002.safetensors", + "layers.29.self_attn.o_proj.weight": "model-00002-of-00002.safetensors", + "layers.29.self_attn.q_norm.weight": "model-00002-of-00002.safetensors", + "layers.29.self_attn.q_proj.weight": "model-00002-of-00002.safetensors", + "layers.29.self_attn.v_proj.weight": "model-00002-of-00002.safetensors", + "layers.3.input_layernorm.weight": "model-00001-of-00002.safetensors", + "layers.3.mlp.down_proj.weight": "model-00001-of-00002.safetensors", + "layers.3.mlp.gate_proj.weight": "model-00001-of-00002.safetensors", + "layers.3.mlp.up_proj.weight": "model-00001-of-00002.safetensors", + "layers.3.post_attention_layernorm.weight": "model-00001-of-00002.safetensors", + "layers.3.self_attn.k_norm.weight": "model-00001-of-00002.safetensors", + "layers.3.self_attn.k_proj.weight": "model-00001-of-00002.safetensors", + "layers.3.self_attn.o_proj.weight": "model-00001-of-00002.safetensors", + "layers.3.self_attn.q_norm.weight": "model-00001-of-00002.safetensors", + "layers.3.self_attn.q_proj.weight": "model-00001-of-00002.safetensors", + "layers.3.self_attn.v_proj.weight": "model-00001-of-00002.safetensors", + "layers.30.input_layernorm.weight": "model-00002-of-00002.safetensors", + "layers.30.mlp.down_proj.weight": "model-00002-of-00002.safetensors", + "layers.30.mlp.gate_proj.weight": "model-00002-of-00002.safetensors", + "layers.30.mlp.up_proj.weight": "model-00002-of-00002.safetensors", + "layers.30.post_attention_layernorm.weight": "model-00002-of-00002.safetensors", + "layers.30.self_attn.k_norm.weight": "model-00002-of-00002.safetensors", + "layers.30.self_attn.k_proj.weight": "model-00002-of-00002.safetensors", + "layers.30.self_attn.o_proj.weight": "model-00002-of-00002.safetensors", + "layers.30.self_attn.q_norm.weight": "model-00002-of-00002.safetensors", + "layers.30.self_attn.q_proj.weight": "model-00002-of-00002.safetensors", + "layers.30.self_attn.v_proj.weight": "model-00002-of-00002.safetensors", + "layers.31.input_layernorm.weight": "model-00002-of-00002.safetensors", + "layers.31.mlp.down_proj.weight": "model-00002-of-00002.safetensors", + "layers.31.mlp.gate_proj.weight": "model-00002-of-00002.safetensors", + "layers.31.mlp.up_proj.weight": "model-00002-of-00002.safetensors", + "layers.31.post_attention_layernorm.weight": "model-00002-of-00002.safetensors", + "layers.31.self_attn.k_norm.weight": "model-00002-of-00002.safetensors", + "layers.31.self_attn.k_proj.weight": "model-00002-of-00002.safetensors", + "layers.31.self_attn.o_proj.weight": "model-00002-of-00002.safetensors", + "layers.31.self_attn.q_norm.weight": "model-00002-of-00002.safetensors", + "layers.31.self_attn.q_proj.weight": "model-00002-of-00002.safetensors", + "layers.31.self_attn.v_proj.weight": "model-00002-of-00002.safetensors", + "layers.32.input_layernorm.weight": "model-00002-of-00002.safetensors", + "layers.32.mlp.down_proj.weight": "model-00002-of-00002.safetensors", + "layers.32.mlp.gate_proj.weight": "model-00002-of-00002.safetensors", + "layers.32.mlp.up_proj.weight": "model-00002-of-00002.safetensors", + "layers.32.post_attention_layernorm.weight": "model-00002-of-00002.safetensors", + "layers.32.self_attn.k_norm.weight": "model-00002-of-00002.safetensors", + "layers.32.self_attn.k_proj.weight": "model-00002-of-00002.safetensors", + "layers.32.self_attn.o_proj.weight": "model-00002-of-00002.safetensors", + "layers.32.self_attn.q_norm.weight": "model-00002-of-00002.safetensors", + "layers.32.self_attn.q_proj.weight": "model-00002-of-00002.safetensors", + "layers.32.self_attn.v_proj.weight": "model-00002-of-00002.safetensors", + "layers.33.input_layernorm.weight": "model-00002-of-00002.safetensors", + "layers.33.mlp.down_proj.weight": "model-00002-of-00002.safetensors", + "layers.33.mlp.gate_proj.weight": "model-00002-of-00002.safetensors", + "layers.33.mlp.up_proj.weight": "model-00002-of-00002.safetensors", + "layers.33.post_attention_layernorm.weight": "model-00002-of-00002.safetensors", + "layers.33.self_attn.k_norm.weight": "model-00002-of-00002.safetensors", + "layers.33.self_attn.k_proj.weight": "model-00002-of-00002.safetensors", + "layers.33.self_attn.o_proj.weight": "model-00002-of-00002.safetensors", + "layers.33.self_attn.q_norm.weight": "model-00002-of-00002.safetensors", + "layers.33.self_attn.q_proj.weight": "model-00002-of-00002.safetensors", + "layers.33.self_attn.v_proj.weight": "model-00002-of-00002.safetensors", + "layers.34.input_layernorm.weight": "model-00002-of-00002.safetensors", + "layers.34.mlp.down_proj.weight": "model-00002-of-00002.safetensors", + "layers.34.mlp.gate_proj.weight": "model-00002-of-00002.safetensors", + "layers.34.mlp.up_proj.weight": "model-00002-of-00002.safetensors", + "layers.34.post_attention_layernorm.weight": "model-00002-of-00002.safetensors", + "layers.34.self_attn.k_norm.weight": "model-00002-of-00002.safetensors", + "layers.34.self_attn.k_proj.weight": "model-00002-of-00002.safetensors", + "layers.34.self_attn.o_proj.weight": "model-00002-of-00002.safetensors", + "layers.34.self_attn.q_norm.weight": "model-00002-of-00002.safetensors", + "layers.34.self_attn.q_proj.weight": "model-00002-of-00002.safetensors", + "layers.34.self_attn.v_proj.weight": "model-00002-of-00002.safetensors", + "layers.35.input_layernorm.weight": "model-00002-of-00002.safetensors", + "layers.35.mlp.down_proj.weight": "model-00002-of-00002.safetensors", + "layers.35.mlp.gate_proj.weight": "model-00002-of-00002.safetensors", + "layers.35.mlp.up_proj.weight": "model-00002-of-00002.safetensors", + "layers.35.post_attention_layernorm.weight": "model-00002-of-00002.safetensors", + "layers.35.self_attn.k_norm.weight": "model-00002-of-00002.safetensors", + "layers.35.self_attn.k_proj.weight": "model-00002-of-00002.safetensors", + "layers.35.self_attn.o_proj.weight": "model-00002-of-00002.safetensors", + "layers.35.self_attn.q_norm.weight": "model-00002-of-00002.safetensors", + "layers.35.self_attn.q_proj.weight": "model-00002-of-00002.safetensors", + "layers.35.self_attn.v_proj.weight": "model-00002-of-00002.safetensors", + "layers.4.input_layernorm.weight": "model-00001-of-00002.safetensors", + "layers.4.mlp.down_proj.weight": "model-00001-of-00002.safetensors", + "layers.4.mlp.gate_proj.weight": "model-00001-of-00002.safetensors", + "layers.4.mlp.up_proj.weight": "model-00001-of-00002.safetensors", + "layers.4.post_attention_layernorm.weight": "model-00001-of-00002.safetensors", + "layers.4.self_attn.k_norm.weight": "model-00001-of-00002.safetensors", + "layers.4.self_attn.k_proj.weight": "model-00001-of-00002.safetensors", + "layers.4.self_attn.o_proj.weight": "model-00001-of-00002.safetensors", + "layers.4.self_attn.q_norm.weight": "model-00001-of-00002.safetensors", + "layers.4.self_attn.q_proj.weight": "model-00001-of-00002.safetensors", + "layers.4.self_attn.v_proj.weight": "model-00001-of-00002.safetensors", + "layers.5.input_layernorm.weight": "model-00001-of-00002.safetensors", + "layers.5.mlp.down_proj.weight": "model-00001-of-00002.safetensors", + "layers.5.mlp.gate_proj.weight": "model-00001-of-00002.safetensors", + "layers.5.mlp.up_proj.weight": "model-00001-of-00002.safetensors", + "layers.5.post_attention_layernorm.weight": "model-00001-of-00002.safetensors", + "layers.5.self_attn.k_norm.weight": "model-00001-of-00002.safetensors", + "layers.5.self_attn.k_proj.weight": "model-00001-of-00002.safetensors", + "layers.5.self_attn.o_proj.weight": "model-00001-of-00002.safetensors", + "layers.5.self_attn.q_norm.weight": "model-00001-of-00002.safetensors", + "layers.5.self_attn.q_proj.weight": "model-00001-of-00002.safetensors", + "layers.5.self_attn.v_proj.weight": "model-00001-of-00002.safetensors", + "layers.6.input_layernorm.weight": "model-00001-of-00002.safetensors", + "layers.6.mlp.down_proj.weight": "model-00001-of-00002.safetensors", + "layers.6.mlp.gate_proj.weight": "model-00001-of-00002.safetensors", + "layers.6.mlp.up_proj.weight": "model-00001-of-00002.safetensors", + "layers.6.post_attention_layernorm.weight": "model-00001-of-00002.safetensors", + "layers.6.self_attn.k_norm.weight": "model-00001-of-00002.safetensors", + "layers.6.self_attn.k_proj.weight": "model-00001-of-00002.safetensors", + "layers.6.self_attn.o_proj.weight": "model-00001-of-00002.safetensors", + "layers.6.self_attn.q_norm.weight": "model-00001-of-00002.safetensors", + "layers.6.self_attn.q_proj.weight": "model-00001-of-00002.safetensors", + "layers.6.self_attn.v_proj.weight": "model-00001-of-00002.safetensors", + "layers.7.input_layernorm.weight": "model-00001-of-00002.safetensors", + "layers.7.mlp.down_proj.weight": "model-00001-of-00002.safetensors", + "layers.7.mlp.gate_proj.weight": "model-00001-of-00002.safetensors", + "layers.7.mlp.up_proj.weight": "model-00001-of-00002.safetensors", + "layers.7.post_attention_layernorm.weight": "model-00001-of-00002.safetensors", + "layers.7.self_attn.k_norm.weight": "model-00001-of-00002.safetensors", + "layers.7.self_attn.k_proj.weight": "model-00001-of-00002.safetensors", + "layers.7.self_attn.o_proj.weight": "model-00001-of-00002.safetensors", + "layers.7.self_attn.q_norm.weight": "model-00001-of-00002.safetensors", + "layers.7.self_attn.q_proj.weight": "model-00001-of-00002.safetensors", + "layers.7.self_attn.v_proj.weight": "model-00001-of-00002.safetensors", + "layers.8.input_layernorm.weight": "model-00001-of-00002.safetensors", + "layers.8.mlp.down_proj.weight": "model-00001-of-00002.safetensors", + "layers.8.mlp.gate_proj.weight": "model-00001-of-00002.safetensors", + "layers.8.mlp.up_proj.weight": "model-00001-of-00002.safetensors", + "layers.8.post_attention_layernorm.weight": "model-00001-of-00002.safetensors", + "layers.8.self_attn.k_norm.weight": "model-00001-of-00002.safetensors", + "layers.8.self_attn.k_proj.weight": "model-00001-of-00002.safetensors", + "layers.8.self_attn.o_proj.weight": "model-00001-of-00002.safetensors", + "layers.8.self_attn.q_norm.weight": "model-00001-of-00002.safetensors", + "layers.8.self_attn.q_proj.weight": "model-00001-of-00002.safetensors", + "layers.8.self_attn.v_proj.weight": "model-00001-of-00002.safetensors", + "layers.9.input_layernorm.weight": "model-00001-of-00002.safetensors", + "layers.9.mlp.down_proj.weight": "model-00001-of-00002.safetensors", + "layers.9.mlp.gate_proj.weight": "model-00001-of-00002.safetensors", + "layers.9.mlp.up_proj.weight": "model-00001-of-00002.safetensors", + "layers.9.post_attention_layernorm.weight": "model-00001-of-00002.safetensors", + "layers.9.self_attn.k_norm.weight": "model-00001-of-00002.safetensors", + "layers.9.self_attn.k_proj.weight": "model-00001-of-00002.safetensors", + "layers.9.self_attn.o_proj.weight": "model-00001-of-00002.safetensors", + "layers.9.self_attn.q_norm.weight": "model-00001-of-00002.safetensors", + "layers.9.self_attn.q_proj.weight": "model-00001-of-00002.safetensors", + "layers.9.self_attn.v_proj.weight": "model-00001-of-00002.safetensors", + "norm.weight": "model-00002-of-00002.safetensors" + } +} diff --git a/catalog/drafts/qwen3-embedding-4b/source/modules.json b/catalog/drafts/qwen3-embedding-4b/source/modules.json new file mode 100644 index 00000000..952a9b81 --- /dev/null +++ b/catalog/drafts/qwen3-embedding-4b/source/modules.json @@ -0,0 +1,20 @@ +[ + { + "idx": 0, + "name": "0", + "path": "", + "type": "sentence_transformers.models.Transformer" + }, + { + "idx": 1, + "name": "1", + "path": "1_Pooling", + "type": "sentence_transformers.models.Pooling" + }, + { + "idx": 2, + "name": "2", + "path": "2_Normalize", + "type": "sentence_transformers.models.Normalize" + } +] \ No newline at end of file diff --git a/catalog/drafts/qwen3-embedding-4b/source/tree.json b/catalog/drafts/qwen3-embedding-4b/source/tree.json new file mode 100644 index 00000000..cc80ec32 --- /dev/null +++ b/catalog/drafts/qwen3-embedding-4b/source/tree.json @@ -0,0 +1 @@ +[{"type":"directory","oid":"d5578f0944c6449af9b8bf48db13212742ef873f","size":0,"path":"1_Pooling","lastCommit":{"id":"636cd9bf47d976946cdbb2b0c3ca0cb2f8eea5ff","title":"Automatically add EOS via Tokenizer, integrate Sentence Transformers (#1)","date":"2025-06-06T05:58:34.000Z"}},{"type":"file","oid":"52373fe24473b1aa44333d318f578ae6bf04b49b","size":1570,"path":".gitattributes","lastCommit":{"id":"d50085f71d391113eff8c6695312f4330dfe6ad6","title":"Upload folder using huggingface_hub","date":"2025-06-03T14:34:03.000Z"},"securityFileStatus":{"status":"safe","protectAiScan":{"status":"safe","message":"This file has no security findings.","reportLink":"https://insights-db.paloaltonetworks.com/models/Qwen/Qwen3-Embedding-4B/5cf2132abc99cad020ac570b19d031efec650f2b/files?blob-id=52373fe24473b1aa44333d318f578ae6bf04b49b&utm_source=huggingface"},"avScan":{"status":"safe","message":"No security issues detected","reportLink":"https://fdtn.ai/ai-supply-chain/hugging-face?utm_source=huggingface"},"pickleImportScan":{"status":"unscanned","pickleImports":[],"version":"0.0.0"},"virusTotalScan":{"status":"unscanned"},"jFrogScan":{"status":"unscanned","message":"Not a machine-learning model"}}},{"type":"file","oid":"81de5602eacbce382009c5af7a23085871801d8f","size":313,"path":"1_Pooling/config.json","lastCommit":{"id":"636cd9bf47d976946cdbb2b0c3ca0cb2f8eea5ff","title":"Automatically add EOS via Tokenizer, integrate Sentence Transformers (#1)","date":"2025-06-06T05:58:34.000Z"},"securityFileStatus":{"status":"safe","protectAiScan":{"status":"safe","message":"This file has no security findings.","reportLink":"https://insights-db.paloaltonetworks.com/models/Qwen/Qwen3-Embedding-4B/5cf2132abc99cad020ac570b19d031efec650f2b/files?blob-id=81de5602eacbce382009c5af7a23085871801d8f&utm_source=huggingface"},"avScan":{"status":"safe","message":"No security issues detected","reportLink":"https://fdtn.ai/ai-supply-chain/hugging-face?utm_source=huggingface"},"pickleImportScan":{"status":"unscanned","pickleImports":[],"version":"0.0.0"},"virusTotalScan":{"status":"unscanned"},"jFrogScan":{"status":"unscanned","message":"Not a machine-learning model"}}},{"type":"file","oid":"81d922bc72353348a181473b9cc0ee53571ae13b","size":17276,"path":"README.md","lastCommit":{"id":"5cf2132abc99cad020ac570b19d031efec650f2b","title":"Update README.md with TEI support (#13)","date":"2025-06-20T09:30:56.000Z"},"securityFileStatus":{"status":"safe","protectAiScan":{"status":"safe","message":"This file has no security findings.","reportLink":"https://insights-db.paloaltonetworks.com/models/Qwen/Qwen3-Embedding-4B/5cf2132abc99cad020ac570b19d031efec650f2b/files?blob-id=81d922bc72353348a181473b9cc0ee53571ae13b&utm_source=huggingface"},"avScan":{"status":"safe","message":"No security issues detected","reportLink":"https://fdtn.ai/ai-supply-chain/hugging-face?utm_source=huggingface"},"pickleImportScan":{"status":"unscanned","pickleImports":[],"version":"0.0.0"},"virusTotalScan":{"status":"unscanned"},"jFrogScan":{"status":"unscanned","message":"Not a machine-learning model"}}},{"type":"file","oid":"8b4b87fc69023e7a224eb6563753aaf3223d8b98","size":727,"path":"config.json","lastCommit":{"id":"bcf11cc78d46793c31fe74afddd2a01fa24bed08","title":"Update config.json","date":"2025-06-05T01:18:33.000Z"},"securityFileStatus":{"status":"safe","protectAiScan":{"status":"safe","message":"This file has no security findings.","reportLink":"https://insights-db.paloaltonetworks.com/models/Qwen/Qwen3-Embedding-4B/5cf2132abc99cad020ac570b19d031efec650f2b/files?blob-id=8b4b87fc69023e7a224eb6563753aaf3223d8b98&utm_source=huggingface"},"avScan":{"status":"safe","message":"No security issues detected","reportLink":"https://fdtn.ai/ai-supply-chain/hugging-face?utm_source=huggingface"},"pickleImportScan":{"status":"unscanned","pickleImports":[],"version":"0.0.0"},"virusTotalScan":{"status":"unscanned"},"jFrogScan":{"status":"unscanned","message":"Not a machine-learning model"}}},{"type":"file","oid":"76aef3ade63553ebb698fe3c2a3264040ed093f8","size":215,"path":"config_sentence_transformers.json","lastCommit":{"id":"636cd9bf47d976946cdbb2b0c3ca0cb2f8eea5ff","title":"Automatically add EOS via Tokenizer, integrate Sentence Transformers (#1)","date":"2025-06-06T05:58:34.000Z"},"securityFileStatus":{"status":"safe","protectAiScan":{"status":"safe","message":"This file has no security findings.","reportLink":"https://insights-db.paloaltonetworks.com/models/Qwen/Qwen3-Embedding-4B/5cf2132abc99cad020ac570b19d031efec650f2b/files?blob-id=76aef3ade63553ebb698fe3c2a3264040ed093f8&utm_source=huggingface"},"avScan":{"status":"safe","message":"No security issues detected","reportLink":"https://fdtn.ai/ai-supply-chain/hugging-face?utm_source=huggingface"},"pickleImportScan":{"status":"unscanned","pickleImports":[],"version":"0.0.0"},"virusTotalScan":{"status":"unscanned"},"jFrogScan":{"status":"unscanned","message":"Not a machine-learning model"}}},{"type":"file","oid":"d46f1983345269c582611bbedb3ca0a13f8e5f7b","size":117,"path":"generation_config.json","lastCommit":{"id":"d50085f71d391113eff8c6695312f4330dfe6ad6","title":"Upload folder using huggingface_hub","date":"2025-06-03T14:34:03.000Z"},"securityFileStatus":{"status":"safe","protectAiScan":{"status":"safe","message":"This file has no security findings.","reportLink":"https://insights-db.paloaltonetworks.com/models/Qwen/Qwen3-Embedding-4B/5cf2132abc99cad020ac570b19d031efec650f2b/files?blob-id=d46f1983345269c582611bbedb3ca0a13f8e5f7b&utm_source=huggingface"},"avScan":{"status":"safe","message":"No security issues detected","reportLink":"https://fdtn.ai/ai-supply-chain/hugging-face?utm_source=huggingface"},"pickleImportScan":{"status":"unscanned","pickleImports":[],"version":"0.0.0"},"virusTotalScan":{"status":"unscanned"},"jFrogScan":{"status":"unscanned","message":"Not a machine-learning model"}}},{"type":"file","oid":"31349551d90c7606f325fe0f11bbb8bd5fa0d7c7","size":1671853,"path":"merges.txt","lastCommit":{"id":"d50085f71d391113eff8c6695312f4330dfe6ad6","title":"Upload folder using huggingface_hub","date":"2025-06-03T14:34:03.000Z"},"securityFileStatus":{"status":"safe","protectAiScan":{"status":"safe","message":"This file has no security findings.","reportLink":"https://insights-db.paloaltonetworks.com/models/Qwen/Qwen3-Embedding-4B/5cf2132abc99cad020ac570b19d031efec650f2b/files?blob-id=31349551d90c7606f325fe0f11bbb8bd5fa0d7c7&utm_source=huggingface"},"avScan":{"status":"safe","message":"No security issues detected","reportLink":"https://fdtn.ai/ai-supply-chain/hugging-face?utm_source=huggingface"},"pickleImportScan":{"status":"unscanned","pickleImports":[],"version":"0.0.0"},"virusTotalScan":{"status":"unscanned"},"jFrogScan":{"status":"unscanned","message":"Not a machine-learning model"}}},{"type":"file","oid":"a8e10f7d684d392e8531cce8b5817518ffe9ebe9","size":4965826464,"lfs":{"oid":"e70bfe3c970523fb7ef4eddffed2254ce3f1e7150c3de2af4342de129dd756f8","size":4965826464,"pointerSize":135},"xetHash":"8391f0f2676c4e78beb9b2aaa09a349fa8e48388aec74516895d2eefab6dd696","path":"model-00001-of-00002.safetensors","lastCommit":{"id":"d50085f71d391113eff8c6695312f4330dfe6ad6","title":"Upload folder using huggingface_hub","date":"2025-06-03T14:34:03.000Z"},"securityFileStatus":{"status":"safe","protectAiScan":{"status":"safe","message":"This file has no security findings.","reportLink":"https://insights-db.paloaltonetworks.com/models/Qwen/Qwen3-Embedding-4B/5cf2132abc99cad020ac570b19d031efec650f2b/files?blob-id=a8e10f7d684d392e8531cce8b5817518ffe9ebe9&utm_source=huggingface"},"avScan":{"status":"unscanned","message":"","version":"1.5.2/27961"},"pickleImportScan":{"status":"unscanned","pickleImports":[],"version":"0.0.0"},"virusTotalScan":{"status":"safe","message":"0/75 engines detect it as malicious.","reportLink":"https://www.virustotal.com/gui/file/e70bfe3c970523fb7ef4eddffed2254ce3f1e7150c3de2af4342de129dd756f8?utm_source=huggingface"},"jFrogScan":{"status":"safe","message":"Safe model, does not support code execution on load.","reportLink":"https://research.jfrog.com/model-threats/noautoload-suscode?utm_source=huggingface"}}},{"type":"file","oid":"9db8ba5ef7e0afa2d94940c5472491e2d4f1dc9e","size":3077765624,"lfs":{"oid":"ed1b87c8e9eb7e535a1a155e4fd00d9f4dba80e58a6db48a4c9f82cede7079c1","size":3077765624,"pointerSize":135},"xetHash":"e9793d8fe61374703e8d2d86772b67c846972acfc259c995c3d052057c882b7b","path":"model-00002-of-00002.safetensors","lastCommit":{"id":"d50085f71d391113eff8c6695312f4330dfe6ad6","title":"Upload folder using huggingface_hub","date":"2025-06-03T14:34:03.000Z"},"securityFileStatus":{"status":"safe","protectAiScan":{"status":"safe","message":"This file has no security findings.","reportLink":"https://insights-db.paloaltonetworks.com/models/Qwen/Qwen3-Embedding-4B/5cf2132abc99cad020ac570b19d031efec650f2b/files?blob-id=9db8ba5ef7e0afa2d94940c5472491e2d4f1dc9e&utm_source=huggingface"},"avScan":{"status":"unscanned","message":"","version":"1.5.2/27961"},"pickleImportScan":{"status":"unscanned","pickleImports":[],"version":"0.0.0"},"virusTotalScan":{"status":"safe","message":"0/75 engines detect it as malicious.","reportLink":"https://www.virustotal.com/gui/file/ed1b87c8e9eb7e535a1a155e4fd00d9f4dba80e58a6db48a4c9f82cede7079c1?utm_source=huggingface"},"jFrogScan":{"status":"safe","message":"Safe model, does not support code execution on load.","reportLink":"https://research.jfrog.com/model-threats/noautoload-suscode?utm_source=huggingface"}}},{"type":"file","oid":"3d736ef26714eee0abde3e05104ee1b3ec26c974","size":30431,"path":"model.safetensors.index.json","lastCommit":{"id":"d50085f71d391113eff8c6695312f4330dfe6ad6","title":"Upload folder using huggingface_hub","date":"2025-06-03T14:34:03.000Z"},"securityFileStatus":{"status":"safe","protectAiScan":{"status":"safe","message":"This file has no security findings.","reportLink":"https://insights-db.paloaltonetworks.com/models/Qwen/Qwen3-Embedding-4B/5cf2132abc99cad020ac570b19d031efec650f2b/files?blob-id=3d736ef26714eee0abde3e05104ee1b3ec26c974&utm_source=huggingface"},"avScan":{"status":"safe","message":"No security issues detected","reportLink":"https://fdtn.ai/ai-supply-chain/hugging-face?utm_source=huggingface"},"pickleImportScan":{"status":"unscanned","pickleImports":[],"version":"0.0.0"},"virusTotalScan":{"status":"unscanned"},"jFrogScan":{"status":"unscanned","message":"Not a machine-learning model"}}},{"type":"file","oid":"952a9b81c0bfd99800fabf352f69c7ccd46c5e43","size":349,"path":"modules.json","lastCommit":{"id":"636cd9bf47d976946cdbb2b0c3ca0cb2f8eea5ff","title":"Automatically add EOS via Tokenizer, integrate Sentence Transformers (#1)","date":"2025-06-06T05:58:34.000Z"},"securityFileStatus":{"status":"safe","protectAiScan":{"status":"safe","message":"This file has no security findings.","reportLink":"https://insights-db.paloaltonetworks.com/models/Qwen/Qwen3-Embedding-4B/5cf2132abc99cad020ac570b19d031efec650f2b/files?blob-id=952a9b81c0bfd99800fabf352f69c7ccd46c5e43&utm_source=huggingface"},"avScan":{"status":"safe","message":"No security issues detected","reportLink":"https://fdtn.ai/ai-supply-chain/hugging-face?utm_source=huggingface"},"pickleImportScan":{"status":"unscanned","pickleImports":[],"version":"0.0.0"},"virusTotalScan":{"status":"unscanned"},"jFrogScan":{"status":"unscanned","message":"Not a machine-learning model"}}},{"type":"file","oid":"982862dae5cfde7feac135a6367874a81a4a9fd5","size":11422947,"lfs":{"oid":"83cdf8c3a34f68862319cb1810ee7b1e2c0a44e0864ae930194ddb76bb7feb8d","size":11422947,"pointerSize":133},"xetHash":"8407b8a5fc62ae108ee9c4cc91f05d5f2bcacedcf3b2e374505ec6a4d9b92c0f","path":"tokenizer.json","lastCommit":{"id":"636cd9bf47d976946cdbb2b0c3ca0cb2f8eea5ff","title":"Automatically add EOS via Tokenizer, integrate Sentence Transformers (#1)","date":"2025-06-06T05:58:34.000Z"},"securityFileStatus":{"status":"safe","protectAiScan":{"status":"safe","message":"This file has no security findings.","reportLink":"https://insights-db.paloaltonetworks.com/models/Qwen/Qwen3-Embedding-4B/5cf2132abc99cad020ac570b19d031efec650f2b/files?blob-id=982862dae5cfde7feac135a6367874a81a4a9fd5&utm_source=huggingface"},"avScan":{"status":"safe","message":"No security issues detected","reportLink":"https://fdtn.ai/ai-supply-chain/hugging-face?utm_source=huggingface"},"pickleImportScan":{"status":"unscanned","pickleImports":[],"version":"0.0.0"},"virusTotalScan":{"status":"safe","message":"0/76 engines detect it as malicious.","reportLink":"https://www.virustotal.com/gui/file/83cdf8c3a34f68862319cb1810ee7b1e2c0a44e0864ae930194ddb76bb7feb8d?utm_source=huggingface"},"jFrogScan":{"status":"unscanned","message":"Not a machine-learning model"}}},{"type":"file","oid":"df3a9d96759529ca1006eb6db024bbb099a97578","size":7256,"path":"tokenizer_config.json","lastCommit":{"id":"d50085f71d391113eff8c6695312f4330dfe6ad6","title":"Upload folder using huggingface_hub","date":"2025-06-03T14:34:03.000Z"},"securityFileStatus":{"status":"safe","protectAiScan":{"status":"safe","message":"This file has no security findings.","reportLink":"https://insights-db.paloaltonetworks.com/models/Qwen/Qwen3-Embedding-4B/5cf2132abc99cad020ac570b19d031efec650f2b/files?blob-id=df3a9d96759529ca1006eb6db024bbb099a97578&utm_source=huggingface"},"avScan":{"status":"safe","message":"No security issues detected","reportLink":"https://fdtn.ai/ai-supply-chain/hugging-face?utm_source=huggingface"},"pickleImportScan":{"status":"unscanned","pickleImports":[],"version":"0.0.0"},"virusTotalScan":{"status":"unscanned"},"jFrogScan":{"status":"unscanned","message":"Not a machine-learning model"}}},{"type":"file","oid":"4783fe10ac3adce15ac8f358ef5462739852c569","size":2776833,"path":"vocab.json","lastCommit":{"id":"d50085f71d391113eff8c6695312f4330dfe6ad6","title":"Upload folder using huggingface_hub","date":"2025-06-03T14:34:03.000Z"},"securityFileStatus":{"status":"safe","protectAiScan":{"status":"safe","message":"This file has no security findings.","reportLink":"https://insights-db.paloaltonetworks.com/models/Qwen/Qwen3-Embedding-4B/5cf2132abc99cad020ac570b19d031efec650f2b/files?blob-id=4783fe10ac3adce15ac8f358ef5462739852c569&utm_source=huggingface"},"avScan":{"status":"safe","message":"No security issues detected","reportLink":"https://fdtn.ai/ai-supply-chain/hugging-face?utm_source=huggingface"},"pickleImportScan":{"status":"unscanned","pickleImports":[],"version":"0.0.0"},"virusTotalScan":{"status":"unscanned"},"jFrogScan":{"status":"unscanned","message":"Not a machine-learning model"}}}] \ No newline at end of file diff --git a/catalog/drafts/qwen3.8-flash-next-nvfp4/CATALOG-PUBLICATION-PLAN.md b/catalog/drafts/qwen3.8-flash-next-nvfp4/CATALOG-PUBLICATION-PLAN.md new file mode 100644 index 00000000..94c64317 --- /dev/null +++ b/catalog/drafts/qwen3.8-flash-next-nvfp4/CATALOG-PUBLICATION-PLAN.md @@ -0,0 +1,269 @@ +# Qwen3.8 Flash-Next NVFP4 catalog publication plan + +This directory is preparation material. It is not a signed catalog release and +must not be used to mutate the live ledger. + +## Canonical identity + +- Public model ID: `Qwen/Qwen3.8-Flash-Next`. +- Official model revision used for model-card, configuration, and license + research: `de4b8e4d43b917e7706784d8bb445c9af86a3540`. +- Calibrated upstream artifact: + `RadixArk/Qwen3.8-Flash-Next-NVFP4@7b719225242aacd3dbd3f9407468c2ee9a9d2594`. +- Immutable catalog mirror: + `TracNetwork/mayhem-catalog-Qwen-Qwen3-8-Flash-Next-NVFP4@29292a09675f9b82e4c0b7b5c0ad028fe58ee52e`. + Independent post-upload verification found exactly 425 files and + 135,318,119,812 bytes, with no path, size, or content-identity mismatch. +- Calibrated snapshot: 419 files and 135,253,622,894 bytes. Its unchanged + `download-manifest.json` has SHA-256 + `5995053edba6d997fa0b4e45363293c0bd4d670f5824c2b6b7f71a7dccc54374`. + It is the sole file list for materialization and verification and is also + stored unchanged as the mirror's 111,025-byte `download-manifest.json` + sidecar (Git blob `09ebe2b9b2d9bc6f6870d37eb246a739891164cf`). +- Mirror compliance file: the 3,235-byte `LICENSE` from the official initial + revision `34567a4712bc9766c4449e2e98e4468bfa24d915`, SHA-256 + `a0dc422560841fd68e06d974907f8b4c709bca44a67daad2b528437bdf676c08`. + It is present in the mirror but excluded from the calibrated snapshot, + directory artifact root, and runtime materialization. + +The catalog artifact source must point at the final mirror revision. Its +`upstream_source` must retain the RadixArk repository and pinned revision. The +unchanged snapshot manifest retains its original ModelScope acquisition fields +and canonical Hugging Face revision. Core validates those as upstream identity, +not as the mirror download location. + +## License disposition + +The exact identifier is `qwen-community-1.0`. Clause 1 permits use, +modification, publishing, distribution, deployment, and hosting, provided that +the copyright and permission notice accompany copies or substantial portions. +It also requires prominent model naming for a commercial product or service +above either 100 million monthly active users or USD 20 million monthly +revenue. Clause 2 requires a separate Qwen license before commercial use in a +Model-as-a-Service or AI Work Assistant business; its internal-use exception +does not expose the model, its outputs, or its capabilities to third parties. + +The immutable mirror is permitted when it includes the notice. Paid third-party +inference activation remains gated on recording the applicable separate +commercial hosted-use license. This preparation neither claims nor denies that +the operator already has such a license. + +## Catalog values + +- `model_class`: `text-generation` +- `family`: `qwen3.8` +- `params_b`: `125`, matching the official base model comparison table. The + auxiliary n-gram embeddings and MTP component must be described in notes + rather than added to this field. +- `tier`: `launch` +- `provenance.license`: `qwen-community-1.0` +- `provenance.license_sha256`: + `a0dc422560841fd68e06d974907f8b4c709bca44a67daad2b528437bdf676c08` +- `caps.ctx_max`: `262144`, the native model context. +- Served runtime context: `524288`, bound only in + `artifact.openai_compatible.served_context`; it is a YaRN factor-2 runtime + configuration and must not replace native `caps.ctx_max`. +- Input modalities: text, image, video. Output modality: text. +- Features: streaming, reasoning, tools, structured JSON, prefix cache, + cancellation, image, and video. +- Thinking-on sampling: temperature 1.0, top-p 0.95, top-k 20, min-p 0, + presence penalty 0, repeat penalty 1. +- Thinking-off sampling: temperature 0.7, top-p 0.8, top-k 20, min-p 0, + presence penalty 1.5, repeat penalty 1. +- Thinking controls: `enable_thinking=true` and `preserve_thinking=true` by + default; `reasoning_effort` is `low|medium|xhigh` with `xhigh` as the catalog + default. +- Reuse the Qwen3.8-27B adapter schemas for OpenAI chat completions, + completions, responses, and HF multimodal chat, substituting this exact model + ID. Do not reuse fingerprints or resource measurements. + +The required provenance conversion row describes the byte mirror, not the +upstream quantization: tool `huggingface_hub`, method +`immutable-byte-mirror:pinned-radixark-nvfp4-snapshot-manifest`, with input and +output SHA-256 both equal to `5995053edba6d997fa0b4e45363293c0bd4d670f5824c2b6b7f71a7dccc54374`. +The artifact notes bind `hf_quant_config.json` SHA-256 +`7e69ef4b94302ae5b6f453b913621f698d5631a1d023d8b3e9e3b829721b98e8` +and `conversion_environment.json` SHA-256 +`10dadc9b3421b669e533ffcdb3ffb1bf56f42d35936fc24aa2020db0df7480ac`. +The upstream card does not pin every base checkpoint input used by RadixArk; +record that limitation without claiming OpenMayhem performed the NVFP4 +quantization. + +The primary artifact uses engine `openai-compatible`. Its signed runtime +binding is prepared in `runtime-binding.template.json`. It pins Pennyroyal +2.5.0 at revision `2c675da096939cb01102f8f4871bda3db55f7f28`, SGLang +`0.0.0.dev1+gd91c3682b`, and container +`lmsysorg/sglang@sha256:12d3392bdc8be8d35e9a95f191df6aef99c5114bdbefd41bfdc7e760e6d25ec1`. +The binding uses lifecycle `managed_or_verified_attach`, advertises maximum +concurrency 2, and contains no endpoint or host location. + +The retained direct runtime advertised backend model name `pennyroyal` and set +the startup `reasoning_effort` default to `medium`. Both conflict with the +public contract. The managed recipe must use served model name +`Qwen/Qwen3.8-Flash-Next` and startup default `xhigh`; `/v1/models` and signed +`/server_info` checks must agree. This semantic-only recipe correction requires +a focused identity and functional canary rerun before activation. + +The final typed recipe is 3,204 bytes, SHA-256 +`7f248046c72b771620da09f36d9cf554134cc70789c3e832017f8d29dbd5b41c`, +and Merkle root +`9c9ba2e9fe4b0cd4570ae5deeb58a363ee904b7a62e4bd02f6b77487f02c4666`. +Its Pennyroyal source bundle is 64,070,181 bytes, SHA-256 +`776f6d4b1883c78c7d1eaafa23a936fe82d6cc2220bfb9631ec3598b5e342df4`, +and Merkle root +`72c6b143926e6d669f474b52e9ddfecd4b25dbc328edb817ee9600bb0e8dbe23`. +The bundle includes the shallow Git identity required by the qualified launcher; +the earlier rootless git archive identity is invalid and must never be used. +The 16,286-byte Docker 29.1.3 seccomp profile has SHA-256 +`c7a33fb8ae1f8346356a61ce833d579c45acf2bc94967c6763634e81010ff816` +and Merkle root +`f3ad6f80ad1ef9702ba87171fd18890d655cfd0df21553b3827c5846dec5a915`. + +The PLE reader is supplied as the 292,987-byte platform wheel +`sglang_ssd_stream-0.2.0+pennyroyal2-cp312-cp312-linux_x86_64.whl`, SHA-256 +`5fd3bf79524aec7068729e99823a8278e4f3bd7dcacd222f2c55f4395454112f`, +and Merkle root +`c53a31e21cd48728e6a6d71b9c7a63927f062efe77c2661dee4d37d4803321ac`. +The recipe restricts it to CPython 3.12, the CPython 3.12 ABI, and +`linux_x86_64`, and installs it offline without dependencies. An empty-cache +offline install, import, and full PLE checker passed. The earlier source-build +recipe depended on build tooling acquired during qualification and cannot +satisfy clean-provider materialization; its recipe identity is superseded. + +The recipe deterministically derives, rather than downloads, its PLE table from +the signed model snapshot. The expected result is 51,200,245,760 bytes, +SHA-256 `b070f9644adf93794d8a1030584ab705809387e64396a9327a68fa3a3a6666b3`, +320,001,536 rows by 160 columns in `float8_e4m3fn`. The derived portable +manifest is 2,563 bytes with SHA-256 +`f3a5a692d577457a1b6166ec17b3006ebc55804812400ac3a7bfaa3ce5af75bd`. +The PLE table is not a catalog sidecar. + +## Pricing and activity calibration + +Catalog `price_ref_au` is denominated in atto-USD per 1,000 tokens. The draft +Tier 1 rates are: + +| Dimension | USD per million | Atto-USD per 1,000 | +| --- | ---: | ---: | +| input | 0.059628 | 59,628,000,000,000 | +| cached input | 0.014907 | 14,907,000,000,000 | +| output | 0.201245 | 201,245,000,000,000 | + +The Tier 2 draft is an exact 1.5 multiplier: input +89,442,000,000,000, cached input 22,360,500,000,000, and output +301,867,500,000,000 atto-USD per 1,000. The catalog compatibility fields carry +input and output. Each ledger price entry must carry the sorted three-dimension +rate map, including cached input. + +`activity-calibration-evidence.json` has SHA-256 +`45911631224860d4bb273278ec4cf2b062e48bd061deffa69fe7de9ece93c3d2`. +Its reference work is derived from the retained concurrent prefill/decode run: +520,000 input tokens over 61,440,000 microseconds and 2,046 post-first-token +output tokens over 32,016,500 microseconds. Cached input uses a conservative +upper bound of 25,472 tokens over 232,011 microseconds. Ledger publication uses +that file hash as the activity calibration source hash. + +These prices were selected from a provisional 50% undercut of the lower +ordinary competitor observed on 2026-09-13. Refresh competitor prices and get +economic-owner acceptance immediately before proposal; do not silently carry +the provisional values into a signed release. + +## Resource admission + +The retained concurrency-two run used two simultaneous requests, each with +260,000 prompt tokens and 1,024 requested output tokens. It observed 2 running +requests, 824,384 configured total tokens, 96,462,700,544 bytes peak GPU memory +used, 5,486,149,632 bytes minimum free GPU memory, 603.87 W peak board power, +and 519.81 W mean board power. The two observed time-to-first-token values were +20.9161 s and 40.5239 s; the requests overlapped for 5.9372 s. + +A 15% F13 budget derived from that peak is 113,485,530,052 bytes +(105.69 GiB). The qualified host did not prove that portable floor on a nominal +96 GiB device. Do not invent `requirements.min_vram_gb_full_offload` or a +portable F13 admission envelope from this run. Measure the final portable +hardware class or obtain an explicitly reviewed admission policy. + +Image and video passed functional probes, but neither has a maximum-size media +working-set profile. Launch admission requires separate image and video +resource profiles with measured item bytes/units, baseline/peak memory, F13 +budget, and default inflight limits. + +## Canary and reuse policy + +Create `canary-qwen3.8-flash-next-nvfp4-v1` by preserving the intent of all 14 +Qwen3.8-27B cases: two deterministic text cases, tool routing, JSON, three +reasoning levels, both thinking-history settings, image, video, thinking-on and +thinking-off sampling, and temperature upper boundary. Run every case through +the final native provider and gateway using the final mirror revision and exact +runtime recipe. Record new token fingerprints and prefixes with exact matching; +none of the Qwen3.8-27B fingerprints may be copied. + +The long 260k concurrency-two qualification need not be repeated when the final +419-file snapshot, directory artifact root, container, runtime revision, +executable plugin source, capacity, and scheduling settings remain unchanged. +The switch from an on-provider source build to the signed wheel is acceptable +for reuse only after its empty-cache install, import, and PLE checker evidence +is retained and the final native-provider path passes a cheap two-request +overlap probe. Repeat the long proof if any weight, loader behavior, executable +plugin source, capacity, token pool, scheduling, cache, or context setting +changes. + +## Exact semantic catalog diff + +The baseline is `origin/main` commit +`ea38f49fcd941b3e4e12546095220c53ccbedab0`. Its catalog SHA-256 is +`2112bce6b7f8fa5660c81a81b22f9d4c4f19c263972939601ff4512e5cb2c069`; +the detached signature SHA-256 is +`8245d5aa93930036277ff6cee0ad27b10a277b74486b23ccba10a23dd85a9c33`. +It contains 22 models, 1 generation execution profile, 1 vLLM execution mode, +and 6 enclave attestation bindings. + +The publication diff must append exactly one model entry and one +`generation_execution_profiles` entry keyed by the final directory artifact +root. The profile engine is `openai-compatible`, permits only the proved two +independently dispatched text requests, and binds the retained concurrency +evidence hash. No vLLM execution mode is added. All pre-existing entries and +their order remain byte-for-byte semantically unchanged. The old signature is +not edited during preparation; release tooling replaces it only after the +complete catalog validates and an authorized signer signs it. + +The model artifact has exactly five catalog sidecars: the unchanged snapshot +manifest, the typed runtime recipe, the Pennyroyal source bundle, the signed +PLE reader wheel, and the recipe-referenced seccomp profile. The 419 model +files are not represented as individual catalog sidecars. + +## Ledger and release gates + +1. Preserve the verified mirror revision. Its exact 425-object set has all 419 + manifest rows matching path, size, and content identity, with the manifest + sidecar and `LICENSE` also matching their official bytes. Repeat this + verification immediately before catalog signing to detect remote drift. +2. Use the verified 419-file Core directory artifact root + `78191c28fa91602e536baa8ede39891d6d500615086ce2ba42cb920c3b46eabe` + in the catalog, generation profile, model reference, and enclave + registration. The framed directory stream SHA-256 is + `e59e92ab3ce981a018c412134707d12bd293bd54d8ab42ba30c5f1037d0031ae` + over 135,253,645,960 stream bytes. +3. Bind the five exact sidecars: `snapshot_manifest`, `runtime_recipe`, + `pennyroyal_source`, `ple_plugin_wheel`, and `seccomp_profile`. All are + already present at the final mirror revision. +4. Complete portable text F13 and image/video resource profiles. +5. Install the generalized `openai-compatible` Core implementation and validate + every signed `server_info_checks` pointer against the attached runtime. +6. Run native-provider discovery, model listing, all endpoint families, + streaming, reasoning, tools, JSON, media, prefix cache, cancellation, and + independent concurrency-two dispatch. Run the same canonical canary through + paid gateway routes and retain billing/settlement evidence. +7. Confirm the applicable Qwen commercial hosted-use license, refresh pricing, + and record economic-owner acceptance. +8. Validate and sign the complete catalog offline. Propose the exact catalog + anchor, then simulate the model reference, Tier 1 enclave and price, required + Tier 2 enclave-market registration (which may be empty of providers), and + paid rooms. The calibration host's missing endorsement-key certificate is a + host qualification limitation, not permission to omit the Tier 2 market. +9. Commit authorized ledger operations, publish the signed catalog pointer, + wait for readers to converge, and rerun independent model listing, paid + routes, canaries, accounting, and settlement verification. + +No version, tag, signature, catalog pointer, ledger operation, or live provider +deployment belongs in this preparation branch. diff --git a/catalog/drafts/qwen3.8-flash-next-nvfp4/activity-calibration-evidence.json b/catalog/drafts/qwen3.8-flash-next-nvfp4/activity-calibration-evidence.json new file mode 100644 index 00000000..5ed23306 --- /dev/null +++ b/catalog/drafts/qwen3.8-flash-next-nvfp4/activity-calibration-evidence.json @@ -0,0 +1,79 @@ +{ + "schema_version": 1, + "model_id": "Qwen/Qwen3.8-Flash-Next", + "artifact_source": { + "repo": "RadixArk/Qwen3.8-Flash-Next-NVFP4", + "revision": "7b719225242aacd3dbd3f9407468c2ee9a9d2594", + "manifest_sha256": "5995053edba6d997fa0b4e45363293c0bd4d670f5824c2b6b7f71a7dccc54374" + }, + "runtime": { + "runtime_id": "pennyroyal", + "runtime_version": "2.5.0", + "runtime_revision": "2c675da096939cb01102f8f4871bda3db55f7f28", + "implementation": "sglang", + "implementation_version": "0.0.0.dev1+gd91c3682b", + "container_image_digest": "sha256:12d3392bdc8be8d35e9a95f191df6aef99c5114bdbefd41bfdc7e760e6d25ec1" + }, + "measurements": { + "concurrent_long_context": { + "evidence_sha256": "adc3b7256a75f32f112a74ce1b453fab16e007013803ded94695dca02f3ccba8", + "requests": [ + { + "prompt_tokens": 260000, + "completion_tokens": 1024, + "prefill_work_us": 20916100, + "post_first_token_decode_units": 1023, + "post_first_token_decode_work_us": 25546400 + }, + { + "prompt_tokens": 260000, + "completion_tokens": 1024, + "prefill_work_us": 40523900, + "post_first_token_decode_units": 1023, + "post_first_token_decode_work_us": 6470100 + } + ], + "aggregate": { + "input_token": { + "units": 520000, + "work_us": 61440000 + }, + "output_token": { + "units": 2046, + "work_us": 32016500 + } + }, + "interpretation": "Reference work is the sum of per-request phase time from the retained concurrency-two run. Input work includes time to first token. Output work uses the measured post-first-token intervals." + }, + "cached_input": { + "evidence_sha256": "dac1c0a092f87f3024d62cecefec8ea5ff506479654e0ff0a08f2187f64e69ca", + "cached_input_tokens": 25472, + "work_us": 232011, + "uncached_input_tokens_in_request": 49, + "output_tokens_in_request": 14, + "interpretation": "Conservative upper bound: the complete second-request elapsed time is assigned to cached-input work because the retained response does not isolate the 49 uncached input tokens and 14 output tokens." + } + }, + "activity_calibration_dimensions": [ + { + "unit": "cached_input_token", + "units": "25472", + "work_us": "232011" + }, + { + "unit": "input_token", + "units": "520000", + "work_us": "61440000" + }, + { + "unit": "output_token", + "units": "2046", + "work_us": "32016500" + } + ], + "limitations": [ + "The long-context prompts were synthetic retrieval probes and are not a quality benchmark.", + "The concurrent run measures shared scheduling, so the two per-request prefill and decode rates are intentionally aggregated as reference work.", + "The cached-input measurement is a conservative upper bound and needs operator acceptance before ledger publication." + ] +} diff --git a/catalog/drafts/qwen3.8-flash-next-nvfp4/artifact-binding.values.json b/catalog/drafts/qwen3.8-flash-next-nvfp4/artifact-binding.values.json new file mode 100644 index 00000000..00185ca4 --- /dev/null +++ b/catalog/drafts/qwen3.8-flash-next-nvfp4/artifact-binding.values.json @@ -0,0 +1,207 @@ +{ + "schema_version": 1, + "status": "complete_artifact_identity_pending_runtime_canaries_and_resource_admission", + "model_id": "Qwen/Qwen3.8-Flash-Next", + "artifact_name": "nvfp4", + "artifact": { + "engine": "openai-compatible", + "kv_cache": { + "dtype": "fp8_e4m3", + "bits": 8, + "group_size": 1, + "quantized_start_tokens": 0, + "full_attention_layers": 12, + "total_layers": 48, + "bytes_per_token": 12288, + "measurement_source": "Pinned model structure and retained SGLang allocation: 12 target full-attention layers, 2 KV heads, 256 dimensions, K plus V at one byte per element; the separately allocated one-layer speculative draft cache is runtime overhead." + }, + "source": { + "kind": "huggingface", + "repo": "TracNetwork/mayhem-catalog-Qwen-Qwen3-8-Flash-Next-NVFP4", + "revision": "29292a09675f9b82e4c0b7b5c0ad028fe58ee52e", + "publisher_key": "huggingface-repo" + }, + "upstream_source": { + "kind": "huggingface", + "repo": "RadixArk/Qwen3.8-Flash-Next-NVFP4", + "revision": "7b719225242aacd3dbd3f9407468c2ee9a9d2594", + "publisher_key": "huggingface-repo" + }, + "path": "download-manifest.json", + "artifact_root": "78191c28fa91602e536baa8ede39891d6d500615086ce2ba42cb920c3b46eabe", + "artifact_root_kind": "blake3_merkle_v1", + "weights_bytes": 135253622894, + "source_sha256": "5995053edba6d997fa0b4e45363293c0bd4d670f5824c2b6b7f71a7dccc54374", + "tokenizer_sha256": "0997f410c57a1f4e53b09e4be8f4a172d90edd9564368fb0847030937229b9f3", + "chat_template_sha256": "c3cf9e34abf4f9e36c2d72165aa9c132d3e2a725b6c2586aaa3a8af9d7a81041", + "min_compute_cap": "12.0", + "download_check": true, + "notes": "Byte-for-byte mirror of the pinned RadixArk ModelOpt NVFP4 snapshot. OpenMayhem did not perform the upstream quantization. hf_quant_config.json is SHA-256 7e69ef4b94302ae5b6f453b913621f698d5631a1d023d8b3e9e3b829721b98e8 and conversion_environment.json is SHA-256 10dadc9b3421b669e533ffcdb3ffb1bf56f42d35936fc24aa2020db0df7480ac; the upstream card does not pin every base checkpoint input used for that conversion.", + "sidecars": { + "pennyroyal_source": { + "source": { + "kind": "huggingface", + "repo": "TracNetwork/mayhem-catalog-Qwen-Qwen3-8-Flash-Next-NVFP4", + "revision": "29292a09675f9b82e4c0b7b5c0ad028fe58ee52e", + "publisher_key": "huggingface-repo" + }, + "path": "runtime/pennyroyal-v2.5.0-source-2c675da096939cb01102f8f4871bda3db55f7f28.tar.gz", + "artifact_root": "72c6b143926e6d669f474b52e9ddfecd4b25dbc328edb817ee9600bb0e8dbe23", + "artifact_root_kind": "blake3_merkle_v1", + "weights_bytes": 64070181, + "source_sha256": "776f6d4b1883c78c7d1eaafa23a936fe82d6cc2220bfb9631ec3598b5e342df4" + }, + "runtime_recipe": { + "source": { + "kind": "huggingface", + "repo": "TracNetwork/mayhem-catalog-Qwen-Qwen3-8-Flash-Next-NVFP4", + "revision": "29292a09675f9b82e4c0b7b5c0ad028fe58ee52e", + "publisher_key": "huggingface-repo" + }, + "path": "runtime/managed-runtime-recipe-v1.json", + "artifact_root": "9c9ba2e9fe4b0cd4570ae5deeb58a363ee904b7a62e4bd02f6b77487f02c4666", + "artifact_root_kind": "blake3_merkle_v1", + "weights_bytes": 3204, + "source_sha256": "7f248046c72b771620da09f36d9cf554134cc70789c3e832017f8d29dbd5b41c" + }, + "seccomp_profile": { + "source": { + "kind": "huggingface", + "repo": "TracNetwork/mayhem-catalog-Qwen-Qwen3-8-Flash-Next-NVFP4", + "revision": "29292a09675f9b82e4c0b7b5c0ad028fe58ee52e", + "publisher_key": "huggingface-repo" + }, + "path": "runtime/docker-29.1.3-ple-io-uring.json", + "artifact_root": "f3ad6f80ad1ef9702ba87171fd18890d655cfd0df21553b3827c5846dec5a915", + "artifact_root_kind": "blake3_merkle_v1", + "weights_bytes": 16286, + "source_sha256": "c7a33fb8ae1f8346356a61ce833d579c45acf2bc94967c6763634e81010ff816" + }, + "snapshot_manifest": { + "source": { + "kind": "huggingface", + "repo": "TracNetwork/mayhem-catalog-Qwen-Qwen3-8-Flash-Next-NVFP4", + "revision": "29292a09675f9b82e4c0b7b5c0ad028fe58ee52e", + "publisher_key": "huggingface-repo" + }, + "path": "download-manifest.json", + "artifact_root": "ee2bc36da770d609bbc09da82f61ca5bfc5ecd17cc89af3dd454f01b929889bf", + "artifact_root_kind": "blake3_merkle_v1", + "weights_bytes": 111025, + "source_sha256": "5995053edba6d997fa0b4e45363293c0bd4d670f5824c2b6b7f71a7dccc54374" + }, + "ple_plugin_wheel": { + "source": { + "kind": "huggingface", + "repo": "TracNetwork/mayhem-catalog-Qwen-Qwen3-8-Flash-Next-NVFP4", + "revision": "29292a09675f9b82e4c0b7b5c0ad028fe58ee52e", + "publisher_key": "huggingface-repo" + }, + "path": "runtime/sglang_ssd_stream-0.2.0+pennyroyal2-cp312-cp312-linux_x86_64.whl", + "artifact_root": "c53a31e21cd48728e6a6d71b9c7a63927f062efe77c2661dee4d37d4803321ac", + "artifact_root_kind": "blake3_merkle_v1", + "weights_bytes": 292987, + "source_sha256": "5fd3bf79524aec7068729e99823a8278e4f3bd7dcacd222f2c55f4395454112f" + } + }, + "openai_compatible": { + "schema_version": 1, + "runtime_id": "pennyroyal", + "runtime_version": "2.5.0", + "runtime_revision": "2c675da096939cb01102f8f4871bda3db55f7f28", + "implementation": "sglang", + "implementation_version": "0.0.0.dev1+gd91c3682b", + "container_image_digest": "sha256:12d3392bdc8be8d35e9a95f191df6aef99c5114bdbefd41bfdc7e760e6d25ec1", + "served_model": "Qwen/Qwen3.8-Flash-Next", + "native_context": 262144, + "served_context": 524288, + "max_concurrent": 2, + "capabilities": [ + "cancellation", + "image", + "json", + "prefix_cache", + "reasoning", + "streaming", + "tools", + "video" + ], + "preflight": { + "non_reasoning_chat_template_kwargs": { + "enable_thinking": false + }, + "reasoning_chat_template_kwargs": { + "enable_thinking": true, + "preserve_thinking": true, + "reasoning_effort": "xhigh" + }, + "streaming_max_tokens": 32, + "tools_max_tokens": 384, + "json_max_tokens": 256, + "reasoning_max_tokens": 1024, + "cache_max_tokens": 32, + "cancellation_max_tokens": 4096, + "concurrency_max_tokens": 4096, + "cancellation_idle_metrics": [ + "sglang:num_queue_reqs", + "sglang:num_running_reqs" + ], + "concurrency_active_metric": "sglang:num_running_reqs" + }, + "server_info_checks": { + "/chunked_prefill_size": 4096, + "/context_length": 524288, + "/default_chat_template_kwargs/enable_thinking": true, + "/default_chat_template_kwargs/preserve_thinking": true, + "/default_chat_template_kwargs/reasoning_effort": "xhigh", + "/disable_cuda_graph": false, + "/disable_radix_cache": false, + "/dtype": "bfloat16", + "/enable_hierarchical_cache": true, + "/hicache_io_backend": "kernel", + "/hicache_size": 32, + "/hicache_storage_backend": "nixl", + "/kv_cache_dtype": "fp8_e4m3", + "/load_format": "safetensors", + "/max_mamba_cache_size": 24, + "/max_running_requests": 4, + "/max_total_num_tokens": 824384, + "/max_total_tokens": 824384, + "/page_size": 64, + "/quantization": "modelopt_fp4", + "/reasoning_parser": "qwen3", + "/served_model_name": "Qwen/Qwen3.8-Flash-Next", + "/speculative_algorithm": "EAGLE", + "/speculative_eagle_topk": 1, + "/speculative_num_draft_tokens": 4, + "/speculative_num_steps": 3, + "/tool_call_parser": "qwen3_coder", + "/version": "0.0.0.dev1+gd91c3682b" + }, + "prefix_cache_metric": "sglang:cached_tokens_total", + "lifecycle": "managed_or_verified_attach", + "snapshot_manifest_sidecar": "snapshot_manifest", + "snapshot_manifest_sha256": "5995053edba6d997fa0b4e45363293c0bd4d670f5824c2b6b7f71a7dccc54374", + "runtime_recipe_sidecar": "runtime_recipe", + "runtime_recipe_sha256": "7f248046c72b771620da09f36d9cf554134cc70789c3e832017f8d29dbd5b41c", + "model_snapshot_file_count": 419 + } + }, + "provenance_conversion": { + "tool": "huggingface_hub", + "method": "immutable-byte-mirror:pinned-radixark-nvfp4-snapshot-manifest", + "input_sha256": "5995053edba6d997fa0b4e45363293c0bd4d670f5824c2b6b7f71a7dccc54374", + "output_sha256": "5995053edba6d997fa0b4e45363293c0bd4d670f5824c2b6b7f71a7dccc54374" + }, + "directory_root_evidence": { + "file_count": 419, + "payload_bytes": 135253622894, + "framed_stream_bytes": 135253645960, + "framed_stream_sha256": "e59e92ab3ce981a018c412134707d12bd293bd54d8ab42ba30c5f1037d0031ae" + }, + "publication_gates": [ + "Fill signed portable text and media admission profiles from final-runtime measurements.", + "Complete the canonical native-provider and gateway canary rerun.", + "Validate this binding with the finalized openai-compatible catalog schema before signing." + ] +} diff --git a/catalog/drafts/qwen3.8-flash-next-nvfp4/catalog-semantic-diff.plan.json b/catalog/drafts/qwen3.8-flash-next-nvfp4/catalog-semantic-diff.plan.json new file mode 100644 index 00000000..99cd5638 --- /dev/null +++ b/catalog/drafts/qwen3.8-flash-next-nvfp4/catalog-semantic-diff.plan.json @@ -0,0 +1,81 @@ +{ + "schema_version": 1, + "status": "blocked_pending_model_measurements_and_canary_values", + "baseline": { + "commit": "ea38f49fcd941b3e4e12546095220c53ccbedab0", + "catalog_sha256": "2112bce6b7f8fa5660c81a81b22f9d4c4f19c263972939601ff4512e5cb2c069", + "signature_sha256": "8245d5aa93930036277ff6cee0ad27b10a277b74486b23ccba10a23dd85a9c33", + "model_count": 22, + "generation_execution_profile_count": 1, + "vllm_execution_mode_count": 1, + "enclave_attestation_binding_count": 6 + }, + "operations": [ + { + "op": "append_model", + "model_id": "Qwen/Qwen3.8-Flash-Next", + "compose_from": [ + "model-surface.values.json#model_fields", + "artifact-binding.values.json#artifact", + "model-surface.values.json#adapter_derivation", + "model-surface.values.json#modality_assessment", + "model-surface.values.json#speciality_assessment", + "model-surface.values.json#canary_plan" + ], + "required_before_apply": [ + "Fill every field listed in model-surface.values.json#pending_catalog_fields.", + "Copy the Qwen/Qwen3.8-27B adapter fields from the exact baseline and apply only the two declared public model-ID substitutions.", + "Validate the resulting complete model object against the finalized catalog schema." + ] + }, + { + "op": "insert_generation_execution_profile", + "key": "78191c28fa91602e536baa8ede39891d6d500615086ce2ba42cb920c3b46eabe", + "value_from": "model-surface.values.json#generation_execution_profile.value" + } + ], + "expected_after_apply": { + "model_count": 23, + "generation_execution_profile_count": 2, + "vllm_execution_mode_count": 1, + "enclave_attestation_binding_count": 6, + "appended_model_id": "Qwen/Qwen3.8-Flash-Next", + "new_generation_execution_profile_key": "78191c28fa91602e536baa8ede39891d6d500615086ce2ba42cb920c3b46eabe" + }, + "preservation_invariants": { + "existing_model_order": [ + "Cactus-Compute/needle", + "ResembleAI/chatterbox", + "SulphurAI/Sulphur-2-base", + "acestep/ace-step-1.5", + "google/gemma-4-E4B-it", + "hauhaucs/qwen3.6-35b-a3b-uncensored", + "huihui-ai/Huihui-Agents-A1-abliterated", + "image.heavy.le17mp", + "image.heavy.le1_2mp", + "nvidia/parakeet-tdt-0.6b-v3", + "prism-ml/Ternary-Bonsai-27B", + "tongyi/z-image-turbo", + "upscale.conv.le24mp", + "upscale.diffusion", + "video.heavy.le0_5mpf", + "video.minimax_h3.t2v_i2v", + "video.minimax_h3.r2v", + "video.minimax_h3.spectrum", + "video.minimax_h3.lowvram_t2v_i2v", + "video.minimax_h3.lowvram_r2v", + "video.lipsync", + "Qwen/Qwen3.8-27B" + ], + "unchanged_sections": [ + "attestation_policy_chain", + "enclave_attestation_bindings", + "vllm_execution_modes", + "every pre-existing models element", + "every pre-existing generation_execution_profiles entry" + ], + "new_vllm_execution_mode": false, + "live_catalog_mutation_in_this_branch": false, + "signature_mutation_in_this_branch": false + } +} diff --git a/catalog/drafts/qwen3.8-flash-next-nvfp4/ledger-operations.template.json b/catalog/drafts/qwen3.8-flash-next-nvfp4/ledger-operations.template.json new file mode 100644 index 00000000..75ea23a3 --- /dev/null +++ b/catalog/drafts/qwen3.8-flash-next-nvfp4/ledger-operations.template.json @@ -0,0 +1,378 @@ +{ + "schema_version": 1, + "status": "blocked_pending_signed_catalog_and_core_release_identities", + "placeholders": { + "$SIGNED_CATALOG_SHA256": "The SHA-256 of the complete validated and signed catalog payload.", + "$TIER1_CORE_MANIFEST_SHA256": "The 32-byte lowercase tier-1 Core release manifest hash.", + "$TIER2_CORE_MANIFEST_SHA256": "The distinct 32-byte lowercase tier-2 Core release manifest hash.", + "$CORE_BINARY_SHA256": "The 32-byte lowercase hash of the finalized generalized Core release binary.", + "$TIER1_ENCLAVE_ID": "Derive from admin public key, model ID, artifact root, sorted sidecar roots, tier-1 manifest hash, and Core binary hash.", + "$TIER2_ENCLAVE_ID": "Derive from admin public key, model ID, artifact root, sorted sidecar roots, tier-2 manifest hash, and Core binary hash.", + "$EFFECTIVE_AT": "Select a nonnegative integer activation time after simulation and reader convergence." + }, + "set_model_ref": { + "op": "set_model_ref", + "model_id": "Qwen/Qwen3.8-Flash-Next", + "model_class": "text-generation", + "rate_map": [ + { + "unit": "cached_input_token", + "per_unit_au": "14907000000000", + "granularity": 1000 + }, + { + "unit": "input_token", + "per_unit_au": "59628000000000", + "granularity": 1000 + }, + { + "unit": "output_token", + "per_unit_au": "201245000000000", + "granularity": 1000 + } + ], + "source_hash": "$SIGNED_CATALOG_SHA256", + "activity_calibration": { + "schema_version": 1, + "source_hash": "45911631224860d4bb273278ec4cf2b062e48bd061deffa69fe7de9ece93c3d2", + "dimensions": [ + { + "unit": "cached_input_token", + "units": "25472", + "work_us": "232011" + }, + { + "unit": "input_token", + "units": "520000", + "work_us": "61440000" + }, + { + "unit": "output_token", + "units": "2046", + "work_us": "32016500" + } + ] + } + }, + "register_enclaves": [ + { + "op": "register_enclave", + "enclave_id": "$TIER1_ENCLAVE_ID", + "model_id": "Qwen/Qwen3.8-Flash-Next", + "model_class": "text-generation", + "backend": "openai-compatible", + "artifact_root": "78191c28fa91602e536baa8ede39891d6d500615086ce2ba42cb920c3b46eabe", + "artifact_root_kind": "blake3_merkle_v1", + "artifact_source": { + "kind": "huggingface", + "repo": "TracNetwork/mayhem-catalog-Qwen-Qwen3-8-Flash-Next-NVFP4", + "revision": "29292a09675f9b82e4c0b7b5c0ad028fe58ee52e", + "path": "download-manifest.json" + }, + "artifact_sidecars": { + "pennyroyal_source": { + "source": { + "kind": "huggingface", + "repo": "TracNetwork/mayhem-catalog-Qwen-Qwen3-8-Flash-Next-NVFP4", + "revision": "29292a09675f9b82e4c0b7b5c0ad028fe58ee52e", + "path": "runtime/pennyroyal-v2.5.0-source-2c675da096939cb01102f8f4871bda3db55f7f28.tar.gz" + }, + "path": "runtime/pennyroyal-v2.5.0-source-2c675da096939cb01102f8f4871bda3db55f7f28.tar.gz", + "artifact_root": "72c6b143926e6d669f474b52e9ddfecd4b25dbc328edb817ee9600bb0e8dbe23", + "artifact_root_kind": "blake3_merkle_v1", + "weights_bytes": 64070181, + "source_sha256": "776f6d4b1883c78c7d1eaafa23a936fe82d6cc2220bfb9631ec3598b5e342df4" + }, + "ple_plugin_wheel": { + "source": { + "kind": "huggingface", + "repo": "TracNetwork/mayhem-catalog-Qwen-Qwen3-8-Flash-Next-NVFP4", + "revision": "29292a09675f9b82e4c0b7b5c0ad028fe58ee52e", + "path": "runtime/sglang_ssd_stream-0.2.0+pennyroyal2-cp312-cp312-linux_x86_64.whl" + }, + "path": "runtime/sglang_ssd_stream-0.2.0+pennyroyal2-cp312-cp312-linux_x86_64.whl", + "artifact_root": "c53a31e21cd48728e6a6d71b9c7a63927f062efe77c2661dee4d37d4803321ac", + "artifact_root_kind": "blake3_merkle_v1", + "weights_bytes": 292987, + "source_sha256": "5fd3bf79524aec7068729e99823a8278e4f3bd7dcacd222f2c55f4395454112f" + }, + "runtime_recipe": { + "source": { + "kind": "huggingface", + "repo": "TracNetwork/mayhem-catalog-Qwen-Qwen3-8-Flash-Next-NVFP4", + "revision": "29292a09675f9b82e4c0b7b5c0ad028fe58ee52e", + "path": "runtime/managed-runtime-recipe-v1.json" + }, + "path": "runtime/managed-runtime-recipe-v1.json", + "artifact_root": "9c9ba2e9fe4b0cd4570ae5deeb58a363ee904b7a62e4bd02f6b77487f02c4666", + "artifact_root_kind": "blake3_merkle_v1", + "weights_bytes": 3204, + "source_sha256": "7f248046c72b771620da09f36d9cf554134cc70789c3e832017f8d29dbd5b41c" + }, + "seccomp_profile": { + "source": { + "kind": "huggingface", + "repo": "TracNetwork/mayhem-catalog-Qwen-Qwen3-8-Flash-Next-NVFP4", + "revision": "29292a09675f9b82e4c0b7b5c0ad028fe58ee52e", + "path": "runtime/docker-29.1.3-ple-io-uring.json" + }, + "path": "runtime/docker-29.1.3-ple-io-uring.json", + "artifact_root": "f3ad6f80ad1ef9702ba87171fd18890d655cfd0df21553b3827c5846dec5a915", + "artifact_root_kind": "blake3_merkle_v1", + "weights_bytes": 16286, + "source_sha256": "c7a33fb8ae1f8346356a61ce833d579c45acf2bc94967c6763634e81010ff816" + }, + "snapshot_manifest": { + "source": { + "kind": "huggingface", + "repo": "TracNetwork/mayhem-catalog-Qwen-Qwen3-8-Flash-Next-NVFP4", + "revision": "29292a09675f9b82e4c0b7b5c0ad028fe58ee52e", + "path": "download-manifest.json" + }, + "path": "download-manifest.json", + "artifact_root": "ee2bc36da770d609bbc09da82f61ca5bfc5ecd17cc89af3dd454f01b929889bf", + "artifact_root_kind": "blake3_merkle_v1", + "weights_bytes": 111025, + "source_sha256": "5995053edba6d997fa0b4e45363293c0bd4d670f5824c2b6b7f71a7dccc54374" + } + }, + "source_sha256": "5995053edba6d997fa0b4e45363293c0bd4d670f5824c2b6b7f71a7dccc54374", + "manifest_hash": "$TIER1_CORE_MANIFEST_SHA256", + "att_tier": 1, + "quant": "nvfp4", + "binary_hash": "$CORE_BINARY_SHA256", + "caps": { + "chat": true, + "tools": true, + "json": true, + "vision": true, + "ctx": 262144, + "ctx_max": 262144, + "tp_degree": 1, + "max_batch_size": 2, + "output_modality": "text", + "output_modalities": [ + "text" + ], + "modality_set": [ + "text", + "image", + "video" + ], + "speciality_levels": { + "reasoning_effort": [ + "low", + "medium", + "xhigh" + ], + "thinking_history": [ + "latest_only", + "preserve" + ], + "thinking_mode": [ + "disabled", + "enabled" + ] + } + } + }, + { + "op": "register_enclave", + "enclave_id": "$TIER2_ENCLAVE_ID", + "model_id": "Qwen/Qwen3.8-Flash-Next", + "model_class": "text-generation", + "backend": "openai-compatible", + "artifact_root": "78191c28fa91602e536baa8ede39891d6d500615086ce2ba42cb920c3b46eabe", + "artifact_root_kind": "blake3_merkle_v1", + "artifact_source": { + "kind": "huggingface", + "repo": "TracNetwork/mayhem-catalog-Qwen-Qwen3-8-Flash-Next-NVFP4", + "revision": "29292a09675f9b82e4c0b7b5c0ad028fe58ee52e", + "path": "download-manifest.json" + }, + "artifact_sidecars": { + "pennyroyal_source": { + "source": { + "kind": "huggingface", + "repo": "TracNetwork/mayhem-catalog-Qwen-Qwen3-8-Flash-Next-NVFP4", + "revision": "29292a09675f9b82e4c0b7b5c0ad028fe58ee52e", + "path": "runtime/pennyroyal-v2.5.0-source-2c675da096939cb01102f8f4871bda3db55f7f28.tar.gz" + }, + "path": "runtime/pennyroyal-v2.5.0-source-2c675da096939cb01102f8f4871bda3db55f7f28.tar.gz", + "artifact_root": "72c6b143926e6d669f474b52e9ddfecd4b25dbc328edb817ee9600bb0e8dbe23", + "artifact_root_kind": "blake3_merkle_v1", + "weights_bytes": 64070181, + "source_sha256": "776f6d4b1883c78c7d1eaafa23a936fe82d6cc2220bfb9631ec3598b5e342df4" + }, + "ple_plugin_wheel": { + "source": { + "kind": "huggingface", + "repo": "TracNetwork/mayhem-catalog-Qwen-Qwen3-8-Flash-Next-NVFP4", + "revision": "29292a09675f9b82e4c0b7b5c0ad028fe58ee52e", + "path": "runtime/sglang_ssd_stream-0.2.0+pennyroyal2-cp312-cp312-linux_x86_64.whl" + }, + "path": "runtime/sglang_ssd_stream-0.2.0+pennyroyal2-cp312-cp312-linux_x86_64.whl", + "artifact_root": "c53a31e21cd48728e6a6d71b9c7a63927f062efe77c2661dee4d37d4803321ac", + "artifact_root_kind": "blake3_merkle_v1", + "weights_bytes": 292987, + "source_sha256": "5fd3bf79524aec7068729e99823a8278e4f3bd7dcacd222f2c55f4395454112f" + }, + "runtime_recipe": { + "source": { + "kind": "huggingface", + "repo": "TracNetwork/mayhem-catalog-Qwen-Qwen3-8-Flash-Next-NVFP4", + "revision": "29292a09675f9b82e4c0b7b5c0ad028fe58ee52e", + "path": "runtime/managed-runtime-recipe-v1.json" + }, + "path": "runtime/managed-runtime-recipe-v1.json", + "artifact_root": "9c9ba2e9fe4b0cd4570ae5deeb58a363ee904b7a62e4bd02f6b77487f02c4666", + "artifact_root_kind": "blake3_merkle_v1", + "weights_bytes": 3204, + "source_sha256": "7f248046c72b771620da09f36d9cf554134cc70789c3e832017f8d29dbd5b41c" + }, + "seccomp_profile": { + "source": { + "kind": "huggingface", + "repo": "TracNetwork/mayhem-catalog-Qwen-Qwen3-8-Flash-Next-NVFP4", + "revision": "29292a09675f9b82e4c0b7b5c0ad028fe58ee52e", + "path": "runtime/docker-29.1.3-ple-io-uring.json" + }, + "path": "runtime/docker-29.1.3-ple-io-uring.json", + "artifact_root": "f3ad6f80ad1ef9702ba87171fd18890d655cfd0df21553b3827c5846dec5a915", + "artifact_root_kind": "blake3_merkle_v1", + "weights_bytes": 16286, + "source_sha256": "c7a33fb8ae1f8346356a61ce833d579c45acf2bc94967c6763634e81010ff816" + }, + "snapshot_manifest": { + "source": { + "kind": "huggingface", + "repo": "TracNetwork/mayhem-catalog-Qwen-Qwen3-8-Flash-Next-NVFP4", + "revision": "29292a09675f9b82e4c0b7b5c0ad028fe58ee52e", + "path": "download-manifest.json" + }, + "path": "download-manifest.json", + "artifact_root": "ee2bc36da770d609bbc09da82f61ca5bfc5ecd17cc89af3dd454f01b929889bf", + "artifact_root_kind": "blake3_merkle_v1", + "weights_bytes": 111025, + "source_sha256": "5995053edba6d997fa0b4e45363293c0bd4d670f5824c2b6b7f71a7dccc54374" + } + }, + "source_sha256": "5995053edba6d997fa0b4e45363293c0bd4d670f5824c2b6b7f71a7dccc54374", + "manifest_hash": "$TIER2_CORE_MANIFEST_SHA256", + "att_tier": 2, + "quant": "nvfp4", + "binary_hash": "$CORE_BINARY_SHA256", + "caps": { + "chat": true, + "tools": true, + "json": true, + "vision": true, + "ctx": 262144, + "ctx_max": 262144, + "tp_degree": 1, + "max_batch_size": 2, + "output_modality": "text", + "output_modalities": [ + "text" + ], + "modality_set": [ + "text", + "image", + "video" + ], + "speciality_levels": { + "reasoning_effort": [ + "low", + "medium", + "xhigh" + ], + "thinking_history": [ + "latest_only", + "preserve" + ], + "thinking_mode": [ + "disabled", + "enabled" + ] + } + } + } + ], + "set_prices": [ + { + "op": "set_price", + "enclave_id": "$TIER1_ENCLAVE_ID", + "rate_map": [ + { + "unit": "cached_input_token", + "per_unit_au": "14907000000000", + "granularity": 1000 + }, + { + "unit": "input_token", + "per_unit_au": "59628000000000", + "granularity": 1000 + }, + { + "unit": "output_token", + "per_unit_au": "201245000000000", + "granularity": 1000 + } + ], + "per_req_au": "0", + "min_session_au": "0", + "effective_at": "$EFFECTIVE_AT" + }, + { + "op": "set_price", + "enclave_id": "$TIER2_ENCLAVE_ID", + "rate_map": [ + { + "unit": "cached_input_token", + "per_unit_au": "22360500000000", + "granularity": 1000 + }, + { + "unit": "input_token", + "per_unit_au": "89442000000000", + "granularity": 1000 + }, + { + "unit": "output_token", + "per_unit_au": "301867500000000", + "granularity": 1000 + } + ], + "per_req_au": "0", + "min_session_au": "0", + "effective_at": "$EFFECTIVE_AT" + } + ], + "open_rooms": [ + { + "op": "open_room", + "enclave_id": "$TIER1_ENCLAVE_ID", + "model_id": "Qwen/Qwen3.8-Flash-Next", + "nonce": "qwen3-8-flash-next-nvfp4-tier1-v1", + "label": "qwen3.8-flash-next-tier1", + "policy": { + "canary_set": "canary-qwen3.8-flash-next-nvfp4-v1" + } + }, + { + "op": "open_room", + "enclave_id": "$TIER2_ENCLAVE_ID", + "model_id": "Qwen/Qwen3.8-Flash-Next", + "nonce": "qwen3-8-flash-next-nvfp4-tier2-v1", + "label": "qwen3.8-flash-next-tier2", + "policy": { + "canary_set": "canary-qwen3.8-flash-next-nvfp4-v1" + } + } + ], + "provider_join_operations": { + "tier_1": [], + "tier_2": [] + }, + "tier_2_empty_market_invariant": "Register the tier-2 enclave, price, and room, but submit no join_enclave or room serving operation until a formally qualified tier-2 provider exists." +} diff --git a/catalog/drafts/qwen3.8-flash-next-nvfp4/ledger-publication.template.json b/catalog/drafts/qwen3.8-flash-next-nvfp4/ledger-publication.template.json new file mode 100644 index 00000000..4fd8b67a --- /dev/null +++ b/catalog/drafts/qwen3.8-flash-next-nvfp4/ledger-publication.template.json @@ -0,0 +1,102 @@ +{ + "schema_version": 1, + "status": "blocked_prepublication_template", + "model_id": "Qwen/Qwen3.8-Flash-Next", + "artifact_name": "nvfp4", + "model_class": "text-generation", + "verified_mirror": { + "repo": "TracNetwork/mayhem-catalog-Qwen-Qwen3-8-Flash-Next-NVFP4", + "revision": "29292a09675f9b82e4c0b7b5c0ad028fe58ee52e", + "file_count": 425, + "total_bytes": 135318119812, + "model_snapshot_file_count": 419, + "model_snapshot_total_bytes": 135253622894, + "snapshot_manifest_sha256": "5995053edba6d997fa0b4e45363293c0bd4d670f5824c2b6b7f71a7dccc54374" + }, + "activity_calibration": { + "schema_version": 1, + "source_hash": "45911631224860d4bb273278ec4cf2b062e48bd061deffa69fe7de9ece93c3d2", + "dimensions": [ + { + "unit": "cached_input_token", + "units": "25472", + "work_us": "232011" + }, + { + "unit": "input_token", + "units": "520000", + "work_us": "61440000" + }, + { + "unit": "output_token", + "units": "2046", + "work_us": "32016500" + } + ] + }, + "markets": [ + { + "attestation_tier": 1, + "registration_required": true, + "provider_enrollment_required_for_activation": true, + "rate_map": [ + { + "unit": "cached_input_token", + "granularity": 1000, + "per_unit_au": "14907000000000" + }, + { + "unit": "input_token", + "granularity": 1000, + "per_unit_au": "59628000000000" + }, + { + "unit": "output_token", + "granularity": 1000, + "per_unit_au": "201245000000000" + } + ] + }, + { + "attestation_tier": 2, + "registration_required": true, + "provider_enrollment_required_for_activation": false, + "rate_map": [ + { + "unit": "cached_input_token", + "granularity": 1000, + "per_unit_au": "22360500000000" + }, + { + "unit": "input_token", + "granularity": 1000, + "per_unit_au": "89442000000000" + }, + { + "unit": "output_token", + "granularity": 1000, + "per_unit_au": "301867500000000" + } + ] + } + ], + "unresolved_identity_fields": [ + "catalog_sha256_after_complete_semantic_diff", + "tier_1_core_manifest_hash", + "tier_2_core_manifest_hash", + "core_binary_hash", + "tier_1_enclave_id", + "tier_2_enclave_id", + "catalog_anchor_revision" + ], + "prepublication_gates": [ + "Repeat exact verification of the immutable mirror revision immediately before signing.", + "Retain the verified 419-file directory artifact root and all five exact sidecar identities.", + "Resolve portable text F13 and media resource profiles.", + "Complete canonical served-model/xhigh native-provider and paid-gateway canaries.", + "Record applicable commercial hosted-use license compliance.", + "Refresh competitor prices and obtain economic-owner acceptance.", + "Validate and sign the complete catalog, then derive exact ledger identifiers.", + "Simulate all model-reference, Tier 1, Tier 2, pricing, and paid-room operations before commit." + ] +} diff --git a/catalog/drafts/qwen3.8-flash-next-nvfp4/managed-runtime-recipe-v1.json b/catalog/drafts/qwen3.8-flash-next-nvfp4/managed-runtime-recipe-v1.json new file mode 100644 index 00000000..3a0e410f --- /dev/null +++ b/catalog/drafts/qwen3.8-flash-next-nvfp4/managed-runtime-recipe-v1.json @@ -0,0 +1,73 @@ +{ + "schema_version": 1, + "kind": "pennyroyal_flash_next_frspec_v1", + "profile_version": 1, + "public_model_id": "Qwen/Qwen3.8-Flash-Next", + "artifact": { + "repo": "RadixArk/Qwen3.8-Flash-Next-NVFP4", + "revision": "7b719225242aacd3dbd3f9407468c2ee9a9d2594", + "snapshot_manifest_sidecar": "snapshot_manifest", + "snapshot_manifest_sha256": "5995053edba6d997fa0b4e45363293c0bd4d670f5824c2b6b7f71a7dccc54374", + "file_count": 419, + "total_bytes": 135253622894 + }, + "source": { + "sidecar": "pennyroyal_source", + "format": "pennyroyal_source_bundle_tar_gzip_v1", + "root_layout": "source", + "revision": "2c675da096939cb01102f8f4871bda3db55f7f28", + "archive_bytes": 64070181, + "archive_sha256": "776f6d4b1883c78c7d1eaafa23a936fe82d6cc2220bfb9631ec3598b5e342df4" + }, + "proofs": { + "launcher_sha256": "eb37c7d2699ff89963932dd56afcd5612e72a83b9f3d5efab871c64b189e4657", + "chat_template_sha256": "e57684bae4156211a55473c5a63be976a405a37ab5be5ae0e5abf1df5349c4b2", + "frspec_map_sha256": "becfa41d394b86c26c632bea8f3c6ea64bbb76d7b238d8673c06afae21269f25", + "frspec_manifest_sha256": "5f5e778589c7740167b34f7c084ae418bd00497ef81026f29f2ac835b278bbd9", + "tokenizer_sha256": "0997f410c57a1f4e53b09e4be8f4a172d90edd9564368fb0847030937229b9f3", + "ple_plugin_source_inventory_sha256": "34ddf6eff174cf1bc3938930a595c4e544f46e1951cf166695a3324d491a8c5f", + "seccomp_sha256": "c7a33fb8ae1f8346356a61ce833d579c45acf2bc94967c6763634e81010ff816" + }, + "ple": { + "materialization": "derive_from_signed_snapshot_v1", + "source_config_sha256": "e765305daba0951974308f4d32c075b52a6a45974730d273f2216718a994d624", + "source_index_sha256": "da5ca9c3b65e48e151329e64e141c2fa700bf2f99aec53cc014e4b52a6ff7a84", + "table_relpath": "ple/layer-0.bin", + "table_bytes": 51200245760, + "table_sha256": "b070f9644adf93794d8a1030584ab705809387e64396a9327a68fa3a3a6666b3", + "table_rows": 320001536, + "table_columns": 160, + "table_dtype": "float8_e4m3fn", + "portable_manifest_bytes": 2563, + "portable_manifest_sha256": "f3a5a692d577457a1b6166ec17b3006ebc55804812400ac3a7bfaa3ce5af75bd", + "reader_version": "0.2.0+pennyroyal2", + "reader_wheel": { + "sidecar": "ple_plugin_wheel", + "filename": "sglang_ssd_stream-0.2.0+pennyroyal2-cp312-cp312-linux_x86_64.whl", + "bytes": 292987, + "sha256": "5fd3bf79524aec7068729e99823a8278e4f3bd7dcacd222f2c55f4395454112f", + "python_tag": "cp312", + "abi_tag": "cp312", + "platform_tag": "linux_x86_64" + } + }, + "runtime": { + "image": "lmsysorg/sglang@sha256:12d3392bdc8be8d35e9a95f191df6aef99c5114bdbefd41bfdc7e760e6d25ec1", + "security_profile": { + "kind": "docker_29_1_3_default_plus_io_uring_v1", + "sidecar": "seccomp_profile", + "bytes": 16286, + "sha256": "c7a33fb8ae1f8346356a61ce833d579c45acf2bc94967c6763634e81010ff816" + }, + "resource_profile": "single_sm120_96g_hostnet_hostipc_v1", + "launch_profile": "pennyroyal_flash_next_frspec_524k_nvme_v1", + "provider_max_concurrent": 2, + "scheduler_max_running_requests": 4, + "reasoning_default": "xhigh", + "reasoning_overrides": [ + "low", + "medium", + "xhigh" + ] + } +} diff --git a/catalog/drafts/qwen3.8-flash-next-nvfp4/media-resource-evidence.schema.json b/catalog/drafts/qwen3.8-flash-next-nvfp4/media-resource-evidence.schema.json new file mode 100644 index 00000000..e2cd32a8 --- /dev/null +++ b/catalog/drafts/qwen3.8-flash-next-nvfp4/media-resource-evidence.schema.json @@ -0,0 +1,126 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://openmayhem.dev/schema/qwen3.8-flash-next-media-resource-evidence-v1.json", + "title": "Qwen3.8 Flash-Next media resource evidence", + "type": "object", + "additionalProperties": false, + "required": [ + "schema_version", + "model_id", + "artifact_root", + "mirror_revision", + "runtime_recipe_sha256", + "profiles" + ], + "properties": { + "schema_version": { + "const": 1 + }, + "model_id": { + "const": "Qwen/Qwen3.8-Flash-Next" + }, + "artifact_root": { + "const": "78191c28fa91602e536baa8ede39891d6d500615086ce2ba42cb920c3b46eabe" + }, + "mirror_revision": { + "const": "29292a09675f9b82e4c0b7b5c0ad028fe58ee52e" + }, + "runtime_recipe_sha256": { + "const": "7f248046c72b771620da09f36d9cf554134cc70789c3e832017f8d29dbd5b41c" + }, + "profiles": { + "type": "object", + "additionalProperties": false, + "required": [ + "image", + "video" + ], + "properties": { + "image": { + "$ref": "#/$defs/profile" + }, + "video": { + "$ref": "#/$defs/profile" + } + } + } + }, + "$defs": { + "positiveInteger": { + "type": "integer", + "minimum": 1 + }, + "sha256": { + "type": "string", + "pattern": "^[0-9a-f]{64}$" + }, + "profile": { + "type": "object", + "additionalProperties": false, + "required": [ + "unit", + "measurement_source", + "max_item_bytes", + "max_item_units", + "measured_item_bytes", + "measured_item_units", + "measured_working_set_bytes", + "calibration_baseline_memory_bytes", + "calibration_peak_memory_bytes", + "calibration_f13_budget_bytes", + "default_max_inflight_items", + "default_max_items_per_request", + "evidence_sha256", + "response_token_fingerprint" + ], + "properties": { + "unit": { + "enum": [ + "pixel", + "frame" + ] + }, + "measurement_source": { + "type": "string", + "minLength": 1 + }, + "max_item_bytes": { + "$ref": "#/$defs/positiveInteger" + }, + "max_item_units": { + "$ref": "#/$defs/positiveInteger" + }, + "measured_item_bytes": { + "$ref": "#/$defs/positiveInteger" + }, + "measured_item_units": { + "$ref": "#/$defs/positiveInteger" + }, + "measured_working_set_bytes": { + "$ref": "#/$defs/positiveInteger" + }, + "calibration_baseline_memory_bytes": { + "$ref": "#/$defs/positiveInteger" + }, + "calibration_peak_memory_bytes": { + "$ref": "#/$defs/positiveInteger" + }, + "calibration_f13_budget_bytes": { + "$ref": "#/$defs/positiveInteger" + }, + "default_max_inflight_items": { + "const": 1 + }, + "default_max_items_per_request": { + "const": 1 + }, + "evidence_sha256": { + "$ref": "#/$defs/sha256" + }, + "response_token_fingerprint": { + "$ref": "#/$defs/sha256" + } + } + } + } +} diff --git a/catalog/drafts/qwen3.8-flash-next-nvfp4/media-resource-measurement.request.json b/catalog/drafts/qwen3.8-flash-next-nvfp4/media-resource-measurement.request.json new file mode 100644 index 00000000..477f759a --- /dev/null +++ b/catalog/drafts/qwen3.8-flash-next-nvfp4/media-resource-measurement.request.json @@ -0,0 +1,52 @@ +{ + "schema_version": 1, + "status": "pending_managed_runtime_measurement", + "model_id": "Qwen/Qwen3.8-Flash-Next", + "artifact_root_kind": "blake3_merkle_v1", + "artifact_root": "78191c28fa91602e536baa8ede39891d6d500615086ce2ba42cb920c3b46eabe", + "mirror_revision": "29292a09675f9b82e4c0b7b5c0ad028fe58ee52e", + "runtime_recipe_sha256": "7f248046c72b771620da09f36d9cf554134cc70789c3e832017f8d29dbd5b41c", + "route_envelope": { + "max_input_files_total": 3, + "media_items_processed_per_turn": 1, + "default_max_inflight_media_items": 1, + "default_max_media_items_per_request": 1 + }, + "probes": [ + { + "modality": "image", + "unit": "pixel", + "max_output_tokens": 128, + "measure": [ + "input_file_bytes", + "input_item_units", + "idle_vram_bytes", + "peak_vram_bytes", + "working_set_delta_bytes", + "response_token_fingerprint" + ] + }, + { + "modality": "video", + "unit": "frame", + "max_output_tokens": 128, + "measure": [ + "input_file_bytes", + "input_item_units", + "idle_vram_bytes", + "peak_vram_bytes", + "working_set_delta_bytes", + "response_token_fingerprint" + ] + } + ], + "acceptance": { + "runtime_identity_must_match": true, + "measure_each_modality_in_isolation": true, + "return_to_signed_idle_metrics_before_next_probe": true, + "working_set_delta_formula": "peak_vram_bytes-idle_vram_bytes", + "f13_budget_formula": "ceil(peak_vram_bytes/0.85)", + "evidence_sha256_required": true, + "portable_profile_signoff_required": true + } +} diff --git a/catalog/drafts/qwen3.8-flash-next-nvfp4/model-surface.values.json b/catalog/drafts/qwen3.8-flash-next-nvfp4/model-surface.values.json new file mode 100644 index 00000000..d3a88b2c --- /dev/null +++ b/catalog/drafts/qwen3.8-flash-next-nvfp4/model-surface.values.json @@ -0,0 +1,201 @@ +{ + "schema_version": 1, + "status": "blocked_pending_resource_profiles_and_final_canary_fingerprints", + "model_fields": { + "model_id": "Qwen/Qwen3.8-Flash-Next", + "model_class": "text-generation", + "family": "qwen3.8", + "params_b": 125, + "tier": "launch", + "provenance": { + "source": { + "kind": "huggingface", + "repo": "Qwen/Qwen3.8-Flash-Next", + "revision": "de4b8e4d43b917e7706784d8bb445c9af86a3540", + "publisher_key": "huggingface-repo" + }, + "conversion": [ + { + "tool": "huggingface_hub", + "method": "immutable-byte-mirror:pinned-radixark-nvfp4-snapshot-manifest", + "input_sha256": "5995053edba6d997fa0b4e45363293c0bd4d670f5824c2b6b7f71a7dccc54374", + "output_sha256": "5995053edba6d997fa0b4e45363293c0bd4d670f5824c2b6b7f71a7dccc54374" + } + ], + "license": "qwen-community-1.0", + "license_sha256": "a0dc422560841fd68e06d974907f8b4c709bca44a67daad2b528437bdf676c08" + }, + "caps": { + "ctx_max": 262144, + "json": true, + "output_modalities": [ + "text" + ], + "output_modality": "text", + "tools": true, + "vision": true + }, + "sampling": { + "temperature": 1, + "top_p": 0.95, + "top_k": 20, + "min_p": 0, + "repeat_penalty": 1, + "presence_penalty": 0 + }, + "price_ref_au": { + "denom": "au_usd", + "in_per_1k": "59628000000000", + "out_per_1k": "201245000000000" + } + }, + "sampling_profiles": { + "thinking_enabled": { + "temperature": 1, + "top_p": 0.95, + "top_k": 20, + "min_p": 0, + "presence_penalty": 0, + "repeat_penalty": 1 + }, + "thinking_disabled": { + "temperature": 0.7, + "top_p": 0.8, + "top_k": 20, + "min_p": 0, + "presence_penalty": 1.5, + "repeat_penalty": 1 + } + }, + "adapter_derivation": { + "source_catalog_sha256": "2112bce6b7f8fa5660c81a81b22f9d4c4f19c263972939601ff4512e5cb2c069", + "source_model_id": "Qwen/Qwen3.8-27B", + "copy_fields": [ + "adapter.chat_template_id", + "adapter.tool_call_strategy", + "adapter.reasoning_passthrough", + "adapter.modality_set", + "adapter.specialities", + "adapter.endpoint_families" + ], + "required_substitutions": [ + { + "json_path_suffix": "request_attribute_specs.model.enum_values", + "from": [ + "Qwen/Qwen3.8-27B" + ], + "to": [ + "Qwen/Qwen3.8-Flash-Next" + ] + }, + { + "json_path_suffix": "request_attribute_specs.model.calibration_values", + "from": [ + "Qwen/Qwen3.8-27B" + ], + "to": [ + "Qwen/Qwen3.8-Flash-Next" + ] + } + ], + "endpoint_families": [ + "openai_chat_completions", + "openai_completions", + "openai_responses", + "hf_multimodal_chat" + ], + "speciality_defaults": { + "reasoning_effort": "xhigh", + "thinking_history": "preserve", + "thinking_mode": "enabled" + }, + "speciality_levels": { + "reasoning_effort": [ + "low", + "medium", + "xhigh" + ], + "thinking_history": [ + "latest_only", + "preserve" + ], + "thinking_mode": [ + "disabled", + "enabled" + ] + } + }, + "modality_assessment": { + "detected": [ + "text", + "image", + "video" + ], + "evidence": [ + "The pinned official and NVFP4 configurations declare the native Qwen3.8 vision-language architecture; the pinned processor and chat-template assets carry image and video inputs." + ], + "calibrated_fingerprints": {}, + "resource_profiles": {} + }, + "speciality_assessment": { + "detected": [ + "reasoning_effort", + "thinking_mode", + "thinking_history" + ], + "evidence": [ + "The pinned official model card and chat template expose low, medium, and xhigh reasoning effort plus thinking enablement and history preservation controls." + ], + "unsupported": {}, + "calibrated": {} + }, + "canary_plan": { + "set_id": "canary-qwen3.8-flash-next-nvfp4-v1", + "match_min": 0.9, + "verification_method": "token_fingerprint", + "prompt_ids": [ + "qwen38-text-deterministic-a", + "qwen38-text-deterministic-b", + "qwen38-tool-routing", + "qwen38-json-object", + "qwen38-reasoning-xhigh", + "qwen38-reasoning-medium", + "qwen38-reasoning-low", + "qwen38-preserve-thinking-true", + "qwen38-preserve-thinking-false", + "qwen38-image-understanding", + "qwen38-video-understanding", + "qwen38-sampling-thinking", + "qwen38-sampling-non-thinking", + "qwen38-temperature-upper-boundary" + ], + "fingerprints": {}, + "token_prefixes": {} + }, + "generation_execution_profile": { + "key": "78191c28fa91602e536baa8ede39891d6d500615086ce2ba42cb920c3b46eabe", + "value": { + "schema_version": 1, + "engine": "openai-compatible", + "independent_dispatch": true, + "request_modalities": [ + [ + "text" + ] + ], + "proof_sha256": "adc3b7256a75f32f112a74ce1b453fab16e007013803ded94695dca02f3ccba8" + } + }, + "served_runtime_context": 524288, + "catalog_native_context_invariant": "Keep model_fields.caps.ctx_max at 262144. The 524288 YaRN runtime setting belongs only in the signed openai-compatible binding.", + "pending_catalog_fields": [ + "requirements.min_ram_gb", + "requirements.min_vram_gb_full_offload", + "modality_assessment.resource_profiles.nvfp4.image", + "modality_assessment.resource_profiles.nvfp4.video", + "modality_assessment.calibrated_fingerprints.nvfp4", + "speciality_assessment.calibrated.nvfp4", + "canary.fingerprints.nvfp4", + "canary.token_prefixes.nvfp4" + ] +} diff --git a/catalog/drafts/qwen3.8-flash-next-nvfp4/runtime-binding.template.json b/catalog/drafts/qwen3.8-flash-next-nvfp4/runtime-binding.template.json new file mode 100644 index 00000000..98d6acd4 --- /dev/null +++ b/catalog/drafts/qwen3.8-flash-next-nvfp4/runtime-binding.template.json @@ -0,0 +1,91 @@ +{ + "schema_version": 1, + "status": "blocked_pending_focused_runtime_canaries", + "artifact_engine": "openai-compatible", + "openai_compatible": { + "schema_version": 1, + "runtime_id": "pennyroyal", + "runtime_version": "2.5.0", + "runtime_revision": "2c675da096939cb01102f8f4871bda3db55f7f28", + "implementation": "sglang", + "implementation_version": "0.0.0.dev1+gd91c3682b", + "container_image_digest": "sha256:12d3392bdc8be8d35e9a95f191df6aef99c5114bdbefd41bfdc7e760e6d25ec1", + "served_model": "Qwen/Qwen3.8-Flash-Next", + "native_context": 262144, + "served_context": 524288, + "max_concurrent": 2, + "capabilities": [ + "cancellation", + "image", + "json", + "prefix_cache", + "reasoning", + "streaming", + "tools", + "video" + ], + "preflight": { + "non_reasoning_chat_template_kwargs": { + "enable_thinking": false + }, + "reasoning_chat_template_kwargs": { + "enable_thinking": true, + "preserve_thinking": true, + "reasoning_effort": "xhigh" + }, + "streaming_max_tokens": 32, + "tools_max_tokens": 384, + "json_max_tokens": 256, + "reasoning_max_tokens": 1024, + "cache_max_tokens": 32, + "cancellation_max_tokens": 4096, + "concurrency_max_tokens": 4096, + "cancellation_idle_metrics": [ + "sglang:num_queue_reqs", + "sglang:num_running_reqs" + ], + "concurrency_active_metric": "sglang:num_running_reqs" + }, + "server_info_checks": { + "/chunked_prefill_size": 4096, + "/context_length": 524288, + "/default_chat_template_kwargs/enable_thinking": true, + "/default_chat_template_kwargs/preserve_thinking": true, + "/default_chat_template_kwargs/reasoning_effort": "xhigh", + "/disable_cuda_graph": false, + "/disable_radix_cache": false, + "/dtype": "bfloat16", + "/enable_hierarchical_cache": true, + "/hicache_io_backend": "kernel", + "/hicache_size": 32, + "/hicache_storage_backend": "nixl", + "/kv_cache_dtype": "fp8_e4m3", + "/load_format": "safetensors", + "/max_mamba_cache_size": 24, + "/max_running_requests": 4, + "/max_total_num_tokens": 824384, + "/max_total_tokens": 824384, + "/page_size": 64, + "/quantization": "modelopt_fp4", + "/reasoning_parser": "qwen3", + "/served_model_name": "Qwen/Qwen3.8-Flash-Next", + "/speculative_algorithm": "EAGLE", + "/speculative_eagle_topk": 1, + "/speculative_num_draft_tokens": 4, + "/speculative_num_steps": 3, + "/tool_call_parser": "qwen3_coder", + "/version": "0.0.0.dev1+gd91c3682b" + }, + "prefix_cache_metric": "sglang:cached_tokens_total", + "lifecycle": "managed_or_verified_attach", + "snapshot_manifest_sidecar": "snapshot_manifest", + "snapshot_manifest_sha256": "5995053edba6d997fa0b4e45363293c0bd4d670f5824c2b6b7f71a7dccc54374", + "runtime_recipe_sidecar": "runtime_recipe", + "runtime_recipe_sha256": "7f248046c72b771620da09f36d9cf554134cc70789c3e832017f8d29dbd5b41c", + "model_snapshot_file_count": 419 + }, + "publication_blockers": [ + "Rerun focused native-provider identity and functional canaries after changing the served model name from pennyroyal to Qwen/Qwen3.8-Flash-Next and the default reasoning effort from medium to xhigh.", + "Validate every server_info_checks pointer against the native provider's final attached runtime response." + ] +} diff --git a/catalog/models.json b/catalog/models.json index 5df108d0..0f27dd8c 100644 --- a/catalog/models.json +++ b/catalog/models.json @@ -1,6 +1,6 @@ { "catalog_id": "openmayhem-mainnet", - "generated_at": "2026-09-02T12:29:47Z", + "generated_at": "2026-09-16T16:50:30Z", "models": [ { "model_id": "Cactus-Compute/needle", @@ -37806,6 +37806,11007 @@ }, "tier": "launch", "min_app_version": "0.2.166" + }, + { + "model_id": "Qwen/Qwen3-Embedding-4B", + "model_class": "embedding", + "family": "qwen3-embedding", + "params_b": 4.0, + "tier": "launch", + "min_app_version": "0.2.232", + "provenance": { + "source": { + "kind": "huggingface", + "repo": "Qwen/Qwen3-Embedding-4B", + "revision": "5cf2132abc99cad020ac570b19d031efec650f2b", + "publisher_key": "huggingface-repo" + }, + "conversion": [ + { + "tool": "huggingface_hub", + "method": "immutable-byte-mirror:pinned-bf16-snapshot", + "input_sha256": "e70bfe3c970523fb7ef4eddffed2254ce3f1e7150c3de2af4342de129dd756f8", + "output_sha256": "e70bfe3c970523fb7ef4eddffed2254ce3f1e7150c3de2af4342de129dd756f8" + } + ], + "license": "apache-2.0", + "license_sha256": "cfc7749b96f63bd31c3c42b5c471bf756814053e847c10f3eb003417bc523d30" + }, + "artifacts": { + "bf16": { + "engine": "vllm", + "source": { + "kind": "huggingface", + "repo": "TracNetwork/mayhem-catalog-Qwen-Qwen3-Embedding-4B-BF16", + "revision": "909825755dc39379f3eb31256602da9cafb29c95", + "publisher_key": "huggingface-repo" + }, + "upstream_source": { + "kind": "huggingface", + "repo": "Qwen/Qwen3-Embedding-4B", + "revision": "5cf2132abc99cad020ac570b19d031efec650f2b", + "publisher_key": "huggingface-repo" + }, + "path": "model-00001-of-00002.safetensors", + "artifact_root": "e228d4e36517c331c3ddf034753cf974a7f73d2ac3dd0be4ef145722eccdc26c", + "artifact_root_kind": "blake3_merkle_v1", + "weights_bytes": 4965826464, + "source_sha256": "e70bfe3c970523fb7ef4eddffed2254ce3f1e7150c3de2af4342de129dd756f8", + "tokenizer_sha256": "83cdf8c3a34f68862319cb1810ee7b1e2c0a44e0864ae930194ddb76bb7feb8d", + "download_check": true, + "notes": "Pinned official BF16 snapshot mirrored byte-for-byte at an immutable TracNetwork revision; normalized last-token pooling and MRL dimensions are calibrated with vLLM 0.24 on Linux GB10.", + "sidecars": { + "vllm_weight_shard_00002": { + "source": { + "kind": "huggingface", + "repo": "TracNetwork/mayhem-catalog-Qwen-Qwen3-Embedding-4B-BF16", + "revision": "909825755dc39379f3eb31256602da9cafb29c95", + "publisher_key": "huggingface-repo" + }, + "upstream_source": { + "kind": "huggingface", + "repo": "Qwen/Qwen3-Embedding-4B", + "revision": "5cf2132abc99cad020ac570b19d031efec650f2b", + "publisher_key": "huggingface-repo" + }, + "path": "model-00002-of-00002.safetensors", + "artifact_root": "20c7a959fb6398c267d3370225629ad7d90f80a77787b9ea7afbeac4d1b36370", + "artifact_root_kind": "blake3_merkle_v1", + "weights_bytes": 3077765624, + "source_sha256": "ed1b87c8e9eb7e535a1a155e4fd00d9f4dba80e58a6db48a4c9f82cede7079c1" + }, + "vllm_model_index": { + "source": { + "kind": "huggingface", + "repo": "TracNetwork/mayhem-catalog-Qwen-Qwen3-Embedding-4B-BF16", + "revision": "909825755dc39379f3eb31256602da9cafb29c95", + "publisher_key": "huggingface-repo" + }, + "upstream_source": { + "kind": "huggingface", + "repo": "Qwen/Qwen3-Embedding-4B", + "revision": "5cf2132abc99cad020ac570b19d031efec650f2b", + "publisher_key": "huggingface-repo" + }, + "path": "model.safetensors.index.json", + "artifact_root": "15218e7fbe432280e05b130e7d965369bba4f4d61b546c09ce1f8c5c083e27ca", + "artifact_root_kind": "blake3_merkle_v1", + "weights_bytes": 30431, + "source_sha256": "9d130c7f24fa1f9a2a7e19fad42c7d6d2d6fea31b180bdf3e8aac1924c26c39a" + }, + "vllm_config": { + "source": { + "kind": "huggingface", + "repo": "TracNetwork/mayhem-catalog-Qwen-Qwen3-Embedding-4B-BF16", + "revision": "909825755dc39379f3eb31256602da9cafb29c95", + "publisher_key": "huggingface-repo" + }, + "upstream_source": { + "kind": "huggingface", + "repo": "Qwen/Qwen3-Embedding-4B", + "revision": "5cf2132abc99cad020ac570b19d031efec650f2b", + "publisher_key": "huggingface-repo" + }, + "path": "config.json", + "artifact_root": "301beb973e39d7dc94163445b914217e29c64f55c0033e3e5e46645f75a96e4f", + "artifact_root_kind": "blake3_merkle_v1", + "weights_bytes": 727, + "source_sha256": "78d2861cbbfd80eee05839200c5a3b7ed64c789f6c1cab4fbb84cc4eae33eaf5" + }, + "vllm_tokenizer_json": { + "source": { + "kind": "huggingface", + "repo": "TracNetwork/mayhem-catalog-Qwen-Qwen3-Embedding-4B-BF16", + "revision": "909825755dc39379f3eb31256602da9cafb29c95", + "publisher_key": "huggingface-repo" + }, + "upstream_source": { + "kind": "huggingface", + "repo": "Qwen/Qwen3-Embedding-4B", + "revision": "5cf2132abc99cad020ac570b19d031efec650f2b", + "publisher_key": "huggingface-repo" + }, + "path": "tokenizer.json", + "artifact_root": "d0462ea067d57af34425ed175418141c1a347ad44cc6069a01acfb87acaf6ce2", + "artifact_root_kind": "blake3_merkle_v1", + "weights_bytes": 11422947, + "source_sha256": "83cdf8c3a34f68862319cb1810ee7b1e2c0a44e0864ae930194ddb76bb7feb8d" + }, + "vllm_tokenizer_config": { + "source": { + "kind": "huggingface", + "repo": "TracNetwork/mayhem-catalog-Qwen-Qwen3-Embedding-4B-BF16", + "revision": "909825755dc39379f3eb31256602da9cafb29c95", + "publisher_key": "huggingface-repo" + }, + "upstream_source": { + "kind": "huggingface", + "repo": "Qwen/Qwen3-Embedding-4B", + "revision": "5cf2132abc99cad020ac570b19d031efec650f2b", + "publisher_key": "huggingface-repo" + }, + "path": "tokenizer_config.json", + "artifact_root": "f335cfe9a2dc1a8be2028825e513aea6f4ce1a190a2048758d79b7780eaadb59", + "artifact_root_kind": "blake3_merkle_v1", + "weights_bytes": 7256, + "source_sha256": "2f58f4bbd7bbce15d683f525954ef3a92cd82f5e06415a9c513859bf8ab72436" + }, + "vllm_generation_config": { + "source": { + "kind": "huggingface", + "repo": "TracNetwork/mayhem-catalog-Qwen-Qwen3-Embedding-4B-BF16", + "revision": "909825755dc39379f3eb31256602da9cafb29c95", + "publisher_key": "huggingface-repo" + }, + "upstream_source": { + "kind": "huggingface", + "repo": "Qwen/Qwen3-Embedding-4B", + "revision": "5cf2132abc99cad020ac570b19d031efec650f2b", + "publisher_key": "huggingface-repo" + }, + "path": "generation_config.json", + "artifact_root": "e4fa3d00de31cf8a2654f8873a7b983421fa5dc7e9e92ec4ecd284240bda28b2", + "artifact_root_kind": "blake3_merkle_v1", + "weights_bytes": 117, + "source_sha256": "28396d421a2108acce96383f6a7de78008f7f1b17f807958f3c14c51dbfb65fb" + }, + "vllm_merges": { + "source": { + "kind": "huggingface", + "repo": "TracNetwork/mayhem-catalog-Qwen-Qwen3-Embedding-4B-BF16", + "revision": "909825755dc39379f3eb31256602da9cafb29c95", + "publisher_key": "huggingface-repo" + }, + "upstream_source": { + "kind": "huggingface", + "repo": "Qwen/Qwen3-Embedding-4B", + "revision": "5cf2132abc99cad020ac570b19d031efec650f2b", + "publisher_key": "huggingface-repo" + }, + "path": "merges.txt", + "artifact_root": "4c9e5374d798802ae6491739da886f1c61f2ebe640cf68be8cd9fe856b51ed11", + "artifact_root_kind": "blake3_merkle_v1", + "weights_bytes": 1671853, + "source_sha256": "8831e4f1a044471340f7c0a83d7bd71306a5b867e95fd870f74d0c5308a904d5" + }, + "vllm_vocab": { + "source": { + "kind": "huggingface", + "repo": "TracNetwork/mayhem-catalog-Qwen-Qwen3-Embedding-4B-BF16", + "revision": "909825755dc39379f3eb31256602da9cafb29c95", + "publisher_key": "huggingface-repo" + }, + "upstream_source": { + "kind": "huggingface", + "repo": "Qwen/Qwen3-Embedding-4B", + "revision": "5cf2132abc99cad020ac570b19d031efec650f2b", + "publisher_key": "huggingface-repo" + }, + "path": "vocab.json", + "artifact_root": "07e084d5bb703cb11e2e1d14570b12572f794244e2f6b850bfeb278db0053cdf", + "artifact_root_kind": "blake3_merkle_v1", + "weights_bytes": 2776833, + "source_sha256": "ca10d7e9fb3ed18575dd1e277a2579c16d108e32f27439684afa0e10b1440910" + }, + "sentence_transformers_config": { + "source": { + "kind": "huggingface", + "repo": "TracNetwork/mayhem-catalog-Qwen-Qwen3-Embedding-4B-BF16", + "revision": "909825755dc39379f3eb31256602da9cafb29c95", + "publisher_key": "huggingface-repo" + }, + "upstream_source": { + "kind": "huggingface", + "repo": "Qwen/Qwen3-Embedding-4B", + "revision": "5cf2132abc99cad020ac570b19d031efec650f2b", + "publisher_key": "huggingface-repo" + }, + "path": "config_sentence_transformers.json", + "artifact_root": "2604cf5d3d4ae70d0a39ef585566408e788e195f470c5e96dcb3e1559816dcc0", + "artifact_root_kind": "blake3_merkle_v1", + "weights_bytes": 215, + "source_sha256": "10667c72ddb772627bf1780cb7f86af8e2ae0032b8c243c731172064105c6961" + }, + "sentence_transformers_modules": { + "source": { + "kind": "huggingface", + "repo": "TracNetwork/mayhem-catalog-Qwen-Qwen3-Embedding-4B-BF16", + "revision": "909825755dc39379f3eb31256602da9cafb29c95", + "publisher_key": "huggingface-repo" + }, + "upstream_source": { + "kind": "huggingface", + "repo": "Qwen/Qwen3-Embedding-4B", + "revision": "5cf2132abc99cad020ac570b19d031efec650f2b", + "publisher_key": "huggingface-repo" + }, + "path": "modules.json", + "artifact_root": "c74ac8c95ab46522ca38ba2706de4ca7da1406d380ca44a26da19d99bda358dd", + "artifact_root_kind": "blake3_merkle_v1", + "weights_bytes": 349, + "source_sha256": "84e40c8e006c9b1d6c122e02cba9b02458120b5fb0c87b746c41e0207cf642cf" + }, + "sentence_transformers_pooling_config": { + "source": { + "kind": "huggingface", + "repo": "TracNetwork/mayhem-catalog-Qwen-Qwen3-Embedding-4B-BF16", + "revision": "909825755dc39379f3eb31256602da9cafb29c95", + "publisher_key": "huggingface-repo" + }, + "upstream_source": { + "kind": "huggingface", + "repo": "Qwen/Qwen3-Embedding-4B", + "revision": "5cf2132abc99cad020ac570b19d031efec650f2b", + "publisher_key": "huggingface-repo" + }, + "path": "1_Pooling/config.json", + "artifact_root": "6d8a7ca7a24500117320af3757784db5d674b24b6c78a8a2064cf1ec9a4898ac", + "artifact_root_kind": "blake3_merkle_v1", + "weights_bytes": 313, + "source_sha256": "0f0ed3380602b252fced3fab6d07c76752c32ffca818ccc61afaf17ae8edd96f" + } + }, + "min_compute_cap": "12.1" + } + }, + "caps": { + "tools": false, + "json": false, + "ctx_max": 32768, + "vision": false, + "output_modality": "embedding", + "output_modalities": [ + "embedding" + ] + }, + "requirements": { + "min_ram_gb": 16, + "min_vram_gb_full_offload": 16, + "cpu_flags": [ + "neon" + ], + "backends": [ + "vllm" + ] + }, + "adapter": { + "chat_template_id": "generic_chatml", + "tool_call_strategy": "none", + "reasoning_passthrough": "strip", + "modality_set": [ + "embedding" + ], + "endpoint_families": [ + { + "family": "openai_embeddings", + "request_attributes": [ + "model", + "input", + "encoding_format", + "dimensions" + ], + "required_request_attributes": [ + "model", + "input" + ], + "response_attributes": [ + "object", + "data", + "model", + "usage", + "mayhem" + ], + "request_attribute_specs": { + "model": { + "value_types": [ + "string" + ], + "calibration_values": [ + "Qwen/Qwen3-Embedding-4B" + ], + "enum_values": [ + "Qwen/Qwen3-Embedding-4B" + ] + }, + "input": { + "value_types": [ + "string", + "array" + ], + "calibration_values": [ + "Mayhem embedding calibration", + [ + "Mayhem batch calibration A", + "Mayhem batch calibration B" + ] + ], + "min_length": 1, + "min_items": 1, + "max_items": 128 + }, + "encoding_format": { + "value_types": [ + "string" + ], + "default": "float", + "enum_values": [ + "float", + "base64" + ], + "calibration_values": [ + "float", + "base64" + ] + }, + "dimensions": { + "value_types": [ + "integer" + ], + "minimum": 32, + "maximum": 2560, + "calibration_values": [ + 1536, + 2560 + ] + } + }, + "response_attribute_specs": { + "object": { + "value_types": [ + "string" + ], + "min_length": 0, + "max_length": 536870912, + "calibration_values": [ + "$RESPONSE_VALUE" + ] + }, + "data": { + "value_types": [ + "array" + ], + "min_items": 0, + "max_items": 1000000, + "calibration_values": [ + [] + ] + }, + "model": { + "value_types": [ + "string" + ], + "min_length": 0, + "max_length": 536870912, + "calibration_values": [ + "$RESPONSE_VALUE" + ] + }, + "usage": { + "value_types": [ + "object" + ], + "calibration_values": [ + {} + ] + }, + "mayhem": { + "value_types": [ + "object" + ], + "calibration_values": [ + {} + ] + } + }, + "interaction_groups": [], + "required_response_attributes": [ + "object", + "data", + "model", + "usage", + "mayhem" + ] + }, + { + "family": "hf_feature_extraction", + "request_attributes": [ + "inputs", + "dimensions" + ], + "required_request_attributes": [ + "inputs" + ], + "response_attributes": [ + "embeddings", + "usage", + "mayhem" + ], + "required_response_attributes": [ + "embeddings", + "usage", + "mayhem" + ], + "request_attribute_specs": { + "inputs": { + "value_types": [ + "string", + "array" + ], + "calibration_values": [ + "Mayhem embedding calibration", + [ + "Mayhem batch calibration A", + "Mayhem batch calibration B" + ] + ], + "min_length": 1, + "min_items": 1, + "max_items": 128 + }, + "dimensions": { + "value_types": [ + "integer" + ], + "minimum": 32, + "maximum": 2560, + "calibration_values": [ + 1536, + 2560 + ] + } + }, + "response_attribute_specs": { + "embeddings": { + "value_types": [ + "array" + ], + "min_items": 0, + "max_items": 1000000, + "calibration_values": [ + [] + ] + }, + "usage": { + "value_types": [ + "object" + ], + "calibration_values": [ + {} + ] + }, + "mayhem": { + "value_types": [ + "object" + ], + "calibration_values": [ + {} + ] + } + }, + "interaction_groups": [] + } + ] + }, + "modality_assessment": { + "detected": [ + "embedding" + ], + "evidence": [ + "Pinned official Qwen3-Embedding-4B card, config, Sentence Transformers pooling metadata, and vLLM pooling API identify normalized last-token embeddings with 32 through 2560 output dimensions." + ], + "calibrated_fingerprints": { + "bf16": { + "embedding": "2cb34c1e7652ce1ac18911de95f087a041591ea07134d7600270bf7136fa7279" + } + }, + "resource_profiles": { + "bf16": { + "embedding": { + "unit": "input_token", + "measurement_source": "process_tree_rss; pool=nvidia_unified_memory; NVIDIA unified memory from hwprobe; --memory-reserve override 24.00GiB", + "max_item_bytes": 65533, + "max_item_units": 32768, + "measured_item_bytes": 65533, + "measured_item_units": 32768, + "measured_working_set_bytes": 2826240, + "calibration_baseline_memory_bytes": 3581898752, + "calibration_peak_memory_bytes": 3584724992, + "calibration_f13_budget_bytes": 96129949696, + "default_max_inflight_items": 256, + "default_max_items_per_request": 128 + } + } + } + }, + "speciality_assessment": { + "detected": [], + "evidence": [ + "Pinned official Qwen3-Embedding-4B card, config, reference inference code, Sentence Transformers pooling metadata, and vLLM 0.24 pooling behavior were reviewed; no separate speciality level is declared." + ], + "unsupported": {}, + "calibrated": {} + }, + "sampling": {}, + "canary": { + "set_id": "canary-qwen3-embedding-4b-bf16-v1", + "match_min": 0.99, + "verification_method": "embedding_cosine", + "verification_tolerance_bps": 9950, + "embedding_vectors": { + "bf16": { + "qwen3-embed-document-1536": [ + -0.00032887139241211116, + -0.03683359920978546, + -0.00734479445964098, + -0.0031242785044014454, + -0.001123643945902586, + 0.08886835724115372, + 0.010377720929682255, + 0.04852680489420891, + 0.030694665387272835, + -0.01629740558564663, + 0.006577427964657545, + -0.03098699450492859, + -0.0006942840409465134, + -0.04121854901313782, + -0.05115777254104614, + -0.06197398900985718, + -0.04355718940496445, + -0.0160781592130661, + -0.03449495881795883, + -0.0053350250236690044, + -0.02397107146680355, + 0.02704053744673729, + 0.035518109798431396, + 0.05349641293287277, + -0.05408107489347458, + 0.024117235094308853, + -0.028209857642650604, + -0.018489880487322807, + 0.045018840581178665, + 0.00537156593054533, + 0.0017174395034089684, + -0.04706515371799469, + 0.007125547155737877, + -0.016589734703302383, + 0.005554272327572107, + -0.02864835225045681, + 0.01702823117375374, + -0.004896529950201511, + -0.03668742999434471, + 0.042680200189352036, + 0.0020919875241816044, + 0.008733362890779972, + 0.011181628331542015, + 0.004403222817927599, + 0.0, + 0.011400875635445118, + 0.001498191966675222, + -0.07775981724262238, + -0.04355718940496445, + -0.012643277645111084, + 0.004147433675825596, + 0.027625199407339096, + 0.045018840581178665, + -0.031425490975379944, + 0.029817674309015274, + -0.0099392244592309, + 0.02294791489839554, + 0.0028867602813988924, + -0.0022107467520982027, + -0.03040233440697193, + -0.0020006343256682158, + 0.009793059900403023, + -0.00031973610748536885, + -0.035518109798431396, + 0.0025396179407835007, + -0.022801751270890236, + -0.002996383933350444, + -0.015712745487689972, + 0.0074544185772538185, + -0.00011076572263846174, + 0.059343017637729645, + 0.0035445031244307756, + 0.006029308773577213, + 0.02309408225119114, + 0.007417877204716206, + -0.05115777254104614, + -0.011912453919649124, + 0.04384952411055565, + -0.0048599885776638985, + 0.016516653820872307, + 0.015055001713335514, + 0.002758865710347891, + -0.011985535733401775, + 0.02397107146680355, + 0.015055001713335514, + -0.0022198818624019623, + 0.03405646234750748, + 0.00913531705737114, + -0.020316943526268005, + -0.006029308773577213, + -0.009098775684833527, + 0.03522578254342079, + 0.0397568978369236, + 0.007052464410662651, + -0.0011008057044818997, + 0.01154704112559557, + -0.019001459702849388, + -0.025140391662716866, + -0.002073716837912798, + -0.0028502189088612795, + 0.02645587921142578, + 0.004659011494368315, + -0.001836198614910245, + -0.012570195831358433, + -0.013958764262497425, + -0.049111466854810715, + -0.018197551369667053, + 0.022509420290589333, + 0.008879527449607849, + 0.020170779898762703, + 0.007965996861457825, + 0.01790522038936615, + 0.031425490975379944, + 0.010377720929682255, + -0.018855294212698936, + -0.00957381259649992, + -0.005992767866700888, + 0.05320408567786217, + 0.016005074605345726, + 0.07249787449836731, + -0.0032887139823287725, + 0.019001459702849388, + -0.001808792701922357, + -0.021047769114375114, + 0.01235094852745533, + -0.05086544528603554, + 0.0008313138387165964, + -0.008477574214339256, + 0.03201014921069145, + 0.02791752852499485, + 0.025725053623318672, + -0.0200246162712574, + 0.017612891271710396, + -0.006285097915679216, + -0.012716361321508884, + 0.003179090330377221, + 0.008623739704489708, + -0.020901605486869812, + -0.032302480190992355, + 0.02060927450656891, + -0.02543272264301777, + -0.016151240095496178, + 0.010085389949381351, + -0.03098699450492859, + -0.021778594702482224, + -0.044141851365566254, + 0.0016352217644453049, + -0.024994228035211563, + -0.005992767866700888, + -0.0005709573160856962, + 0.012058617547154427, + 0.016955148428678513, + 0.026894373819231987, + 0.0055908141657710075, + 0.01827063411474228, + 0.038587577641010284, + -0.0099392244592309, + 0.0180513858795166, + 0.009500729851424694, + 0.002046311041340232, + 0.0018727399874478579, + 0.007125547155737877, + 0.04881913214921951, + 0.015274249948561192, + -0.01768597401678562, + 0.0099392244592309, + -0.02543272264301777, + 0.03771058842539787, + -0.010596968233585358, + -0.0033069849014282227, + -0.016151240095496178, + -0.008989151567220688, + 0.008112161420285702, + -0.01593199372291565, + 0.005079235881567001, + 0.009646894410252571, + -0.020463110879063606, + 0.0160781592130661, + -0.0138856815174222, + 0.035518109798431396, + -0.007564042694866657, + -0.015055001713335514, + -0.05086544528603554, + -0.017978303134441376, + 0.006833217106759548, + 0.03317946940660477, + 0.004622470121830702, + -0.01629740558564663, + 0.015420414507389069, + -0.006029308773577213, + -0.013520268723368645, + 0.014178012497723103, + -0.012935608625411987, + 0.020463110879063606, + -0.01914762519299984, + 0.012204783037304878, + -0.024848060682415962, + -0.0017813867889344692, + 0.014251094311475754, + 0.009427647106349468, + 0.013520268723368645, + 0.007892914116382599, + -0.015347332693636417, + -0.0318639874458313, + 0.03507961705327034, + -0.02397107146680355, + 0.01074313186109066, + -0.0018179280450567603, + 0.018709128722548485, + -0.02382490783929825, + 0.007417877204716206, + -0.02791752852499485, + -0.019293788820505142, + -0.03683359920978546, + 0.009427647106349468, + 0.036395102739334106, + -0.021193936467170715, + -0.020901605486869812, + 0.04443417862057686, + 0.020316943526268005, + -0.010596968233585358, + -0.005773520562797785, + -0.024263402447104454, + 0.013081774115562439, + -0.03654126822948456, + 0.0013246210291981697, + -0.0069793821312487125, + -0.00451284646987915, + 0.0, + -0.022655587643384933, + 0.02557888627052307, + 0.0069793821312487125, + 0.0090256929397583, + 0.0032887139823287725, + -0.01680898293852806, + -0.013812599703669548, + -0.026602042838931084, + 0.007564042694866657, + 0.020463110879063606, + -0.03259481117129326, + -0.02791752852499485, + -0.017832139506936073, + -0.0027771361637860537, + 0.029671508818864822, + 0.022070925682783127, + -0.028502188622951508, + -0.0030877371318638325, + -0.010158471763134003, + -0.008989151567220688, + -0.007710207253694534, + -0.014835754409432411, + 0.00429359870031476, + 0.001233267830684781, + -0.021193936467170715, + -0.030110003426671028, + -0.009793059900403023, + -0.02294791489839554, + -0.035518109798431396, + 0.00451284646987915, + 0.012277866713702679, + -0.016882065683603287, + -0.009208399802446365, + -0.0035079617518931627, + -0.0048234472051262856, + -0.021924762055277824, + -0.020316943526268005, + 0.00803907960653305, + 0.0030146543867886066, + 0.00023180866264738142, + -0.038587577641010284, + 0.09939224272966385, + -0.021047769114375114, + 0.015201167203485966, + 0.004056080710142851, + 0.03507961705327034, + -0.018709128722548485, + -0.012789443135261536, + 0.004147433675825596, + 0.0160781592130661, + 0.019586119800806046, + -0.009646894410252571, + 0.0011647528735920787, + 0.01162012293934822, + -0.017247479408979416, + 0.01666281931102276, + 0.01556657999753952, + 0.05758903920650482, + -0.020901605486869812, + -0.0099392244592309, + 0.020170779898762703, + 0.019732285290956497, + -0.037125930190086365, + 0.015639662742614746, + -0.009281482547521591, + -0.007965996861457825, + -0.0180513858795166, + 0.026602042838931084, + -0.014616507105529308, + 0.006504345219582319, + 0.0022929643746465445, + -0.01790522038936615, + -0.008660280145704746, + -0.0055908141657710075, + 0.05846602842211723, + 0.02060927450656891, + 0.0024117236025631428, + 0.012131701223552227, + 0.0100123081356287, + -0.024409566074609756, + -0.0027954070828855038, + -0.00540810776874423, + -0.0100123081356287, + -0.0005686734803020954, + -0.0026675125118345022, + 0.00456765852868557, + -0.0020919875241816044, + 0.02309408225119114, + -0.011181628331542015, + -0.006906299851834774, + -0.02791752852499485, + -0.00119672657456249, + -0.030694665387272835, + 0.048234470188617706, + -0.009427647106349468, + 0.044141851365566254, + 0.02543272264301777, + 0.032302480190992355, + 0.014543424360454082, + 0.038587577641010284, + 0.033033307641744614, + -0.00884298700839281, + 0.03332563862204552, + -0.07717515528202057, + -0.03040233440697193, + -0.009793059900403023, + -0.00456765852868557, + 0.026163548231124878, + 0.06226631999015808, + 0.012131701223552227, + -0.0009318023221567273, + -0.011693204753100872, + -0.010377720929682255, + -0.04677281901240349, + 0.04063388705253601, + -0.06928224116563797, + -0.016224322840571404, + -0.02543272264301777, + 0.008623739704489708, + -0.012570195831358433, + -0.006796675734221935, + -0.03537194803357124, + -0.03171781823039055, + -0.10114622116088867, + -0.027479032054543495, + 0.050573110580444336, + 0.02543272264301777, + -0.011400875635445118, + 0.01841679960489273, + 0.023532576858997345, + 0.009427647106349468, + -0.011254710145294666, + -0.004147433675825596, + -0.002722324337810278, + 0.01629740558564663, + 0.011035462841391563, + -0.024848060682415962, + -0.03405646234750748, + 0.033033307641744614, + 0.039464570581912994, + -0.032302480190992355, + 0.02718670293688774, + -0.0400492325425148, + 0.048234470188617706, + -0.03113315999507904, + -0.03127932548522949, + -0.009866142645478249, + 0.02630971185863018, + -0.018489880487322807, + -0.02148626372218132, + 0.01476267259567976, + 0.03727209195494652, + 0.04209553822875023, + -0.020170779898762703, + 0.01863604597747326, + -0.025725053623318672, + 0.013666434213519096, + -0.039464570581912994, + -0.012643277645111084, + -0.015055001713335514, + 0.0069428407587111, + 0.02397107146680355, + 0.023678740486502647, + 0.014251094311475754, + -0.011912453919649124, + 0.024555733427405357, + -0.018709128722548485, + 0.02075544185936451, + 0.02163243107497692, + 0.057004377245903015, + -0.02163243107497692, + -0.02148626372218132, + 0.002704053884372115, + -0.06606661528348923, + -0.014470341615378857, + 0.014543424360454082, + -0.018855294212698936, + 0.0019184165867045522, + 0.025286555290222168, + 0.017101313918828964, + -0.006650510709732771, + 0.04531117528676987, + -0.02879451960325241, + 0.02557888627052307, + -0.0640202984213829, + -0.03347180038690567, + 0.002704053884372115, + 0.03829525038599968, + -0.021193936467170715, + -0.02937917783856392, + 0.007198629900813103, + 0.012643277645111084, + -0.0358104407787323, + -0.013520268723368645, + 0.0006486075581051409, + 0.0318639874458313, + 0.020901605486869812, + -0.022655587643384933, + 0.011693204753100872, + -0.07015923410654068, + 0.0030146543867886066, + -0.014543424360454082, + -0.0033983378671109676, + -0.020901605486869812, + 0.027479032054543495, + -0.0198784489184618, + 0.03113315999507904, + 0.007417877204716206, + -0.02557888627052307, + -0.03171781823039055, + -0.031425490975379944, + -0.016882065683603287, + -0.05349641293287277, + -0.008916069753468037, + 0.03434878960251808, + -0.004074351396411657, + 0.02221708931028843, + 0.00429359870031476, + 0.03347180038690567, + 0.03727209195494652, + 0.006138933356851339, + -0.030694665387272835, + -0.011327793821692467, + -0.03405646234750748, + -0.02543272264301777, + -0.06080466881394386, + -0.014543424360454082, + 0.015055001713335514, + -0.012570195831358433, + -0.03420262783765793, + 0.025725053623318672, + -0.1432417631149292, + -0.011181628331542015, + 0.03274097666144371, + -0.021778594702482224, + -0.03654126822948456, + -0.033764131367206573, + 0.008112161420285702, + 0.04443417862057686, + 0.013739516027271748, + 0.013374103233218193, + -0.0037454799748957157, + 0.007417877204716206, + 0.001023155520670116, + 0.00738133629783988, + 0.003416608553379774, + 0.013958764262497425, + 0.03771058842539787, + -0.01827063411474228, + 0.03727209195494652, + -0.000799340195953846, + -0.005444648675620556, + -0.04063388705253601, + 0.03566427901387215, + 0.026894373819231987, + 0.04209553822875023, + 0.037125930190086365, + -0.011693204753100872, + 0.05408107489347458, + 0.0070159235037863255, + 0.05963534861803055, + -0.008769904263317585, + -0.00014559410919900984, + -0.006650510709732771, + -0.01739364303648472, + -0.01863604597747326, + -0.0025396179407835007, + 0.024117235094308853, + -0.009793059900403023, + 0.019586119800806046, + 0.025140391662716866, + 0.0011647528735920787, + -0.01717439480125904, + 0.024117235094308853, + -0.02148626372218132, + 0.0008952610078267753, + -0.02543272264301777, + 0.06635893881320953, + -0.012716361321508884, + -0.02791752852499485, + 0.015420414507389069, + 0.010450802743434906, + 0.0358104407787323, + -0.02791752852499485, + -0.05905069038271904, + -0.007965996861457825, + -0.004458034876734018, + -0.013958764262497425, + -0.025725053623318672, + 0.0018818752141669393, + -0.03537194803357124, + -0.029671508818864822, + -0.0017722515622153878, + 0.04706515371799469, + -0.04121854901313782, + -0.013812599703669548, + 0.01468958891928196, + -0.01629740558564663, + 0.02060927450656891, + 0.003982998430728912, + 0.012424030341207981, + -0.026602042838931084, + 0.0038002918008714914, + -0.01702823117375374, + -0.004458034876734018, + 0.017101313918828964, + -0.017978303134441376, + 0.005444648675620556, + -0.019293788820505142, + 0.017101313918828964, + -0.004896529950201511, + -0.014397259801626205, + -0.015201167203485966, + -0.002466535661369562, + 0.021193936467170715, + 0.01739364303648472, + -0.02718670293688774, + -0.03829525038599968, + -0.0065408870577812195, + -0.04648049175739288, + 0.05758903920650482, + 0.007052464410662651, + -0.012277866713702679, + -0.02060927450656891, + -0.03332563862204552, + -0.016955148428678513, + 0.0160781592130661, + 0.06051234155893326, + 0.0012789444299414754, + 0.026017380878329277, + -0.005663896910846233, + 0.003453149925917387, + 0.03917223960161209, + 0.01322793960571289, + 0.022801751270890236, + 0.036248937249183655, + 0.0017539808759465814, + -0.01790522038936615, + 0.03741825744509697, + -0.011985535733401775, + -0.03522578254342079, + 0.022655587643384933, + 0.009646894410252571, + -0.00026149844052270055, + 0.00734479445964098, + 0.01768597401678562, + 0.004604199901223183, + -0.022801751270890236, + 0.009793059900403023, + -0.013520268723368645, + 0.0012698089703917503, + -0.000255788880167529, + 0.014178012497723103, + -0.007417877204716206, + 0.006906299851834774, + -0.02952534519135952, + -0.025725053623318672, + -4.08234482165426e-05, + 0.008550656959414482, + 0.02324024587869644, + -0.007929454557597637, + -0.02791752852499485, + 0.003928186371922493, + 0.007819831371307373, + 0.005079235881567001, + -0.01827063411474228, + -0.020316943526268005, + 0.010231555439531803, + -0.05408107489347458, + 0.0053350250236690044, + 0.014543424360454082, + 0.030694665387272835, + 0.0066870516166090965, + -0.05758903920650482, + 0.026017380878329277, + -0.0009363699937239289, + -0.004074351396411657, + 0.00045904965372756124, + 0.04589582979679108, + -0.04092622175812721, + 0.05729670450091362, + -0.006613969802856445, + 0.03317946940660477, + 0.052327096462249756, + -0.006723593454807997, + 0.05028078332543373, + -0.03478728607296944, + -0.006833217106759548, + -0.020316943526268005, + -0.026748206466436386, + 0.030840829014778137, + -0.04384952411055565, + -0.02382490783929825, + 0.01162012293934822, + -0.03274097666144371, + 0.014104928821325302, + -0.04618816450238228, + -0.04648049175739288, + -0.0053350250236690044, + 0.02864835225045681, + -0.005079235881567001, + -0.03420262783765793, + -0.018343714997172356, + 0.01768597401678562, + -0.02236325666308403, + 0.014981919899582863, + -0.012862526811659336, + -0.022509420290589333, + 0.0014433801406994462, + -0.027625199407339096, + -0.0200246162712574, + 0.029817674309015274, + 0.014251094311475754, + 0.045018840581178665, + 0.006431263405829668, + 0.015712745487689972, + -0.02148626372218132, + -0.053788747638463974, + -0.029671508818864822, + 0.035518109798431396, + 0.026017380878329277, + -0.0037272092886269093, + 0.03361796587705612, + -0.0400492325425148, + 0.007600583601742983, + -0.00869682151824236, + -0.015785828232765198, + 0.0358104407787323, + -0.0021011228673160076, + 0.045018840581178665, + -0.005481190513819456, + 0.018855294212698936, + -0.02645587921142578, + 0.007929454557597637, + 0.011766288429498672, + -0.033033307641744614, + 0.022070925682783127, + -0.012716361321508884, + -0.019293788820505142, + 0.045018840581178665, + -0.029086848720908165, + -0.02879451960325241, + 0.009866142645478249, + -0.02397107146680355, + -0.006723593454807997, + 0.01322793960571289, + -0.0008039079257287085, + 0.003708938602358103, + -0.017320560291409492, + -0.013666434213519096, + 0.026748206466436386, + 0.02704053744673729, + 0.03274097666144371, + 0.019586119800806046, + 0.02557888627052307, + -0.013301021419465542, + -0.017247479408979416, + -0.003654126776382327, + -0.02309408225119114, + 0.015712745487689972, + 0.03171781823039055, + -0.005481190513819456, + -0.04648049175739288, + 0.016370488330721855, + 0.0476498156785965, + 0.014324176125228405, + 0.018489880487322807, + 0.014981919899582863, + -6.651652802247554e-05, + -0.0035810440313071012, + -0.035518109798431396, + -0.06840525567531586, + 0.0038733743131160736, + 0.03098699450492859, + -0.008002537302672863, + 0.0012880796566605568, + -0.004183975048363209, + -0.005444648675620556, + -0.01322793960571289, + 0.02060927450656891, + -0.016882065683603287, + -0.022655587643384933, + 0.00913531705737114, + -0.03741825744509697, + -0.07542117685079575, + -0.04384952411055565, + -0.002905030734837055, + -0.06548195332288742, + -0.04121854901313782, + -0.041803210973739624, + -0.013374103233218193, + 0.0028136775363236666, + -0.013081774115562439, + -0.03771058842539787, + -0.002923301188275218, + -0.0013520269421860576, + -0.001059696776792407, + 0.013447186909615993, + -0.0009363699937239289, + 0.0558350570499897, + 0.01593199372291565, + 0.022801751270890236, + 0.007235170807689428, + -0.050573110580444336, + 0.016882065683603287, + -0.005846602376550436, + 0.015785828232765198, + -0.012716361321508884, + -0.02060927450656891, + 0.01680898293852806, + -0.014251094311475754, + 0.02864835225045681, + 0.016005074605345726, + 0.009354564361274242, + 0.007490959949791431, + 0.026894373819231987, + -0.004220516420900822, + 0.00021125419880263507, + -0.025725053623318672, + 0.036979760974645615, + -0.020170779898762703, + 0.007198629900813103, + -0.0011464821873232722, + 0.02630971185863018, + 0.03478728607296944, + -0.0053350250236690044, + 0.06577427685260773, + 0.004458034876734018, + 0.057004377245903015, + 0.009354564361274242, + -0.03040233440697193, + -0.05525039881467819, + -0.02324024587869644, + 0.0039099156856536865, + -0.014397259801626205, + -0.04881913214921951, + -0.021340100094676018, + 0.00347142037935555, + -0.060220006853342056, + -0.010523884557187557, + 0.027332868427038193, + -0.02470189705491066, + 0.03595660626888275, + -0.0012880796566605568, + -0.012424030341207981, + 0.042680200189352036, + -0.021924762055277824, + -0.024848060682415962, + -0.0015530037926509976, + 0.06928224116563797, + 0.03683359920978546, + 0.020901605486869812, + 0.006760134361684322, + 0.023386409506201744, + 0.05437340587377548, + -0.04384952411055565, + 0.0015164626529440284, + -0.04560350254178047, + -0.03347180038690567, + 0.00025693076895549893, + -0.014981919899582863, + 0.009793059900403023, + 0.02952534519135952, + -0.035518109798431396, + -0.03244864568114281, + -0.0069793821312487125, + -0.03829525038599968, + 0.02630971185863018, + -0.033033307641744614, + -0.03887990862131119, + -0.008367950096726418, + -0.019001459702849388, + -0.01827063411474228, + -0.018855294212698936, + 0.018855294212698936, + 0.005261942278593779, + 0.011693204753100872, + 0.018124468624591827, + 0.02163243107497692, + 0.01739364303648472, + 0.03829525038599968, + 0.029086848720908165, + -0.06197398900985718, + -0.0558350570499897, + -0.003179090330377221, + -0.001598680391907692, + -0.005481190513819456, + 0.00020097698143217713, + -0.014104928821325302, + 0.01154704112559557, + -0.017978303134441376, + 0.017978303134441376, + 0.004202245734632015, + 0.01629740558564663, + 0.018489880487322807, + -0.01322793960571289, + -0.030110003426671028, + 0.028502188622951508, + -0.028063694015145302, + -0.0033617967274039984, + 0.04677281901240349, + 0.006577427964657545, + -0.0035079617518931627, + 0.00025921460473909974, + 0.00416570482775569, + 0.0011373469606041908, + -0.00022952482686378062, + 0.015201167203485966, + -0.021924762055277824, + -0.020463110879063606, + -0.015201167203485966, + 0.032302480190992355, + 0.016735900193452835, + 0.03361796587705612, + -0.033910296857357025, + -0.01154704112559557, + 0.014251094311475754, + -0.014104928821325302, + 0.031571656465530396, + 0.008550656959414482, + 0.0138856815174222, + -0.025725053623318672, + 0.07103621959686279, + 0.01914762519299984, + 0.056127388030290604, + 0.038002919405698776, + 0.04618816450238228, + -0.0030511957593262196, + -0.0027954070828855038, + 0.007198629900813103, + -0.0030511957593262196, + 0.02543272264301777, + -0.017612891271710396, + -0.04531117528676987, + -0.003928186371922493, + 0.0, + 0.0070159235037863255, + -0.036395102739334106, + -0.018709128722548485, + 0.01235094852745533, + -0.04355718940496445, + 0.015639662742614746, + -0.01593199372291565, + 0.04384952411055565, + 0.025140391662716866, + 0.014616507105529308, + -0.022509420290589333, + -0.0160781592130661, + 0.011035462841391563, + -0.006138933356851339, + 0.005188860464841127, + 0.019439954310655594, + 0.004019539803266525, + 0.019001459702849388, + 0.032302480190992355, + -0.009427647106349468, + 0.02309408225119114, + 0.016443569213151932, + 0.04238787293434143, + 0.010450802743434906, + 0.029817674309015274, + -0.04443417862057686, + 0.04618816450238228, + -0.007892914116382599, + 0.006248557008802891, + 0.015128085389733315, + 0.005225401371717453, + 0.004933071322739124, + -0.04706515371799469, + 0.015858909115195274, + -0.04881913214921951, + 0.03478728607296944, + 0.0071620880626142025, + -0.017832139506936073, + 0.008002537302672863, + 0.04940379410982132, + -0.019001459702849388, + -0.022509420290589333, + 0.04531117528676987, + 0.0, + -0.024263402447104454, + -0.04881913214921951, + 0.019439954310655594, + -0.0277713630348444, + 0.005079235881567001, + -0.034641120582818985, + -0.05115777254104614, + -0.037125930190086365, + 0.013739516027271748, + -0.052034761756658554, + 0.018197551369667053, + -0.006029308773577213, + 0.00239345314912498, + -0.03507961705327034, + 0.0015073271933943033, + 0.03361796587705612, + -0.029963839799165726, + -0.052327096462249756, + -0.014543424360454082, + -0.00957381259649992, + 0.009646894410252571, + 0.027332868427038193, + 0.012204783037304878, + -0.018562965095043182, + 0.03683359920978546, + -0.03522578254342079, + 0.06518962234258652, + 0.021047769114375114, + -0.02060927450656891, + 0.00027405950822867453, + -0.01074313186109066, + -0.028209857642650604, + 0.013520268723368645, + 0.0066870516166090965, + 0.07015923410654068, + -0.033910296857357025, + 0.0198784489184618, + 0.03741825744509697, + 0.009719977155327797, + -0.002466535661369562, + 0.011839370243251324, + 0.015712745487689972, + -0.026894373819231987, + -0.019439954310655594, + 0.0200246162712574, + -0.018489880487322807, + -0.0321563184261322, + 0.0099392244592309, + 0.0138856815174222, + 0.012789443135261536, + 0.0005892279441468418, + -0.002722324337810278, + 0.03683359920978546, + 0.006029308773577213, + 0.020901605486869812, + 0.003708938602358103, + -0.005736978724598885, + 0.0180513858795166, + 0.002283829264342785, + 0.020170779898762703, + 0.04648049175739288, + -0.013520268723368645, + -0.0038368331734091043, + 0.009793059900403023, + -0.0198784489184618, + 0.02060927450656891, + -0.04384952411055565, + -0.02382490783929825, + 0.012204783037304878, + 0.0071620880626142025, + 0.008989151567220688, + -0.019439954310655594, + 0.002073716837912798, + 0.0099392244592309, + -0.021193936467170715, + -0.025725053623318672, + 0.010962381027638912, + 0.027625199407339096, + -0.019586119800806046, + 0.008258326910436153, + -0.021193936467170715, + -0.0030329253058880568, + 0.03887990862131119, + -0.029671508818864822, + 0.029086848720908165, + 0.02557888627052307, + 0.01235094852745533, + 0.03507961705327034, + 0.019293788820505142, + 0.026602042838931084, + -0.02382490783929825, + -0.02163243107497692, + 0.038002919405698776, + 0.0022016114089637995, + -0.021047769114375114, + -0.00347142037935555, + -0.044141851365566254, + 0.0037454799748957157, + -0.003233902156352997, + 0.03025617077946663, + 0.01476267259567976, + -0.007819831371307373, + -0.010085389949381351, + -0.013812599703669548, + -0.021778594702482224, + 0.00367239722982049, + -0.01768597401678562, + -0.0022107467520982027, + 0.049111466854810715, + -0.013447186909615993, + 0.030840829014778137, + 0.02075544185936451, + 0.017320560291409492, + -0.035518109798431396, + -0.0138856815174222, + 0.012497114017605782, + -0.017466725781559944, + 0.011400875635445118, + 0.033910296857357025, + -0.0200246162712574, + 0.007052464410662651, + -0.008769904263317585, + -0.016224322840571404, + -0.014616507105529308, + 0.024994228035211563, + 0.014397259801626205, + -0.01914762519299984, + 0.03537194803357124, + 0.02309408225119114, + 0.006869758013635874, + -0.013008690439164639, + -0.04706515371799469, + -0.0180513858795166, + -0.030694665387272835, + 0.007856372743844986, + -0.008185244165360928, + 0.004677282180637121, + -0.03449495881795883, + 0.026602042838931084, + -0.024555733427405357, + 0.023386409506201744, + 0.007673666346818209, + 0.017466725781559944, + -0.011181628331542015, + -0.010962381027638912, + -0.05261942371726036, + -0.013812599703669548, + 0.006906299851834774, + 0.013008690439164639, + -0.0014890567399561405, + 0.0099392244592309, + -0.010085389949381351, + -0.039464570581912994, + 0.020316943526268005, + -0.013812599703669548, + -0.015055001713335514, + -0.004037810023874044, + -0.02163243107497692, + 0.015055001713335514, + -0.007564042694866657, + 0.013374103233218193, + -0.02470189705491066, + -0.016735900193452835, + -0.0397568978369236, + -0.021340100094676018, + 0.015128085389733315, + -0.03317946940660477, + 0.004531117156147957, + 0.006102391518652439, + -0.015201167203485966, + -0.038002919405698776, + -0.045018840581178665, + -0.036979760974645615, + -0.020170779898762703, + -0.049696121364831924, + 0.033910296857357025, + -0.03098699450492859, + 0.021047769114375114, + -0.005554272327572107, + -0.03259481117129326, + 0.02236325666308403, + -0.0044763050973415375, + 0.002704053884372115, + 0.000392818619729951, + 0.016224322840571404, + 0.034641120582818985, + -0.0318639874458313, + 0.0014799212804064155, + 0.002905030734837055, + 0.008148702792823315, + -0.03317946940660477, + -0.0026675125118345022, + -0.011327793821692467, + -0.02543272264301777, + -0.00500615406781435, + 0.0014433801406994462, + -0.038002919405698776, + -0.02294791489839554, + -0.0030146543867886066, + -0.019732285290956497, + 0.017612891271710396, + -0.06314331293106079, + 0.025286555290222168, + -0.0037272092886269093, + 0.010596968233585358, + 0.0400492325425148, + 0.040341559797525406, + -0.010962381027638912, + 0.013008690439164639, + -0.027332868427038193, + -0.01739364303648472, + 0.014397259801626205, + 0.011693204753100872, + -0.04618816450238228, + 0.015347332693636417, + 0.04531117528676987, + -0.029671508818864822, + 0.008806445635855198, + -0.0001998350489884615, + -0.015274249948561192, + 0.010889297351241112, + -0.0099392244592309, + 0.037125930190086365, + 0.01154704112559557, + -0.010304637253284454, + 0.021778594702482224, + 0.0021011228673160076, + 0.007819831371307373, + 0.013666434213519096, + 0.01790522038936615, + 0.00913531705737114, + -0.03098699450492859, + -0.033033307641744614, + -0.024555733427405357, + -0.009244940243661404, + 0.011108544655144215, + 0.03668742999434471, + -0.006431263405829668, + 0.0071620880626142025, + 0.03040233440697193, + 0.02718670293688774, + -0.03478728607296944, + 0.05641971528530121, + -0.00884298700839281, + -0.03025617077946663, + -0.013739516027271748, + -0.02397107146680355, + 0.028502188622951508, + 0.03771058842539787, + -0.00957381259649992, + 0.008879527449607849, + -0.0397568978369236, + -0.0035445031244307756, + 0.015858909115195274, + 0.01753980852663517, + -0.004458034876734018, + -0.01702823117375374, + 0.012131701223552227, + -0.020170779898762703, + -0.011327793821692467, + 0.0071620880626142025, + 0.000922667037229985, + -0.03887990862131119, + -0.024848060682415962, + 0.045018840581178665, + 0.0012149971444159746, + 0.012277866713702679, + 0.02543272264301777, + -0.038002919405698776, + 0.0397568978369236, + -0.010523884557187557, + -0.006833217106759548, + -0.017320560291409492, + 0.052327096462249756, + -0.02543272264301777, + -0.008112161420285702, + 0.03405646234750748, + -0.0020554461516439915, + 0.02148626372218132, + 0.007052464410662651, + -0.01322793960571289, + 0.015128085389733315, + -0.025871217250823975, + -0.0048599885776638985, + -0.03361796587705612, + 0.015858909115195274, + -0.009427647106349468, + -0.00184533407445997, + -0.012424030341207981, + -0.040341559797525406, + -0.01593199372291565, + -0.007052464410662651, + -0.03668742999434471, + -0.009244940243661404, + -0.022655587643384933, + 3.368648322066292e-05, + 0.032302480190992355, + 0.002320370636880398, + -0.007271712180227041, + 0.01081621553748846, + 0.06051234155893326, + -0.004622470121830702, + 0.019001459702849388, + -0.02704053744673729, + 0.03887990862131119, + -0.005992767866700888, + -0.017612891271710396, + 0.0055908141657710075, + 0.009354564361274242, + 0.016516653820872307, + -0.021047769114375114, + -0.0060658506117761135, + 0.020170779898762703, + 0.007600583601742983, + -0.01629740558564663, + -0.019586119800806046, + -0.029086848720908165, + -0.007235170807689428, + 0.031571656465530396, + 0.018562965095043182, + -0.02645587921142578, + 0.022070925682783127, + 0.025871217250823975, + 0.004092622082680464, + -0.013666434213519096, + -0.022070925682783127, + -0.008441032841801643, + -0.03405646234750748, + 0.024555733427405357, + -0.0012149971444159746, + -0.029086848720908165, + -0.019293788820505142, + 0.029817674309015274, + 0.03420262783765793, + 0.04297252744436264, + 0.05729670450091362, + 0.014178012497723103, + -0.00957381259649992, + 0.012935608625411987, + -0.020170779898762703, + -0.00957381259649992, + -0.028356024995446205, + 0.023532576858997345, + -0.05437340587377548, + -0.0361027717590332, + 0.04092622175812721, + 0.014908837154507637, + 0.05729670450091362, + 0.007819831371307373, + 0.03171781823039055, + 0.03507961705327034, + -0.03259481117129326, + -0.054665736854076385, + -0.02645587921142578, + -0.06489729136228561, + 0.007637124974280596, + -0.026017380878329277, + 0.025725053623318672, + 0.00013360401499085128, + 0.0070159235037863255, + -0.021924762055277824, + 0.02060927450656891, + -0.017320560291409492, + 0.017759054899215698, + 0.014908837154507637, + -0.021778594702482224, + 0.021924762055277824, + -0.0010140201775357127, + -0.05992767959833145, + 0.01702823117375374, + -0.009244940243661404, + -0.018124468624591827, + -0.005554272327572107, + -0.010304637253284454, + -0.045018840581178665, + 0.008112161420285702, + -0.01914762519299984, + -0.015493497252464294, + 0.027479032054543495, + -0.016589734703302383, + -0.018343714997172356, + 0.004311869852244854, + 0.016735900193452835, + -0.0637279748916626, + -0.006321638822555542, + -0.00931802298873663, + -0.00107339967507869, + 0.03127932548522949, + -0.022070925682783127, + 0.018489880487322807, + 0.010596968233585358, + 0.004622470121830702, + 0.0037820213474333286, + 0.017247479408979416, + -0.016370488330721855, + -0.0018179280450567603, + -0.041803210973739624, + 0.005736978724598885, + -0.015712745487689972, + -0.01315485592931509, + -0.004056080710142851, + 0.004074351396411657, + -0.020170779898762703, + 0.033910296857357025, + 0.013812599703669548, + 0.023386409506201744, + -0.028502188622951508, + -0.011327793821692467, + 0.010596968233585358, + -0.013812599703669548, + -0.01753980852663517, + -0.012277866713702679, + -0.018489880487322807, + 0.008733362890779972, + 0.03829525038599968, + 0.0277713630348444, + 0.03347180038690567, + 0.02543272264301777, + -0.011327793821692467, + -0.0397568978369236, + -0.0017722515622153878, + 0.013447186909615993, + -0.018489880487322807, + -0.004622470121830702, + 0.024848060682415962, + -0.0008450167952105403, + -0.030110003426671028, + -0.00416570482775569, + 0.036248937249183655, + 0.03917223960161209, + -0.022070925682783127, + -0.007235170807689428, + -0.02791752852499485, + -0.03771058842539787, + -0.039464570581912994, + -0.004713823553174734, + 0.049111466854810715, + -0.05671204999089241, + 0.002183340722694993, + 0.030840829014778137, + -0.026894373819231987, + 0.0358104407787323, + 0.020463110879063606, + 0.022509420290589333, + -0.0025578888598829508, + 0.021778594702482224, + 0.0318639874458313, + 0.019001459702849388, + -0.002082852413877845, + 0.017832139506936073, + 0.00840449146926403, + 0.016151240095496178, + -0.016589734703302383, + 0.03127932548522949, + 0.04092622175812721, + 0.02952534519135952, + -0.002110258210450411, + 0.002521347487345338, + 0.0024299942888319492, + 0.014031847007572651, + 0.027479032054543495, + -0.017759054899215698, + 0.007637124974280596, + 0.013374103233218193, + -0.018489880487322807, + 0.030110003426671028, + -0.02148626372218132, + -0.00456765852868557, + -0.0023295057471841574, + 0.004074351396411657, + -0.022070925682783127, + 0.009427647106349468, + -0.04355718940496445, + 0.01753980852663517, + -0.006723593454807997, + 0.021340100094676018, + 0.00013360401499085128, + -0.05408107489347458, + 0.04209553822875023, + 0.027332868427038193, + 0.008952610194683075, + -0.026748206466436386, + 0.0030146543867886066, + -0.03361796587705612, + 0.008221784606575966, + 0.014104928821325302, + -0.025286555290222168, + 0.005773520562797785, + -0.0100123081356287, + -0.027332868427038193, + -0.006102391518652439, + -0.012570195831358433, + 0.004531117156147957, + -0.048234470188617706, + 0.014908837154507637, + 0.04326486214995384, + 0.005700437817722559, + 0.019586119800806046, + -0.010523884557187557, + -0.00858719740062952, + -0.01154704112559557, + -0.010962381027638912, + 0.036979760974645615, + -0.008623739704489708, + -0.006029308773577213, + 0.012935608625411987, + -0.006613969802856445, + 0.027625199407339096, + -0.025725053623318672, + 0.0003517096920404583, + -0.04092622175812721, + -0.0019640931859612465, + -0.032302480190992355, + -0.012497114017605782, + -0.0030511957593262196, + -0.0397568978369236, + -0.02864835225045681, + 0.015639662742614746, + 0.013812599703669548, + -0.00913531705737114, + -0.04384952411055565, + -0.0039099156856536865, + 0.0033617967274039984, + 0.0160781592130661, + -0.008879527449607849, + -0.015201167203485966, + 0.012424030341207981, + -0.013739516027271748, + 0.0, + 0.06869757920503616, + -0.01468958891928196, + -0.010377720929682255, + -0.014324176125228405, + -0.02163243107497692, + -0.028940683230757713, + -0.0001998350489884615, + -0.0026309711392968893, + -0.02324024587869644, + -0.010523884557187557, + -0.014908837154507637, + 0.0022655585780739784, + -0.029671508818864822, + -0.00869682151824236, + 0.0036358560901135206, + -0.017247479408979416, + -0.015493497252464294, + 0.01235094852745533, + -0.0024299942888319492, + -0.014397259801626205, + 0.017759054899215698, + -0.038587577641010284, + 0.011912453919649124, + 0.022509420290589333, + -0.004019539803266525, + 0.017247479408979416, + 0.0318639874458313, + -0.0160781592130661, + -0.02718670293688774, + 0.017101313918828964, + -0.01702823117375374, + -0.006285097915679216, + -0.02557888627052307, + 0.005956226959824562, + 0.014470341615378857, + 0.002758865710347891, + -0.005883144214749336, + -0.0021011228673160076, + 0.039464570581912994, + -0.01476267259567976, + 0.003416608553379774, + -0.04326486214995384, + 0.0006074985722079873, + 0.0358104407787323, + 0.007271712180227041, + 0.015493497252464294, + -0.03405646234750748, + -0.011181628331542015, + -0.03361796587705612, + -0.0025944302324205637, + 0.009719977155327797, + 0.03244864568114281, + -3.554209251888096e-05, + 0.0008039079257287085, + 0.054665736854076385, + 0.000392818619729951, + 0.03771058842539787, + 0.007673666346818209, + 0.0053350250236690044, + -0.013666434213519096, + -0.016005074605345726, + -0.03201014921069145, + 0.03829525038599968, + 0.0033617967274039984, + 0.006796675734221935, + -0.001708304276689887, + 0.03434878960251808, + 0.011108544655144215, + 0.01556657999753952, + 0.006504345219582319, + 0.014616507105529308, + -0.03434878960251808, + -0.03434878960251808, + -0.025286555290222168, + 0.029817674309015274, + -0.0008541521383449435, + 3.325826401123777e-05, + -0.0011784558882936835, + -0.00803907960653305, + -0.010085389949381351, + -0.027625199407339096, + -0.012058617547154427, + -0.005152318626642227, + -0.04384952411055565, + 0.0038002918008714914, + 0.020316943526268005, + 0.015858909115195274, + -0.0003836833348032087, + 0.01593199372291565, + 0.03917223960161209, + -0.01235094852745533, + 0.014251094311475754, + 0.0361027717590332, + 0.0030694666784256697, + -0.01717439480125904, + 0.012058617547154427, + 0.010377720929682255, + -0.03347180038690567 + ], + "qwen3-embed-document-native": [ + -0.00026054048794321716, + -0.029180536046624184, + -0.005818737670779228, + -0.0024751347955316305, + -0.0008901800028979778, + 0.07040382921695709, + 0.008221500553190708, + 0.03844419866800308, + 0.024317113682627678, + -0.012911229394376278, + 0.005210809875279665, + -0.02454870380461216, + -0.0005500299157574773, + -0.03265440836548805, + -0.040528520941734314, + -0.04909740760922432, + -0.0345071405172348, + -0.012737535871565342, + -0.027327803894877434, + -0.004226545803248882, + -0.01899050734937191, + 0.021422218531370163, + 0.02813837304711342, + 0.042381253093481064, + -0.042844437062740326, + 0.01910630241036415, + -0.022348584607243538, + -0.014648165553808212, + 0.03566509857773781, + 0.0042554945684969425, + 0.0013606003485620022, + -0.037286240607500076, + 0.005645044147968292, + -0.013142820447683334, + 0.004400239326059818, + -0.02269597165286541, + 0.013490208424627781, + -0.0038791585247963667, + -0.029064739122986794, + 0.03381236642599106, + 0.0016573270549997687, + 0.006918797735124826, + 0.008858377113938332, + 0.003488347865641117, + 0.0, + 0.009032070636749268, + 0.0011869067093357444, + -0.061603352427482605, + -0.0345071405172348, + -0.010016334243118763, + 0.0032857051119208336, + 0.021885402500629425, + 0.03566509857773781, + -0.02489609271287918, + 0.023622337728738785, + -0.007874112576246262, + 0.018179936334490776, + 0.0022869666572660208, + -0.0017514111241325736, + -0.024085521697998047, + -0.0015849546762183309, + 0.007758317049592733, + -0.00025330326752737164, + -0.02813837304711342, + 0.0020119515247642994, + -0.018064141273498535, + -0.0023738134186714888, + -0.01244804635643959, + 0.005905584432184696, + -8.775149035500363e-05, + 0.04701308533549309, + 0.002808047691360116, + 0.004776575602591038, + 0.018295733258128166, + 0.005876635666936636, + -0.040528520941734314, + -0.009437356144189835, + 0.03473873436450958, + -0.003850209526717663, + 0.013084922917187214, + 0.011926964856684208, + 0.002185645280405879, + -0.009495253674685955, + 0.01899050734937191, + 0.011926964856684208, + -0.0017586483154445887, + 0.026980416849255562, + 0.007237236015498638, + -0.016095612198114395, + -0.004776575602591038, + -0.007208287250250578, + 0.027906782925128937, + 0.031496450304985046, + 0.0055871461518108845, + -0.0008720869664102793, + 0.009147866629064083, + -0.01505345106124878, + -0.019916873425245285, + -0.0016428525559604168, + -0.002258017659187317, + 0.02095903642475605, + 0.003690990386530757, + -0.001454684417694807, + -0.009958436712622643, + -0.011058496311306953, + -0.038907382637262344, + -0.014416574500501156, + 0.017832549288868904, + 0.007034593261778355, + 0.015979817137122154, + 0.006310869939625263, + 0.014184982515871525, + 0.02489609271287918, + 0.008221500553190708, + -0.014937655068933964, + -0.007584623526781797, + -0.0047476268373429775, + 0.04214966297149658, + 0.012679637409746647, + 0.05743470415472984, + -0.0026054049376398325, + 0.01505345106124878, + -0.00143297272734344, + -0.016674591228365898, + 0.009784743189811707, + -0.04029693081974983, + 0.0006585884839296341, + -0.006716154981404543, + 0.025359274819493294, + 0.022116992622613907, + 0.020380057394504547, + -0.015864022076129913, + 0.013953391462564468, + -0.004979218356311321, + -0.010074232704937458, + 0.0025185581762343645, + 0.0068319509737193584, + -0.016558796167373657, + -0.025590866804122925, + 0.016327204182744026, + -0.020148465409874916, + -0.012795433402061462, + 0.007989908568561077, + -0.02454870380461216, + -0.0172535702586174, + -0.034970324486494064, + 0.0012954652775079012, + -0.019801078364253044, + -0.0047476268373429775, + -0.0004523272509686649, + 0.009553151205182076, + 0.013432309962809086, + 0.021306423470377922, + 0.004429188556969166, + 0.014474472030997276, + 0.03057008422911167, + -0.007874112576246262, + 0.01430077850818634, + 0.00752672553062439, + 0.0016211408656090498, + 0.001483633415773511, + 0.005645044147968292, + 0.038675788789987564, + 0.012100658379495144, + -0.014011288993060589, + 0.007874112576246262, + -0.020148465409874916, + 0.029875310137867928, + -0.008395194076001644, + -0.0026198795530945063, + -0.012795433402061462, + -0.007121440023183823, + 0.006426665466278791, + -0.012621739879250526, + 0.004023903049528599, + 0.007642521057277918, + -0.016211409121751785, + 0.012737535871565342, + -0.011000598780810833, + 0.02813837304711342, + -0.0059924316592514515, + -0.011926964856684208, + -0.04029693081974983, + -0.014242880046367645, + 0.0054134526289999485, + 0.02628564089536667, + 0.003662041388452053, + -0.012911229394376278, + 0.01221645437180996, + -0.004776575602591038, + -0.010711109265685081, + 0.011232190765440464, + -0.010247926227748394, + 0.016211409121751785, + -0.015169247053563595, + 0.009668947197496891, + -0.019685281440615654, + -0.001411261036992073, + 0.011290088295936584, + 0.007468827534466982, + 0.010711109265685081, + 0.0062529719434678555, + -0.012158556841313839, + -0.025243479758501053, + 0.027790986001491547, + -0.01899050734937191, + 0.008510989136993885, + -0.0014402099186554551, + 0.014821859076619148, + -0.01887471228837967, + 0.005876635666936636, + -0.022116992622613907, + -0.015285042114555836, + -0.029180536046624184, + 0.007468827534466982, + 0.02883314900100231, + -0.01679038815200329, + -0.016558796167373657, + 0.035201914608478546, + 0.016095612198114395, + -0.008395194076001644, + -0.0045739333145320415, + -0.01922209933400154, + 0.01036372222006321, + -0.028948944061994553, + 0.0010493992595002055, + -0.005529248155653477, + -0.003575194627046585, + 0.0, + -0.017948346212506294, + 0.020264260470867157, + 0.005529248155653477, + 0.00715038925409317, + 0.0026054049376398325, + -0.01331651397049427, + -0.010942701250314713, + -0.02107483148574829, + 0.0059924316592514515, + 0.016211409121751785, + -0.025822458788752556, + -0.022116992622613907, + -0.014127084985375404, + -0.002200119663029909, + 0.023506542667746544, + 0.017485162243247032, + -0.02258017659187317, + -0.0024461857974529266, + -0.008047806099057198, + -0.007121440023183823, + -0.00610822718590498, + -0.011753271333873272, + 0.0034015008714050055, + 0.0009770268807187676, + -0.01679038815200329, + -0.023853929713368416, + -0.007758317049592733, + -0.018179936334490776, + -0.02813837304711342, + 0.003575194627046585, + 0.009726845659315586, + -0.013374412432312965, + -0.007295134011656046, + -0.002779098693281412, + -0.003821260528638959, + -0.01736936718225479, + -0.016095612198114395, + 0.006368767935782671, + 0.002388287801295519, + 0.00018364486459176987, + -0.03057008422911167, + 0.07874112576246262, + -0.016674591228365898, + 0.012042760848999023, + 0.0032133327331393957, + 0.027790986001491547, + -0.014821859076619148, + -0.010132130235433578, + 0.0032857051119208336, + 0.012737535871565342, + 0.015516634099185467, + -0.007642521057277918, + 0.0009227475966326892, + 0.009205764159560204, + -0.013663901947438717, + 0.01320071890950203, + 0.012332250364124775, + 0.0456235371530056, + -0.016558796167373657, + -0.007874112576246262, + 0.015979817137122154, + 0.015632430091500282, + -0.029412128031253815, + 0.012390147894620895, + -0.007353032007813454, + -0.006310869939625263, + -0.01430077850818634, + 0.02107483148574829, + -0.011579577811062336, + 0.005152911879122257, + 0.0018165461951866746, + -0.014184982515871525, + -0.006860899738967419, + -0.004429188556969166, + 0.046318311244249344, + 0.016327204182744026, + 0.0019106302643194795, + 0.00961104966700077, + 0.007932011038064957, + -0.019337894394993782, + -0.002214594278484583, + -0.00428444379940629, + -0.007932011038064957, + -0.0004505179531406611, + -0.002113272901624441, + 0.003618618007749319, + -0.0016573270549997687, + 0.018295733258128166, + -0.008858377113938332, + -0.005471350625157356, + -0.022116992622613907, + -0.0009480779408477247, + -0.024317113682627678, + 0.0382126048207283, + -0.007468827534466982, + 0.034970324486494064, + 0.020148465409874916, + 0.025590866804122925, + 0.011521679349243641, + 0.03057008422911167, + 0.026169845834374428, + -0.007005644496530294, + 0.02640143781900406, + -0.06114016845822334, + -0.024085521697998047, + -0.007758317049592733, + -0.003618618007749319, + 0.02072744444012642, + 0.0493290014564991, + 0.00961104966700077, + -0.000738198054023087, + -0.009263661690056324, + -0.008221500553190708, + -0.037054646760225296, + 0.03219122439622879, + -0.05488719791173935, + -0.012853330932557583, + -0.020148465409874916, + 0.0068319509737193584, + -0.009958436712622643, + -0.005384503398090601, + -0.028022577986121178, + -0.025127682834863663, + -0.0801306739449501, + -0.021769605576992035, + 0.04006533697247505, + 0.020148465409874916, + -0.009032070636749268, + 0.014590268023312092, + 0.018643120303750038, + 0.007468827534466982, + -0.008916274644434452, + -0.0032857051119208336, + -0.002156696282327175, + 0.012911229394376278, + 0.008742581121623516, + -0.019685281440615654, + -0.026980416849255562, + 0.026169845834374428, + 0.031264860183000565, + -0.025590866804122925, + 0.021538013592362404, + -0.03172804415225983, + 0.0382126048207283, + -0.02466450072824955, + -0.02478029578924179, + -0.007816215045750141, + 0.02084323950111866, + -0.014648165553808212, + -0.01702197827398777, + 0.011695373803377151, + 0.029527923092246056, + 0.033349182456731796, + -0.015979817137122154, + 0.014763961546123028, + -0.020380057394504547, + 0.010826905257999897, + -0.031264860183000565, + -0.010016334243118763, + -0.011926964856684208, + 0.0055002993904054165, + 0.01899050734937191, + 0.01875891536474228, + 0.011290088295936584, + -0.009437356144189835, + 0.019453691318631172, + -0.014821859076619148, + 0.016443001106381416, + 0.01713777519762516, + 0.04516035318374634, + -0.01713777519762516, + -0.01702197827398777, + 0.002142221899703145, + -0.052339691668748856, + -0.01146378181874752, + 0.011521679349243641, + -0.014937655068933964, + 0.0015198196051642299, + 0.020032668486237526, + 0.013548105955123901, + -0.005268707871437073, + 0.03589669242501259, + -0.0228117685765028, + 0.020264260470867157, + -0.05071854963898659, + -0.0265172328799963, + 0.002142221899703145, + 0.03033849410712719, + -0.01679038815200329, + -0.023274950683116913, + 0.0057029421441257, + 0.010016334243118763, + -0.02836996503174305, + -0.010711109265685081, + 0.0005138437845744193, + 0.025243479758501053, + 0.016558796167373657, + -0.017948346212506294, + 0.009263661690056324, + -0.05558197200298309, + 0.002388287801295519, + -0.011521679349243641, + -0.0026922516990453005, + -0.016558796167373657, + 0.021769605576992035, + -0.015748225152492523, + 0.02466450072824955, + 0.005876635666936636, + -0.020264260470867157, + -0.025127682834863663, + -0.02489609271287918, + -0.013374412432312965, + -0.042381253093481064, + -0.007063542492687702, + 0.027212006971240044, + -0.0032278073485940695, + 0.017600957304239273, + 0.0034015008714050055, + 0.0265172328799963, + 0.029527923092246056, + 0.004863422829657793, + -0.024317113682627678, + -0.008974173106253147, + -0.026980416849255562, + -0.020148465409874916, + -0.048171043395996094, + -0.011521679349243641, + 0.011926964856684208, + -0.009958436712622643, + -0.027096211910247803, + 0.020380057394504547, + -0.1134798601269722, + -0.008858377113938332, + 0.025938253849744797, + -0.0172535702586174, + -0.028948944061994553, + -0.02674882486462593, + 0.006426665466278791, + 0.035201914608478546, + 0.010884802788496017, + 0.010595313273370266, + -0.002967266831547022, + 0.005876635666936636, + 0.0008105704328045249, + 0.005847686901688576, + 0.0027067263144999743, + 0.011058496311306953, + 0.029875310137867928, + -0.014474472030997276, + 0.029527923092246056, + -0.0006332581397145987, + -0.00431339256465435, + -0.03219122439622879, + 0.02825416997075081, + 0.021306423470377922, + 0.033349182456731796, + 0.029412128031253815, + -0.009263661690056324, + 0.042844437062740326, + 0.005558197386562824, + 0.04724467545747757, + -0.006947746500372887, + -0.00011534344957908615, + -0.005268707871437073, + -0.013779697008430958, + -0.014763961546123028, + -0.0020119515247642994, + 0.01910630241036415, + -0.007758317049592733, + 0.015516634099185467, + 0.019916873425245285, + 0.0009227475966326892, + -0.013606003485620022, + 0.01910630241036415, + -0.01702197827398777, + 0.0007092491141520441, + -0.020148465409874916, + 0.05257128179073334, + -0.010074232704937458, + -0.022116992622613907, + 0.01221645437180996, + 0.008279398083686829, + 0.02836996503174305, + -0.022116992622613907, + -0.046781495213508606, + -0.006310869939625263, + -0.003531771246343851, + -0.011058496311306953, + -0.020380057394504547, + 0.001490870607085526, + -0.028022577986121178, + -0.023506542667746544, + -0.001404023845680058, + 0.037286240607500076, + -0.03265440836548805, + -0.010942701250314713, + 0.011637475341558456, + -0.012911229394376278, + 0.016327204182744026, + 0.0031554349698126316, + 0.009842640720307827, + -0.02107483148574829, + 0.003010690212249756, + -0.013490208424627781, + -0.003531771246343851, + 0.013548105955123901, + -0.014242880046367645, + 0.00431339256465435, + -0.015285042114555836, + 0.013548105955123901, + -0.0038791585247963667, + -0.0114058842882514, + -0.012042760848999023, + -0.0019540537614375353, + 0.01679038815200329, + 0.013779697008430958, + -0.021538013592362404, + -0.03033849410712719, + -0.005181861110031605, + -0.036823056638240814, + 0.0456235371530056, + 0.0055871461518108845, + -0.009726845659315586, + -0.016327204182744026, + -0.02640143781900406, + -0.013432309962809086, + 0.012737535871565342, + 0.04793945327401161, + 0.0010132130701094866, + 0.02061164751648903, + -0.0044870865531265736, + 0.002735675312578678, + 0.031033268198370934, + 0.010479518212378025, + 0.018064141273498535, + 0.02871735207736492, + 0.001389549346640706, + -0.014184982515871525, + 0.029643718153238297, + -0.009495253674685955, + -0.027906782925128937, + 0.017948346212506294, + 0.007642521057277918, + -0.0002071658818749711, + 0.005818737670779228, + 0.014011288993060589, + 0.003647567005828023, + -0.018064141273498535, + 0.007758317049592733, + -0.010711109265685081, + 0.0010059757623821497, + -0.00020264260820113122, + 0.011232190765440464, + -0.005876635666936636, + 0.005471350625157356, + -0.023390747606754303, + -0.020380057394504547, + -3.2341398764401674e-05, + 0.006774052977561951, + 0.018411528319120407, + -0.006281920708715916, + -0.022116992622613907, + 0.0031120115891098976, + 0.006195073947310448, + 0.004023903049528599, + -0.014474472030997276, + -0.016095612198114395, + 0.008105704560875893, + -0.042844437062740326, + 0.004226545803248882, + 0.011521679349243641, + 0.024317113682627678, + 0.005297656636685133, + -0.0456235371530056, + 0.02061164751648903, + -0.0007418167078867555, + -0.0032278073485940695, + 0.00036367110442370176, + 0.03635987266898155, + -0.03242281824350357, + 0.04539194330573082, + -0.0052397591061890125, + 0.02628564089536667, + 0.04145488888025284, + -0.0053266058675944805, + 0.03983374685049057, + -0.027559394016861916, + -0.0054134526289999485, + -0.016095612198114395, + -0.021190626546740532, + 0.02443290874361992, + -0.03473873436450958, + -0.01887471228837967, + 0.009205764159560204, + -0.025938253849744797, + 0.011174292303621769, + -0.03659146651625633, + -0.036823056638240814, + -0.004226545803248882, + 0.02269597165286541, + -0.004023903049528599, + -0.027096211910247803, + -0.014532369561493397, + 0.014011288993060589, + -0.017716754227876663, + 0.011869067326188087, + -0.010190028697252274, + -0.017832549288868904, + 0.0011434833286330104, + -0.021885402500629425, + -0.015864022076129913, + 0.023622337728738785, + 0.011290088295936584, + 0.03566509857773781, + 0.005095014348626137, + 0.01244804635643959, + -0.01702197827398777, + -0.042612846940755844, + -0.023506542667746544, + 0.02813837304711342, + 0.02061164751648903, + -0.002952792216092348, + 0.02663302794098854, + -0.03172804415225983, + 0.006021380424499512, + -0.006889848504215479, + -0.012505943886935711, + 0.02836996503174305, + -0.0016645642463117838, + 0.03566509857773781, + -0.004342341795563698, + 0.014937655068933964, + -0.02095903642475605, + 0.006281920708715916, + 0.009321560151875019, + -0.026169845834374428, + 0.017485162243247032, + -0.010074232704937458, + -0.015285042114555836, + 0.03566509857773781, + -0.023043358698487282, + -0.0228117685765028, + 0.007816215045750141, + -0.01899050734937191, + -0.0053266058675944805, + 0.010479518212378025, + -0.0006368767935782671, + 0.002938317833468318, + -0.013721799477934837, + -0.010826905257999897, + 0.021190626546740532, + 0.021422218531370163, + 0.025938253849744797, + 0.015516634099185467, + 0.020264260470867157, + -0.010537415742874146, + -0.013663901947438717, + -0.002894894452765584, + -0.018295733258128166, + 0.01244804635643959, + 0.025127682834863663, + -0.004342341795563698, + -0.036823056638240814, + 0.012969126924872398, + 0.03774942457675934, + 0.011347985826432705, + 0.014648165553808212, + 0.011869067326188087, + -5.269612665870227e-05, + -0.0028369964566081762, + -0.02813837304711342, + -0.054192423820495605, + 0.00306858797557652, + 0.02454870380461216, + -0.0063398187048733234, + 0.0010204502614215016, + -0.0033146541099995375, + -0.00431339256465435, + -0.010479518212378025, + 0.016327204182744026, + -0.013374412432312965, + -0.017948346212506294, + 0.007237236015498638, + -0.029643718153238297, + -0.059750620275735855, + -0.03473873436450958, + -0.002301441039890051, + -0.051876507699489594, + -0.03265440836548805, + -0.033117592334747314, + -0.010595313273370266, + 0.002229068661108613, + -0.01036372222006321, + -0.029875310137867928, + -0.002315915422514081, + -0.0010711109498515725, + -0.0008395193726755679, + 0.010653211735188961, + -0.0007418167078867555, + 0.044233985245227814, + 0.012621739879250526, + 0.018064141273498535, + 0.00573189090937376, + -0.04006533697247505, + 0.013374412432312965, + -0.004631830845028162, + 0.012505943886935711, + -0.010074232704937458, + -0.016327204182744026, + 0.01331651397049427, + -0.011290088295936584, + 0.02269597165286541, + 0.012679637409746647, + 0.007410929538309574, + 0.005934533663094044, + 0.021306423470377922, + -0.0033436031080782413, + 0.0001673610822763294, + -0.020380057394504547, + 0.029296331107616425, + -0.015979817137122154, + 0.0057029421441257, + -0.0009082730975933373, + 0.02084323950111866, + 0.027559394016861916, + -0.004226545803248882, + 0.052108097821474075, + 0.003531771246343851, + 0.04516035318374634, + 0.007410929538309574, + -0.024085521697998047, + -0.04377080500125885, + -0.018411528319120407, + 0.003097536973655224, + -0.0114058842882514, + -0.038675788789987564, + -0.01690618321299553, + 0.0027501496952027082, + -0.04770785942673683, + -0.008337295614182949, + 0.021653810515999794, + -0.019569486379623413, + 0.02848576009273529, + -0.0010204502614215016, + -0.009842640720307827, + 0.03381236642599106, + -0.01736936718225479, + -0.019685281440615654, + -0.0012303300900384784, + 0.05488719791173935, + 0.029180536046624184, + 0.016558796167373657, + 0.005355554632842541, + 0.018527323380112648, + 0.04307602718472481, + -0.03473873436450958, + 0.0012013812083750963, + -0.03612828254699707, + -0.0265172328799963, + 0.0002035472571151331, + -0.011869067326188087, + 0.007758317049592733, + 0.023390747606754303, + -0.02813837304711342, + -0.025706661865115166, + -0.005529248155653477, + -0.03033849410712719, + 0.02084323950111866, + -0.026169845834374428, + -0.030801676213741302, + -0.006629308219999075, + -0.01505345106124878, + -0.014474472030997276, + -0.014937655068933964, + 0.014937655068933964, + 0.0041686478070914745, + 0.009263661690056324, + 0.01435867603868246, + 0.01713777519762516, + 0.013779697008430958, + 0.03033849410712719, + 0.023043358698487282, + -0.04909740760922432, + -0.044233985245227814, + -0.0025185581762343645, + -0.0012665162794291973, + -0.004342341795563698, + 0.00015921919839456677, + -0.011174292303621769, + 0.009147866629064083, + -0.014242880046367645, + 0.014242880046367645, + 0.0033291284926235676, + 0.012911229394376278, + 0.014648165553808212, + -0.010479518212378025, + -0.023853929713368416, + 0.02258017659187317, + -0.022232789546251297, + -0.0026633029337972403, + 0.037054646760225296, + 0.005210809875279665, + -0.002779098693281412, + 0.0002053565694950521, + 0.0033001797273755074, + 0.0009010359062813222, + -0.00018183555221185088, + 0.012042760848999023, + -0.01736936718225479, + -0.016211409121751785, + -0.012042760848999023, + 0.025590866804122925, + 0.01325861643999815, + 0.02663302794098854, + -0.02686461992561817, + -0.009147866629064083, + 0.011290088295936584, + -0.011174292303621769, + 0.025011887773871422, + 0.006774052977561951, + 0.011000598780810833, + -0.020380057394504547, + 0.05627674609422684, + 0.015169247053563595, + 0.044465579092502594, + 0.03010690212249756, + 0.03659146651625633, + -0.0024172367993742228, + -0.002214594278484583, + 0.0057029421441257, + -0.0024172367993742228, + 0.020148465409874916, + -0.013953391462564468, + -0.03589669242501259, + -0.0031120115891098976, + 0.0, + 0.005558197386562824, + -0.02883314900100231, + -0.014821859076619148, + 0.009784743189811707, + -0.0345071405172348, + 0.012390147894620895, + -0.012621739879250526, + 0.03473873436450958, + 0.019916873425245285, + 0.011579577811062336, + -0.017832549288868904, + -0.012737535871565342, + 0.008742581121623516, + -0.004863422829657793, + 0.004110750276595354, + 0.015400838106870651, + 0.0031843839678913355, + 0.01505345106124878, + 0.025590866804122925, + -0.007468827534466982, + 0.018295733258128166, + 0.013027024455368519, + 0.03358077630400658, + 0.008279398083686829, + 0.023622337728738785, + -0.035201914608478546, + 0.03659146651625633, + -0.0062529719434678555, + 0.004950269591063261, + 0.011984863318502903, + 0.004139699041843414, + 0.003908107522875071, + -0.037286240607500076, + 0.012563841417431831, + -0.038675788789987564, + 0.027559394016861916, + 0.0056739929132163525, + -0.014127084985375404, + 0.0063398187048733234, + 0.039138972759246826, + -0.01505345106124878, + -0.017832549288868904, + 0.03589669242501259, + 0.0, + -0.01922209933400154, + -0.038675788789987564, + 0.015400838106870651, + -0.022001197561621666, + 0.004023903049528599, + -0.027443598955869675, + -0.040528520941734314, + -0.029412128031253815, + 0.010884802788496017, + -0.04122329503297806, + 0.014416574500501156, + -0.004776575602591038, + 0.0018961558816954494, + -0.027790986001491547, + 0.0011941439006477594, + 0.02663302794098854, + -0.023738134652376175, + -0.04145488888025284, + -0.011521679349243641, + -0.007584623526781797, + 0.007642521057277918, + 0.021653810515999794, + 0.009668947197496891, + -0.014706064015626907, + 0.029180536046624184, + -0.027906782925128937, + 0.05164491757750511, + 0.016674591228365898, + -0.016327204182744026, + 0.0002171170781366527, + -0.008510989136993885, + -0.022348584607243538, + 0.010711109265685081, + 0.005297656636685133, + 0.05558197200298309, + -0.02686461992561817, + 0.015748225152492523, + 0.029643718153238297, + 0.007700419053435326, + -0.0019540537614375353, + 0.00937945768237114, + 0.01244804635643959, + -0.021306423470377922, + -0.015400838106870651, + 0.015864022076129913, + -0.014648165553808212, + -0.025475071743130684, + 0.007874112576246262, + 0.011000598780810833, + 0.010132130235433578, + 0.00046680172090418637, + -0.002156696282327175, + 0.029180536046624184, + 0.004776575602591038, + 0.016558796167373657, + 0.002938317833468318, + -0.004544984083622694, + 0.01430077850818634, + 0.0018093090038746595, + 0.015979817137122154, + 0.036823056638240814, + -0.010711109265685081, + -0.0030396392103284597, + 0.007758317049592733, + -0.015748225152492523, + 0.016327204182744026, + -0.03473873436450958, + -0.01887471228837967, + 0.009668947197496891, + 0.0056739929132163525, + 0.007121440023183823, + -0.015400838106870651, + 0.0016428525559604168, + 0.007874112576246262, + -0.01679038815200329, + -0.020380057394504547, + 0.008684683591127396, + 0.021885402500629425, + -0.015516634099185467, + 0.006542461458593607, + -0.01679038815200329, + -0.0024027624167501926, + 0.030801676213741302, + -0.023506542667746544, + 0.023043358698487282, + 0.020264260470867157, + 0.009784743189811707, + 0.027790986001491547, + 0.015285042114555836, + 0.02107483148574829, + -0.01887471228837967, + -0.01713777519762516, + 0.03010690212249756, + 0.0017441739328205585, + -0.016674591228365898, + -0.0027501496952027082, + -0.034970324486494064, + 0.002967266831547022, + -0.0025619815569370985, + 0.023969726637005806, + 0.011695373803377151, + -0.006195073947310448, + -0.007989908568561077, + -0.010942701250314713, + -0.0172535702586174, + 0.002909368835389614, + -0.014011288993060589, + -0.0017514111241325736, + 0.038907382637262344, + -0.010653211735188961, + 0.02443290874361992, + 0.016443001106381416, + 0.013721799477934837, + -0.02813837304711342, + -0.011000598780810833, + 0.009900539182126522, + -0.013837595470249653, + 0.009032070636749268, + 0.02686461992561817, + -0.015864022076129913, + 0.0055871461518108845, + -0.006947746500372887, + -0.012853330932557583, + -0.011579577811062336, + 0.019801078364253044, + 0.0114058842882514, + -0.015169247053563595, + 0.028022577986121178, + 0.018295733258128166, + 0.005442401394248009, + -0.010305823758244514, + -0.037286240607500076, + -0.01430077850818634, + -0.024317113682627678, + 0.006224023178219795, + -0.006484563462436199, + 0.003705464769154787, + -0.027327803894877434, + 0.02107483148574829, + -0.019453691318631172, + 0.018527323380112648, + 0.0060792784206569195, + 0.013837595470249653, + -0.008858377113938332, + -0.008684683591127396, + -0.04168647900223732, + -0.010942701250314713, + 0.005471350625157356, + 0.010305823758244514, + -0.0011796695180237293, + 0.007874112576246262, + -0.007989908568561077, + -0.031264860183000565, + 0.016095612198114395, + -0.010942701250314713, + -0.011926964856684208, + -0.0031988583505153656, + -0.01713777519762516, + 0.011926964856684208, + -0.0059924316592514515, + 0.010595313273370266, + -0.019569486379623413, + -0.01325861643999815, + -0.031496450304985046, + -0.01690618321299553, + 0.011984863318502903, + -0.02628564089536667, + 0.003589669009670615, + 0.0048344735987484455, + -0.012042760848999023, + -0.03010690212249756, + -0.03566509857773781, + -0.029296331107616425, + -0.015979817137122154, + -0.03937056288123131, + 0.02686461992561817, + -0.02454870380461216, + 0.016674591228365898, + -0.004400239326059818, + -0.025822458788752556, + 0.017716754227876663, + -0.003546245628967881, + 0.002142221899703145, + 0.0003112011472694576, + 0.012853330932557583, + 0.027443598955869675, + -0.025243479758501053, + 0.0011724322102963924, + 0.002301441039890051, + 0.006455614697188139, + -0.02628564089536667, + -0.002113272901624441, + -0.008974173106253147, + -0.020148465409874916, + -0.003966005519032478, + 0.0011434833286330104, + -0.03010690212249756, + -0.018179936334490776, + -0.002388287801295519, + -0.015632430091500282, + 0.013953391462564468, + -0.050023775547742844, + 0.020032668486237526, + -0.002952792216092348, + 0.008395194076001644, + 0.03172804415225983, + 0.03195963427424431, + -0.008684683591127396, + 0.010305823758244514, + -0.021653810515999794, + -0.013779697008430958, + 0.0114058842882514, + 0.009263661690056324, + -0.03659146651625633, + 0.012158556841313839, + 0.03589669242501259, + -0.023506542667746544, + 0.006976695731282234, + -0.00015831453492864966, + -0.012100658379495144, + 0.0086267851293087, + -0.007874112576246262, + 0.029412128031253815, + 0.009147866629064083, + -0.008163602091372013, + 0.0172535702586174, + 0.0016645642463117838, + 0.006195073947310448, + 0.010826905257999897, + 0.014184982515871525, + 0.007237236015498638, + -0.02454870380461216, + -0.026169845834374428, + -0.019453691318631172, + -0.007324082776904106, + 0.008800478652119637, + 0.029064739122986794, + -0.005095014348626137, + 0.0056739929132163525, + 0.024085521697998047, + 0.021538013592362404, + -0.027559394016861916, + 0.044697169214487076, + -0.007005644496530294, + -0.023969726637005806, + -0.010884802788496017, + -0.01899050734937191, + 0.02258017659187317, + 0.029875310137867928, + -0.007584623526781797, + 0.007034593261778355, + -0.031496450304985046, + -0.002808047691360116, + 0.012563841417431831, + 0.013895493000745773, + -0.003531771246343851, + -0.013490208424627781, + 0.00961104966700077, + -0.015979817137122154, + -0.008974173106253147, + 0.0056739929132163525, + 0.000730960862711072, + -0.030801676213741302, + -0.019685281440615654, + 0.03566509857773781, + 0.0009625523816794157, + 0.009726845659315586, + 0.020148465409874916, + -0.03010690212249756, + 0.031496450304985046, + -0.008337295614182949, + -0.0054134526289999485, + -0.013721799477934837, + 0.04145488888025284, + -0.020148465409874916, + -0.006426665466278791, + 0.026980416849255562, + -0.0016283780569210649, + 0.01702197827398777, + 0.0055871461518108845, + -0.010479518212378025, + 0.011984863318502903, + -0.020495852455496788, + -0.003850209526717663, + -0.02663302794098854, + 0.012563841417431831, + -0.007468827534466982, + -0.001461921725422144, + -0.009842640720307827, + -0.03195963427424431, + -0.012621739879250526, + -0.0055871461518108845, + -0.029064739122986794, + -0.007324082776904106, + -0.017948346212506294, + 2.6687308491091244e-05, + 0.025590866804122925, + 0.0018382580019533634, + -0.0057608396746218204, + 0.00856888759881258, + 0.04793945327401161, + -0.003662041388452053, + 0.01505345106124878, + -0.021422218531370163, + 0.030801676213741302, + -0.0047476268373429775, + -0.013953391462564468, + 0.004429188556969166, + 0.007410929538309574, + 0.013084922917187214, + -0.016674591228365898, + -0.004805524833500385, + 0.015979817137122154, + 0.006021380424499512, + -0.012911229394376278, + -0.015516634099185467, + -0.023043358698487282, + -0.00573189090937376, + 0.025011887773871422, + 0.014706064015626907, + -0.02095903642475605, + 0.017485162243247032, + 0.020495852455496788, + 0.0032422817312180996, + -0.010826905257999897, + -0.017485162243247032, + -0.006687206216156483, + -0.026980416849255562, + 0.019453691318631172, + -0.0009625523816794157, + -0.023043358698487282, + -0.015285042114555836, + 0.023622337728738785, + 0.027096211910247803, + 0.03404395654797554, + 0.04539194330573082, + 0.011232190765440464, + -0.007584623526781797, + 0.010247926227748394, + -0.015979817137122154, + -0.007584623526781797, + -0.022464381530880928, + 0.018643120303750038, + -0.04307602718472481, + -0.02860155701637268, + 0.03242281824350357, + 0.011811168864369392, + 0.04539194330573082, + 0.006195073947310448, + 0.025127682834863663, + 0.027790986001491547, + -0.025822458788752556, + -0.04330762103199959, + -0.02095903642475605, + -0.05141332373023033, + 0.006050329189747572, + -0.02061164751648903, + 0.020380057394504547, + 0.00010584457777440548, + 0.005558197386562824, + -0.01736936718225479, + 0.016327204182744026, + -0.013721799477934837, + 0.01406918652355671, + 0.011811168864369392, + -0.0172535702586174, + 0.01736936718225479, + -0.0008033331832848489, + -0.04747626930475235, + 0.013490208424627781, + -0.007324082776904106, + -0.01435867603868246, + -0.004400239326059818, + -0.008163602091372013, + -0.03566509857773781, + 0.006426665466278791, + -0.015169247053563595, + -0.01227435190230608, + 0.021769605576992035, + -0.013142820447683334, + -0.014532369561493397, + 0.0034159754868596792, + 0.01325861643999815, + -0.050486959517002106, + -0.0050081671215593815, + -0.007381980773061514, + -0.0008503752178512514, + 0.02478029578924179, + -0.017485162243247032, + 0.014648165553808212, + 0.008395194076001644, + 0.003662041388452053, + 0.0029962158296257257, + 0.013663901947438717, + -0.012969126924872398, + -0.0014402099186554551, + -0.033117592334747314, + 0.004544984083622694, + -0.01244804635643959, + -0.01042161975055933, + -0.0032133327331393957, + 0.0032278073485940695, + -0.015979817137122154, + 0.02686461992561817, + 0.010942701250314713, + 0.018527323380112648, + -0.02258017659187317, + -0.008974173106253147, + 0.008395194076001644, + -0.010942701250314713, + -0.013895493000745773, + -0.009726845659315586, + -0.014648165553808212, + 0.006918797735124826, + 0.03033849410712719, + 0.022001197561621666, + 0.0265172328799963, + 0.020148465409874916, + -0.008974173106253147, + -0.031496450304985046, + -0.001404023845680058, + 0.010653211735188961, + -0.014648165553808212, + -0.003662041388452053, + 0.019685281440615654, + -0.0006694443291053176, + -0.023853929713368416, + -0.0033001797273755074, + 0.02871735207736492, + 0.031033268198370934, + -0.017485162243247032, + -0.00573189090937376, + -0.022116992622613907, + -0.029875310137867928, + -0.031264860183000565, + -0.003734413767233491, + 0.038907382637262344, + -0.044928763061761856, + 0.0017296994337812066, + 0.02443290874361992, + -0.021306423470377922, + 0.02836996503174305, + 0.016211409121751785, + 0.017832549288868904, + -0.002026426140218973, + 0.0172535702586174, + 0.025243479758501053, + 0.01505345106124878, + -0.0016500898636877537, + 0.014127084985375404, + 0.006658256985247135, + 0.012795433402061462, + -0.013142820447683334, + 0.02478029578924179, + 0.03242281824350357, + 0.023390747606754303, + -0.0016718015540391207, + 0.0019974771421402693, + 0.0019251047633588314, + 0.011116394773125648, + 0.021769605576992035, + -0.01406918652355671, + 0.006050329189747572, + 0.010595313273370266, + -0.014648165553808212, + 0.023853929713368416, + -0.01702197827398777, + -0.003618618007749319, + -0.0018454951932653785, + 0.0032278073485940695, + -0.017485162243247032, + 0.007468827534466982, + -0.0345071405172348, + 0.013895493000745773, + -0.0053266058675944805, + 0.01690618321299553, + 0.00010584457777440548, + -0.042844437062740326, + 0.033349182456731796, + 0.021653810515999794, + 0.007092491257935762, + -0.021190626546740532, + 0.002388287801295519, + -0.02663302794098854, + 0.006513512227684259, + 0.011174292303621769, + -0.020032668486237526, + 0.0045739333145320415, + -0.007932011038064957, + -0.021653810515999794, + -0.0048344735987484455, + -0.009958436712622643, + 0.003589669009670615, + -0.0382126048207283, + 0.011811168864369392, + 0.03427555039525032, + 0.004516035318374634, + 0.015516634099185467, + -0.008337295614182949, + -0.006803001742810011, + -0.009147866629064083, + -0.008684683591127396, + 0.029296331107616425, + -0.0068319509737193584, + -0.004776575602591038, + 0.010247926227748394, + -0.0052397591061890125, + 0.021885402500629425, + -0.020380057394504547, + 0.0002786335826385766, + -0.03242281824350357, + -0.0015560057945549488, + -0.025590866804122925, + -0.009900539182126522, + -0.0024172367993742228, + -0.031496450304985046, + -0.02269597165286541, + 0.012390147894620895, + 0.010942701250314713, + -0.007237236015498638, + -0.03473873436450958, + -0.003097536973655224, + 0.0026633029337972403, + 0.012737535871565342, + -0.007034593261778355, + -0.012042760848999023, + 0.009842640720307827, + -0.010884802788496017, + 0.0, + 0.05442401394248009, + -0.011637475341558456, + -0.008221500553190708, + -0.011347985826432705, + -0.01713777519762516, + -0.02292756363749504, + -0.00015831453492864966, + -0.0020843239035457373, + -0.018411528319120407, + -0.008337295614182949, + -0.011811168864369392, + 0.0017948345048353076, + -0.023506542667746544, + -0.006889848504215479, + 0.0028804198373109102, + -0.013663901947438717, + -0.01227435190230608, + 0.009784743189811707, + -0.0019251047633588314, + -0.0114058842882514, + 0.01406918652355671, + -0.03057008422911167, + 0.009437356144189835, + 0.017832549288868904, + -0.0031843839678913355, + 0.013663901947438717, + 0.025243479758501053, + -0.012737535871565342, + -0.021538013592362404, + 0.013548105955123901, + -0.013490208424627781, + -0.004979218356311321, + -0.020264260470867157, + 0.004718678072094917, + 0.01146378181874752, + 0.002185645280405879, + -0.0046607800759375095, + -0.0016645642463117838, + 0.031264860183000565, + -0.011695373803377151, + 0.0027067263144999743, + -0.03427555039525032, + 0.00048127619083970785, + 0.02836996503174305, + 0.0057608396746218204, + 0.01227435190230608, + -0.026980416849255562, + -0.008858377113938332, + -0.02663302794098854, + -0.002055375138297677, + 0.007700419053435326, + 0.025706661865115166, + -2.815737207129132e-05, + 0.0006368767935782671, + 0.04330762103199959, + 0.0003112011472694576, + 0.029875310137867928, + 0.0060792784206569195, + 0.004226545803248882, + -0.010826905257999897, + -0.012679637409746647, + -0.025359274819493294, + 0.03033849410712719, + 0.0026633029337972403, + 0.005384503398090601, + -0.0013533631572499871, + 0.027212006971240044, + 0.008800478652119637, + 0.012332250364124775, + 0.005152911879122257, + 0.011579577811062336, + -0.027212006971240044, + -0.027212006971240044, + -0.020032668486237526, + 0.023622337728738785, + -0.0006766815786249936, + 2.6348063329351135e-05, + -0.0009336034418083727, + -0.006368767935782671, + -0.007989908568561077, + -0.021885402500629425, + -0.009553151205182076, + -0.0040818010456860065, + -0.03473873436450958, + 0.003010690212249756, + 0.016095612198114395, + 0.012563841417431831, + -0.00030396392685361207, + 0.012621739879250526, + 0.031033268198370934, + -0.009784743189811707, + 0.011290088295936584, + 0.02860155701637268, + 0.0024317114148288965, + -0.013606003485620022, + 0.009553151205182076, + 0.008221500553190708, + -0.0265172328799963, + 0.014184982515871525, + 0.017485162243247032, + -0.007324082776904106, + -0.005181861110031605, + 0.018295733258128166, + 0.008163602091372013, + 0.010942701250314713, + 0.007642521057277918, + 0.011579577811062336, + -0.000730960862711072, + 0.04724467545747757, + 0.00613717595115304, + 0.017832549288868904, + 0.02269597165286541, + -0.011000598780810833, + -0.014416574500501156, + 0.015632430091500282, + 0.020032668486237526, + -0.02813837304711342, + 0.01406918652355671, + 0.006310869939625263, + 0.013084922917187214, + 0.008800478652119637, + 0.0024317114148288965, + -0.010826905257999897, + 0.046086717396974564, + -0.053960833698511124, + 0.03172804415225983, + -0.006513512227684259, + 0.015285042114555836, + -0.0054134526289999485, + 0.011984863318502903, + 0.001512582297436893, + -0.025938253849744797, + -0.03033849410712719, + 0.007353032007813454, + 0.017716754227876663, + 0.012100658379495144, + 0.002185645280405879, + 0.007266184780746698, + -0.006281920708715916, + -0.02628564089536667, + -0.006310869939625263, + 0.007758317049592733, + 0.008684683591127396, + 0.0055871461518108845, + -0.00047765756607986987, + -0.0014402099186554551, + 0.03288600221276283, + 0.00020987984316889197, + -0.0059924316592514515, + 0.0021277472842484713, + -0.029643718153238297, + 0.003025164594873786, + 0.008684683591127396, + -0.0038791585247963667, + 0.02848576009273529, + -0.004110750276595354, + -0.0027646240778267384, + -0.0010566364508122206, + 0.010884802788496017, + 0.029875310137867928, + 0.0382126048207283, + 0.01227435190230608, + -0.0265172328799963, + 0.0016139036742970347, + -0.003140960354357958, + 0.006803001742810011, + -0.010942701250314713, + -0.01713777519762516, + 0.027790986001491547, + -0.007642521057277918, + 0.004023903049528599, + -0.00011760508641600609, + -0.011984863318502903, + -0.010826905257999897, + -0.010942701250314713, + -0.01679038815200329, + -0.009089968167245388, + 0.0026777773164212704, + -0.03473873436450958, + 0.024201316758990288, + 0.012332250364124775, + 0.01910630241036415, + 0.014706064015626907, + 0.011174292303621769, + -0.008163602091372013, + -0.004602882079780102, + 0.03635987266898155, + 0.01690618321299553, + -0.0046607800759375095, + -0.013490208424627781, + 0.004371290560811758, + -0.009147866629064083, + 0.025011887773871422, + -0.016211409121751785, + -0.0006477326387539506, + -0.002981741214171052, + -0.02836996503174305, + -0.007584623526781797, + -0.0018020718125626445, + 0.025359274819493294, + 0.02061164751648903, + 0.020495852455496788, + -0.019337894394993782, + -0.011984863318502903, + 0.0034304498694837093, + -0.04214966297149658, + 0.00610822718590498, + -0.039138972759246826, + -0.018064141273498535, + 0.005152911879122257, + 0.023853929713368416, + -0.02466450072824955, + 0.010595313273370266, + -0.00021349846792872995, + -0.008337295614182949, + -0.016327204182744026, + 0.029527923092246056, + 0.008510989136993885, + 0.005818737670779228, + -0.0008431380265392363, + 0.01320071890950203, + -0.018179936334490776, + -0.014648165553808212, + -0.005037116352468729, + 0.04793945327401161, + 0.014184982515871525, + 0.003705464769154787, + -0.0016790387453511357, + 0.023853929713368416, + 0.014706064015626907, + -0.013779697008430958, + 0.01505345106124878, + -0.014937655068933964, + -0.04724467545747757, + 0.011521679349243641, + -0.030801676213741302, + -0.016211409121751785, + -0.040528520941734314, + -0.008395194076001644, + 0.0004269969358574599, + -0.031264860183000565, + -0.011637475341558456, + -0.006889848504215479, + -0.019685281440615654, + -0.001461921725422144, + 0.020148465409874916, + 0.027675190940499306, + -0.03659146651625633, + 0.01702197827398777, + 0.00752672553062439, + -0.011174292303621769, + 0.0038791585247963667, + 0.0086267851293087, + 0.01320071890950203, + 0.00015198196342680603, + 0.04770785942673683, + 0.006687206216156483, + 0.007063542492687702, + 0.016327204182744026, + -0.004544984083622694, + -0.0034449242521077394, + 0.00010539225331740454, + -0.0172535702586174, + 0.012100658379495144, + 0.007381980773061514, + -0.009263661690056324, + 0.027212006971240044, + 0.012332250364124775, + -0.020032668486237526, + -0.004342341795563698, + -0.005471350625157356, + 0.018643120303750038, + -0.00961104966700077, + 0.0035172966308891773, + -0.00573189090937376, + -0.029643718153238297, + -0.03195963427424431, + 0.018064141273498535, + -0.0024896091781556606, + 0.004458137322217226, + 0.0114058842882514, + 5.269612665870227e-05, + -0.004342341795563698, + 0.018295733258128166, + -0.00020897519425489008, + -0.029875310137867928, + 0.021653810515999794, + -0.009668947197496891, + 0.006889848504215479, + 0.00613717595115304, + 0.021306423470377922, + 0.020495852455496788, + 0.014706064015626907, + 0.007237236015498638, + 0.010247926227748394, + -0.008858377113938332, + 0.033117592334747314, + -0.025822458788752556, + -0.007381980773061514, + 0.0025619815569370985, + -0.014763961546123028, + 0.0003473873366601765, + -0.006889848504215479, + 0.010479518212378025, + -0.014590268023312092, + -0.00752672553062439, + -0.02269597165286541, + 0.029296331107616425, + 0.0012665162794291973, + 0.0005825975094921887, + 0.009726845659315586, + -0.010479518212378025, + 0.010884802788496017, + 0.04701308533549309, + -0.002388287801295519, + -0.014937655068933964, + -0.025706661865115166, + 0.006224023178219795, + 0.0025764559395611286, + 0.028022577986121178, + -0.023969726637005806, + 0.008163602091372013, + -0.005066065117716789, + -0.021653810515999794, + -0.01690618321299553, + 0.007266184780746698, + -0.018179936334490776, + -0.022348584607243538, + -0.007208287250250578, + 0.015285042114555836, + -0.031496450304985046, + -0.006426665466278791, + -0.02084323950111866, + 0.005355554632842541, + 0.01320071890950203, + 0.01713777519762516, + -0.001512582297436893, + 0.027790986001491547, + 0.012795433402061462, + 0.03960215672850609, + 0.004400239326059818, + -0.03960215672850609, + -0.003140960354357958, + -0.008105704560875893, + -0.013606003485620022, + -0.009263661690056324, + 0.011000598780810833, + 0.0025764559395611286, + 0.006484563462436199, + 0.0033001797273755074, + 0.01505345106124878, + -0.0016935132443904877, + -0.004979218356311321, + 0.014474472030997276, + -0.01146378181874752, + -0.025822458788752556, + -0.010884802788496017, + 0.03265440836548805, + 0.010537415742874146, + -0.017948346212506294, + 0.005818737670779228, + 0.015400838106870651, + -0.02454870380461216, + -0.03774942457675934, + -0.006281920708715916, + 0.021306423470377922, + 0.011811168864369392, + 0.015979817137122154, + -0.004718678072094917, + 0.0017224621260538697, + 0.0006368767935782671, + 0.019685281440615654, + 0.02628564089536667, + 0.017716754227876663, + 0.003821260528638959, + -0.009089968167245388, + -0.0013316514668986201, + 0.009321560151875019, + -0.01320071890950203, + 0.025011887773871422, + -0.003662041388452053, + 0.0031264859717339277, + 0.010537415742874146, + -0.009321560151875019, + -0.044928763061761856, + -0.005963482428342104, + -0.012563841417431831, + 0.007874112576246262, + -0.037054646760225296, + 0.019801078364253044, + -0.012737535871565342, + 0.0031843839678913355, + -0.0382126048207283, + 0.010595313273370266, + -0.017485162243247032, + -0.037054646760225296, + -0.01036372222006321, + 0.008395194076001644, + 0.029527923092246056, + -0.04886581748723984, + 0.011232190765440464, + 0.008279398083686829, + 0.03427555039525032, + 0.029064739122986794, + -0.02466450072824955, + 0.008395194076001644, + -0.003908107522875071, + -0.0010276875691488385, + 0.0018382580019533634, + -0.02072744444012642, + -0.0006441139848902822, + 0.00856888759881258, + -0.0002587311901152134, + -0.042844437062740326, + 0.02084323950111866, + -0.007816215045750141, + -0.014474472030997276, + -0.0002062612329609692, + -0.0025040835607796907, + 0.0018672068836167455, + -0.02072744444012642, + 0.003488347865641117, + -0.008742581121623516, + -0.03798101469874382, + 0.01875891536474228, + -0.023043358698487282, + 0.017485162243247032, + 0.0033001797273755074, + 0.01221645437180996, + -0.02061164751648903, + 0.015169247053563595, + -0.013606003485620022, + 0.00306858797557652, + -0.003850209526717663, + 0.003690990386530757, + -0.02443290874361992, + 0.019337894394993782, + -0.002214594278484583, + 0.0345071405172348, + 0.01713777519762516, + 0.0019106302643194795, + 0.02258017659187317, + 0.006803001742810011, + 0.03057008422911167, + 0.0382126048207283, + 0.0172535702586174, + 0.029180536046624184, + 0.015516634099185467, + -0.0017369366250932217, + 0.013490208424627781, + 0.017832549288868904, + 0.004400239326059818, + -0.02072744444012642, + -0.02489609271287918, + 0.011058496311306953, + 0.0045739333145320415, + -0.007063542492687702, + 0.02640143781900406, + -0.008742581121623516, + 0.04793945327401161, + 0.01505345106124878, + 0.031496450304985046, + -0.007758317049592733, + -0.022232789546251297, + -0.012505943886935711, + 0.0024896091781556606, + 0.013027024455368519, + 0.019337894394993782, + -0.03242281824350357, + -0.0013171769678592682, + -0.011232190765440464, + 0.011926964856684208, + -0.010884802788496017, + 0.01736936718225479, + -0.015748225152492523, + 0.018643120303750038, + -0.01244804635643959, + 0.04168647900223732, + -0.006513512227684259, + 0.019337894394993782, + -0.007816215045750141, + -0.03473873436450958, + 0.012563841417431831, + -0.006368767935782671, + 0.0022869666572660208, + 0.0025330325588583946, + 0.008858377113938332, + 0.02686461992561817, + 0.022001197561621666, + -0.002315915422514081, + -0.040528520941734314, + 0.007353032007813454, + 0.014590268023312092, + -0.016443001106381416, + 0.0006513512344099581, + -0.0045739333145320415, + -0.02095903642475605, + 0.002113272901624441, + 0.003531771246343851, + -0.01227435190230608, + 0.0011217716382816434, + -0.02095903642475605, + -0.012911229394376278, + 0.015632430091500282, + 0.010132130235433578, + 0.0011869067093357444, + -0.009958436712622643, + -0.005645044147968292, + -0.0011941439006477594, + 0.029875310137867928, + -0.0005283182254061103, + 0.015169247053563595, + 0.009032070636749268, + -0.03404395654797554, + -0.013953391462564468, + 0.044233985245227814, + 0.008279398083686829, + -0.003140960354357958, + -0.01406918652355671, + -0.025590866804122925, + -0.012679637409746647, + 0.016095612198114395, + -0.000738198054023087, + 0.0025475071743130684, + -0.05859266221523285, + 0.0032567561138421297, + -0.010537415742874146, + 0.014011288993060589, + 0.037054646760225296, + -0.022348584607243538, + 0.00856888759881258, + -0.013895493000745773, + -0.004892371594905853, + -0.023043358698487282, + -0.0114058842882514, + 0.0032278073485940695, + -0.005095014348626137, + 0.019685281440615654, + -0.05720311403274536, + 0.007324082776904106, + -0.0006224022945389152, + -0.029296331107616425, + -0.01042161975055933, + -0.007381980773061514, + 0.010190028697252274, + 0.020032668486237526, + 0.014011288993060589, + -0.011753271333873272, + 0.024085521697998047, + -0.015516634099185467, + -0.0030830625910311937, + 0.017716754227876663, + -0.016095612198114395, + 0.007410929538309574, + -0.040991704910993576, + -0.011058496311306953, + 0.029180536046624184, + -0.023390747606754303, + 0.0228117685765028, + 0.038907382637262344, + -0.015632430091500282, + 0.0012665162794291973, + 0.017832549288868904, + -0.007584623526781797, + 0.04909740760922432, + 0.0382126048207283, + -0.008279398083686829, + -0.04400239512324333, + -0.018179936334490776, + 0.012853330932557583, + -0.007237236015498638, + -0.0024461857974529266, + -0.014474472030997276, + -0.0055871461518108845, + 0.04747626930475235, + -0.029643718153238297, + -0.0419180728495121, + 0.0017948345048353076, + 0.009263661690056324, + -0.004458137322217226, + -0.012911229394376278, + 0.023506542667746544, + 0.0020409005228430033, + -0.01690618321299553, + -0.022348584607243538, + 0.016095612198114395, + 0.0056739929132163525, + 0.0019540537614375353, + 0.021306423470377922, + -0.0059924316592514515, + 0.0086267851293087, + 0.02061164751648903, + -0.008047806099057198, + 0.004892371594905853, + -0.00937945768237114, + 0.020148465409874916, + -0.003097536973655224, + 0.0025330325588583946, + -0.02258017659187317, + 0.011174292303621769, + 0.00015831453492864966, + 0.013374412432312965, + 0.016095612198114395, + 0.023853929713368416, + 0.007092491257935762, + 0.019685281440615654, + 0.01899050734937191, + 0.01244804635643959, + -0.008047806099057198, + -0.01505345106124878, + -0.019453691318631172, + -0.0024461857974529266, + 0.006368767935782671, + 0.008684683591127396, + -0.015632430091500282, + 0.010826905257999897, + -0.04747626930475235, + -0.011290088295936584, + 0.03473873436450958, + -0.007410929538309574, + -0.006658256985247135, + 0.018411528319120407, + -0.023853929713368416, + 0.044465579092502594, + -0.0057608396746218204, + -0.05164491757750511, + 0.0028369964566081762, + -0.0021277472842484713, + 0.006513512227684259, + 0.003662041388452053, + 0.031264860183000565, + -0.0020119515247642994, + -0.006600359454751015, + 0.013837595470249653, + -0.029180536046624184, + 0.021190626546740532, + -0.011058496311306953, + -0.008800478652119637, + 0.023853929713368416, + -0.02860155701637268, + -0.025475071743130684, + -0.015979817137122154, + -0.018411528319120407, + -0.012042760848999023, + 0.014937655068933964, + 0.024201316758990288, + -0.023622337728738785, + -0.007642521057277918, + 0.03937056288123131, + 0.0040818010456860065, + -0.037054646760225296, + -0.02813837304711342, + -0.012679637409746647, + 0.018643120303750038, + 0.029875310137867928, + -0.0033436031080782413, + 0.031033268198370934, + -0.008453091606497765, + 0.010537415742874146, + 0.002735675312578678, + -0.006947746500372887, + -0.013084922917187214, + 0.023853929713368416, + -0.011637475341558456, + 0.0068319509737193584, + 0.0038791585247963667, + 0.0019540537614375353, + -0.0018165461951866746, + 0.013606003485620022, + -0.0009697896311990917, + -0.016095612198114395, + -0.00026054048794321716, + -0.013084922917187214, + -0.017600957304239273, + -0.022348584607243538, + -0.03659146651625633, + -0.005152911879122257, + -0.012795433402061462, + 0.0031120115891098976, + 0.00010855853906832635, + 0.013548105955123901, + 0.016558796167373657, + 0.012042760848999023, + -0.01690618321299553, + -0.003705464769154787, + 0.027906782925128937, + 0.023274950683116913, + 0.011058496311306953, + -0.016211409121751785, + -0.0013461258495226502, + 0.0011434833286330104, + -0.040528520941734314, + -0.001570480177178979, + -0.0382126048207283, + 0.011695373803377151, + 0.009032070636749268, + 0.03172804415225983, + -0.029643718153238297, + 0.03960215672850609, + 0.027096211910247803, + 0.005876635666936636, + -0.0020409005228430033, + -0.00015831453492864966, + 0.008337295614182949, + 0.029180536046624184, + 0.01736936718225479, + 0.015516634099185467, + 0.013142820447683334, + 0.05488719791173935, + 0.007208287250250578, + 0.020495852455496788, + -0.012042760848999023, + 0.03659146651625633, + 0.020495852455496788, + -0.03635987266898155, + 0.0049213203601539135, + -0.01875891536474228, + -0.05349764972925186, + 0.01505345106124878, + -0.014011288993060589, + -0.029527923092246056, + 0.0035172966308891773, + 0.025127682834863663, + -0.03219122439622879, + 0.0010421619517728686, + -0.02454870380461216, + -0.010826905257999897, + -0.006368767935782671, + -0.018295733258128166, + -0.05488719791173935, + 0.029180536046624184, + -0.027675190940499306, + 0.02292756363749504, + 0.00021078450663480908, + 0.00021078450663480908, + 0.012563841417431831, + 0.002171170897781849, + -0.029412128031253815, + -0.021306423470377922, + 0.016327204182744026, + 0.02883314900100231, + 0.001367837656289339, + -0.01221645437180996, + 0.012042760848999023, + -0.050023775547742844, + -0.017832549288868904, + -0.01406918652355671, + -0.023043358698487282, + 0.020032668486237526, + -0.010537415742874146, + 0.013548105955123901, + 0.029412128031253815, + -0.001548768486827612, + 0.013027024455368519, + 0.044233985245227814, + -0.03219122439622879, + -0.005471350625157356, + 0.02836996503174305, + 0.001512582297436893, + 0.011058496311306953, + -0.0009118917514570057, + 0.019569486379623413, + -0.004226545803248882, + 0.0025764559395611286, + -0.02443290874361992, + 0.0017369366250932217, + 0.00428444379940629, + -0.02686461992561817, + 0.013027024455368519, + -0.010190028697252274, + 0.020264260470867157, + 0.016674591228365898, + -0.01702197827398777, + 0.0012158557074144483, + -0.018064141273498535, + 0.006455614697188139, + 0.021306423470377922, + 0.02871735207736492, + 0.026054048910737038, + -0.0019395792623981833, + -0.021190626546740532, + -0.0027935730759054422, + 0.040991704910993576, + -0.012505943886935711, + 0.0045739333145320415, + -0.005384503398090601, + 0.013606003485620022, + 0.016327204182744026, + 0.002069849520921707, + 0.00021349846792872995, + 0.0047476268373429775, + 0.018179936334490776, + 0.01505345106124878, + -0.0031120115891098976, + 0.023159155622124672, + 0.005442401394248009, + -0.00076714699389413, + 0.022232789546251297, + -0.0382126048207283, + -0.012042760848999023, + 0.01690618321299553, + 0.018527323380112648, + -0.019685281440615654, + 0.0055002993904054165, + -0.03774942457675934, + -0.006195073947310448, + -0.009495253674685955, + 0.021885402500629425, + -0.002069849520921707, + 0.0018020718125626445, + 0.007468827534466982, + 0.010132130235433578, + -0.008163602091372013, + -0.010190028697252274, + 0.020032668486237526, + 0.010942701250314713, + -0.015169247053563595, + -0.008800478652119637, + -0.02836996503174305, + -0.0031120115891098976, + 0.029180536046624184, + 0.031496450304985046, + -0.00752672553062439, + -0.01679038815200329, + 0.03010690212249756, + 0.031033268198370934, + -0.004110750276595354, + 0.013779697008430958, + 0.0017369366250932217, + -0.020264260470867157, + -0.018179936334490776, + 0.008684683591127396, + -0.027906782925128937, + 0.020148465409874916, + -0.007816215045750141, + -0.005963482428342104, + 0.013490208424627781, + 0.04330762103199959, + -0.012737535871565342, + 0.005818737670779228, + 0.01702197827398777, + -0.010653211735188961, + 0.01679038815200329, + 0.002214594278484583, + 0.005876635666936636, + 0.008279398083686829, + -0.014648165553808212, + -0.021422218531370163, + -0.0002071658818749711, + -0.008974173106253147, + -0.019916873425245285, + 0.019801078364253044, + -0.01899050734937191, + 0.03566509857773781, + 0.010653211735188961, + 0.03659146651625633, + 0.021190626546740532, + 0.0013316514668986201, + 0.007642521057277918, + 0.0045739333145320415, + -0.02883314900100231, + 0.02269597165286541, + 0.014706064015626907, + 0.0006006906041875482, + 0.006687206216156483, + -0.01887471228837967, + 0.02107483148574829, + 0.010190028697252274, + -0.0048344735987484455, + 0.006658256985247135, + -0.01875891536474228, + -0.009495253674685955, + 0.022464381530880928, + -0.004892371594905853, + -0.011926964856684208, + -0.0419180728495121, + -0.020264260470867157, + -0.013432309962809086, + -0.021306423470377922, + 0.02466450072824955, + 0.04353921115398407, + -0.02443290874361992, + -0.02072744444012642, + 0.003763362765312195, + -0.0026922516990453005, + 0.011926964856684208, + 0.013721799477934837, + -0.029875310137867928, + 0.01042161975055933, + 0.017948346212506294, + -0.005037116352468729, + -0.0046607800759375095, + -0.011058496311306953, + -0.007758317049592733, + 0.008163602091372013, + 0.02095903642475605, + -0.017716754227876663, + 0.0002840615052264184, + -0.002909368835389614, + -0.02836996503174305, + 0.0265172328799963, + -0.01887471228837967, + -0.0002659684105310589, + 0.0026777773164212704, + 0.029296331107616425, + -0.003546245628967881, + -0.01690618321299553, + 0.02084323950111866, + 0.00143297272734344, + 0.015864022076129913, + 0.00035643388400785625, + -0.009495253674685955, + 0.02836996503174305, + -0.025359274819493294, + -0.019453691318631172, + 0.00715038925409317, + -0.009900539182126522, + -0.03195963427424431, + 0.004400239326059818, + 0.013432309962809086, + -0.029643718153238297, + -0.01325861643999815, + -0.0057029421441257, + 0.012390147894620895, + 0.01505345106124878, + -0.0005608858191408217, + 0.009437356144189835, + -0.01899050734937191, + 0.001505345106124878, + 0.027675190940499306, + -0.015632430091500282, + 0.018527323380112648, + -0.004892371594905853, + -0.015979817137122154, + 0.004516035318374634, + 0.02860155701637268, + -0.010769006796181202, + -0.008800478652119637, + 0.025011887773871422, + -0.014706064015626907, + 0.024085521697998047, + -0.001490870607085526, + -0.009958436712622643, + -0.007005644496530294, + 0.011637475341558456, + 0.013142820447683334, + -0.029296331107616425, + -0.017485162243247032, + -0.0021277472842484713, + -0.010595313273370266, + 0.00010720155842136592, + 0.010711109265685081, + 0.018179936334490776, + 0.03242281824350357, + -0.007381980773061514, + -0.009842640720307827, + 0.06484563648700714, + -0.015169247053563595, + -0.021769605576992035, + -0.03010690212249756, + 0.010537415742874146, + 0.02072744444012642, + 0.03358077630400658, + 0.019337894394993782, + -0.005616095382720232, + -0.03659146651625633, + -0.018411528319120407, + -0.008105704560875893, + -0.019337894394993782, + 0.024201316758990288, + 0.00856888759881258, + 0.014821859076619148, + -0.0003998572938144207, + 0.0012882279697805643, + -0.014937655068933964, + -0.044928763061761856, + 0.003025164594873786, + -0.0026054049376398325, + 0.012853330932557583, + 0.024317113682627678, + -0.011753271333873272, + -0.010479518212378025, + -0.0026633029337972403, + 0.0005174623802304268, + -0.003546245628967881, + 0.036823056638240814, + -0.03172804415225983, + -0.040991704910993576, + -0.012505943886935711, + -0.021769605576992035, + 0.023274950683116913, + -0.02095903642475605, + -0.015748225152492523, + 0.01910630241036415, + -0.002055375138297677, + -0.02813837304711342, + 0.009437356144189835, + -0.04168647900223732, + 0.006860899738967419, + 0.018643120303750038, + -0.026054048910737038, + -0.010479518212378025, + 0.034970324486494064, + 0.033117592334747314, + 0.01036372222006321, + 0.03265440836548805, + -0.014011288993060589, + 0.0060792784206569195, + -0.013374412432312965, + 0.01690618321299553, + -0.012042760848999023, + 0.04330762103199959, + -0.014474472030997276, + -0.021653810515999794, + -0.0008178076823242009, + -0.0032567561138421297, + -0.008221500553190708, + -0.027096211910247803, + 7.78002868173644e-05, + -0.004776575602591038, + -0.006629308219999075, + 0.0, + 0.0053266058675944805, + -0.007237236015498638, + 0.0057029421441257, + 0.029412128031253815, + -0.0057029421441257, + 0.008221500553190708, + 0.004052852280437946, + -0.011290088295936584, + -0.013779697008430958, + 0.016211409121751785, + -0.009205764159560204, + 0.006716154981404543, + -0.010305823758244514, + 0.031264860183000565, + 0.0008576124673709273, + 0.002388287801295519, + 0.021653810515999794, + 0.010942701250314713, + 0.010595313273370266, + 0.04168647900223732, + 0.006368767935782671, + 0.018295733258128166, + -0.0041686478070914745, + -0.0016573270549997687, + -0.019916873425245285, + 0.010305823758244514, + -0.008221500553190708, + -0.02813837304711342, + 0.0010855854488909245, + 0.012969126924872398, + 0.004950269591063261, + 0.012505943886935711, + 0.0053266058675944805, + -0.01406918652355671, + 0.024201316758990288, + 0.0005283182254061103, + -0.03033849410712719, + 0.02107483148574829, + 0.005268707871437073, + 0.005876635666936636, + 0.013084922917187214, + 0.007932011038064957, + -0.024317113682627678, + 0.0055002993904054165, + 0.005066065117716789, + 0.011232190765440464, + 0.007468827534466982, + -0.023274950683116913, + -0.02883314900100231, + -0.00752672553062439, + -0.014648165553808212, + -0.014532369561493397, + 0.025243479758501053, + 0.039138972759246826, + -0.0012882279697805643, + 0.01899050734937191, + 0.011926964856684208, + -0.011174292303621769, + -0.012042760848999023, + -0.008684683591127396, + -0.012737535871565342, + -0.0057029421441257, + 0.011579577811062336, + -0.024201316758990288, + -0.029180536046624184, + 0.03589669242501259, + -0.033117592334747314, + -0.024085521697998047, + -0.005529248155653477, + -0.02258017659187317, + 0.008279398083686829, + -0.012621739879250526, + -0.021538013592362404, + 0.0057608396746218204, + -0.034970324486494064, + -0.011869067326188087, + 0.0228117685765028, + 0.022464381530880928, + -0.0008684683125466108, + -0.0059924316592514515, + -0.005268707871437073, + 0.018295733258128166, + -0.009321560151875019, + 0.020495852455496788, + -0.0032133327331393957, + -0.006571410223841667, + -0.025011887773871422, + 0.003850209526717663, + 0.0013750748476013541, + -0.031033268198370934, + -0.009263661690056324, + 0.0012954652775079012, + 0.0059924316592514515, + 0.014937655068933964, + 0.013490208424627781, + 0.012911229394376278, + -0.017485162243247032, + -0.035433508455753326, + -0.015400838106870651, + 0.0010132130701094866 + ], + "qwen3-embed-max-1536": [ + 0.0003033418033737689, + 0.022268857806921005, + 0.06052561476826668, + -0.0013293508673086762, + 0.000990321859717369, + 0.05110417306423187, + 0.05852712690830231, + -0.01805775985121727, + 0.030120057985186577, + 0.01998487114906311, + 0.02198336087167263, + -0.03797125816345215, + -0.004246785305440426, + -0.04510870948433876, + 0.02783607318997383, + 0.0006959018064662814, + 0.03154754638671875, + -0.05167516693472862, + 0.02897806465625763, + -0.012133672833442688, + -0.021555110812187195, + 0.011205802671611309, + -0.0010795400012284517, + -0.0374002605676651, + -0.09307239949703217, + -0.02383909747004509, + -0.03140479698777199, + -0.0736585333943367, + 0.04567970708012581, + 0.003693632548674941, + 0.001489943591877818, + -0.03268954157829285, + 0.0725165382027626, + -0.01748676411807537, + 0.005031905137002468, + -0.03497352823615074, + -0.014060785062611103, + -0.008600632660090923, + -0.019556624814867973, + -0.03911324962973595, + 0.015488276258111, + -0.02583758346736431, + 0.011348553001880646, + -0.0011286099907010794, + -0.012704668566584587, + -0.049962177872657776, + 0.0025516399182379246, + 0.0385422520339489, + 0.004264628980308771, + -0.03397428244352341, + 0.0005330786225385964, + -0.005567214451730251, + 0.02255435474216938, + -0.03882775083184242, + -0.0491056852042675, + -0.04767819494009018, + 0.03268954157829285, + -0.003854225156828761, + -0.034545280039310455, + -0.04653619974851608, + -0.01870013028383255, + -0.02269710600376129, + -0.028549818322062492, + 0.000811885460279882, + 0.0023732036352157593, + -0.03468802943825722, + 0.03882775083184242, + -0.09649837762117386, + 0.029120812192559242, + 0.04168273136019707, + -0.048820190131664276, + 0.0026230146177113056, + -0.043681222945451736, + 0.006423708982765675, + 0.0013382727047428489, + -0.027550572529435158, + -0.007708450313657522, + 0.03682926669716835, + 0.0332605354487896, + 0.02954906038939953, + -0.012347796000540257, + -0.0020252526737749577, + 0.007351578213274479, + 0.07537151873111725, + 0.0491056852042675, + -0.010848930105566978, + 0.005174654070287943, + 0.003961287438869476, + -0.012276421301066875, + 0.007458639796823263, + 0.017272640019655228, + 0.04653619974851608, + 0.005567214451730251, + -0.0256948359310627, + -0.0003256463387515396, + 0.018557380884885788, + -0.013061542063951492, + -0.012276421301066875, + -0.023410849273204803, + 0.024981088936328888, + -0.008529257960617542, + -0.004425221588462591, + -0.026836829259991646, + -0.0008431118330918252, + -0.032546792179346085, + -0.02583758346736431, + -0.03939874842762947, + -0.0035508836153894663, + 0.0, + 0.0018557381117716432, + -0.023696348071098328, + -0.0056029013358056545, + 0.0256948359310627, + 0.02255435474216938, + -0.009564189240336418, + -0.004104036372154951, + 0.007244516164064407, + 0.10734731703996658, + -0.00435384688898921, + 0.017272640019655228, + -0.0031047926750034094, + 0.03597277030348778, + 0.006780581548810005, + 0.052531663328409195, + 0.0022572199814021587, + 0.009849687106907368, + -0.019556624814867973, + 0.0017219107830896974, + -0.011348553001880646, + 0.012918791733682156, + 0.003336759749799967, + 0.006851955782622099, + -0.03939874842762947, + -0.0009011036017909646, + -0.023410849273204803, + -0.02255435474216938, + -0.009492814540863037, + 0.005317403469234705, + 0.0035687272902578115, + -0.026123084127902985, + -0.003765007248148322, + -0.01127717737108469, + 0.02640858106315136, + 0.017700886353850365, + -0.016558893024921417, + -0.05281716212630272, + -0.012276421301066875, + -0.061953105032444, + 0.0007092845626175404, + 0.0014721000334247947, + 0.027550572529435158, + -0.0027657635509967804, + -0.0025873270351439714, + 0.02526658959686756, + 0.0022661418188363314, + 0.017700886353850365, + -0.01798638515174389, + 0.018557380884885788, + 0.012918791733682156, + 0.0385422520339489, + -0.010848930105566978, + 0.025552086532115936, + -0.023410849273204803, + 0.002747920108959079, + 0.0007360500167123973, + -0.020413119345903397, + -0.003586570732295513, + -0.010492058470845222, + -0.023410849273204803, + -0.043395720422267914, + -0.021412363275885582, + -0.03154754638671875, + 0.01263329479843378, + -0.024981088936328888, + 0.00011654124682536349, + 0.0028371382504701614, + -0.014988653361797333, + -0.03240404278039932, + 0.0032832289580255747, + -0.02712232619524002, + -0.007708450313657522, + -0.0009189472766593099, + 0.03468802943825722, + -0.009921061806380749, + 0.024124594405293465, + -0.0028014506679028273, + -0.01413215883076191, + 0.02312535233795643, + 0.01263329479843378, + 0.016344770789146423, + -0.05367365851998329, + -0.012419170700013638, + 0.014417657628655434, + -0.015845149755477905, + -0.0001940495421877131, + -0.0017129889456555247, + -0.02954906038939953, + -0.0010527744889259338, + 0.019556624814867973, + 0.023696348071098328, + -0.0007494327146559954, + 0.021697860211133957, + 0.0012401327257975936, + 0.019128378480672836, + 0.0027122325263917446, + -0.018271882086992264, + 0.0007003626669757068, + -0.014417657628655434, + -0.003943443298339844, + -0.02583758346736431, + -0.0008921818225644529, + -0.033117786049842834, + -0.020984116941690445, + -0.05567214637994766, + -0.0083151338621974, + 0.026694077998399734, + -0.019699374213814735, + -0.015488276258111, + 0.044823210686445236, + 0.004139723256230354, + 0.004710719920694828, + 0.02583758346736431, + -0.013846661895513535, + 0.015631025657057762, + -0.04625070467591286, + 0.009706937707960606, + -0.017272640019655228, + -0.02069861628115177, + 0.018842879682779312, + -0.014988653361797333, + 0.015702398493885994, + 0.02069861628115177, + -0.019128378480672836, + -0.009064567275345325, + -0.005745650734752417, + 0.03083380125463009, + 0.013989410363137722, + -0.013989410363137722, + 0.013132915832102299, + 0.0014007253339514136, + -0.006173898000270128, + -0.022839853540062904, + 0.006423708982765675, + 0.013846661895513535, + 0.0031404800247401, + -0.03397428244352341, + -0.004782094620168209, + -0.026551328599452972, + -0.018200507387518883, + -0.0125619200989604, + -0.0016594580374658108, + -0.005246029235422611, + 0.030405554920434952, + -0.02055586874485016, + -0.01620202139019966, + 0.011990923434495926, + -0.017558138817548752, + -0.05538664758205414, + 0.005174654070287943, + 0.010563433170318604, + 0.013918036594986916, + 0.037685759365558624, + -0.012276421301066875, + -0.007851199246942997, + 0.013632537797093391, + -0.019556624814867973, + 0.02383909747004509, + 0.011419926770031452, + -0.008779069408774376, + -0.006816268898546696, + -0.026123084127902985, + -0.03283229097723961, + -0.016344770789146423, + -0.01127717737108469, + -0.0012401327257975936, + 0.006245272234082222, + -0.008850443176925182, + -0.0008743381476961076, + 0.03026280738413334, + 0.053959157317876816, + 0.007244516164064407, + 0.0039255996234714985, + -0.03169029951095581, + 0.005781338084489107, + 0.04111173748970032, + -0.004246785305440426, + 0.06737756729125977, + 0.000405942730139941, + -0.010063810274004936, + -0.009100253693759441, + 0.0025516399182379246, + -0.03825675696134567, + -0.0026765454094856977, + -0.012704668566584587, + -0.026123084127902985, + 0.031262051314115524, + 0.022839853540062904, + -0.012776043266057968, + 0.012062297202646732, + -0.0037293198984116316, + 0.004960530903190374, + 0.010063810274004936, + -0.014060785062611103, + 0.014203534461557865, + 0.015559650957584381, + 0.0003055722627323121, + 0.009028879925608635, + -0.013632537797093391, + -0.03797125816345215, + -0.024981088936328888, + 0.016987141221761703, + -0.03682926669716835, + 0.014346282929182053, + -0.005924087017774582, + -0.012347796000540257, + -0.03939874842762947, + 0.0019271125784143806, + 0.02055586874485016, + -0.028121570125222206, + -0.02769332379102707, + -0.007993948645889759, + -0.057099636644124985, + -0.04653619974851608, + -0.014703156426548958, + -0.0005933009088039398, + -0.020841365680098534, + 0.01941387541592121, + -0.005103279836475849, + 0.004460908938199282, + 0.01684439182281494, + -0.028121570125222206, + 0.03340328857302666, + -0.0034259778913110495, + -0.0007137454231269658, + -0.021126866340637207, + -0.0083151338621974, + -0.006637832149863243, + 0.03497352823615074, + 0.021126866340637207, + 0.015416900627315044, + 0.0037293198984116316, + -0.013489789329469204, + 0.02897806465625763, + 0.016987141221761703, + -0.04082623869180679, + 0.003854225156828761, + 0.027978820726275444, + -0.0023375162854790688, + 0.0006780581315979362, + -0.006209585350006819, + 0.004318160004913807, + -0.01356116309762001, + -0.01670164428651333, + 0.011491301469504833, + -0.019699374213814735, + 0.010706181637942791, + -0.004371691029518843, + 0.0014096471713855863, + 0.0782264992594719, + 5.046403020969592e-05, + -0.0030512618832290173, + 0.008600632660090923, + -0.014631781727075577, + 0.004603658337146044, + 0.0038899127393960953, + -0.028549818322062492, + -0.007208828814327717, + 0.049962177872657776, + -0.037685759365558624, + -0.039684247225522995, + -0.010135185904800892, + -0.025980334728956223, + -0.011134428903460503, + 0.0070303925313055515, + -0.006673519499599934, + -0.0076370760798454285, + 0.04567970708012581, + -0.017201265320181847, + 0.005959774367511272, + 0.015987897291779518, + 0.010848930105566978, + 0.02969180978834629, + 0.000785120006185025, + 0.02783607318997383, + -0.0251238401979208, + 0.01748676411807537, + -0.00513896718621254, + 0.03226129338145256, + 0.006423708982765675, + 0.008457883261144161, + -0.0037828509230166674, + -0.029120812192559242, + 0.011705424636602402, + 0.028835315257310867, + -0.042253732681274414, + 0.05481564626097679, + 0.004924843553453684, + -0.012490544468164444, + 0.03240404278039932, + 0.012133672833442688, + 0.03611551970243454, + -0.02640858106315136, + -0.03425978124141693, + -0.011063054203987122, + -0.0083151338621974, + -0.019842123612761497, + 0.0062809600494802, + 0.018842879682779312, + -0.006423708982765675, + -0.03996974229812622, + -0.009778312407433987, + -0.0004728563653770834, + 0.006066836416721344, + -0.03154754638671875, + -0.017915010452270508, + 0.013918036594986916, + 0.011919548735022545, + -0.06052561476826668, + 0.018414633348584175, + -0.0056029013358056545, + -0.011205802671611309, + -0.01870013028383255, + 0.012133672833442688, + -0.012776043266057968, + 0.040255241096019745, + 0.03425978124141693, + -0.01413215883076191, + 0.042253732681274414, + 0.030120057985186577, + -0.017843635752797127, + 0.014489032328128815, + -0.015060028992593288, + 0.011990923434495926, + 0.06109660863876343, + -0.0032118544913828373, + -0.01163404993712902, + 0.008779069408774376, + 0.004924843553453684, + -0.03483077511191368, + -0.020841365680098534, + -0.044823210686445236, + 0.016416145488619804, + -0.009992435574531555, + 0.002203688956797123, + -0.014560406096279621, + -0.026979578658938408, + 0.019699374213814735, + -0.01127717737108469, + 0.044823210686445236, + -0.014203534461557865, + -0.027550572529435158, + 0.006994705181568861, + -0.01284741796553135, + 0.03939874842762947, + -0.04196823388338089, + 0.0018111290410161018, + 0.01320429053157568, + -0.009564189240336418, + -0.012419170700013638, + -0.007851199246942997, + -0.13989411294460297, + -0.030120057985186577, + 0.014988653361797333, + -0.04539421200752258, + -0.0047464072704315186, + -0.03682926669716835, + -0.017129890620708466, + 0.009564189240336418, + 0.013918036594986916, + 0.018414633348584175, + -0.035687271505594254, + -0.02969180978834629, + -0.017272640019655228, + -0.021412363275885582, + -0.006959018297493458, + 0.034545280039310455, + 0.006459395866841078, + 0.006637832149863243, + 0.010920305736362934, + -0.0021590800024569035, + 0.06880506128072739, + 0.003122636117041111, + 0.011562676168978214, + 0.023410849273204803, + -0.008886130526661873, + 0.011848174035549164, + 0.010706181637942791, + 0.029120812192559242, + -0.024410095065832138, + 0.01263329479843378, + 0.024410095065832138, + 0.05567214637994766, + -0.013632537797093391, + -0.05281716212630272, + 0.0007048235856927931, + -0.023981846868991852, + 0.0321185439825058, + -0.02312535233795643, + 0.0125619200989604, + -0.005995461717247963, + 0.006495083682239056, + -0.0016594580374658108, + 0.01798638515174389, + -0.03611551970243454, + 0.0015256309416145086, + 0.02198336087167263, + 0.023410849273204803, + 0.0026587017346173525, + -0.0037293198984116316, + 0.03996974229812622, + 0.007137454580515623, + -0.0026587017346173525, + -0.012490544468164444, + -0.026694077998399734, + 0.029406312853097916, + -0.024410095065832138, + -0.010777555406093597, + -0.011705424636602402, + 0.009849687106907368, + -0.0125619200989604, + -0.042824726551771164, + 0.009564189240336418, + 0.018985629081726074, + -0.02055586874485016, + -0.04196823388338089, + 0.03411703184247017, + 0.05481564626097679, + -0.04539421200752258, + -0.021555110812187195, + 0.017772261053323746, + -0.005674276500940323, + -0.011205802671611309, + -0.010920305736362934, + 0.031119301915168762, + -0.023410849273204803, + 0.029834559187293053, + 0.037685759365558624, + 0.030120057985186577, + -0.007387265097349882, + -0.002462421776726842, + -0.013347038999199867, + -0.000852033612318337, + -0.002962043508887291, + -0.06138210743665695, + 0.011419926770031452, + -0.033831533044576645, + -0.027978820726275444, + -0.009778312407433987, + -0.01670164428651333, + 0.04510870948433876, + -0.009421439841389656, + 0.009564189240336418, + 0.04824918881058693, + -0.013703911565244198, + -0.0022750634234398603, + 0.03797125816345215, + 0.02583758346736431, + 0.03611551970243454, + -0.012347796000540257, + 0.027407823130488396, + -0.022126108407974243, + 0.009278690442442894, + 0.017629511654376984, + 0.03825675696134567, + 0.025552086532115936, + 0.006173898000270128, + 0.006173898000270128, + -0.0037828509230166674, + -0.05110417306423187, + 0.0022393763065338135, + -0.015488276258111, + 0.013061542063951492, + -0.02326809987425804, + 0.026123084127902985, + 0.002498108893632889, + -0.02640858106315136, + -0.020413119345903397, + -0.01677301712334156, + -0.010063810274004936, + -0.0017219107830896974, + 0.0072802030481398106, + 0.05652863532304764, + 0.014560406096279621, + -0.030120057985186577, + -0.010848930105566978, + 0.007173141464591026, + 0.0062809600494802, + -0.01870013028383255, + 0.0010483135702088475, + 0.011705424636602402, + -0.03140479698777199, + 0.03026280738413334, + -0.020413119345903397, + 0.017201265320181847, + 0.005388777703046799, + -0.049962177872657776, + 0.014631781727075577, + -0.0256948359310627, + -0.04082623869180679, + -0.009564189240336418, + 0.049962177872657776, + -0.015631025657057762, + 0.01927112601697445, + 0.0010483135702088475, + -0.035116277635097504, + -0.011063054203987122, + -0.02126961387693882, + 0.029834559187293053, + -0.028121570125222206, + -0.011348553001880646, + -0.014845904894173145, + 0.009278690442442894, + -0.01798638515174389, + 0.04111173748970032, + 0.026694077998399734, + -0.023410849273204803, + -0.006673519499599934, + 0.005852712318301201, + 0.0034616654738783836, + -0.023696348071098328, + -0.042253732681274414, + -0.04653619974851608, + -0.022126108407974243, + -0.028549818322062492, + 0.0077798254787921906, + -0.04682169854640961, + -0.022268857806921005, + 0.025409337133169174, + -0.02712232619524002, + -0.025552086532115936, + 0.03297504037618637, + -0.03169029951095581, + 0.05538664758205414, + 0.016487520188093185, + 0.009350065141916275, + 0.0062809600494802, + -0.04453771561384201, + 0.019842123612761497, + 0.0036757891066372395, + -0.002203688956797123, + 0.018557380884885788, + -0.010991680435836315, + 0.03939874842762947, + -0.015916524454951286, + 0.07651351392269135, + -0.04311022162437439, + -0.02954906038939953, + -0.01870013028383255, + -0.026551328599452972, + 0.0160592719912529, + 0.01812913455069065, + 0.037114761769771576, + -0.009992435574531555, + -0.0004260168061591685, + 0.003800694365054369, + 0.0251238401979208, + 0.020413119345903397, + 0.003336759749799967, + 0.02383909747004509, + -0.017129890620708466, + -0.02840706892311573, + 0.009100253693759441, + 0.018271882086992264, + 0.010135185904800892, + -0.017272640019655228, + -0.009492814540863037, + -0.0321185439825058, + -0.01741538755595684, + 0.04082623869180679, + 0.008957505226135254, + -0.028121570125222206, + -0.01320429053157568, + -0.010492058470845222, + -0.009849687106907368, + 0.03268954157829285, + -0.05795612931251526, + -0.011063054203987122, + 0.007958262227475643, + 0.011205802671611309, + -0.02269710600376129, + 0.03169029951095581, + -0.029406312853097916, + 0.005995461717247963, + -0.03268954157829285, + -0.004371691029518843, + -0.009350065141916275, + -0.04853469133377075, + -0.022268857806921005, + -0.013132915832102299, + 0.02383909747004509, + 0.016344770789146423, + -0.002962043508887291, + 0.0008208072395063937, + 0.05281716212630272, + 0.021555110812187195, + -0.002962043508887291, + -0.0031047926750034094, + 0.02055586874485016, + -0.01798638515174389, + -0.03597277030348778, + -0.010420682840049267, + 0.02455284260213375, + 0.013418414629995823, + 0.026694077998399734, + 0.010206559672951698, + 0.058241624385118484, + 0.036401014775037766, + 0.009849687106907368, + 0.011776800267398357, + 0.025980334728956223, + 0.0015880835708230734, + -0.04311022162437439, + -0.035830020904541016, + -0.03026280738413334, + -0.019556624814867973, + -0.002096627140417695, + -0.017915010452270508, + -0.02840706892311573, + -0.02969180978834629, + -0.010206559672951698, + -0.006245272234082222, + 0.018842879682779312, + -0.04853469133377075, + -0.019128378480672836, + 0.01798638515174389, + 0.005174654070287943, + 0.01734401471912861, + 0.039684247225522995, + -0.01513140369206667, + 0.03425978124141693, + 0.023981846868991852, + -0.048820190131664276, + 0.019699374213814735, + 0.022411605343222618, + -0.010206559672951698, + 0.03026280738413334, + 0.01027793437242508, + 0.008779069408774376, + -0.0028906690422445536, + 0.009992435574531555, + -0.0022750634234398603, + -0.012776043266057968, + -3.652369196061045e-05, + 0.003979131113737822, + -0.04824918881058693, + 0.028121570125222206, + 0.017201265320181847, + 0.005460152868181467, + 0.013632537797093391, + 0.011063054203987122, + 0.010135185904800892, + 0.01998487114906311, + 0.01127717737108469, + 0.0035508836153894663, + 0.011848174035549164, + 0.012990167364478111, + 0.02326809987425804, + -0.007886886596679688, + 0.02954906038939953, + 0.019699374213814735, + -0.035687271505594254, + -0.009100253693759441, + 0.004050505347549915, + -0.015916524454951286, + 0.009278690442442894, + 0.0014364126836881042, + 0.014417657628655434, + -0.016987141221761703, + 0.0010260090930387378, + 0.047963693737983704, + -0.014060785062611103, + 0.06709206849336624, + -0.013061542063951492, + -0.00810101069509983, + 0.04054074361920357, + -0.0032832289580255747, + -0.015773773193359375, + -0.017629511654376984, + 0.05852712690830231, + 0.005067592952400446, + 0.042824726551771164, + 0.010349308140575886, + 0.02126961387693882, + -0.005959774367511272, + -0.026551328599452972, + -0.033117786049842834, + 0.009350065141916275, + -0.012704668566584587, + -0.035116277635097504, + -0.042253732681274414, + 0.0, + 0.03996974229812622, + -0.0385422520339489, + -0.0125619200989604, + -0.005424465052783489, + -0.009992435574531555, + 0.004086192697286606, + 0.014346282929182053, + -0.03825675696134567, + 0.006673519499599934, + -0.011776800267398357, + -0.03226129338145256, + -0.042253732681274414, + 0.03483077511191368, + -0.010563433170318604, + 0.025552086532115936, + 0.006209585350006819, + 0.005674276500940323, + 0.04596520587801933, + 0.009992435574531555, + -0.002016330836340785, + -0.035830020904541016, + -0.0491056852042675, + -0.0008609553915448487, + -0.01027793437242508, + -0.0028192943427711725, + 0.0026051707100123167, + -0.013918036594986916, + 0.009921061806380749, + 0.024695592001080513, + 0.0020074089989066124, + 0.05852712690830231, + -0.03425978124141693, + -0.0077441381290555, + 0.039684247225522995, + -0.0166302677243948, + 0.018557380884885788, + -0.01413215883076191, + 0.0027836072258651257, + -0.020270371809601784, + -0.004603658337146044, + -0.0037293198984116316, + -0.0036401015240699053, + 0.010420682840049267, + -0.019556624814867973, + -0.013347038999199867, + 0.0009813999058678746, + -0.05909812077879906, + -0.03368878364562988, + 0.007672763429582119, + -0.0017219107830896974, + 7.109573198249564e-05, + 0.03183304890990257, + -0.017558138817548752, + 0.01684439182281494, + 0.024838341400027275, + 0.004996217787265778, + -0.016344770789146423, + -0.01805775985121727, + 0.020984116941690445, + -0.026694077998399734, + 0.08450746536254883, + 0.035687271505594254, + 0.020413119345903397, + 0.017201265320181847, + 0.020127620548009872, + 0.028692565858364105, + 0.0007271281210705638, + 0.005888400133699179, + -0.00330107263289392, + -0.024695592001080513, + -0.004175411071628332, + -0.04539421200752258, + 0.010349308140575886, + -0.018557380884885788, + 0.017558138817548752, + -0.003657945431768894, + -0.0019271125784143806, + -0.005745650734752417, + -0.04196823388338089, + 0.01320429053157568, + 0.04767819494009018, + 0.03525902330875397, + 0.028692565858364105, + 0.02269710600376129, + -0.00838650856167078, + 0.03597277030348778, + -0.02255435474216938, + 0.015274152159690857, + -0.026836829259991646, + 0.01941387541592121, + -0.0015345526626333594, + 0.08222347497940063, + 0.02969180978834629, + -0.029406312853097916, + 0.011990923434495926, + 0.011205802671611309, + 0.017915010452270508, + -0.022126108407974243, + -0.0008029636228457093, + -0.02326809987425804, + 0.0023732036352157593, + 0.01941387541592121, + 0.028121570125222206, + 0.023553600534796715, + -0.009350065141916275, + -0.017843635752797127, + -0.005103279836475849, + 0.01677301712334156, + -0.02326809987425804, + 0.02954906038939953, + 0.007387265097349882, + 0.01063480693846941, + 0.01998487114906311, + 0.01127717737108469, + 0.010920305736362934, + 0.024838341400027275, + -0.015773773193359375, + 0.030120057985186577, + 0.005781338084489107, + -0.018200507387518883, + -0.03440253064036369, + 0.0017219107830896974, + 0.01284741796553135, + -0.0665210708975792, + -0.007315890863537788, + -0.022126108407974243, + 0.0385422520339489, + 0.001516709104180336, + 0.004960530903190374, + 0.0067092073149979115, + -0.00023865862749516964, + -0.023553600534796715, + -0.01684439182281494, + 0.04196823388338089, + -0.024695592001080513, + -0.01613064669072628, + -0.024124594405293465, + -0.04139723256230354, + 0.0021323144901543856, + -0.04824918881058693, + 0.014203534461557865, + -0.0160592719912529, + -0.023981846868991852, + -0.019842123612761497, + -0.031119301915168762, + 0.008136698044836521, + 0.009278690442442894, + 0.019699374213814735, + 0.009421439841389656, + -0.005638588685542345, + -0.013775286264717579, + 0.027550572529435158, + 0.09764038026332855, + -0.021840611472725868, + 0.03797125816345215, + 0.023696348071098328, + 0.014917279593646526, + -0.005531527101993561, + 0.03397428244352341, + -0.005638588685542345, + -0.0013471944257616997, + 0.006566457916051149, + 0.009706937707960606, + 0.022982602939009666, + -0.02326809987425804, + -0.008208072744309902, + 0.017629511654376984, + 0.023553600534796715, + -0.009350065141916275, + 0.010135185904800892, + 0.0018111290410161018, + -0.04082623869180679, + -0.015202777460217476, + 0.01263329479843378, + -0.0028014506679028273, + -0.006245272234082222, + -0.017272640019655228, + 0.018985629081726074, + -0.01677301712334156, + 0.017558138817548752, + -0.005852712318301201, + 3.652369196061045e-05, + -0.009706937707960606, + 0.020413119345903397, + 0.01063480693846941, + -0.031975794583559036, + 0.04082623869180679, + 0.03140479698777199, + 0.03083380125463009, + -0.030691053718328476, + -0.018414633348584175, + 0.013846661895513535, + -0.022411605343222618, + -0.010991680435836315, + -0.024267345666885376, + 0.0125619200989604, + -0.008743382059037685, + -0.0063523342832922935, + -0.03240404278039932, + 0.014988653361797333, + 0.006245272234082222, + 0.003961287438869476, + 0.004228941630572081, + 0.01613064669072628, + 0.006566457916051149, + -0.01812913455069065, + -0.01413215883076191, + 0.009778312407433987, + -0.0251238401979208, + -0.0251238401979208, + 0.04196823388338089, + 0.04824918881058693, + -0.026694077998399734, + 0.011848174035549164, + -0.030691053718328476, + 0.04196823388338089, + -0.031119301915168762, + 0.010777555406093597, + -0.04253922775387764, + -0.029977306723594666, + -0.007815512828528881, + -0.01870013028383255, + -0.001084000919945538, + -0.035116277635097504, + -0.005638588685542345, + 0.013632537797093391, + 0.007922574877738953, + -0.03468802943825722, + 0.009849687106907368, + -0.003033418208360672, + 0.010135185904800892, + 0.02069861628115177, + -0.018842879682779312, + -0.0006557536544278264, + -0.01413215883076191, + 0.015274152159690857, + -0.012276421301066875, + 0.012347796000540257, + 0.017201265320181847, + -0.011990923434495926, + 0.012276421301066875, + 0.019556624814867973, + 0.007886886596679688, + 0.0018111290410161018, + -0.01356116309762001, + 0.007244516164064407, + -0.017201265320181847, + 0.004443065263330936, + 0.005460152868181467, + -0.027265075594186783, + -0.01998487114906311, + 0.007494326680898666, + -0.016558893024921417, + 0.007708450313657522, + 0.016915766522288322, + 0.01670164428651333, + 0.0001416338636772707, + -0.05167516693472862, + 0.020270371809601784, + 0.03540177270770073, + 0.028692565858364105, + -0.0010929227573797107, + -0.0023732036352157593, + -0.03497352823615074, + 0.005959774367511272, + -0.0004884695517830551, + -0.011848174035549164, + 0.01163404993712902, + 0.00330107263289392, + 0.007494326680898666, + 0.016416145488619804, + -0.03240404278039932, + -0.02198336087167263, + -0.019699374213814735, + -0.042253732681274414, + 0.010135185904800892, + -0.009992435574531555, + 0.00040148181142285466, + 0.0491056852042675, + -0.024267345666885376, + 0.02455284260213375, + -0.03283229097723961, + -0.010206559672951698, + 0.007351578213274479, + -0.00870769377797842, + -0.010492058470845222, + -0.027978820726275444, + -0.0012668982381001115, + -0.05881262570619583, + -0.011491301469504833, + 0.01320429053157568, + -0.01163404993712902, + -0.032546792179346085, + -0.035830020904541016, + -0.026836829259991646, + 0.04054074361920357, + 0.03354603424668312, + -0.008243760094046593, + -0.007137454580515623, + 0.052531663328409195, + 0.00660214526578784, + -0.03026280738413334, + -0.026265831664204597, + 0.05624314025044441, + -0.05881262570619583, + -0.000990321859717369, + -0.012205047532916069, + 0.0068876431323587894, + -0.003979131113737822, + 0.012990167364478111, + 0.007565701846033335, + -0.005852712318301201, + -0.030548304319381714, + -0.027407823130488396, + -0.02583758346736431, + 0.004550126846879721, + 0.031975794583559036, + 0.033117786049842834, + 0.03882775083184242, + 0.01127717737108469, + 0.016487520188093185, + 0.026123084127902985, + 0.015631025657057762, + 0.01684439182281494, + -0.018985629081726074, + -0.024838341400027275, + 0.012490544468164444, + -0.02383909747004509, + -0.02255435474216938, + -0.003586570732295513, + 0.019699374213814735, + 0.020413119345903397, + -0.009635563008487225, + 0.00631664739921689, + -0.02526658959686756, + 0.026836829259991646, + 0.024981088936328888, + -0.05852712690830231, + 0.020841365680098534, + -0.0362582691013813, + 0.03939874842762947, + 0.009921061806380749, + -0.02526658959686756, + 0.05909812077879906, + 0.022839853540062904, + 0.014845904894173145, + 0.004443065263330936, + 0.009100253693759441, + 0.017201265320181847, + 0.00513896718621254, + -0.013275664299726486, + -0.012062297202646732, + 0.01063480693846941, + 0.0027300764340907335, + 0.04625070467591286, + -0.01748676411807537, + 0.037114761769771576, + -0.019842123612761497, + 0.024267345666885376, + -0.03825675696134567, + -0.008172385394573212, + -0.012776043266057968, + 0.021126866340637207, + 0.021126866340637207, + 0.018842879682779312, + 0.010135185904800892, + 0.027407823130488396, + -0.0010170872556045651, + 0.053102657198905945, + 0.012062297202646732, + 0.01127717737108469, + -0.023553600534796715, + -0.004282472655177116, + -0.01941387541592121, + 0.032546792179346085, + -0.05224616825580597, + -0.021126866340637207, + -0.03882775083184242, + -0.01320429053157568, + -0.009100253693759441, + -0.011419926770031452, + 0.0332605354487896, + 0.01027793437242508, + 0.02712232619524002, + 0.031975794583559036, + 0.05081867426633835, + 0.007601388730108738, + 0.008564945310354233, + -0.043395720422267914, + 0.020127620548009872, + 0.022126108407974243, + 0.019842123612761497, + -0.040255241096019745, + -0.024981088936328888, + 0.019699374213814735, + 0.02269710600376129, + -0.006423708982765675, + 0.03525902330875397, + 0.007208828814327717, + 0.03825675696134567, + -0.0491056852042675, + -0.0016416144790127873, + -0.0077441381290555, + 0.01027793437242508, + 0.028835315257310867, + 0.011848174035549164, + -0.027265075594186783, + -0.01513140369206667, + -0.011205802671611309, + -0.029263563454151154, + -0.02712232619524002, + 0.012276421301066875, + -0.022982602939009666, + -0.0021323144901543856, + 0.011491301469504833, + 0.017058515921235085, + 0.00403266167268157, + 0.013703911565244198, + 0.04253922775387764, + 0.001516709104180336, + 0.005210341420024633, + -0.004300316330045462, + 0.06109660863876343, + -0.01513140369206667, + -0.02069861628115177, + 0.0385422520339489, + 0.0007226672605611384, + 0.005103279836475849, + -0.005924087017774582, + -0.05738513171672821, + 0.012347796000540257, + 0.027978820726275444, + -0.01870013028383255, + -0.01263329479843378, + 0.012419170700013638, + 0.04054074361920357, + 0.01870013028383255, + -0.004228941630572081, + -0.007530014496296644, + -0.005031905137002468, + 0.01798638515174389, + 0.0031761671416461468, + 0.03268954157829285, + -0.08108148723840714, + 0.024838341400027275, + -0.006923330947756767, + 0.011419926770031452, + 0.003015574300661683, + 0.057099636644124985, + -0.009992435574531555, + 0.03540177270770073, + -0.02069861628115177, + 0.028121570125222206, + 0.06052561476826668, + -0.0011598363053053617, + -0.029120812192559242, + 0.03411703184247017, + -0.016487520188093185, + -0.02526658959686756, + -0.034545280039310455, + -0.03525902330875397, + -0.00513896718621254, + -0.001516709104180336, + 0.0024267344269901514, + 0.00024981089518405497, + -0.0012222890509292483, + -0.01284741796553135, + 0.00038363816565833986, + 0.026265831664204597, + 0.012918791733682156, + -0.028835315257310867, + 0.012990167364478111, + -0.002640858292579651, + 0.024124594405293465, + 0.007066079415380955, + 0.013489789329469204, + 0.0166302677243948, + -0.008243760094046593, + -0.0026765454094856977, + 0.031262051314115524, + 0.0038185380399227142, + -0.03797125816345215, + 0.023696348071098328, + -0.008814755827188492, + 0.0374002605676651, + -0.027265075594186783, + -0.049962177872657776, + 0.02897806465625763, + -0.03283229097723961, + 0.0031761671416461468, + 0.019556624814867973, + 0.014631781727075577, + -0.005959774367511272, + -0.043681222945451736, + -0.020127620548009872, + -0.026265831664204597, + 0.06709206849336624, + 0.02055586874485016, + -0.03882775083184242, + 0.01927112601697445, + -0.011919548735022545, + -0.026123084127902985, + -0.004478752613067627, + -0.005281716585159302, + -0.005103279836475849, + 0.0053530908189713955, + -0.023553600534796715, + -0.03611551970243454, + -0.024695592001080513, + -0.009207316674292088, + -0.027265075594186783, + 0.0026943888515233994, + 0.002301829168573022, + 0.011848174035549164, + -0.03440253064036369, + 0.042253732681274414, + -0.004710719920694828, + -0.04710720106959343, + 0.00043270818423479795, + 0.039684247225522995, + -0.05081867426633835, + -0.0035330397076904774, + 0.009064567275345325, + 0.04054074361920357, + -0.03654376417398453, + -0.028692565858364105, + -0.037685759365558624, + 0.0015345526626333594, + -0.016487520188093185, + -0.008208072744309902, + -0.0160592719912529, + 0.015202777460217476, + 0.022982602939009666, + 0.052531663328409195, + -0.033831533044576645, + -0.013061542063951492, + -0.027407823130488396, + -0.017272640019655228, + 0.003086949000135064, + 0.015845149755477905, + 0.03554452210664749, + -0.022268857806921005, + 0.008993192575871944, + -0.026979578658938408, + 0.014203534461557865, + 0.02712232619524002, + 0.027265075594186783, + 0.043681222945451736, + -0.06909056007862091, + -0.009135941043496132, + -0.028835315257310867, + 0.02269710600376129, + 0.017201265320181847, + 0.03597277030348778, + -0.008636320009827614, + -0.03240404278039932, + 0.015488276258111, + 0.03654376417398453, + -0.0166302677243948, + -0.006173898000270128, + 0.013703911565244198, + 0.030691053718328476, + -0.0034438215661793947, + 0.024124594405293465, + 0.022126108407974243, + 0.043681222945451736, + -0.0014096471713855863, + -0.02455284260213375, + 0.02312535233795643, + 0.024838341400027275, + -0.016487520188093185, + -0.023981846868991852, + 0.017915010452270508, + 0.013132915832102299, + 0.010349308140575886, + 0.004817781504243612, + 0.0077798254787921906, + -0.03283229097723961, + 0.040255241096019745, + 0.015274152159690857, + -0.0010483135702088475, + -0.0083151338621974, + -0.020413119345903397, + 0.015845149755477905, + 0.021126866340637207, + -0.006459395866841078, + 0.008600632660090923, + -0.02069861628115177, + -0.006816268898546696, + -0.0005130045465193689, + -0.039684247225522995, + -0.0029442000668495893, + -0.03354603424668312, + 0.03797125816345215, + 0.02383909747004509, + -0.030120057985186577, + 0.0491056852042675, + 0.011919548735022545, + 0.0003278768272139132, + 0.016558893024921417, + -0.004532283637672663, + -0.029977306723594666, + 0.04853469133377075, + 0.042824726551771164, + 0.0026943888515233994, + 0.015773773193359375, + -0.04054074361920357, + -0.01870013028383255, + 0.03882775083184242, + 0.0374002605676651, + 0.005246029235422611, + -0.02326809987425804, + -0.007672763429582119, + 0.018842879682779312, + -0.026551328599452972, + 0.03882775083184242, + -0.025409337133169174, + -0.0028371382504701614, + -0.01812913455069065, + 0.0321185439825058, + 0.05852712690830231, + -0.011990923434495926, + -0.039684247225522995, + -0.005317403469234705, + -0.035116277635097504, + 0.0321185439825058, + -0.020127620548009872, + -0.04168273136019707, + -0.025409337133169174, + 0.05338815599679947, + -0.017843635752797127, + -0.004121880047023296, + -0.053959157317876816, + -0.004228941630572081, + -0.006495083682239056, + 0.024124594405293465, + -0.017843635752797127, + 0.011491301469504833, + -0.01063480693846941, + 0.006851955782622099, + -7.137453940231353e-05, + 0.009564189240336418, + -0.010991680435836315, + 0.018985629081726074, + 0.04767819494009018, + 0.04111173748970032, + -0.03340328857302666, + 0.037685759365558624, + 0.0022572199814021587, + -0.02783607318997383, + -0.018557380884885788, + -0.010777555406093597, + -0.05367365851998329, + -0.027978820726275444, + 0.012347796000540257, + -0.0025873270351439714, + -0.0008074244833551347, + -0.012918791733682156, + 0.04196823388338089, + -0.01677301712334156, + -0.008529257960617542, + 0.03140479698777199, + 0.018985629081726074, + -0.01163404993712902, + 0.01163404993712902, + 0.018842879682779312, + -0.003194010816514492, + 0.022126108407974243, + -0.05224616825580597, + 0.0004148645093664527, + 0.011562676168978214, + -0.017700886353850365, + 0.025409337133169174, + 0.03654376417398453, + -0.026123084127902985, + 0.004050505347549915, + -0.020270371809601784, + -0.00631664739921689, + 0.008743382059037685, + -0.018842879682779312, + -0.005281716585159302, + -0.049962177872657776, + 0.004443065263330936, + 0.021840611472725868, + -0.010563433170318604, + 0.06052561476826668, + -0.04168273136019707, + 0.025552086532115936, + -0.044823210686445236, + 0.01127717737108469, + 0.009849687106907368, + -0.011063054203987122, + 0.009992435574531555, + -0.04139723256230354, + 0.004139723256230354, + -0.005388777703046799, + -0.044823210686445236, + -0.00513896718621254, + -0.004853468853980303, + 0.018271882086992264, + 0.007387265097349882, + 0.02126961387693882, + -0.013489789329469204, + 0.06395158916711807, + 0.018842879682779312, + -0.026551328599452972, + 0.023410849273204803, + -0.023981846868991852, + -0.011776800267398357, + 0.030548304319381714, + -0.01798638515174389, + -0.00838650856167078, + -0.018842879682779312, + 0.01677301712334156, + -0.021555110812187195, + 0.011348553001880646, + -0.02069861628115177, + 0.0166302677243948, + -0.006816268898546696, + 0.0006200663628987968, + 0.019128378480672836, + -0.0024445781018584967, + 0.03825675696134567, + -0.025409337133169174, + 0.02455284260213375, + -0.01320429053157568, + 0.011134428903460503, + -0.001070618163794279, + 0.022268857806921005, + 0.0003992513520643115, + -0.011919548735022545, + 0.0016059272456914186, + -0.0031761671416461468, + -0.0041575669310987, + -0.02712232619524002, + -0.027978820726275444, + 0.03226129338145256, + 0.014988653361797333, + 0.06452258676290512, + 0.011919548735022545, + -0.015631025657057762, + 0.027550572529435158, + -0.011491301469504833, + -0.03340328857302666, + -0.00030780272209085524 + ], + "qwen3-embed-query-1536": [ + -0.0003852232184726745, + 0.0014851978048682213, + -0.05257600545883179, + 0.01990165188908577, + -0.0008261413313448429, + 0.08376516401767731, + 0.04039738327264786, + 0.007574509363621473, + -0.005049672909080982, + 0.02346612885594368, + 0.03653587028384209, + -0.01604013890028, + 0.0017915199277922511, + -0.041288502514362335, + 0.00590366218239069, + -0.056734561920166016, + 0.0, + 0.030595077201724052, + 0.0018936273409053683, + -0.0008539888658560812, + -0.01611439883708954, + 0.010247865691781044, + 0.028961358591914177, + 0.05168488994240761, + -0.025545405223965645, + -0.017228296026587486, + -0.030446557328104973, + 0.012995482422411442, + -0.004492723383009434, + -0.0037872546818107367, + -0.013292521238327026, + -0.014035120606422424, + -0.018564973026514053, + -0.031040634959936142, + 0.00478976359590888, + -0.018639232963323593, + 0.01626291684806347, + -0.000327207671944052, + -0.011213244870305061, + 0.033416952937841415, + -0.005643751937896013, + 0.017599595710635185, + 5.598499774350785e-05, + 0.017673855647444725, + 0.03831810876727104, + 0.04217962175607681, + 0.0005267811357043684, + -0.05406120419502258, + -0.045744094997644424, + -0.00950526725500822, + -0.009728046134114265, + 0.026733562350273132, + 0.041882582008838654, + -0.020495731383562088, + 0.11109280586242676, + 0.01915905438363552, + -0.013960860669612885, + -0.00036665826337412, + 0.008539888076484203, + 0.03208027780056, + 0.013218261301517487, + 0.03623883053660393, + -0.0075002494268119335, + -0.03252583369612694, + -0.006683390587568283, + -0.02346612885594368, + -0.0028033112175762653, + 0.006943300366401672, + -0.0020142998546361923, + 0.0018843448488041759, + 0.06178423389792442, + 0.013515301048755646, + 0.009653786197304726, + -0.011287503875792027, + 0.04069442301988602, + -0.034011032432317734, + -0.022872047498822212, + 0.030149517580866814, + 0.03222879767417908, + 0.0033602602779865265, + 0.03415955230593681, + -0.011361763812601566, + -0.010767684318125248, + 0.05406120419502258, + -0.016931256279349327, + 0.022426489740610123, + 0.044852979481220245, + 0.002274209400638938, + -0.002487706486135721, + -0.012624181807041168, + 0.0005499873659573495, + -0.008131458424031734, + 0.02807024121284485, + 0.014777719974517822, + -0.006349220871925354, + 0.012401402927935123, + -0.02019869163632393, + -0.03802106902003288, + -0.020792771130800247, + -0.0028590059373527765, + 0.0009468136704526842, + 0.0016430001705884933, + 0.04841745272278786, + 0.006349220871925354, + 0.020644251257181168, + -0.03549623116850853, + -0.016931256279349327, + 0.017451075837016106, + -0.0033045653253793716, + 0.017822375521063805, + 0.01373807992786169, + 0.03891218453645706, + 0.0386151447892189, + 0.005718011874705553, + 0.013069742359220982, + -0.013589560985565186, + 0.00246914173476398, + 0.05049672722816467, + 0.012846962548792362, + 0.049605611711740494, + -0.0014202204765751958, + 0.01618865691125393, + 0.005977921653538942, + -0.012698441743850708, + 0.018639232963323593, + -0.05881384015083313, + -0.007574509363621473, + 0.0007472402066923678, + 0.02747615985572338, + 0.009802306070923805, + 0.02376316487789154, + -0.020941291004419327, + -0.010693424381315708, + 0.0013830906245857477, + -0.044852979481220245, + -0.005680881906300783, + -0.012846962548792362, + -0.014109380543231964, + -0.03950626775622368, + 0.0011603108141571283, + -0.01901053451001644, + -0.03415955230593681, + 0.01641143672168255, + -0.028961358591914177, + -0.006609130650758743, + -0.0561404824256897, + 0.0010906922398135066, + -0.04782337322831154, + 0.015297538600862026, + -0.004864023067057133, + 0.0006822628201916814, + 0.001958604669198394, + 0.009802306070923805, + -0.005346712190657854, + 0.00043627689592540264, + 0.045447055250406265, + -0.007908678613603115, + -0.030446557328104973, + 0.030298037454485893, + -0.0009607374086044729, + -0.015000498853623867, + 0.02019869163632393, + 0.049011532217264175, + 0.033268433064222336, + -0.02717912197113037, + 0.017154036089777946, + -0.0019957346376031637, + 0.03252583369612694, + -0.001253135735169053, + 0.0, + -0.004177119117230177, + -0.005532362498342991, + 0.009133967570960522, + -0.04039738327264786, + 0.01990165188908577, + 0.021386848762631416, + -0.0038986443541944027, + 0.01945609226822853, + -0.020644251257181168, + 0.005569492466747761, + -0.006237830966711044, + -0.018787752836942673, + -0.008168588392436504, + -0.007463119458407164, + -0.0023206216283142567, + 0.044852979481220245, + 0.005532362498342991, + -0.01990165188908577, + 0.010099345818161964, + -0.017005516216158867, + 0.0071660797111690044, + 0.012624181807041168, + -0.008725537918508053, + 0.022277969866991043, + -0.006757650524377823, + 0.005123932845890522, + -0.025099843740463257, + -0.03653587028384209, + 0.028961358591914177, + 0.006163571495562792, + 0.018639232963323593, + 0.004047164227813482, + -0.02019869163632393, + -0.0378725491464138, + 0.030149517580866814, + -0.029258400201797485, + 0.012401402927935123, + -0.005532362498342991, + 0.01960461214184761, + -0.01648569665849209, + 0.0077601587399840355, + -0.010470645502209663, + -0.012252883054316044, + -0.07515101134777069, + 0.03118915483355522, + 0.0386151447892189, + -0.023020567372441292, + -0.01915905438363552, + 0.02376316487789154, + -0.0014851978048682213, + -0.005495232529938221, + -0.003545910120010376, + -0.018787752836942673, + 0.022723527625203133, + -0.02391168661415577, + -0.01596587710082531, + -0.0004455593880265951, + -0.0033973902463912964, + 0.007574509363621473, + -0.009022577665746212, + 0.041882582008838654, + 0.01581735722720623, + -0.010396385565400124, + 0.008094328455626965, + -0.015520317479968071, + -0.017376815900206566, + -0.041585542261600494, + 0.008651277981698513, + 0.017451075837016106, + -0.03579327091574669, + -0.014183640480041504, + -0.010619165375828743, + -0.018564973026514053, + 0.015371798537671566, + -0.0008725537918508053, + -0.03594178706407547, + 0.013589560985565186, + 0.00590366218239069, + -0.025990964844822884, + -0.0020328646060079336, + -0.010099345818161964, + 0.002673356095328927, + -0.0005917585804127157, + -0.0189362745732069, + -0.0328228734433651, + -0.022277969866991043, + -0.02376316487789154, + -0.06653686612844467, + 0.004121424164623022, + 0.010099345818161964, + -0.01611439883708954, + 0.033713992685079575, + -0.014851979911327362, + 0.0032303056214004755, + -0.021980930119752884, + -0.015149018727242947, + 0.010173605754971504, + -0.009282486513257027, + -0.018119415268301964, + -0.025099843740463257, + 0.02019869163632393, + -0.012772702611982822, + -0.012698441743850708, + 0.006497741211205721, + 0.045150015503168106, + -0.03297139331698418, + -0.03237731382250786, + -0.010544905439019203, + 0.005680881906300783, + -0.005420972127467394, + -0.007351730018854141, + -0.011658803559839725, + 0.008911187760531902, + -0.012624181807041168, + 0.0, + 0.030000997707247734, + 0.06178423389792442, + -0.010173605754971504, + -0.008539888076484203, + 0.002005017362535, + 0.007463119458407164, + -0.0003991469566244632, + 0.005161062814295292, + 0.0032303056214004755, + -0.041585542261600494, + -0.022723527625203133, + 0.010247865691781044, + -0.033416952937841415, + 0.024951323866844177, + 0.0011138984700664878, + -0.04247666150331497, + -0.014703460037708282, + -0.0069061703979969025, + 0.04247666150331497, + 0.006200701463967562, + 0.04247666150331497, + 0.02420872636139393, + 0.011287503875792027, + 0.008836927823722363, + 0.00037826134939678013, + -0.021683888509869576, + -0.010990465059876442, + 0.03148619458079338, + 0.0016430001705884933, + 0.003935774322599173, + 0.017822375521063805, + 0.010322125628590584, + -0.021980930119752884, + -0.0023020568769425154, + -0.02331760711967945, + -0.02005017176270485, + -0.04307073727250099, + 0.05851680040359497, + -0.012846962548792362, + 0.018713492900133133, + 0.025693925097584724, + 0.04069442301988602, + 0.029555439949035645, + 0.037724025547504425, + 0.014332159422338009, + 0.0028590059373527765, + 0.029852477833628654, + -0.06386350840330124, + -0.014703460037708282, + -0.01611439883708954, + -0.007388859987258911, + 0.008799797855317593, + 0.046041134744882584, + 0.010693424381315708, + -0.01589161716401577, + -0.007611639332026243, + -0.008836927823722363, + -0.018416455015540123, + -0.03534771129488945, + -0.05406120419502258, + -0.009245357476174831, + -0.010990465059876442, + 0.012327142991125584, + 0.021238330751657486, + -0.007388859987258911, + -0.03638735041022301, + -0.014035120606422424, + -0.10336977988481522, + -0.014926238916814327, + 0.008539888076484203, + -0.0015965878264978528, + -0.00957952719181776, + 0.03252583369612694, + -0.02420872636139393, + 0.015520317479968071, + -0.009950825944542885, + -0.009393876418471336, + -0.0071660797111690044, + 0.03222879767417908, + 0.05911087989807129, + -0.03445659205317497, + -0.014554939232766628, + 0.02331760711967945, + 0.04217962175607681, + -0.022575007751584053, + 0.05257600545883179, + 0.024654285982251167, + 0.06237830966711044, + -0.03920922428369522, + -0.006720520555973053, + -0.012252883054316044, + 0.037724025547504425, + -0.02762468159198761, + -0.017896635457873344, + -0.004641243256628513, + 0.033713992685079575, + 0.03505067154765129, + 0.003156045451760292, + 0.0019771696534007788, + -0.024951323866844177, + 0.011138984933495522, + -0.03222879767417908, + -0.049308571964502335, + 0.018713492900133133, + 0.015149018727242947, + 0.012921222485601902, + 0.04633817449212074, + 0.008911187760531902, + -0.03490215167403221, + 0.05138785019516945, + 0.02792172133922577, + 0.016634216532111168, + 0.012846962548792362, + 0.01975313387811184, + -0.020495731383562088, + -0.009802306070923805, + -0.00987656693905592, + -0.03980330377817154, + -0.0020514295902103186, + 0.016856996342539787, + -0.005198192782700062, + 0.009282486513257027, + 0.0401003435254097, + 0.002237079432234168, + 0.029258400201797485, + 0.060596074908971786, + -8.528285252396017e-05, + 0.01618865691125393, + -0.034011032432317734, + -0.011584543623030186, + -0.02703060209751129, + 0.014406419359147549, + -0.013292521238327026, + 0.026139484718441963, + 0.015297538600862026, + -0.0006080028833821416, + -0.02792172133922577, + -0.0037686896976083517, + -0.0028775709215551615, + 0.028961358591914177, + 0.008874057792127132, + 0.02792172133922577, + 0.006497741211205721, + -0.050199687480926514, + 0.0027661812491714954, + -0.03579327091574669, + -0.03089211694896221, + -0.03519919142127037, + 0.04307073727250099, + -0.005086802877485752, + 0.03118915483355522, + 0.034011032432317734, + -0.009171097539365292, + -0.025842444971203804, + -0.07782436907291412, + -0.03980330377817154, + -0.04782337322831154, + 0.006349220871925354, + 0.01915905438363552, + 0.007054690271615982, + -0.008651277981698513, + 0.00014851980085950345, + 0.02034721150994301, + 0.031040634959936142, + 0.03891218453645706, + -0.0273276437073946, + 0.037724025547504425, + -0.020644251257181168, + -0.0032488706056028605, + -0.05109081044793129, + 7.484005618607625e-05, + -0.009950825944542885, + 0.014406419359147549, + -0.012624181807041168, + 0.03712994605302811, + -0.06772502511739731, + 0.0002262606139993295, + -0.008242848329246044, + 0.020792771130800247, + -0.06029903516173363, + -0.04336777701973915, + -0.0039543393068015575, + 0.025842444971203804, + -0.03564475104212761, + 0.044555939733982086, + 0.010247865691781044, + 0.021089810878038406, + 0.0008261413313448429, + -0.002673356095328927, + -0.0009653786546550691, + 0.01581735722720623, + 0.024951323866844177, + -0.017748115584254265, + 0.012327142991125584, + -0.021832410246133804, + -0.03475363180041313, + -0.024357246235013008, + 0.05584344267845154, + 0.022872047498822212, + 0.030149517580866814, + 0.018787752836942673, + -0.016708476468920708, + 0.053170084953308105, + 0.009245357476174831, + 0.05940791964530945, + 0.007797288708388805, + 0.028218761086463928, + -0.018787752836942673, + -0.057328637689352036, + -0.021980930119752884, + -0.010619165375828743, + 0.0273276437073946, + -0.004399898927658796, + 0.02361464872956276, + 0.04277369752526283, + 0.0001520007208455354, + -0.021832410246133804, + 0.015446058474481106, + -0.005198192782700062, + -0.0018008024198934436, + -0.021238330751657486, + 0.06980430334806442, + -0.0008029351592995226, + -0.012104363180696964, + 0.003490215167403221, + -0.010173605754971504, + 0.04693225771188736, + -0.020644251257181168, + -0.01901053451001644, + -0.021089810878038406, + -0.014777719974517822, + -0.01589161716401577, + -0.030298037454485893, + 0.025990964844822884, + -0.04396186023950577, + -0.04247666150331497, + -0.03222879767417908, + 0.0015873052179813385, + -0.02316908724606037, + -0.0006404916057363153, + 0.025248363614082336, + -0.003638734808191657, + 0.031040634959936142, + -0.017079776152968407, + 0.010619165375828743, + -0.00023206218611449003, + 0.03237731382250786, + -0.009728046134114265, + 0.0038243846502155066, + 0.02361464872956276, + -0.026436522603034973, + -0.009728046134114265, + -0.025842444971203804, + -0.03579327091574669, + 0.0010349972872063518, + -0.00802006945014, + 0.0016894126310944557, + -0.0015316103817895055, + 0.0273276437073946, + 0.002005017362535, + -0.03178323432803154, + -0.05049672722816467, + 0.017079776152968407, + 0.009208227507770061, + 0.044852979481220245, + 0.007425989955663681, + -0.01180732436478138, + -0.008688407950103283, + -0.021683888509869576, + -0.014777719974517822, + 0.044852979481220245, + 0.045150015503168106, + 0.018267935141921043, + 0.02420872636139393, + -0.020792771130800247, + 0.022872047498822212, + 0.005606622435152531, + 0.04069442301988602, + 0.0328228734433651, + 0.048714492470026016, + -0.006237830966711044, + -0.013589560985565186, + 0.04633817449212074, + 0.01626291684806347, + -0.002561966422945261, + 0.03564475104212761, + 0.00590366218239069, + 0.02361464872956276, + -0.001253135735169053, + 0.028961358591914177, + 0.06326942890882492, + 0.004715503193438053, + 0.01618865691125393, + 0.0011092572240158916, + -0.013589560985565186, + 0.008465628139674664, + 0.010099345818161964, + -0.010841944254934788, + -0.02703060209751129, + 0.010322125628590584, + -0.018193675205111504, + -0.008539888076484203, + 0.021386848762631416, + -0.012327142991125584, + 0.014777719974517822, + -0.045744094997644424, + -0.01559457741677761, + -0.0027476162649691105, + 0.01990165188908577, + -0.003471650183200836, + -0.006089311558753252, + 0.06000199541449547, + -0.022872047498822212, + 0.003527345135807991, + 0.033862512558698654, + 0.046041134744882584, + 0.003063220763579011, + -0.03950626775622368, + -0.007425989955663681, + -0.01945609226822853, + -0.010322125628590584, + 0.029703959822654724, + 0.046041134744882584, + -0.021832410246133804, + 0.06683390587568283, + 0.05138785019516945, + 0.015520317479968071, + 0.037426985800266266, + -0.011138984933495522, + 0.050199687480926514, + -0.012104363180696964, + 0.008094328455626965, + -0.020941291004419327, + -0.011213244870305061, + 0.005123932845890522, + -0.017599595710635185, + -0.057328637689352036, + -0.014035120606422424, + 0.0018564974889159203, + 0.018713492900133133, + -0.017822375521063805, + -0.0466352142393589, + -0.011213244870305061, + 0.026436522603034973, + 0.00987656693905592, + 0.00401003472507, + 0.0009839435806497931, + -0.009356746450066566, + -0.010247865691781044, + 0.0031931756529957056, + -0.034011032432317734, + -0.014703460037708282, + 0.0401003435254097, + 0.008205718360841274, + -0.003917209338396788, + 0.0010257147951051593, + -0.00046412437222898006, + 0.03237731382250786, + 0.01581735722720623, + 0.017599595710635185, + -0.005829401779919863, + -0.045744094997644424, + 0.01990165188908577, + 0.057328637689352036, + 0.01188158243894577, + -0.001958604669198394, + 0.024802805855870247, + -0.022872047498822212, + 0.02005017176270485, + -0.02703060209751129, + 0.012178623117506504, + 0.017896635457873344, + -0.03594178706407547, + 0.04752632975578308, + -0.044555939733982086, + 0.044852979481220245, + -0.03950626775622368, + 0.011287503875792027, + -0.03297139331698418, + -0.022872047498822212, + 0.010247865691781044, + -0.014554939232766628, + -0.028664318844676018, + 0.020792771130800247, + -0.05109081044793129, + -0.04336777701973915, + 0.03490215167403221, + 0.03193175420165062, + 0.011584543623030186, + 0.01975313387811184, + 0.001253135735169053, + 0.005792271811515093, + -0.006683390587568283, + -0.022723527625203133, + 0.033119913190603256, + -0.02019869163632393, + 0.020941291004419327, + 0.01559457741677761, + 0.010470645502209663, + 0.005123932845890522, + -0.025842444971203804, + 0.02034721150994301, + -0.017970893532037735, + 0.0024134465493261814, + 0.020644251257181168, + -0.020792771130800247, + -0.03950626775622368, + 0.03475363180041313, + 0.014183640480041504, + 0.05138785019516945, + 0.012772702611982822, + 0.016931256279349327, + -0.029703959822654724, + -0.011510283686220646, + -0.013292521238327026, + -0.053170084953308105, + 0.004288509022444487, + 0.01626291684806347, + -0.025693925097584724, + -0.033862512558698654, + -0.018713492900133133, + -0.025990964844822884, + -0.022129449993371964, + 0.021089810878038406, + -0.0386151447892189, + -0.04069442301988602, + 0.036684390157461166, + -0.036981429904699326, + -0.04782337322831154, + -0.03802106902003288, + -0.01641143672168255, + -0.07158654183149338, + -0.018787752836942673, + -0.0692102238535881, + -0.00293326610699296, + -0.002042147098109126, + -0.029109878465533257, + -0.024951323866844177, + -0.008131458424031734, + -0.004864023067057133, + 0.013366781175136566, + 0.030743597075343132, + -0.012921222485601902, + 0.0386151447892189, + 0.044852979481220245, + -0.00073331652674824, + 0.006089311558753252, + -0.044852979481220245, + 0.010544905439019203, + -0.014035120606422424, + -0.007723029237240553, + -0.008651277981698513, + -0.041288502514362335, + 0.04039738327264786, + -0.025693925097584724, + 0.037278465926647186, + 0.025990964844822884, + -0.029703959822654724, + -0.0003388107579667121, + 0.03950626775622368, + -0.020792771130800247, + 0.005643751937896013, + -0.03594178706407547, + 0.03208027780056, + -0.04277369752526283, + 0.03623883053660393, + 0.006237830966711044, + 0.005383842159062624, + 0.004622678738087416, + -0.01180732436478138, + 0.030298037454485893, + 0.01945609226822853, + 0.03920922428369522, + 0.004288509022444487, + -0.03118915483355522, + -0.04069442301988602, + -0.022426489740610123, + -0.03089211694896221, + -0.00946813728660345, + -0.018045155331492424, + -0.01626291684806347, + 0.025396883487701416, + -0.024951323866844177, + 0.006980430334806442, + 0.03148619458079338, + -0.024654285982251167, + -0.010916205123066902, + 0.002561966422945261, + -0.0010860509937629104, + -0.0021071245428174734, + 0.011955843307077885, + -0.031040634959936142, + -0.022723527625203133, + 0.05049672722816467, + 0.004232814069837332, + 0.005680881906300783, + -0.013886600732803345, + 0.030298037454485893, + 0.041288502514362335, + -0.05109081044793129, + 0.0016244353028014302, + -0.02792172133922577, + -0.01648569665849209, + 0.021386848762631416, + -0.009950825944542885, + -0.014035120606422424, + 0.02376316487789154, + -0.045447055250406265, + -0.017896635457873344, + -0.017451075837016106, + -0.03831810876727104, + 0.014257900416851044, + -0.01611439883708954, + -0.020792771130800247, + 0.009208227507770061, + -0.018862012773752213, + -0.011361763812601566, + -0.014777719974517822, + 0.026139484718441963, + -0.016856996342539787, + 0.005680881906300783, + 0.008317108266055584, + -0.020644251257181168, + -0.011955843307077885, + 0.00987656693905592, + 0.010247865691781044, + -0.03178323432803154, + -0.04782337322831154, + 0.013886600732803345, + -0.026585042476654053, + 0.012252883054316044, + 0.033416952937841415, + -0.03549623116850853, + -0.010173605754971504, + -0.017896635457873344, + 0.007314600050449371, + 0.01990165188908577, + 0.021980930119752884, + 0.026882082223892212, + -0.024505766108632088, + -0.004826893098652363, + 0.018564973026514053, + -0.03208027780056, + 0.010470645502209663, + 0.010619165375828743, + 0.017525335773825645, + 0.029555439949035645, + -0.044852979481220245, + -0.011436023749411106, + 0.005569492466747761, + 0.0033788252621889114, + 0.0328228734433651, + 0.00987656693905592, + -0.016856996342539787, + 0.028367280960083008, + 0.03445659205317497, + 0.00957952719181776, + -0.010916205123066902, + -0.04366482049226761, + 0.018490714952349663, + -0.008911187760531902, + -0.010322125628590584, + 0.0019121923251077533, + -0.006683390587568283, + 0.04277369752526283, + -0.01960461214184761, + 0.026585042476654053, + -0.003118915483355522, + 0.044852979481220245, + 0.03920922428369522, + 0.04396186023950577, + 0.0008168588974513113, + 0.0018750623567029834, + 0.009133967570960522, + -0.017154036089777946, + 0.03594178706407547, + -0.015520317479968071, + -0.02019869163632393, + -0.010619165375828743, + 0.004177119117230177, + 0.02005017176270485, + -0.06178423389792442, + 0.009728046134114265, + -0.01173306442797184, + -0.04693225771188736, + 0.02316908724606037, + -0.016782736405730247, + 0.05821975693106651, + 0.0386151447892189, + 0.03891218453645706, + -0.007240339647978544, + -0.045744094997644424, + -0.007054690271615982, + -0.017005516216158867, + 0.005012542940676212, + 0.0024505765177309513, + 0.009282486513257027, + -0.0007286752224899828, + 0.03193175420165062, + 0.005606622435152531, + 0.028367280960083008, + 0.009950825944542885, + 0.03712994605302811, + -0.006869039963930845, + 0.049308571964502335, + -0.045744094997644424, + 0.0378725491464138, + -0.010396385565400124, + -0.01975313387811184, + -0.022426489740610123, + -0.018862012773752213, + -0.025842444971203804, + -0.04693225771188736, + 0.025693925097584724, + -0.04247666150331497, + 0.033119913190603256, + 0.011584543623030186, + -0.014480679295957088, + 0.003100350731983781, + 0.018193675205111504, + 0.009356746450066566, + -0.03252583369612694, + 0.03638735041022301, + 0.01930757239460945, + -0.02376316487789154, + -0.03564475104212761, + 0.026585042476654053, + -0.018639232963323593, + 0.017599595710635185, + -0.010767684318125248, + -0.018564973026514053, + -0.04841745272278786, + -0.01990165188908577, + -0.01975313387811184, + 0.026436522603034973, + 0.0037872546818107367, + -0.0038243846502155066, + -0.026288002729415894, + 0.015223278664052486, + 0.026585042476654053, + -0.0013738081324845552, + -0.044852979481220245, + -0.02747615985572338, + 0.012846962548792362, + 0.01960461214184761, + 0.024654285982251167, + 0.036684390157461166, + -0.016931256279349327, + 0.024951323866844177, + -0.03712994605302811, + 0.061190154403448105, + -0.004641243256628513, + -0.026139484718441963, + 0.03564475104212761, + 0.006423481274396181, + -0.04039738327264786, + -0.006349220871925354, + 0.01618865691125393, + 0.07425989210605621, + -0.022129449993371964, + 0.014480679295957088, + 0.00475263362750411, + -0.0018936273409053683, + -0.017970893532037735, + -0.008131458424031734, + 0.015000498853623867, + -0.01173306442797184, + -0.01901053451001644, + -0.013515301048755646, + -0.015074758790433407, + -0.06534870713949203, + 0.012549921870231628, + 0.007574509363621473, + 0.01633717678487301, + -0.010247865691781044, + 0.02703060209751129, + 0.017154036089777946, + -0.0033416952937841415, + -0.0004502006049733609, + -0.0273276437073946, + 0.008874057792127132, + -0.006572000682353973, + 0.0036944299936294556, + 0.002255644416436553, + 0.026733562350273132, + -0.010693424381315708, + -0.014332159422338009, + 0.011510283686220646, + -0.023020567372441292, + 0.01930757239460945, + -0.021683888509869576, + -0.034011032432317734, + 0.005680881906300783, + 0.01611439883708954, + 0.02346612885594368, + -0.03475363180041313, + 0.0, + -0.010990465059876442, + -0.03089211694896221, + -0.017376815900206566, + 0.009356746450066566, + 0.03920922428369522, + -0.033268433064222336, + -0.02762468159198761, + -0.004715503193438053, + -0.004381333943456411, + 0.04425889998674393, + -0.02376316487789154, + 0.06445759534835815, + 0.002005017362535, + -0.01975313387811184, + 0.034011032432317734, + 0.0018472149968147278, + 0.036832910031080246, + -0.016931256279349327, + -0.0023670343216508627, + 0.02316908724606037, + -0.0018193674040958285, + -0.012030103243887424, + 0.01373807992786169, + -0.018416455015540123, + 0.025842444971203804, + 0.010619165375828743, + 0.003007525810971856, + 0.0006265678675845265, + 0.011213244870305061, + -0.01596587710082531, + -0.0018379325047135353, + -0.0313376747071743, + -0.03623883053660393, + -0.008242848329246044, + -0.010396385565400124, + 0.03980330377817154, + 0.015223278664052486, + 0.017005516216158867, + 0.011436023749411106, + 0.01373807992786169, + -0.028218761086463928, + -0.010841944254934788, + 0.017228296026587486, + -0.014109380543231964, + 0.0038243846502155066, + 0.03920922428369522, + -0.030446557328104973, + 0.02019869163632393, + 0.02034721150994301, + -0.018564973026514053, + -0.011287503875792027, + 0.014257900416851044, + -0.008725537918508053, + -0.017302555963397026, + 0.026733562350273132, + 0.03163471445441246, + -0.025248363614082336, + -0.053764164447784424, + -0.007871548645198345, + -0.02420872636139393, + -0.021386848762631416, + 0.030595077201724052, + -0.009653786197304726, + 0.02005017176270485, + -0.03579327091574669, + 0.009802306070923805, + -0.005569492466747761, + 0.017228296026587486, + 0.008651277981698513, + 0.005532362498342991, + 0.0011092572240158916, + 0.013886600732803345, + -0.057625677436590195, + -0.026288002729415894, + -0.007314600050449371, + 0.006015051621943712, + -0.008948317728936672, + 0.005977921653538942, + -0.029555439949035645, + -0.02792172133922577, + 0.008168588392436504, + -0.005569492466747761, + 0.00493828346952796, + 0.0189362745732069, + -0.000905042455997318, + -0.007797288708388805, + -0.001118539716117084, + 0.023020567372441292, + -0.049011532217264175, + 0.026288002729415894, + -0.021238330751657486, + -0.012995482422411442, + 0.0008771950379014015, + -0.01960461214184761, + -0.009059707634150982, + 0.03118915483355522, + 0.021683888509869576, + -0.04633817449212074, + -0.014554939232766628, + -0.028812838718295097, + -0.008836927823722363, + -0.036684390157461166, + 0.029852477833628654, + -0.029109878465533257, + 0.04069442301988602, + -0.01930757239460945, + -0.03891218453645706, + 0.0013923731166869402, + 0.0035830398555845022, + 0.02406020648777485, + 0.008465628139674664, + 0.013589560985565186, + 0.0014016556087881327, + -0.04722929745912552, + -0.009802306070923805, + 0.010767684318125248, + -0.045744094997644424, + -0.03891218453645706, + -0.011955843307077885, + -0.012327142991125584, + -0.02703060209751129, + -0.015297538600862026, + -0.013812340795993805, + -0.018713492900133133, + -0.029258400201797485, + -0.0021442545112222433, + 0.007091820240020752, + 0.003100350731983781, + -0.0546552874147892, + 0.012624181807041168, + -0.007797288708388805, + -0.005458102561533451, + 0.01915905438363552, + 0.002274209400638938, + 0.016931256279349327, + 0.00957952719181776, + -0.02361464872956276, + -0.011287503875792027, + -0.0053095826879143715, + 0.026436522603034973, + -0.04069442301988602, + 0.028367280960083008, + 0.05198192968964577, + -0.02717912197113037, + 0.017005516216158867, + 0.00957952719181776, + -0.010767684318125248, + 0.003137480467557907, + -0.007723029237240553, + 0.016931256279349327, + -0.014777719974517822, + -0.04069442301988602, + -0.006052181590348482, + 0.03163471445441246, + -0.0016801301389932632, + 0.03549623116850853, + 0.0008632712997496128, + 0.013441041111946106, + -0.021980930119752884, + -0.02807024121284485, + 0.002599096391350031, + -0.03609031066298485, + 0.015371798537671566, + 0.0035830398555845022, + -0.022277969866991043, + -0.011361763812601566, + 0.034011032432317734, + 0.006943300366401672, + 0.008539888076484203, + 0.045744094997644424, + -0.0014944804133847356, + -0.00023902404063846916, + -0.02019869163632393, + -0.031040634959936142, + 0.02807024121284485, + 0.011361763812601566, + 0.01915905438363552, + -0.017005516216158867, + -0.025545405223965645, + -0.018713492900133133, + 0.005606622435152531, + 0.041288502514362335, + 0.02762468159198761, + 0.016559956595301628, + 0.02762468159198761, + -0.025545405223965645, + -0.0017079777317121625, + 0.029703959822654724, + -0.014332159422338009, + -0.004604113753885031, + -0.021386848762631416, + 0.04307073727250099, + 0.0019864521455019712, + -0.014851979911327362, + 0.024802805855870247, + -0.01611439883708954, + -0.010916205123066902, + -0.005346712190657854, + -0.013960860669612885, + -0.025842444971203804, + 0.06178423389792442, + -0.018342195078730583, + -0.021980930119752884, + 0.06742798537015915, + -0.013144001364707947, + 0.022723527625203133, + 0.012698441743850708, + 0.002079277066513896, + 0.018490714952349663, + -0.028812838718295097, + 0.015000498853623867, + -0.0386151447892189, + 0.016559956595301628, + -0.01990165188908577, + 0.014332159422338009, + 0.01648569665849209, + -0.008762667886912823, + -0.009096837602555752, + 0.03089211694896221, + -0.03415955230593681, + -0.01915905438363552, + 0.003545910120010376, + 0.01566883735358715, + 0.01990165188908577, + -0.018045155331492424, + 0.024505766108632088, + 0.030743597075343132, + 0.017599595710635185, + -0.00987656693905592, + 0.018045155331492424, + -0.037575505673885345, + 0.022426489740610123, + -0.03980330377817154, + -0.0003156045568175614, + -0.011213244870305061, + -0.022277969866991043, + -0.02361464872956276, + -0.03594178706407547, + 0.037278465926647186, + 0.01960461214184761, + -0.00046876558917574584, + -0.018416455015540123, + -0.0378725491464138, + -0.03653587028384209, + 0.00987656693905592, + 0.033862512558698654, + 0.02019869163632393, + -0.03564475104212761, + 0.0401003435254097, + 0.008911187760531902, + 0.021832410246133804, + -0.04722929745912552, + -0.017079776152968407, + 0.012624181807041168, + -0.011064724996685982, + 0.040991462767124176, + -0.012252883054316044, + -0.045447055250406265, + -0.033713992685079575, + 0.04247666150331497, + 0.02331760711967945, + 0.04307073727250099, + 0.030298037454485893, + -0.021535368636250496, + 0.005792271811515093, + 0.02703060209751129, + -0.025545405223965645, + -0.011436023749411106, + -0.004529853817075491, + 0.022129449993371964, + -0.029555439949035645, + -0.049011532217264175, + 0.044852979481220245, + -0.0021628194954246283, + 0.029406920075416565, + 0.025842444971203804, + 0.0386151447892189, + 0.0018286500126123428, + -0.03920922428369522, + -0.03609031066298485, + -0.006980430334806442, + -0.049308571964502335, + -0.004307074006646872, + -0.015000498853623867, + 0.02703060209751129, + -0.008465628139674664, + 0.0, + -0.007314600050449371, + 0.029406920075416565, + -0.024951323866844177, + 0.03579327091574669, + 0.011658803559839725, + -0.0313376747071743, + 0.01641143672168255, + -0.010322125628590584, + -0.011955843307077885, + -0.005792271811515093, + -0.022575007751584053, + -0.0026547913439571857, + -0.040991462767124176, + -0.01990165188908577, + -0.044555939733982086, + 0.02406020648777485, + 0.0016615651547908783, + -3.916049172403291e-05, + 0.024802805855870247, + -0.0401003435254097, + -0.008651277981698513, + 0.005680881906300783, + 0.03638735041022301, + -0.015520317479968071, + -0.009282486513257027, + 0.007797288708388805, + 0.016634216532111168, + -0.0018379325047135353, + -0.01626291684806347, + 0.033119913190603256, + 0.010619165375828743, + -0.012252883054316044, + 0.041288502514362335, + 0.007203209679573774, + -0.0003086427168454975, + -0.017154036089777946, + -0.03891218453645706, + 0.01618865691125393, + -0.007314600050449371, + 0.024357246235013008, + -0.016559956595301628, + -0.018193675205111504, + -0.021238330751657486, + 0.0386151447892189, + -0.009282486513257027, + -0.002292774384841323, + -0.028515800833702087, + 0.0032860003411769867, + -0.010544905439019203, + -0.05138785019516945, + -0.017970893532037735, + -0.01990165188908577, + 0.0007797288708388805, + -0.005049672909080982, + 0.041288502514362335, + 0.031040634959936142, + 0.045744094997644424, + 0.03252583369612694, + -0.025693925097584724, + -0.021980930119752884, + -0.0033602602779865265, + 0.020644251257181168, + -0.002237079432234168, + 0.012104363180696964, + 0.022426489740610123, + -0.01990165188908577, + -0.03653587028384209, + 0.011658803559839725, + 0.03252583369612694, + 0.03920922428369522, + -0.01990165188908577, + -0.018862012773752213, + -0.012104363180696964, + -0.028664318844676018, + -0.041882582008838654, + 0.0003063220647163689, + 0.07901253551244736, + -0.01633717678487301, + 0.003731559729203582, + 0.02406020648777485, + -0.03460511192679405, + 0.04336777701973915, + 0.037278465926647186, + 0.014703460037708282, + -0.033119913190603256, + 0.016708476468920708, + 0.0032860003411769867, + -0.004474158864468336, + -0.013366781175136566, + 0.03505067154765129, + 0.01633717678487301, + 0.02703060209751129, + 0.0, + 0.025396883487701416, + 0.07901253551244736, + 0.0006265678675845265, + 0.0032303056214004755, + -0.0007797288708388805, + 0.015074758790433407, + 0.005680881906300783, + 0.008354238234460354, + -0.018193675205111504, + -0.014926238916814327, + 0.010693424381315708, + 0.00683191092684865, + 0.014109380543231964, + -0.021832410246133804, + 0.0005314223817549646, + 0.013366781175136566, + 0.026288002729415894, + -0.0014944804133847356, + -0.009431006386876106, + -0.05495231971144676, + 0.006423481274396181, + 0.02019869163632393, + 0.022129449993371964, + 0.02005017176270485, + -0.013069742359220982, + 0.03237731382250786, + -0.0036573000252246857, + 0.01975313387811184, + -0.004826893098652363, + 0.012104363180696964, + -0.012327142991125584, + -0.022575007751584053, + -0.008762667886912823, + -0.01596587710082531, + 0.0077601587399840355, + 0.033268433064222336, + 0.04039738327264786, + -0.018639232963323593, + -0.020941291004419327, + 0.01990165188908577, + 0.002506271470338106, + 0.03118915483355522, + 0.025248363614082336, + 0.03222879767417908, + 0.010470645502209663, + -0.021832410246133804, + -0.008836927823722363, + -0.013812340795993805, + -0.02807024121284485, + 0.0328228734433651, + -0.013960860669612885, + 0.024357246235013008, + 0.00025526838726364076, + -0.010099345818161964, + 0.025099843740463257, + -0.03564475104212761, + -0.005420972127467394, + -0.025396883487701416, + 0.007277469616383314, + -0.03623883053660393, + -0.020792771130800247, + -0.017525335773825645, + 0.004901153035461903, + -0.006980430334806442, + 0.01589161716401577, + -0.000299360224744305, + 0.012624181807041168, + -0.04247666150331497, + -0.005383842159062624, + 0.010099345818161964, + 0.0016801301389932632, + -0.012549921870231628, + -0.024802805855870247, + 0.00042931499774567783, + -0.002292774384841323, + -0.024357246235013008, + 0.05257600545883179, + -0.008465628139674664, + 0.01596587710082531, + -0.02420872636139393, + 0.018119415268301964, + -0.005420972127467394, + 0.006497741211205721, + 0.02034721150994301, + -0.001522327889688313, + -0.018713492900133133, + -0.012327142991125584, + 0.017525335773825645, + -0.029703959822654724, + 0.000770446436945349, + -0.02747615985572338, + -0.004901153035461903, + -0.006720520555973053, + 0.0313376747071743, + -0.006794780492782593, + 0.011510283686220646, + 0.02406020648777485, + -0.012995482422411442, + -0.005123932845890522, + 0.025248363614082336, + -0.0024320115335285664, + 0.009096837602555752, + 0.025693925097584724, + 0.009096837602555752, + -0.033268433064222336, + 0.01574309729039669, + -0.012698441743850708, + 0.002329904120415449, + 0.001559457741677761, + 0.029555439949035645, + 0.01566883735358715, + -0.012252883054316044, + -0.04425889998674393, + 0.007091820240020752, + 0.008836927823722363, + -0.03519919142127037, + 0.029555439949035645, + -0.012921222485601902, + -0.001345960539765656, + 0.04693225771188736, + 0.012772702611982822, + -0.024802805855870247, + -0.017228296026587486, + 0.01633717678487301, + -0.030149517580866814, + 0.03980330377817154, + 0.0011092572240158916, + 0.02703060209751129, + -0.003508780151605606, + 0.014109380543231964, + 0.050199687480926514, + 0.008279978297650814, + 0.02316908724606037, + 0.02331760711967945, + -0.02034721150994301, + -0.037724025547504425, + -0.017376815900206566, + -0.013292521238327026, + 0.03193175420165062, + 0.01990165188908577, + 0.014629200100898743, + -0.009319616481661797, + 0.048714492470026016, + 0.03534771129488945, + 0.017525335773825645, + 0.01188158243894577, + 0.033119913190603256, + -0.024357246235013008, + -0.041882582008838654, + -0.016634216532111168, + 0.029109878465533257, + -0.0037872546818107367, + 0.017525335773825645, + 0.004065729212015867, + -0.008539888076484203, + -0.014109380543231964, + -0.030743597075343132, + 0.0028033112175762653, + 0.0136638218536973, + -0.026585042476654053, + -0.024802805855870247, + 0.011658803559839725, + 0.020941291004419327, + 0.013144001364707947, + -0.011436023749411106, + 0.04277369752526283, + -0.012846962548792362, + -0.017079776152968407, + 0.01901053451001644, + 0.04069442301988602, + -0.025396883487701416, + 0.00987656693905592, + -0.01990165188908577, + -0.025248363614082336 + ], + "qwen3-embed-query-native": [ + -0.00029763736529275775, + 0.0011475174687802792, + -0.04062211886048317, + 0.015376734547317028, + -0.0006383066065609455, + 0.06471998989582062, + 0.031212477013468742, + 0.005852339323610067, + -0.003901559626683593, + 0.018130777403712273, + 0.02822893112897873, + -0.01239318959414959, + 0.001384192961268127, + -0.03190098702907562, + 0.004561382345855236, + -0.043835170567035675, + 0.0, + 0.023638861253857613, + 0.0014630848309025168, + -0.0006598226027563214, + -0.012450565584003925, + 0.00791787076741457, + 0.022376591339707375, + 0.03993361070752144, + -0.019737301394343376, + -0.013311203569173813, + -0.02352410927414894, + 0.010040778666734695, + -0.0034712404012680054, + -0.0029261696618050337, + -0.010270281694829464, + -0.010844040662050247, + -0.014343968592584133, + -0.023983115330338478, + 0.003700744127854705, + -0.014401344582438469, + 0.012565316632390022, + -0.0002528124605305493, + -0.00866375770419836, + 0.025819145143032074, + -0.004360566381365061, + 0.013598082587122917, + 4.325603003962897e-05, + 0.013655458576977253, + 0.02960595302283764, + 0.03258949890732765, + 0.0004070101131219417, + -0.041769638657569885, + -0.03534353896975517, + -0.007344112265855074, + -0.007516239769756794, + 0.0206553153693676, + 0.03235999494791031, + -0.01583574153482914, + 0.0858343094587326, + 0.01480297651141882, + -0.010786664672195911, + -0.00028329339693300426, + 0.006598225794732571, + 0.02478637918829918, + 0.010212905704975128, + 0.027999427169561386, + -0.005794963333755732, + -0.025130633264780045, + -0.0051638288423419, + -0.018130777403712273, + -0.00216593942604959, + 0.005364644341170788, + -0.0015563206980004907, + 0.0014559128321707249, + 0.04773673042654991, + 0.010442409664392471, + 0.007458863779902458, + -0.008721132762730122, + 0.031441979110240936, + -0.02627815119922161, + -0.017671769484877586, + 0.023294605314731598, + 0.02490113116800785, + 0.002596258418634534, + 0.026392903178930283, + -0.008778508752584457, + -0.008319501765072346, + 0.041769638657569885, + -0.01308169960975647, + 0.01732751540839672, + 0.03465503081679344, + 0.0017571361968293786, + -0.0019220918184146285, + -0.009753898717463017, + 0.00042494008084759116, + -0.006282658316195011, + 0.021688081324100494, + 0.01141779962927103, + -0.004905637353658676, + 0.009581771679222584, + -0.015606238506734371, + -0.029376449063420296, + -0.016065245494246483, + -0.0022089711856096983, + 0.0007315424154512584, + 0.001269441214390099, + 0.03740907087922096, + 0.004905637353658676, + 0.01595049351453781, + -0.027425669133663177, + -0.01308169960975647, + 0.013483330607414246, + -0.002553226426243782, + 0.013770210556685925, + 0.010614536702632904, + 0.030064959079027176, + 0.029835455119609833, + 0.004417942371219397, + 0.010098154656589031, + -0.010499785654246807, + 0.0019077479373663664, + 0.039015594869852066, + 0.009926026687026024, + 0.038327086716890335, + -0.0010973135940730572, + 0.012507940642535686, + 0.0046187578700482845, + -0.009811274707317352, + 0.014401344582438469, + -0.04544169455766678, + -0.005852339323610067, + 0.0005773447337560356, + 0.021229073405265808, + 0.00757361575961113, + 0.018360279500484467, + -0.016179997473955154, + -0.00826212577521801, + 0.0010686257155612111, + -0.03465503081679344, + -0.004389254376292229, + -0.009926026687026024, + -0.010901416651904583, + -0.030523966997861862, + 0.0008964980370365083, + -0.014688224531710148, + -0.026392903178930283, + 0.012680068612098694, + -0.022376591339707375, + -0.005106452852487564, + -0.04337616264820099, + 0.0008427081629633904, + -0.036950062960386276, + 0.011819430626928806, + -0.003758119884878397, + 0.0005271408590488136, + 0.0015132887056097388, + 0.00757361575961113, + -0.004131062887609005, + 0.00033708327100612223, + 0.03511403501033783, + -0.006110530812293291, + -0.02352410927414894, + 0.02340935729444027, + -0.0007423004135489464, + -0.011589926667511463, + 0.015606238506734371, + 0.03786807879805565, + 0.025704393163323402, + -0.020999571308493614, + 0.013253827579319477, + -0.0015419767005369067, + 0.025130633264780045, + -0.0009682179079391062, + 0.0, + -0.0032273931428790092, + -0.0042745028622448444, + 0.007057232782244682, + -0.031212477013468742, + 0.015376734547317028, + 0.01652425155043602, + -0.0030122334137558937, + 0.015032479539513588, + -0.01595049351453781, + 0.004303190857172012, + -0.0048195733688771725, + -0.01451609656214714, + -0.006311346311122179, + -0.005766275338828564, + -0.0017929960740730166, + 0.03465503081679344, + 0.0042745028622448444, + -0.015376734547317028, + 0.007803119253367186, + -0.013139075599610806, + 0.005536771845072508, + 0.009753898717463017, + -0.006741665303707123, + 0.01721276342868805, + -0.005221204832196236, + 0.003958935383707285, + -0.01939304545521736, + -0.02822893112897873, + 0.022376591339707375, + 0.004762197844684124, + 0.014401344582438469, + 0.0031269851606339216, + -0.015606238506734371, + -0.029261697083711624, + 0.023294605314731598, + -0.022606095299124718, + 0.009581771679222584, + -0.0042745028622448444, + 0.01514723151922226, + -0.01273744460195303, + 0.0059957788325846195, + -0.008089998736977577, + -0.009467019699513912, + -0.05806438624858856, + 0.02409786731004715, + 0.029835455119609833, + -0.017786521464586258, + -0.01480297651141882, + 0.018360279500484467, + -0.0011475174687802792, + -0.0042458148673176765, + -0.0027396981604397297, + -0.01451609656214714, + 0.017557017505168915, + -0.018475031480193138, + -0.012335813604295254, + -0.0003442552697379142, + -0.0026249464135617018, + 0.005852339323610067, + -0.006971168797463179, + 0.03235999494791031, + 0.012221061624586582, + -0.008032622747123241, + 0.006253970321267843, + -0.011991557665169239, + -0.01342595461755991, + -0.032130490988492966, + 0.006684289779514074, + 0.013483330607414246, + -0.02765517309308052, + -0.010958792641758919, + -0.008204750716686249, + -0.014343968592584133, + 0.011876806616783142, + -0.0006741665420122445, + -0.027769923210144043, + 0.010499785654246807, + 0.004561382345855236, + -0.02008155733346939, + -0.0015706645790487528, + -0.007803119253367186, + 0.0020655314438045025, + -0.00045721401693299413, + -0.014630848541855812, + -0.025360137224197388, + -0.01721276342868805, + -0.018360279500484467, + -0.051408786326646805, + 0.0031843611504882574, + 0.007803119253367186, + -0.012450565584003925, + 0.026048647239804268, + -0.011475175619125366, + 0.00249585066922009, + -0.016983259469270706, + -0.011704678647220135, + 0.007860494777560234, + -0.007171984296292067, + -0.013999713584780693, + -0.01939304545521736, + 0.015606238506734371, + -0.009868650697171688, + -0.009811274707317352, + 0.005020389333367348, + 0.034884531050920486, + -0.02547488920390606, + -0.025015881285071373, + -0.008147374726831913, + 0.004389254376292229, + -0.004188438877463341, + -0.005680211819708347, + -0.0090080127120018, + 0.006885105278342962, + -0.009753898717463017, + 0.0, + 0.023179853335022926, + 0.04773673042654991, + -0.007860494777560234, + -0.006598225794732571, + 0.0015491486992686987, + 0.005766275338828564, + -0.00030839533428661525, + 0.003987623378634453, + 0.00249585066922009, + -0.032130490988492966, + -0.017557017505168915, + 0.00791787076741457, + -0.025819145143032074, + 0.01927829347550869, + 0.0008606381597928703, + -0.032819002866744995, + -0.011360423639416695, + -0.00533595634624362, + 0.032819002866744995, + 0.004790885839611292, + 0.032819002866744995, + 0.01870453543961048, + 0.008721132762730122, + 0.0068277292884886265, + 0.00029225836624391377, + -0.016753755509853363, + -0.008491629734635353, + 0.024327371269464493, + 0.001269441214390099, + 0.0030409214086830616, + 0.013770210556685925, + 0.007975246757268906, + -0.016983259469270706, + -0.0017786521930247545, + -0.0180160254240036, + -0.0154914865270257, + -0.03327800706028938, + 0.045212190598249435, + -0.009926026687026024, + 0.014458720572292805, + 0.019852053374052048, + 0.031441979110240936, + 0.02283559925854206, + 0.029146945104002953, + 0.011073543690145016, + 0.0022089711856096983, + 0.023065101355314255, + -0.049343254417181015, + -0.011360423639416695, + -0.012450565584003925, + -0.005708899814635515, + 0.006799041293561459, + 0.035573042929172516, + 0.00826212577521801, + -0.012278437614440918, + -0.005881027318537235, + -0.0068277292884886265, + -0.014229217544198036, + -0.027310917153954506, + -0.041769638657569885, + -0.007143296767026186, + -0.008491629734635353, + 0.009524395689368248, + 0.016409501433372498, + -0.005708899814635515, + -0.028114179149270058, + -0.010844040662050247, + -0.07986722141504288, + -0.011532550677657127, + 0.006598225794732571, + -0.001233581337146461, + -0.00740148825570941, + 0.025130633264780045, + -0.01870453543961048, + 0.011991557665169239, + -0.007688367273658514, + -0.00725804828107357, + -0.005536771845072508, + 0.02490113116800785, + 0.04567119851708412, + -0.026622407138347626, + -0.011245671659708023, + 0.0180160254240036, + 0.03258949890732765, + -0.017442265525460243, + 0.04062211886048317, + 0.019048791378736496, + 0.0481957346200943, + -0.03029446303844452, + -0.005192516837269068, + -0.009467019699513912, + 0.029146945104002953, + -0.02134382538497448, + -0.01382758654654026, + -0.0035859921481460333, + 0.026048647239804268, + 0.027081413194537163, + 0.002438474679365754, + 0.0015276327030733228, + -0.01927829347550869, + 0.008606381714344025, + -0.02490113116800785, + -0.03809758275747299, + 0.014458720572292805, + 0.011704678647220135, + 0.00998340267688036, + 0.03580254688858986, + 0.006885105278342962, + -0.02696666121482849, + 0.039704106748104095, + 0.021573329344391823, + 0.012852196581661701, + 0.009926026687026024, + 0.015261983498930931, + -0.01583574153482914, + -0.00757361575961113, + -0.0076309917494654655, + -0.030753469094634056, + -0.0015850085765123367, + 0.013024323619902134, + -0.004016311373561621, + 0.007171984296292067, + 0.0309829730540514, + 0.0017284483183175325, + 0.022606095299124718, + 0.04681871458888054, + -6.58926073811017e-05, + 0.012507940642535686, + -0.02627815119922161, + -0.008950636722147465, + -0.020884819328784943, + 0.011130919679999352, + -0.010270281694829464, + 0.020196309313178062, + 0.011819430626928806, + -0.0004697649856097996, + -0.021573329344391823, + -0.0029118256643414497, + -0.0022233151830732822, + 0.022376591339707375, + 0.006856417283415794, + 0.021573329344391823, + 0.005020389333367348, + -0.03878609091043472, + 0.0021372514311224222, + -0.02765517309308052, + -0.023868365213274956, + -0.027196165174245834, + 0.03327800706028938, + -0.003930247388780117, + 0.02409786731004715, + 0.02627815119922161, + -0.00708592077717185, + -0.01996680535376072, + -0.06012991815805435, + -0.030753469094634056, + -0.036950062960386276, + 0.004905637353658676, + 0.01480297651141882, + 0.0054507083259522915, + -0.006684289779514074, + 0.00011475175415398553, + 0.015720989555120468, + 0.023983115330338478, + 0.030064959079027176, + -0.021114323288202286, + 0.029146945104002953, + -0.01595049351453781, + -0.002510194666683674, + -0.03947460278868675, + 5.7824126997729763e-05, + -0.007688367273658514, + 0.011130919679999352, + -0.009753898717463017, + 0.028687937185168266, + -0.05232679843902588, + 0.00017481711984146386, + -0.006368722300976515, + 0.016065245494246483, + -0.046589210629463196, + -0.033507511019706726, + -0.0030552654061466455, + 0.01996680535376072, + -0.02754042111337185, + 0.0344255268573761, + 0.00791787076741457, + 0.016294749453663826, + 0.0006383066065609455, + -0.0020655314438045025, + -0.0007458864129148424, + 0.012221061624586582, + 0.01927829347550869, + -0.013712834566831589, + 0.009524395689368248, + -0.016868507489562035, + -0.02685190923511982, + -0.018819287419319153, + 0.043146658688783646, + 0.017671769484877586, + 0.023294605314731598, + 0.01451609656214714, + -0.012909572571516037, + 0.041081126779317856, + 0.007143296767026186, + 0.045900702476501465, + 0.006024466827511787, + 0.021802833303809166, + -0.01451609656214714, + -0.04429417476058006, + -0.016983259469270706, + -0.008204750716686249, + 0.021114323288202286, + -0.0033995206467807293, + 0.018245529383420944, + 0.03304850310087204, + 0.0001174412464024499, + -0.016868507489562035, + 0.011934182606637478, + -0.004016311373561621, + -0.001391364959999919, + -0.016409501433372498, + 0.05393332242965698, + -0.0006203766679391265, + -0.00935226771980524, + 0.002696666168048978, + -0.007860494777560234, + 0.036261554807424545, + -0.01595049351453781, + -0.014688224531710148, + -0.016294749453663826, + -0.01141779962927103, + -0.012278437614440918, + -0.02340935729444027, + 0.02008155733346939, + -0.03396651893854141, + -0.032819002866744995, + -0.02490113116800785, + 0.001226409338414669, + -0.01790127344429493, + -0.0004948669229634106, + 0.019507797434926033, + -0.0028114179149270058, + 0.023983115330338478, + -0.013196451589465141, + 0.008204750716686249, + -0.00017929961904883385, + 0.025015881285071373, + -0.007516239769756794, + 0.0029548576567322016, + 0.018245529383420944, + -0.020425811409950256, + -0.007516239769756794, + -0.01996680535376072, + -0.02765517309308052, + 0.0007996762869879603, + -0.006196594797074795, + 0.001305301208049059, + -0.001183377462439239, + 0.021114323288202286, + 0.0015491486992686987, + -0.024556875228881836, + -0.039015594869852066, + 0.013196451589465141, + 0.007114608772099018, + 0.03465503081679344, + 0.005737587809562683, + -0.009122764691710472, + -0.006712977308779955, + -0.016753755509853363, + -0.01141779962927103, + 0.03465503081679344, + 0.034884531050920486, + 0.014114465564489365, + 0.01870453543961048, + -0.016065245494246483, + 0.017671769484877586, + 0.00433187885209918, + 0.031441979110240936, + 0.025360137224197388, + 0.037638574838638306, + -0.0048195733688771725, + -0.010499785654246807, + 0.03580254688858986, + 0.012565316632390022, + -0.0019794676918536425, + 0.02754042111337185, + 0.004561382345855236, + 0.018245529383420944, + -0.0009682179079391062, + 0.022376591339707375, + 0.04888424649834633, + 0.003643368138000369, + 0.012507940642535686, + 0.0008570521604269743, + -0.010499785654246807, + 0.006540849804878235, + 0.007803119253367186, + -0.008376877754926682, + -0.020884819328784943, + 0.007975246757268906, + -0.014057089574635029, + -0.006598225794732571, + 0.01652425155043602, + -0.009524395689368248, + 0.01141779962927103, + -0.03534353896975517, + -0.012048933655023575, + -0.0021229074336588383, + 0.015376734547317028, + -0.002682322170585394, + -0.004704821854829788, + 0.04635970667004585, + -0.017671769484877586, + 0.0027253541629761457, + 0.02616339921951294, + 0.035573042929172516, + 0.002366754924878478, + -0.030523966997861862, + -0.005737587809562683, + -0.015032479539513588, + -0.007975246757268906, + 0.022950351238250732, + 0.035573042929172516, + -0.016868507489562035, + 0.05163829028606415, + 0.039704106748104095, + 0.011991557665169239, + 0.02891744114458561, + -0.008606381714344025, + 0.03878609091043472, + -0.00935226771980524, + 0.006253970321267843, + -0.016179997473955154, + -0.00866375770419836, + 0.003958935383707285, + -0.013598082587122917, + -0.04429417476058006, + -0.010844040662050247, + 0.0014343969523906708, + 0.014458720572292805, + -0.013770210556685925, + -0.0360320508480072, + -0.00866375770419836, + 0.020425811409950256, + 0.0076309917494654655, + 0.0030982973985373974, + 0.0007602303521707654, + -0.007229360286146402, + -0.00791787076741457, + 0.002467162674292922, + -0.02627815119922161, + -0.011360423639416695, + 0.0309829730540514, + 0.006340034306049347, + -0.0030265774112194777, + 0.0007925042882561684, + -0.0003585992380976677, + 0.025015881285071373, + 0.012221061624586582, + 0.013598082587122917, + -0.0045040063560009, + -0.03534353896975517, + 0.015376734547317028, + 0.04429417476058006, + 0.009180139750242233, + -0.0015132887056097388, + 0.019163543358445168, + -0.017671769484877586, + 0.0154914865270257, + -0.020884819328784943, + 0.009409643709659576, + 0.01382758654654026, + -0.027769923210144043, + 0.03672055900096893, + -0.0344255268573761, + 0.03465503081679344, + -0.030523966997861862, + 0.008721132762730122, + -0.02547488920390606, + -0.017671769484877586, + 0.00791787076741457, + -0.011245671659708023, + -0.02214708738029003, + 0.016065245494246483, + -0.03947460278868675, + -0.033507511019706726, + 0.02696666121482849, + 0.024671627208590508, + 0.008950636722147465, + 0.015261983498930931, + 0.0009682179079391062, + 0.004475318361073732, + -0.0051638288423419, + -0.017557017505168915, + 0.02558964118361473, + -0.015606238506734371, + 0.016179997473955154, + 0.012048933655023575, + 0.008089998736977577, + 0.003958935383707285, + -0.01996680535376072, + 0.015720989555120468, + -0.013884961605072021, + 0.0018647159449756145, + 0.01595049351453781, + -0.016065245494246483, + -0.030523966997861862, + 0.02685190923511982, + 0.010958792641758919, + 0.039704106748104095, + 0.009868650697171688, + 0.01308169960975647, + -0.022950351238250732, + -0.008893260732293129, + -0.010270281694829464, + -0.041081126779317856, + 0.0033134568948298693, + 0.012565316632390022, + -0.019852053374052048, + -0.02616339921951294, + -0.014458720572292805, + -0.02008155733346939, + -0.017098011448979378, + 0.016294749453663826, + -0.029835455119609833, + -0.031441979110240936, + 0.0283436831086874, + -0.028573187068104744, + -0.036950062960386276, + -0.029376449063420296, + -0.012680068612098694, + -0.05531034618616104, + -0.01451609656214714, + -0.053474318236112595, + -0.002266347175464034, + -0.0015778365777805448, + -0.022491343319416046, + -0.01927829347550869, + -0.006282658316195011, + -0.003758119884878397, + 0.0103276576846838, + 0.023753613233566284, + -0.00998340267688036, + 0.029835455119609833, + 0.03465503081679344, + -0.0005665867938660085, + 0.004704821854829788, + -0.03465503081679344, + 0.008147374726831913, + -0.010844040662050247, + -0.005967091303318739, + -0.006684289779514074, + -0.03190098702907562, + 0.031212477013468742, + -0.019852053374052048, + 0.028802689164876938, + 0.02008155733346939, + -0.022950351238250732, + -0.0002617774298414588, + 0.030523966997861862, + -0.016065245494246483, + 0.004360566381365061, + -0.027769923210144043, + 0.02478637918829918, + -0.03304850310087204, + 0.027999427169561386, + 0.0048195733688771725, + 0.004159750882536173, + 0.003571648383513093, + -0.009122764691710472, + 0.02340935729444027, + 0.015032479539513588, + 0.03029446303844452, + 0.0033134568948298693, + -0.02409786731004715, + -0.031441979110240936, + -0.01732751540839672, + -0.023868365213274956, + -0.007315424270927906, + -0.013942337594926357, + -0.012565316632390022, + 0.019622549414634705, + -0.01927829347550869, + 0.005393332336097956, + 0.024327371269464493, + -0.019048791378736496, + -0.008434253744781017, + 0.0019794676918536425, + -0.0008391221635974944, + -0.0016280404524877667, + 0.009237515740096569, + -0.023983115330338478, + -0.017557017505168915, + 0.039015594869852066, + 0.0032704249024391174, + 0.004389254376292229, + -0.010729288682341576, + 0.02340935729444027, + 0.03190098702907562, + -0.03947460278868675, + 0.001255097333341837, + -0.021573329344391823, + -0.01273744460195303, + 0.01652425155043602, + -0.007688367273658514, + -0.010844040662050247, + 0.018360279500484467, + -0.03511403501033783, + -0.01382758654654026, + -0.013483330607414246, + -0.02960595302283764, + 0.011016168631613255, + -0.012450565584003925, + -0.016065245494246483, + 0.007114608772099018, + -0.014573472552001476, + -0.008778508752584457, + -0.01141779962927103, + 0.020196309313178062, + -0.013024323619902134, + 0.004389254376292229, + 0.006426098290830851, + -0.01595049351453781, + -0.009237515740096569, + 0.0076309917494654655, + 0.00791787076741457, + -0.024556875228881836, + -0.036950062960386276, + 0.010729288682341576, + -0.020540563389658928, + 0.009467019699513912, + 0.025819145143032074, + -0.027425669133663177, + -0.007860494777560234, + -0.01382758654654026, + 0.005651523824781179, + 0.015376734547317028, + 0.016983259469270706, + 0.02077006734907627, + -0.018934039399027824, + -0.003729431889951229, + 0.014343968592584133, + -0.02478637918829918, + 0.008089998736977577, + 0.008204750716686249, + 0.013540706597268581, + 0.02283559925854206, + -0.03465503081679344, + -0.008835884742438793, + 0.004303190857172012, + 0.002610602416098118, + 0.025360137224197388, + 0.0076309917494654655, + -0.013024323619902134, + 0.021917585283517838, + 0.026622407138347626, + 0.00740148825570941, + -0.008434253744781017, + -0.03373701497912407, + 0.014286593534052372, + -0.006885105278342962, + -0.007975246757268906, + 0.0014774288283661008, + -0.0051638288423419, + 0.03304850310087204, + -0.01514723151922226, + 0.020540563389658928, + -0.0024097866844385862, + 0.03465503081679344, + 0.03029446303844452, + 0.03396651893854141, + 0.0006311346660368145, + 0.001448740833438933, + 0.007057232782244682, + -0.013253827579319477, + 0.027769923210144043, + -0.011991557665169239, + -0.015606238506734371, + -0.008204750716686249, + 0.0032273931428790092, + 0.0154914865270257, + -0.04773673042654991, + 0.007516239769756794, + -0.009065388701856136, + -0.036261554807424545, + 0.01790127344429493, + -0.012966947630047798, + 0.04498268663883209, + 0.029835455119609833, + 0.030064959079027176, + -0.005594147834926844, + -0.03534353896975517, + -0.0054507083259522915, + -0.013139075599610806, + 0.003872871631756425, + 0.0018934039399027824, + 0.007171984296292067, + -0.0005630007945001125, + 0.024671627208590508, + 0.00433187885209918, + 0.021917585283517838, + 0.007688367273658514, + 0.028687937185168266, + -0.005307268351316452, + 0.03809758275747299, + -0.03534353896975517, + 0.029261697083711624, + -0.008032622747123241, + -0.015261983498930931, + -0.01732751540839672, + -0.014573472552001476, + -0.01996680535376072, + -0.036261554807424545, + 0.019852053374052048, + -0.032819002866744995, + 0.02558964118361473, + 0.008950636722147465, + -0.011188295669853687, + 0.002395442919805646, + 0.014057089574635029, + 0.007229360286146402, + -0.025130633264780045, + 0.028114179149270058, + 0.014917727559804916, + -0.018360279500484467, + -0.02754042111337185, + 0.020540563389658928, + -0.014401344582438469, + 0.013598082587122917, + -0.008319501765072346, + -0.014343968592584133, + -0.03740907087922096, + -0.015376734547317028, + -0.015261983498930931, + 0.020425811409950256, + 0.0029261696618050337, + -0.0029548576567322016, + -0.020311059430241585, + 0.01176205463707447, + 0.020540563389658928, + -0.0010614537168294191, + -0.03465503081679344, + -0.021229073405265808, + 0.009926026687026024, + 0.01514723151922226, + 0.019048791378736496, + 0.0283436831086874, + -0.01308169960975647, + 0.01927829347550869, + -0.028687937185168266, + 0.047277722507715225, + -0.0035859921481460333, + -0.020196309313178062, + 0.02754042111337185, + 0.004963013343513012, + -0.031212477013468742, + -0.004905637353658676, + 0.012507940642535686, + 0.05737587437033653, + -0.017098011448979378, + 0.011188295669853687, + 0.003672056132927537, + -0.0014630848309025168, + -0.013884961605072021, + -0.006282658316195011, + 0.011589926667511463, + -0.009065388701856136, + -0.014688224531710148, + -0.010442409664392471, + -0.011647302657365799, + -0.05049077048897743, + 0.00969652272760868, + 0.005852339323610067, + 0.012622692622244358, + -0.00791787076741457, + 0.020884819328784943, + 0.013253827579319477, + -0.00258191442117095, + -0.00034784123999997973, + -0.021114323288202286, + 0.006856417283415794, + -0.005077764857560396, + 0.0028544499073177576, + 0.0017427921993657947, + 0.0206553153693676, + -0.00826212577521801, + -0.011073543690145016, + 0.008893260732293129, + -0.017786521464586258, + 0.014917727559804916, + -0.016753755509853363, + -0.02627815119922161, + 0.004389254376292229, + 0.012450565584003925, + 0.018130777403712273, + -0.02685190923511982, + 0.0, + -0.008491629734635353, + -0.023868365213274956, + -0.01342595461755991, + 0.007229360286146402, + 0.03029446303844452, + -0.025704393163323402, + -0.02134382538497448, + -0.003643368138000369, + -0.0033851766493171453, + 0.034196022897958755, + -0.018360279500484467, + 0.0498022623360157, + 0.0015491486992686987, + -0.015261983498930931, + 0.02627815119922161, + 0.0014272249536588788, + 0.028458435088396072, + -0.01308169960975647, + -0.0018288560677319765, + 0.01790127344429493, + -0.0014057089574635029, + -0.009294891729950905, + 0.010614536702632904, + -0.014229217544198036, + 0.01996680535376072, + 0.008204750716686249, + 0.002323722932487726, + 0.0004841089539695531, + 0.00866375770419836, + -0.012335813604295254, + -0.0014200529549270868, + -0.02421261928975582, + -0.027999427169561386, + -0.006368722300976515, + -0.008032622747123241, + 0.030753469094634056, + 0.01176205463707447, + 0.013139075599610806, + 0.008835884742438793, + 0.010614536702632904, + -0.021802833303809166, + -0.008376877754926682, + 0.013311203569173813, + -0.010901416651904583, + 0.0029548576567322016, + 0.03029446303844452, + -0.02352410927414894, + 0.015606238506734371, + 0.015720989555120468, + -0.014343968592584133, + -0.008721132762730122, + 0.011016168631613255, + -0.006741665303707123, + -0.013368579559028149, + 0.0206553153693676, + 0.024442123249173164, + -0.019507797434926033, + -0.04154013469815254, + -0.006081842817366123, + -0.01870453543961048, + -0.01652425155043602, + 0.023638861253857613, + -0.007458863779902458, + 0.0154914865270257, + -0.02765517309308052, + 0.00757361575961113, + -0.004303190857172012, + 0.013311203569173813, + 0.006684289779514074, + 0.0042745028622448444, + 0.0008570521604269743, + 0.010729288682341576, + -0.044523678719997406, + -0.020311059430241585, + -0.005651523824781179, + 0.004647445864975452, + -0.00691379327327013, + 0.0046187578700482845, + -0.02283559925854206, + -0.021573329344391823, + 0.006311346311122179, + -0.004303190857172012, + 0.0038154958747327328, + 0.014630848541855812, + -0.0006992684793658555, + -0.006024466827511787, + -0.0008642241591587663, + 0.017786521464586258, + -0.03786807879805565, + 0.020311059430241585, + -0.016409501433372498, + -0.010040778666734695, + 0.0006777525413781404, + -0.01514723151922226, + -0.0069998567923903465, + 0.02409786731004715, + 0.016753755509853363, + -0.03580254688858986, + -0.011245671659708023, + -0.022261839359998703, + -0.0068277292884886265, + -0.0283436831086874, + 0.023065101355314255, + -0.022491343319416046, + 0.031441979110240936, + -0.014917727559804916, + -0.030064959079027176, + 0.001075797714293003, + 0.002768385922536254, + 0.01858978345990181, + 0.006540849804878235, + 0.010499785654246807, + 0.001082969713024795, + -0.03649105876684189, + -0.00757361575961113, + 0.008319501765072346, + -0.03534353896975517, + -0.030064959079027176, + -0.009237515740096569, + -0.009524395689368248, + -0.020884819328784943, + -0.011819430626928806, + -0.01067191269248724, + -0.014458720572292805, + -0.022606095299124718, + -0.0016567284474149346, + 0.005479396320879459, + 0.002395442919805646, + -0.04222864657640457, + 0.009753898717463017, + -0.006024466827511787, + -0.004217126872390509, + 0.01480297651141882, + 0.0017571361968293786, + 0.01308169960975647, + 0.00740148825570941, + -0.018245529383420944, + -0.008721132762730122, + -0.004102375358343124, + 0.020425811409950256, + -0.031441979110240936, + 0.021917585283517838, + 0.04016311466693878, + -0.020999571308493614, + 0.013139075599610806, + 0.00740148825570941, + -0.008319501765072346, + 0.00242413068190217, + -0.005967091303318739, + 0.01308169960975647, + -0.01141779962927103, + -0.031441979110240936, + -0.00467613385990262, + 0.024442123249173164, + -0.001298129209317267, + 0.027425669133663177, + 0.0006669945432804525, + 0.010385033674538136, + -0.016983259469270706, + -0.021688081324100494, + 0.0020081556867808104, + -0.027884675189852715, + 0.011876806616783142, + 0.002768385922536254, + -0.01721276342868805, + -0.008778508752584457, + 0.02627815119922161, + 0.005364644341170788, + 0.006598225794732571, + 0.03534353896975517, + -0.0011546894675120711, + -0.0001846786035457626, + -0.015606238506734371, + -0.023983115330338478, + 0.021688081324100494, + 0.008778508752584457, + 0.01480297651141882, + -0.013139075599610806, + -0.019737301394343376, + -0.014458720572292805, + 0.00433187885209918, + 0.03190098702907562, + 0.02134382538497448, + 0.012794820591807365, + 0.02134382538497448, + -0.019737301394343376, + -0.0013196452055126429, + 0.022950351238250732, + -0.011073543690145016, + -0.003557304386049509, + -0.01652425155043602, + 0.03327800706028938, + 0.0015348047018051147, + -0.011475175619125366, + 0.019163543358445168, + -0.012450565584003925, + -0.008434253744781017, + -0.004131062887609005, + -0.010786664672195911, + -0.01996680535376072, + 0.04773673042654991, + -0.0141718415543437, + -0.016983259469270706, + 0.052097294479608536, + -0.010155529715120792, + 0.017557017505168915, + 0.009811274707317352, + 0.0016065245727077127, + 0.014286593534052372, + -0.022261839359998703, + 0.011589926667511463, + -0.029835455119609833, + 0.012794820591807365, + -0.015376734547317028, + 0.011073543690145016, + 0.01273744460195303, + -0.006770353298634291, + -0.007028544787317514, + 0.023868365213274956, + -0.026392903178930283, + -0.01480297651141882, + 0.0027396981604397297, + 0.01210630964487791, + 0.015376734547317028, + -0.013942337594926357, + 0.018934039399027824, + 0.023753613233566284, + 0.013598082587122917, + -0.0076309917494654655, + 0.013942337594926357, + -0.02903219312429428, + 0.01732751540839672, + -0.030753469094634056, + -0.00024384747666772455, + -0.00866375770419836, + -0.01721276342868805, + -0.018245529383420944, + -0.027769923210144043, + 0.028802689164876938, + 0.01514723151922226, + -0.0003621852083597332, + -0.014229217544198036, + -0.029261697083711624, + -0.02822893112897873, + 0.0076309917494654655, + 0.02616339921951294, + 0.015606238506734371, + -0.02754042111337185, + 0.0309829730540514, + 0.006885105278342962, + 0.016868507489562035, + -0.03649105876684189, + -0.013196451589465141, + 0.009753898717463017, + -0.008549005724489689, + 0.03167148306965828, + -0.009467019699513912, + -0.03511403501033783, + -0.026048647239804268, + 0.032819002866744995, + 0.0180160254240036, + 0.03327800706028938, + 0.02340935729444027, + -0.01663900353014469, + 0.004475318361073732, + 0.020884819328784943, + -0.019737301394343376, + -0.008835884742438793, + -0.0034999283961951733, + 0.017098011448979378, + -0.02283559925854206, + -0.03786807879805565, + 0.03465503081679344, + -0.0016710724448785186, + 0.02272084727883339, + 0.01996680535376072, + 0.029835455119609833, + 0.0014128809561952949, + -0.03029446303844452, + -0.027884675189852715, + -0.005393332336097956, + -0.03809758275747299, + -0.003327800892293453, + -0.011589926667511463, + 0.020884819328784943, + -0.006540849804878235, + 0.0, + -0.005651523824781179, + 0.02272084727883339, + -0.01927829347550869, + 0.02765517309308052, + 0.0090080127120018, + -0.02421261928975582, + 0.012680068612098694, + -0.007975246757268906, + -0.009237515740096569, + -0.004475318361073732, + -0.017442265525460243, + -0.002051187679171562, + -0.03167148306965828, + -0.015376734547317028, + -0.0344255268573761, + 0.01858978345990181, + 0.001283785211853683, + -3.025680962309707e-05, + 0.019163543358445168, + -0.0309829730540514, + -0.006684289779514074, + 0.004389254376292229, + 0.028114179149270058, + -0.011991557665169239, + -0.007171984296292067, + 0.006024466827511787, + 0.012852196581661701, + -0.0014200529549270868, + -0.012565316632390022, + 0.02558964118361473, + 0.008204750716686249, + -0.009467019699513912, + 0.03190098702907562, + 0.005565459839999676, + -0.0002384684921707958, + -0.013253827579319477, + -0.030064959079027176, + 0.012507940642535686, + -0.005651523824781179, + 0.018819287419319153, + -0.012794820591807365, + -0.014057089574635029, + -0.016409501433372498, + 0.029835455119609833, + -0.007171984296292067, + -0.0017714801942929626, + -0.02203233726322651, + 0.002538882428780198, + -0.008147374726831913, + -0.039704106748104095, + -0.013884961605072021, + -0.015376734547317028, + 0.0006024466711096466, + -0.003901559626683593, + 0.03190098702907562, + 0.023983115330338478, + 0.03534353896975517, + 0.025130633264780045, + -0.019852053374052048, + -0.016983259469270706, + -0.002596258418634534, + 0.01595049351453781, + -0.0017284483183175325, + 0.00935226771980524, + 0.01732751540839672, + -0.015376734547317028, + -0.02822893112897873, + 0.0090080127120018, + 0.025130633264780045, + 0.03029446303844452, + -0.015376734547317028, + -0.014573472552001476, + -0.00935226771980524, + -0.02214708738029003, + -0.03235999494791031, + 0.0002366754924878478, + 0.061047933995723724, + -0.012622692622244358, + 0.002883137669414282, + 0.01858978345990181, + -0.026737159118056297, + 0.033507511019706726, + 0.028802689164876938, + 0.011360423639416695, + -0.02558964118361473, + 0.012909572571516037, + 0.002538882428780198, + -0.003456896636635065, + -0.0103276576846838, + 0.027081413194537163, + 0.012622692622244358, + 0.020884819328784943, + 0.0, + 0.019622549414634705, + 0.061047933995723724, + 0.0004841089539695531, + 0.00249585066922009, + -0.0006024466711096466, + 0.011647302657365799, + 0.004389254376292229, + 0.0064547862857580185, + -0.014057089574635029, + -0.011532550677657127, + 0.00826212577521801, + 0.0052785808220505714, + 0.010901416651904583, + -0.016868507489562035, + 0.00041059611248783767, + 0.0103276576846838, + 0.020311059430241585, + -0.0011546894675120711, + -0.007286736276000738, + -0.042458146810531616, + 0.004963013343513012, + 0.015606238506734371, + 0.017098011448979378, + 0.0154914865270257, + -0.010098154656589031, + 0.025015881285071373, + -0.0028257619123905897, + 0.015261983498930931, + -0.003729431889951229, + 0.00935226771980524, + -0.009524395689368248, + -0.017442265525460243, + -0.006770353298634291, + -0.012335813604295254, + 0.0059957788325846195, + 0.025704393163323402, + 0.031212477013468742, + -0.014401344582438469, + -0.016179997473955154, + 0.015376734547317028, + 0.0019364358158782125, + 0.02409786731004715, + 0.019507797434926033, + 0.02490113116800785, + 0.008089998736977577, + -0.016868507489562035, + -0.0068277292884886265, + -0.01067191269248724, + -0.021688081324100494, + 0.025360137224197388, + -0.010786664672195911, + 0.018819287419319153, + 0.0001972295722225681, + -0.007803119253367186, + 0.01939304545521736, + -0.02754042111337185, + -0.004188438877463341, + -0.019622549414634705, + 0.0056228358298540115, + -0.027999427169561386, + -0.016065245494246483, + -0.013540706597268581, + 0.003786807879805565, + -0.005393332336097956, + 0.012278437614440918, + -0.00023129650799091905, + 0.009753898717463017, + -0.032819002866744995, + -0.004159750882536173, + 0.007803119253367186, + 0.001298129209317267, + -0.00969652272760868, + -0.019163543358445168, + 0.00033170427195727825, + -0.0017714801942929626, + -0.018819287419319153, + 0.04062211886048317, + -0.006540849804878235, + 0.012335813604295254, + -0.01870453543961048, + 0.013999713584780693, + -0.004188438877463341, + 0.005020389333367348, + 0.015720989555120468, + -0.001176205463707447, + -0.014458720572292805, + -0.009524395689368248, + 0.013540706597268581, + -0.022950351238250732, + 0.0005952747305855155, + -0.021229073405265808, + -0.003786807879805565, + -0.005192516837269068, + 0.02421261928975582, + -0.0052498928271234035, + 0.008893260732293129, + 0.01858978345990181, + -0.010040778666734695, + -0.003958935383707285, + 0.019507797434926033, + -0.0018790599424391985, + 0.007028544787317514, + 0.019852053374052048, + 0.007028544787317514, + -0.025704393163323402, + 0.012163685634732246, + -0.009811274707317352, + 0.0018001680728048086, + 0.0012048933422192931, + 0.02283559925854206, + 0.01210630964487791, + -0.009467019699513912, + -0.034196022897958755, + 0.005479396320879459, + 0.0068277292884886265, + -0.027196165174245834, + 0.02283559925854206, + -0.00998340267688036, + -0.0010399377206340432, + 0.036261554807424545, + 0.009868650697171688, + -0.019163543358445168, + -0.013311203569173813, + 0.012622692622244358, + -0.023294605314731598, + 0.030753469094634056, + 0.0008570521604269743, + 0.020884819328784943, + -0.002711010165512562, + 0.010901416651904583, + 0.03878609091043472, + 0.006397410295903683, + 0.01790127344429493, + 0.0180160254240036, + -0.015720989555120468, + -0.029146945104002953, + -0.01342595461755991, + -0.010270281694829464, + 0.024671627208590508, + 0.015376734547317028, + 0.011303047649562359, + -0.0072006722912192345, + 0.037638574838638306, + 0.027310917153954506, + 0.013540706597268581, + 0.009180139750242233, + 0.02558964118361473, + -0.018819287419319153, + -0.03235999494791031, + -0.012852196581661701, + 0.022491343319416046, + -0.0029261696618050337, + 0.013540706597268581, + 0.0031413291580975056, + -0.006598225794732571, + -0.010901416651904583, + -0.023753613233566284, + 0.00216593942604959, + 0.010557161644101143, + -0.020540563389658928, + -0.019163543358445168, + 0.0090080127120018, + 0.016179997473955154, + 0.010155529715120792, + -0.008835884742438793, + 0.03304850310087204, + -0.009926026687026024, + -0.013196451589465141, + 0.014688224531710148, + 0.031441979110240936, + -0.019622549414634705, + 0.0076309917494654655, + -0.015376734547317028, + -0.019507797434926033, + 0.035573042929172516, + -0.00866375770419836, + 0.011073543690145016, + -0.0022089711856096983, + -0.004475318361073732, + 0.020884819328784943, + 0.012565316632390022, + -0.008606381714344025, + 0.00012550973042380065, + -0.0019077479373663664, + 0.04429417476058006, + 0.01652425155043602, + 0.012565316632390022, + 0.04475318267941475, + 0.008376877754926682, + -0.011934182606637478, + -0.003700744127854705, + 0.014630848541855812, + -0.029835455119609833, + 0.023179853335022926, + 0.003557304386049509, + 0.012335813604295254, + 0.020425811409950256, + -0.023753613233566284, + -0.022261839359998703, + 0.045212190598249435, + -0.04567119851708412, + 0.034884531050920486, + -0.004532694350928068, + 0.027196165174245834, + -0.030523966997861862, + -0.019163543358445168, + 0.008893260732293129, + -0.023179853335022926, + 0.002438474679365754, + -0.009180139750242233, + -0.0026249464135617018, + 0.010212905704975128, + -0.008319501765072346, + 0.006368722300976515, + 0.0008391221635974944, + 0.004217126872390509, + -0.013712834566831589, + 0.018475031480193138, + -0.006311346311122179, + 0.017442265525460243, + 0.009926026687026024, + -0.005049077328294516, + 0.019737301394343376, + 0.0005988607299514115, + -0.016753755509853363, + -0.030753469094634056, + -0.05875289812684059, + 0.01870453543961048, + -0.011245671659708023, + -0.015261983498930931, + 0.007028544787317514, + -0.005852339323610067, + 0.0017427921993657947, + -0.0141718415543437, + -0.0042745028622448444, + 0.02822893112897873, + 0.030064959079027176, + 0.026048647239804268, + -0.018934039399027824, + 0.008778508752584457, + -0.009811274707317352, + 0.025704393163323402, + -0.00015509416698478162, + -0.0018001680728048086, + 0.01652425155043602, + -0.031441979110240936, + 0.01732751540839672, + -0.00691379327327013, + 0.0002546054602134973, + -0.011589926667511463, + -0.009926026687026024, + -0.023753613233566284, + -0.03258949890732765, + 0.006368722300976515, + -0.03258949890732765, + 0.034884531050920486, + 0.00033170427195727825, + 0.039015594869852066, + -0.001190549461171031, + 0.036261554807424545, + -0.00433187885209918, + -0.01652425155043602, + 0.028114179149270058, + 0.02490113116800785, + -0.01583574153482914, + -0.01141779962927103, + 0.01858978345990181, + 0.006971168797463179, + 0.020540563389658928, + -0.012163685634732246, + 0.0022233151830732822, + -0.013311203569173813, + -0.0028257619123905897, + -0.010098154656589031, + 0.0010327657219022512, + 0.0054507083259522915, + 0.029376449063420296, + 0.02960595302283764, + -0.004417942371219397, + -0.00774574326351285, + -0.0015634925803169608, + -0.02490113116800785, + 0.012335813604295254, + -0.010098154656589031, + 0.001212065340951085, + 0.015720989555120468, + 0.019852053374052048, + -0.020196309313178062, + -0.024442123249173164, + 0.0031700171530246735, + -0.0002384684921707958, + 0.0001183377462439239, + 0.010557161644101143, + 0.008950636722147465, + 0.003786807879805565, + 0.012335813604295254, + 0.01870453543961048, + -0.03786807879805565, + -0.018360279500484467, + -0.008204750716686249, + 0.018934039399027824, + -0.0004195610817987472, + 0.014573472552001476, + 0.0040736873634159565, + 0.04567119851708412, + -0.007688367273658514, + 0.0021802831906825304, + -0.009811274707317352, + 0.0005916887312196195, + -0.028114179149270058, + 0.015376734547317028, + -0.03327800706028938, + -0.019737301394343376, + 0.017098011448979378, + -0.011475175619125366, + -0.0076309917494654655, + -0.02421261928975582, + 0.0031843611504882574, + -0.007803119253367186, + -0.025819145143032074, + 0.0002384684921707958, + 0.03167148306965828, + 0.027196165174245834, + -0.0360320508480072, + -0.01870453543961048, + -0.017557017505168915, + -0.019852053374052048, + 0.001169033464975655, + 0.04222864657640457, + 0.01721276342868805, + 0.01308169960975647, + 0.03396651893854141, + 0.025130633264780045, + -0.007975246757268906, + 0.0072006722912192345, + 0.01176205463707447, + 0.005909715313464403, + 0.0004841089539695531, + -0.017557017505168915, + 0.016868507489562035, + -0.0040736873634159565, + -0.007860494777560234, + -0.003901559626683593, + -0.007458863779902458, + 0.002381098922342062, + 0.007344112265855074, + -0.020999571308493614, + 0.0004697649856097996, + -0.011360423639416695, + 0.0021802831906825304, + 0.004963013343513012, + -0.02490113116800785, + -0.04704821854829788, + -0.009467019699513912, + -0.00725804828107357, + 0.027196165174245834, + 0.015032479539513588, + -0.012335813604295254, + 0.001269441214390099, + 0.020311059430241585, + 0.0001192342460853979, + -0.011188295669853687, + 0.020311059430241585, + 0.037638574838638306, + -0.007860494777560234, + -0.0020081556867808104, + 0.01210630964487791, + 0.0029692016541957855, + 0.01451609656214714, + 0.008549005724489689, + -0.006311346311122179, + -0.008319501765072346, + 0.029261697083711624, + -0.00708592077717185, + 0.005192516837269068, + 0.020540563389658928, + -0.008434253744781017, + 0.028573187068104744, + -0.01663900353014469, + 0.017557017505168915, + -0.036950062960386276, + -0.007315424270927906, + -0.012507940642535686, + 0.019622549414634705, + 0.005565459839999676, + -0.011303047649562359, + 0.008491629734635353, + 0.0010112498421221972, + 0.027196165174245834, + 0.031212477013468742, + -0.00266797817312181, + -0.009237515740096569, + -0.02754042111337185, + 0.007860494777560234, + -0.019507797434926033, + 0.012852196581661701, + 0.009639146737754345, + 0.003729431889951229, + 0.013540706597268581, + -0.02903219312429428, + -0.039704106748104095, + 0.021688081324100494, + -0.027081413194537163, + -0.010844040662050247, + -0.027310917153954506, + 0.019852053374052048, + -0.031441979110240936, + -0.006655601784586906, + -0.019163543358445168, + 0.01308169960975647, + -0.013196451589465141, + 0.03649105876684189, + -0.025015881285071373, + 0.0283436831086874, + 0.039704106748104095, + 0.023294605314731598, + -0.00012013073865091428, + -0.04567119851708412, + -0.001448740833438933, + -0.026622407138347626, + -0.02478637918829918, + -0.012622692622244358, + -0.001197721459902823, + 0.00467613385990262, + 0.027999427169561386, + 0.011360423639416695, + 0.019048791378736496, + 0.008376877754926682, + 0.003930247388780117, + 0.011934182606637478, + 0.028458435088396072, + -0.0007925042882561684, + -0.019737301394343376, + -0.00258191442117095, + 0.010270281694829464, + -0.03947460278868675, + 0.02558964118361473, + 0.006167906802147627, + -0.03855658695101738, + -0.034884531050920486, + 0.006741665303707123, + 0.011360423639416695, + 0.032130490988492966, + 0.009639146737754345, + -0.007458863779902458, + 0.002524538664147258, + -0.01732751540839672, + 0.023638861253857613, + 0.0033995206467807293, + 0.012565316632390022, + 0.014458720572292805, + -0.01583574153482914, + 0.0009395299712195992, + 0.021229073405265808, + -0.010901416651904583, + 0.031441979110240936, + -0.00691379327327013, + -0.002481506671756506, + 0.008089998736977577, + 0.0016639004461467266, + -0.02352410927414894, + -0.011188295669853687, + -0.0003908731450792402, + 0.03167148306965828, + -0.03924509882926941, + 0.020999571308493614, + -0.01721276342868805, + 0.010098154656589031, + -0.011819430626928806, + 0.0103276576846838, + -0.012794820591807365, + -0.016294749453663826, + -0.022376591339707375, + 0.012794820591807365, + -0.0003012233355548233, + -0.018934039399027824, + 0.017557017505168915, + -0.016753755509853363, + 0.022491343319416046, + -0.004159750882536173, + -0.012909572571516037, + 0.00969652272760868, + 0.010442409664392471, + -0.0004733509849756956, + 0.04154013469815254, + -0.027196165174245834, + 0.011073543690145016, + 0.01732751540839672, + -0.0019651236943900585, + -0.026622407138347626, + 0.023065101355314255, + -0.024327371269464493, + -0.037638574838638306, + -0.001298129209317267, + 0.010270281694829464, + -0.010270281694829464, + -0.03190098702907562, + 0.01480297651141882, + 0.006799041293561459, + -0.03878609091043472, + 0.01652425155043602, + -0.02203233726322651, + 0.02960595302283764, + -0.020196309313178062, + 0.006770353298634291, + -0.01480297651141882, + 0.02891744114458561, + -0.01583574153482914, + -0.0031700171530246735, + 0.019852053374052048, + 0.0103276576846838, + -0.023868365213274956, + 0.01870453543961048, + 0.0021085634361952543, + 0.034196022897958755, + 0.00740148825570941, + -0.01514723151922226, + -0.007516239769756794, + 0.0032417369075119495, + 0.02421261928975582, + 0.035573042929172516, + -0.014343968592584133, + -0.0016997603233903646, + -0.003557304386049509, + 0.001082969713024795, + 0.013540706597268581, + 0.01996680535376072, + -0.008089998736977577, + -0.002395442919805646, + -0.03947460278868675, + 0.007975246757268906, + 0.01996680535376072, + -0.014229217544198036, + 0.023294605314731598, + -0.023638861253857613, + 0.050031762570142746, + 0.010385033674538136, + 0.02627815119922161, + 0.012680068612098694, + -0.011647302657365799, + -0.016294749453663826, + 0.012507940642535686, + -0.0017714801942929626, + 0.018245529383420944, + -0.04406467452645302, + 0.006885105278342962, + 0.001348333084024489, + 0.013598082587122917, + -0.0090080127120018, + 0.02754042111337185, + -0.0309829730540514, + 0.02490113116800785, + 0.002682322170585394, + 0.024442123249173164, + -0.016065245494246483, + 0.01583574153482914, + -0.03580254688858986, + -0.0031269851606339216, + -0.01858978345990181, + 0.0007279564160853624, + 0.005393332336097956, + 0.010729288682341576, + -0.01210630964487791, + -0.0006777525413781404, + 0.010270281694829464, + -0.011934182606637478, + -0.03855658695101738, + 0.01790127344429493, + 0.014343968592584133, + 0.0021946271881461143, + -0.011303047649562359, + -0.005307268351316452, + -0.022950351238250732, + -0.01514723151922226, + -0.033507511019706726, + -0.018360279500484467, + -0.013368579559028149, + -0.04199914261698723, + -0.005766275338828564, + 0.014401344582438469, + 0.005794963333755732, + 0.005020389333367348, + -0.02077006734907627, + -0.007344112265855074, + -0.0030122334137558937, + 0.012335813604295254, + 0.029835455119609833, + 0.017557017505168915, + 0.03649105876684189, + -0.01927829347550869, + -0.0044466303661465645, + 0.038327086716890335, + 0.024442123249173164, + -0.009581771679222584, + -0.03580254688858986, + -0.011934182606637478, + -0.023294605314731598, + 0.03534353896975517, + 0.015261983498930931, + 0.005192516837269068, + -0.012909572571516037, + -0.0069998567923903465, + 0.010212905704975128, + 0.01870453543961048, + 0.006196594797074795, + -0.003844183636829257, + -0.0066269137896597385, + -0.016065245494246483, + 0.003672056132927537, + -0.01652425155043602, + -0.009294891729950905, + 0.022950351238250732, + -0.010442409664392471, + 0.0007817462901584804, + -0.009524395689368248, + 0.0031269851606339216, + 0.013770210556685925, + -0.025933895260095596, + -0.012163685634732246, + -0.02754042111337185, + 0.006081842817366123, + 0.04199914261698723, + 0.025130633264780045, + -0.006053154822438955, + -0.004790885839611292, + 0.0141718415543437, + -0.02008155733346939, + -0.006598225794732571, + -0.028687937185168266, + 0.0032273931428790092, + -0.03786807879805565, + -0.0005594147951342165, + 0.007803119253367186, + -0.017442265525460243, + 0.03396651893854141, + 0.0344255268573761, + -0.00225200317800045, + -0.0008462941623292863, + 0.01996680535376072, + -0.016868507489562035, + 0.02960595302283764, + 0.05026126652956009, + -0.028114179149270058, + -0.023179853335022926, + -0.01342595461755991, + 4.661790080717765e-05, + -0.01451609656214714, + 0.034884531050920486, + -0.01480297651141882, + 0.003987623378634453, + 0.06793303787708282, + -0.032130490988492966, + -0.012622692622244358, + -0.002237659180536866, + 0.0032991128973662853, + -0.0033851766493171453, + -0.03327800706028938, + 0.051408786326646805, + 0.005393332336097956, + -0.014229217544198036, + -0.021917585283517838, + 0.017442265525460243, + 0.00791787076741457, + 0.00866375770419836, + 0.021114323288202286, + 0.0007889182888902724, + -0.0040736873634159565, + 0.022950351238250732, + -0.014917727559804916, + -0.011991557665169239, + -0.010098154656589031, + 0.006139218807220459, + 8.920155960367993e-05, + -0.013253827579319477, + -0.004934325348585844, + -0.005106452852487564, + 9.05463020899333e-05, + 0.027884675189852715, + 0.05117928236722946, + 0.009122764691710472, + 0.003442552639171481, + 0.018130777403712273, + 0.015720989555120468, + 0.018245529383420944, + -0.03924509882926941, + -0.009868650697171688, + -0.01514723151922226, + -0.002366754924878478, + -0.011704678647220135, + 0.009237515740096569, + -0.03373701497912407, + 0.03190098702907562, + -0.005680211819708347, + 0.013483330607414246, + 0.02214708738029003, + -0.007171984296292067, + -0.004905637353658676, + 0.019622549414634705, + -0.03740907087922096, + 0.05324481427669525, + 0.0021085634361952543, + -0.02627815119922161, + -0.012909572571516037, + -0.006340034306049347, + -0.010958792641758919, + -0.011876806616783142, + 0.00935226771980524, + -0.025015881285071373, + -0.011819430626928806, + -0.00691379327327013, + -0.016753755509853363, + 0.00017840311920735985, + -0.014917727559804916, + 0.0005881027318537235, + 0.01308169960975647, + -0.005967091303318739, + -0.027081413194537163, + -0.020884819328784943, + -0.00216593942604959, + -0.021917585283517838, + -0.004532694350928068, + 0.04016311466693878, + 0.002897481666877866, + 0.0007171984761953354, + 0.04911375045776367, + -0.020540563389658928, + -0.026392903178930283, + -0.01858978345990181, + -0.010958792641758919, + -0.0007028544787317514, + 0.020196309313178062, + -0.016868507489562035, + 0.007458863779902458, + -0.01870453543961048, + -0.01480297651141882, + 0.007688367273658514, + 0.006512161809951067, + -0.016983259469270706, + 0.016753755509853363, + -0.0010399377206340432, + 0.01721276342868805, + 0.01176205463707447, + 0.010901416651904583, + -0.005881027318537235, + 0.020196309313178062, + -0.0030265774112194777, + 0.009409643709659576, + -0.013024323619902134, + -0.02960595302283764, + -0.016983259469270706, + -0.013770210556685925, + -0.02214708738029003, + -0.003987623378634453, + -0.023983115330338478, + -0.018475031480193138, + -0.01790127344429493, + -0.007028544787317514, + -0.005192516837269068, + 0.0413106307387352, + -0.004762197844684124, + 0.021688081324100494, + 0.013139075599610806, + 0.0016710724448785186, + -0.003643368138000369, + -0.0019364358158782125, + -0.014229217544198036, + 0.016983259469270706, + -0.053474318236112595, + -0.0009753899066708982, + -0.004561382345855236, + 0.016868507489562035, + 0.007028544787317514, + 0.012450565584003925, + -0.029146945104002953, + 0.0619659461081028, + 0.008606381714344025, + -0.016753755509853363, + -0.01870453543961048, + -0.0033851766493171453, + 0.011016168631613255, + 0.014286593534052372, + 0.004647445864975452, + 0.02478637918829918, + 0.002510194666683674, + 0.039015594869852066, + 0.037638574838638306, + 0.030523966997861862, + -0.03809758275747299, + 0.044523678719997406, + -0.00017571361968293786, + -0.025704393163323402, + 0.018245529383420944, + -0.047277722507715225, + -0.016753755509853363, + 0.010958792641758919, + -0.04199914261698723, + 0.01652425155043602, + 0.00866375770419836, + 0.03855658695101738, + 0.0015706645790487528, + -0.0019507798133417964, + -0.025933895260095596, + 0.010901416651904583, + -0.005364644341170788, + 0.03167148306965828, + -0.05645786225795746, + 0.004647445864975452, + -0.00740148825570941, + 0.01652425155043602, + 0.009753898717463017, + 0.006885105278342962, + -0.016753755509853363, + 0.01451609656214714, + 0.016983259469270706, + 0.008835884742438793, + 0.029835455119609833, + 0.024556875228881836, + -0.006885105278342962, + -0.0206553153693676, + 0.026507655158638954, + -0.023179853335022926, + 0.005852339323610067, + -0.011016168631613255, + -0.01996680535376072, + 0.006540849804878235, + -0.010040778666734695, + 0.010557161644101143, + -0.003729431889951229, + -0.017671769484877586, + 0.007516239769756794, + 0.023179853335022926, + -0.04567119851708412, + -0.0031126413960009813, + 0.016753755509853363, + 0.002510194666683674, + -0.019737301394343376, + 0.013253827579319477, + 0.008434253744781017, + 0.005479396320879459, + 0.013368579559028149, + -0.020999571308493614, + 0.015720989555120468, + -2.9584436560980976e-05, + 0.0030122334137558937, + 0.01583574153482914, + -0.0052498928271234035, + 0.026622407138347626, + -0.026392903178930283, + -0.016753755509853363, + 0.017786521464586258, + -0.007114608772099018, + 0.00266797817312181, + 0.013712834566831589, + 0.008491629734635353, + -0.006885105278342962, + 0.005708899814635515, + -0.022491343319416046, + -0.012335813604295254, + 0.01858978345990181, + 0.0018001680728048086, + -0.0018073400715366006, + 0.0003012233355548233, + -0.005364644341170788, + 0.00826212577521801, + 0.003327800892293453, + -0.02214708738029003, + -0.016868507489562035, + 0.03258949890732765, + 0.016065245494246483, + -0.0029548576567322016, + -0.006081842817366123, + 0.008089998736977577, + 0.021802833303809166, + 0.014688224531710148, + -0.04475318267941475, + -0.001398536958731711, + 0.027310917153954506, + 0.018475031480193138, + 0.006253970321267843, + 0.010844040662050247, + -0.01732751540839672, + -0.028802689164876938, + 0.0023093789350241423, + 0.01927829347550869, + 0.005737587809562683, + 0.022261839359998703, + 0.01652425155043602, + 0.021114323288202286, + -0.006368722300976515, + -0.00024026147730182856, + -0.007171984296292067, + 0.03029446303844452, + -0.02891744114458561, + -0.013483330607414246, + -0.016983259469270706, + 0.010212905704975128, + 0.008549005724489689, + 0.005565459839999676, + 0.0069998567923903465, + -0.006196594797074795, + 0.005135140847414732, + 0.023065101355314255, + -0.0014630848309025168, + 0.027769923210144043, + 0.009581771679222584, + -0.018360279500484467, + -0.01652425155043602, + -0.004934325348585844, + -0.03327800706028938, + 0.02627815119922161, + 0.010385033674538136, + -0.02490113116800785, + -0.007458863779902458, + 0.02822893112897873, + -0.018934039399027824, + 0.015606238506734371, + 0.0141718415543437, + -0.019622549414634705, + 0.010844040662050247, + -0.004475318361073732, + 0.01067191269248724, + 0.019622549414634705, + -0.020425811409950256, + 0.004016311373561621, + 0.005536771845072508, + 0.007315424270927906, + 0.009294891729950905, + 0.018475031480193138, + -0.02627815119922161, + 0.023868365213274956, + -0.00533595634624362, + 0.025933895260095596, + 0.0051638288423419, + -0.0154914865270257, + 0.012278437614440918, + 0.003514272393658757, + -0.024556875228881836, + 0.03304850310087204, + 0.009639146737754345, + 0.011130919679999352, + -0.018360279500484467, + -0.0006096186698414385, + 0.03304850310087204, + 0.013196451589465141, + 0.008778508752584457, + 0.013942337594926357, + -0.007975246757268906, + 0.006024466827511787, + 0.014401344582438469, + -0.013942337594926357, + 0.004417942371219397, + -0.027999427169561386, + -0.03947460278868675, + -0.01480297651141882, + -0.02960595302283764, + -0.0009323579724878073, + 0.018934039399027824, + -0.028802689164876938, + -0.0141718415543437, + 0.01732751540839672, + -0.01663900353014469, + 0.018245529383420944, + 0.003758119884878397, + -0.01480297651141882, + 0.00935226771980524, + 0.003872871631756425, + -0.0051638288423419, + -0.03235999494791031, + 0.02558964118361473, + -0.006110530812293291, + 0.010729288682341576, + 0.03235999494791031, + -0.014573472552001476, + 0.0005737587343901396, + -0.02203233726322651, + -0.02891744114458561, + 0.029376449063420296, + -0.035573042929172516, + -0.006512161809951067, + 0.001262269332073629, + 0.027884675189852715, + -0.0040736873634159565, + 0.012794820591807365, + 0.004303190857172012, + -0.017557017505168915, + -0.00757361575961113, + 0.012048933655023575, + -0.0001443361834390089, + 0.01583574153482914, + -0.009237515740096569, + -0.0360320508480072, + -0.0007602303521707654, + -0.006770353298634291, + -0.001448740833438933, + 0.017557017505168915, + 0.038327086716890335, + -0.019622549414634705, + -0.022950351238250732, + -0.013139075599610806, + 0.017098011448979378, + 0.04360566660761833, + -0.021802833303809166, + 0.014343968592584133, + -0.001341161085292697, + 0.01996680535376072, + 0.018934039399027824, + -0.018934039399027824, + 0.02685190923511982, + -0.01663900353014469, + 6.141011544968933e-05, + 0.03924509882926941, + -0.0005056249210610986, + -0.0052785808220505714, + 0.010901416651904583, + 0.031212477013468742, + -0.015261983498930931, + 0.01480297651141882, + 0.02272084727883339, + -0.007315424270927906, + -0.006540849804878235, + -0.0036003361456096172, + 0.0006562366033904254, + -0.0058236513286828995, + -0.03258949890732765, + 0.01870453543961048, + -0.016065245494246483, + -0.00740148825570941, + 0.0180160254240036, + 0.013598082587122917, + 0.012565316632390022, + -0.012966947630047798, + -0.009467019699513912, + 0.06931006163358688, + -0.015606238506734371, + -0.03878609091043472, + -0.01732751540839672, + 0.011475175619125366, + 0.026737159118056297, + 0.01514723151922226, + 0.009409643709659576, + 0.008549005724489689, + -0.025704393163323402, + -0.0206553153693676, + -0.0030839534010738134, + -0.01141779962927103, + 0.014114465564489365, + 0.01067191269248724, + 0.031441979110240936, + 0.001190549461171031, + 0.0021802831906825304, + -0.01342595461755991, + -0.020540563389658928, + -0.013540706597268581, + 0.006598225794732571, + 0.01067191269248724, + 0.018819287419319153, + 0.005881027318537235, + 0.013024323619902134, + 0.0014917728258296847, + -0.004590069875121117, + -0.010499785654246807, + 0.06150693818926811, + -0.01480297651141882, + -0.020425811409950256, + -0.01663900353014469, + 0.0048195733688771725, + -0.01721276342868805, + -0.025360137224197388, + -0.006253970321267843, + 0.011704678647220135, + 0.015606238506734371, + -0.0154914865270257, + -0.007028544787317514, + -0.031441979110240936, + -0.014401344582438469, + 0.0309829730540514, + -0.006569537799805403, + -0.03717956691980362, + 0.02822893112897873, + 0.027310917153954506, + 0.011303047649562359, + 0.035573042929172516, + -0.012335813604295254, + 0.0038154958747327328, + -0.026507655158638954, + 0.022606095299124718, + -0.02409786731004715, + 0.02547488920390606, + -0.007516239769756794, + -0.02008155733346939, + 0.007688367273658514, + -0.019737301394343376, + -0.003643368138000369, + -0.032130490988492966, + 0.01721276342868805, + -0.008893260732293129, + -0.0009682179079391062, + -0.0004088031128048897, + -0.0004841089539695531, + 0.01870453543961048, + -0.001025593839585781, + 0.018245529383420944, + -0.01342595461755991, + -0.008606381714344025, + 0.011130919679999352, + -0.029146945104002953, + -0.0021372514311224222, + 0.007688367273658514, + -0.0026249464135617018, + 0.01939304545521736, + -0.0014702568296343088, + 0.026507655158638954, + -0.01732751540839672, + 0.023179853335022926, + 0.024327371269464493, + 0.025245385244488716, + 0.007975246757268906, + 0.03993361070752144, + -0.003729431889951229, + 0.023983115330338478, + -0.005020389333367348, + -0.012966947630047798, + -0.036261554807424545, + -0.010614536702632904, + -0.00024205447698477656, + -0.020311059430241585, + 0.02421261928975582, + 0.0009682179079391062, + -0.019507797434926033, + -0.010442409664392471, + 0.0027396981604397297, + -0.014057089574635029, + 0.013655458576977253, + -0.018819287419319153, + -0.01732751540839672, + 0.012680068612098694, + -0.021917585283517838, + -0.02765517309308052, + 0.005565459839999676, + 0.024556875228881836, + -0.031441979110240936, + 0.002481506671756506, + -0.005393332336097956, + -0.0005988607299514115, + -0.0020368436817079782, + -0.028458435088396072, + -0.014688224531710148, + -0.002768385922536254, + -0.029146945104002953, + -0.009294891729950905, + 0.034884531050920486, + 0.03993361070752144, + -0.005479396320879459, + 0.009467019699513912, + 0.004790885839611292, + -0.022606095299124718, + -0.01514723151922226, + 0.013253827579319477, + -0.028458435088396072, + -0.011647302657365799, + 0.010212905704975128, + -0.03534353896975517, + -0.03534353896975517, + 0.059670910239219666, + -0.04613020271062851, + -0.03717956691980362, + -0.004934325348585844, + -0.05117928236722946, + 0.021573329344391823, + 0.010040778666734695, + -0.03534353896975517, + -0.002782729919999838, + -0.03167148306965828, + 0.011130919679999352, + 0.01663900353014469, + 0.022950351238250732, + 0.01858978345990181, + 0.006598225794732571, + -0.01514723151922226, + 0.015376734547317028, + 0.00935226771980524, + 0.019852053374052048, + -0.00774574326351285, + -0.00499170133844018, + -0.01996680535376072, + -0.005794963333755732, + -0.0154914865270257, + -0.032819002866744995, + -0.01858978345990181, + 0.006712977308779955, + 0.004303190857172012, + -0.00998340267688036, + 0.0028257619123905897, + -0.005794963333755732, + -0.0016423844499513507, + -0.039704106748104095, + -0.018819287419319153, + 0.004417942371219397 + ] + } + } + }, + "price_ref_au": { + "denom": "au_usd", + "in_per_1k": "60000000000000", + "out_per_1k": "0" + } + }, + { + "model_id": "convaiinnovations/laya", + "min_app_version": "0.2.261", + "model_class": "decision", + "family": "laya", + "params_b": 1.164, + "tier": "launch", + "provenance": { + "source": { + "kind": "huggingface", + "publisher_key": "huggingface-repo", + "repo": "convaiinnovations/laya", + "revision": "1c5edc17a7acd8701df6fc341c0d179f1c62c982" + }, + "conversion": [ + { + "tool": "huggingface_hub", + "method": "immutable-byte-mirror:pinned-full-three-checkpoint-snapshot", + "input_sha256": "1213bb35234c9375176bc9f65f93096f23043fd28738dafd7e47414eb59ef7ea", + "output_sha256": "1213bb35234c9375176bc9f65f93096f23043fd28738dafd7e47414eb59ef7ea" + } + ], + "license": "apache-2.0", + "license_sha256": "a6cba85bc92e0cff7a450b1d873c0eaa2e9fc96bf472df0247a26bec77bf3ff9" + }, + "artifacts": { + "safetensors-bf16": { + "engine": "laya", + "source": { + "kind": "huggingface", + "publisher_key": "huggingface-repo", + "repo": "TracNetwork/mayhem-catalog-convaiinnovations-laya", + "revision": "d288cbfe560a0ff904401f57e28820aa140c11e7" + }, + "upstream_source": { + "kind": "huggingface", + "publisher_key": "huggingface-repo", + "repo": "convaiinnovations/laya", + "revision": "1c5edc17a7acd8701df6fc341c0d179f1c62c982" + }, + "path": "model.safetensors", + "artifact_root": "51ad4528dc734e31e1feb8ad4652e81fe70554e9c628f9aca1cd7518ec1885a3", + "artifact_root_kind": "blake3_merkle_v1", + "weights_bytes": 842609210, + "source_sha256": "891102d372688fc2a094dac56a384bc537b87c63f21f9f3dac0be2b7cbc8d86c", + "tokenizer_sha256": "6c8aaa9a542084f2457eab775d4eeb51f92a70c0fd9de28d5edb0ddec3c08d30", + "min_compute_cap": "7.5", + "download_check": true, + "notes": "Pinned complete Laya snapshot with English, multilingual, and typed-decisions checkpoints; all three checkpoints are preloaded and must remain CUDA-resident.", + "sidecars": { + "laya_encoder_config": { + "source": { + "kind": "huggingface", + "publisher_key": "huggingface-repo", + "repo": "TracNetwork/mayhem-catalog-convaiinnovations-laya", + "revision": "d288cbfe560a0ff904401f57e28820aa140c11e7" + }, + "upstream_source": { + "kind": "huggingface", + "publisher_key": "huggingface-repo", + "repo": "convaiinnovations/laya", + "revision": "1c5edc17a7acd8701df6fc341c0d179f1c62c982" + }, + "path": "encoder/config.json", + "artifact_root": "2ef876e3157c4a4963350ebd5fbd391c576b8bfe5f9f96cdfc25f8a30612e29e", + "artifact_root_kind": "blake3_merkle_v1", + "weights_bytes": 2083, + "source_sha256": "bf3ab80598fdccf414855a2ce80f22859e4492d06ca8a62ddd1cfb63972f8979" + }, + "laya_agent_config": { + "source": { + "kind": "huggingface", + "publisher_key": "huggingface-repo", + "repo": "TracNetwork/mayhem-catalog-convaiinnovations-laya", + "revision": "d288cbfe560a0ff904401f57e28820aa140c11e7" + }, + "upstream_source": { + "kind": "huggingface", + "publisher_key": "huggingface-repo", + "repo": "convaiinnovations/laya", + "revision": "1c5edc17a7acd8701df6fc341c0d179f1c62c982" + }, + "path": "rl_agent_config.json", + "artifact_root": "fd54d74f48524982fc923ec83407cc30746b2591aed0a411688211a2cddb5e09", + "artifact_root_kind": "blake3_merkle_v1", + "weights_bytes": 745, + "source_sha256": "ae287b56bbcf5f8c4f4541ae9dfd00c914c4c48b940b8398c3058af37ba92bbd" + }, + "laya_tokenizer": { + "source": { + "kind": "huggingface", + "publisher_key": "huggingface-repo", + "repo": "TracNetwork/mayhem-catalog-convaiinnovations-laya", + "revision": "d288cbfe560a0ff904401f57e28820aa140c11e7" + }, + "upstream_source": { + "kind": "huggingface", + "publisher_key": "huggingface-repo", + "repo": "convaiinnovations/laya", + "revision": "1c5edc17a7acd8701df6fc341c0d179f1c62c982" + }, + "path": "tokenizer/tokenizer.json", + "artifact_root": "0dffd509e083200fff68397489a923cfdcb55217aec5f6622d6b2b7f51cbbd30", + "artifact_root_kind": "blake3_merkle_v1", + "weights_bytes": 3583228, + "source_sha256": "6c8aaa9a542084f2457eab775d4eeb51f92a70c0fd9de28d5edb0ddec3c08d30" + }, + "laya_tokenizer_config": { + "source": { + "kind": "huggingface", + "publisher_key": "huggingface-repo", + "repo": "TracNetwork/mayhem-catalog-convaiinnovations-laya", + "revision": "d288cbfe560a0ff904401f57e28820aa140c11e7" + }, + "upstream_source": { + "kind": "huggingface", + "publisher_key": "huggingface-repo", + "repo": "convaiinnovations/laya", + "revision": "1c5edc17a7acd8701df6fc341c0d179f1c62c982" + }, + "path": "tokenizer/tokenizer_config.json", + "artifact_root": "1813f2e15a19bc82ff3fdb0fab1cf5f1dcb7b5f04d1f7bfb982f343ffece06b1", + "artifact_root_kind": "blake3_merkle_v1", + "weights_bytes": 308, + "source_sha256": "50044de60daaa73df97d262e15a40d4faf0160e7d742df64b377877a1320dd12" + }, + "laya_multilingual_model": { + "source": { + "kind": "huggingface", + "publisher_key": "huggingface-repo", + "repo": "TracNetwork/mayhem-catalog-convaiinnovations-laya", + "revision": "d288cbfe560a0ff904401f57e28820aa140c11e7" + }, + "upstream_source": { + "kind": "huggingface", + "publisher_key": "huggingface-repo", + "repo": "convaiinnovations/laya", + "revision": "1c5edc17a7acd8701df6fc341c0d179f1c62c982" + }, + "path": "multilingual/model.safetensors", + "artifact_root": "f6a38ff4955ce85f245e453b57eab5169028eef15de0c59f2e7a46b13360e456", + "artifact_root_kind": "blake3_merkle_v1", + "weights_bytes": 643835514, + "source_sha256": "9d628fd971b700382ac6f65920a86f149777b2e748e0c955fb3b19695aa8f204" + }, + "laya_multilingual_encoder_config": { + "source": { + "kind": "huggingface", + "publisher_key": "huggingface-repo", + "repo": "TracNetwork/mayhem-catalog-convaiinnovations-laya", + "revision": "d288cbfe560a0ff904401f57e28820aa140c11e7" + }, + "upstream_source": { + "kind": "huggingface", + "publisher_key": "huggingface-repo", + "repo": "convaiinnovations/laya", + "revision": "1c5edc17a7acd8701df6fc341c0d179f1c62c982" + }, + "path": "multilingual/encoder/config.json", + "artifact_root": "43273722f230d62c88f498b6ae64a438ea2e95b446939ebfdbbb5be4650680e6", + "artifact_root_kind": "blake3_merkle_v1", + "weights_bytes": 1938, + "source_sha256": "83f6916d13ef0f556ac461f28308dc2bffa7ebeadee8ec9e2db5812020ea5bb4" + }, + "laya_multilingual_agent_config": { + "source": { + "kind": "huggingface", + "publisher_key": "huggingface-repo", + "repo": "TracNetwork/mayhem-catalog-convaiinnovations-laya", + "revision": "d288cbfe560a0ff904401f57e28820aa140c11e7" + }, + "upstream_source": { + "kind": "huggingface", + "publisher_key": "huggingface-repo", + "repo": "convaiinnovations/laya", + "revision": "1c5edc17a7acd8701df6fc341c0d179f1c62c982" + }, + "path": "multilingual/rl_agent_config.json", + "artifact_root": "301155769ef0d3caa5e2fa2494e6296668af5a032a7174df18e0687a22b47c09", + "artifact_root_kind": "blake3_merkle_v1", + "weights_bytes": 472, + "source_sha256": "25061739243b617ad88d1219ba6f8a9c86c5881ca28df024fa2d9b3b2fcc30c6" + }, + "laya_multilingual_tokenizer": { + "source": { + "kind": "huggingface", + "publisher_key": "huggingface-repo", + "repo": "TracNetwork/mayhem-catalog-convaiinnovations-laya", + "revision": "d288cbfe560a0ff904401f57e28820aa140c11e7" + }, + "upstream_source": { + "kind": "huggingface", + "publisher_key": "huggingface-repo", + "repo": "convaiinnovations/laya", + "revision": "1c5edc17a7acd8701df6fc341c0d179f1c62c982" + }, + "path": "multilingual/tokenizer/tokenizer.json", + "artifact_root": "1d254e265e07518a8bbede19dd84b8574c97edfb5f97df332241989543d26dee", + "artifact_root_kind": "blake3_merkle_v1", + "weights_bytes": 34363188, + "source_sha256": "609d8f4c067cd3950f88594c5a802616cea245823836ef5848ee4fc40aab5b6f" + }, + "laya_multilingual_tokenizer_config": { + "source": { + "kind": "huggingface", + "publisher_key": "huggingface-repo", + "repo": "TracNetwork/mayhem-catalog-convaiinnovations-laya", + "revision": "d288cbfe560a0ff904401f57e28820aa140c11e7" + }, + "upstream_source": { + "kind": "huggingface", + "publisher_key": "huggingface-repo", + "repo": "convaiinnovations/laya", + "revision": "1c5edc17a7acd8701df6fc341c0d179f1c62c982" + }, + "path": "multilingual/tokenizer/tokenizer_config.json", + "artifact_root": "0081bcd5b6692a64aea3f32bcf56b90cef0fdb638c4160db870317ddbf5a54e4", + "artifact_root_kind": "blake3_merkle_v1", + "weights_bytes": 524, + "source_sha256": "6c6b2d8e3c84ce0e671c129cd6b374b235d6f9863042a5836358d00a89bbb5a1" + }, + "laya_typed_decisions_model": { + "source": { + "kind": "huggingface", + "publisher_key": "huggingface-repo", + "repo": "TracNetwork/mayhem-catalog-convaiinnovations-laya", + "revision": "d288cbfe560a0ff904401f57e28820aa140c11e7" + }, + "upstream_source": { + "kind": "huggingface", + "publisher_key": "huggingface-repo", + "repo": "convaiinnovations/laya", + "revision": "1c5edc17a7acd8701df6fc341c0d179f1c62c982" + }, + "path": "typed-decisions/model.safetensors", + "artifact_root": "da57720e920005372bbd1d6f6fbb9b0d37d74f0d7bc0a1181bf832fb0f2690a7", + "artifact_root_kind": "blake3_merkle_v1", + "weights_bytes": 842609220, + "source_sha256": "4fa56de72383a9d3efa9cfa78955733c81b9fc8067a587ca4beb82c78107a24e" + }, + "laya_typed_decisions_encoder_config": { + "source": { + "kind": "huggingface", + "publisher_key": "huggingface-repo", + "repo": "TracNetwork/mayhem-catalog-convaiinnovations-laya", + "revision": "d288cbfe560a0ff904401f57e28820aa140c11e7" + }, + "upstream_source": { + "kind": "huggingface", + "publisher_key": "huggingface-repo", + "repo": "convaiinnovations/laya", + "revision": "1c5edc17a7acd8701df6fc341c0d179f1c62c982" + }, + "path": "typed-decisions/encoder/config.json", + "artifact_root": "c13e935799e247d15acc9bc039aff4ad0e812587d4cdaee0c3aa5419ae126f33", + "artifact_root_kind": "blake3_merkle_v1", + "weights_bytes": 2084, + "source_sha256": "5268d24ad3b77c8151de5dcb0762ba4391619aad9ab0bda33e36fb083cfeae6d" + }, + "laya_typed_decisions_agent_config": { + "source": { + "kind": "huggingface", + "publisher_key": "huggingface-repo", + "repo": "TracNetwork/mayhem-catalog-convaiinnovations-laya", + "revision": "d288cbfe560a0ff904401f57e28820aa140c11e7" + }, + "upstream_source": { + "kind": "huggingface", + "publisher_key": "huggingface-repo", + "repo": "convaiinnovations/laya", + "revision": "1c5edc17a7acd8701df6fc341c0d179f1c62c982" + }, + "path": "typed-decisions/rl_agent_config.json", + "artifact_root": "0db65108ff10bd1737f4d2087696e7dbff00c57a33cf2d5d6ebd8360e9d3a778", + "artifact_root_kind": "blake3_merkle_v1", + "weights_bytes": 847, + "source_sha256": "ebf0cd524d92342a6be5e48e9fca3d7c2babfb5a56ccd79d2171ef5d8c7f7be8" + }, + "laya_typed_decisions_tokenizer": { + "source": { + "kind": "huggingface", + "publisher_key": "huggingface-repo", + "repo": "TracNetwork/mayhem-catalog-convaiinnovations-laya", + "revision": "d288cbfe560a0ff904401f57e28820aa140c11e7" + }, + "upstream_source": { + "kind": "huggingface", + "publisher_key": "huggingface-repo", + "repo": "convaiinnovations/laya", + "revision": "1c5edc17a7acd8701df6fc341c0d179f1c62c982" + }, + "path": "typed-decisions/tokenizer/tokenizer.json", + "artifact_root": "0dffd509e083200fff68397489a923cfdcb55217aec5f6622d6b2b7f51cbbd30", + "artifact_root_kind": "blake3_merkle_v1", + "weights_bytes": 3583228, + "source_sha256": "6c8aaa9a542084f2457eab775d4eeb51f92a70c0fd9de28d5edb0ddec3c08d30" + }, + "laya_typed_decisions_tokenizer_config": { + "source": { + "kind": "huggingface", + "publisher_key": "huggingface-repo", + "repo": "TracNetwork/mayhem-catalog-convaiinnovations-laya", + "revision": "d288cbfe560a0ff904401f57e28820aa140c11e7" + }, + "upstream_source": { + "kind": "huggingface", + "publisher_key": "huggingface-repo", + "repo": "convaiinnovations/laya", + "revision": "1c5edc17a7acd8701df6fc341c0d179f1c62c982" + }, + "path": "typed-decisions/tokenizer/tokenizer_config.json", + "artifact_root": "6476314c3aaac80b46446340e97b6a7cde12320997b8c5621d5ad6ab8cae3643", + "artifact_root_kind": "blake3_merkle_v1", + "weights_bytes": 337, + "source_sha256": "08d4cf3ac4dca381759441b85b91a6d40e688471dcd33d15d6649eb0a9a854d1" + } + } + } + }, + "caps": { + "tools": false, + "json": true, + "ctx_max": 1024, + "vision": false, + "image": false, + "video": false, + "audio": false, + "output_modality": "text", + "output_modalities": [ + "text" + ] + }, + "requirements": { + "min_ram_gb": 8, + "min_vram_gb_full_offload": 8, + "cpu_flags": [ + "neon" + ], + "backends": [ + "laya" + ] + }, + "adapter": { + "endpoint_families": [ + { + "family": "mayhem_decisions", + "request_attributes": [ + "model", + "state", + "questions", + "checkpoint", + "task", + "lang", + "auto_task_detection", + "email", + "shortlist", + "temperature", + "limits", + "user" + ], + "required_request_attributes": [ + "model", + "state", + "questions" + ], + "response_attributes": [ + "id", + "object", + "created", + "model", + "answers", + "routing", + "shortlist", + "preprocessing", + "usage", + "mayhem" + ], + "required_response_attributes": [ + "id", + "object", + "created", + "model", + "answers", + "routing", + "usage", + "mayhem" + ], + "request_attribute_specs": { + "auto_task_detection": { + "value_types": [ + "boolean" + ], + "default": false, + "calibration_values": [ + false, + true + ] + }, + "checkpoint": { + "value_types": [ + "string" + ], + "enum_values": [ + "english", + "multilingual", + "typed-decisions" + ], + "calibration_values": [ + "english", + "multilingual", + "typed-decisions" + ] + }, + "email": { + "value_types": [ + "object" + ], + "calibration_values": [ + { + "clean": true, + "max_chars": 3000 + } + ] + }, + "lang": { + "value_types": [ + "string" + ], + "min_length": 1, + "max_length": 128, + "calibration_values": [ + "en" + ] + }, + "limits": { + "value_types": [ + "object" + ], + "calibration_values": [ + { + "max_len": 1024, + "head_max_len": 256 + } + ] + }, + "model": { + "value_types": [ + "string" + ], + "calibration_values": [ + "$MODEL" + ] + }, + "questions": { + "value_types": [ + "object" + ], + "calibration_values": [ + { + "intent": { + "type": "choice", + "instructions": "What is the request about?", + "criteria": { + "support": "support request", + "other": "another topic" + } + }, + "urgent": { + "type": "noul", + "instructions": "Is the request urgent?" + } + } + ] + }, + "shortlist": { + "value_types": [ + "object" + ], + "calibration_values": [ + { + "k": 20, + "max_length": 512, + "batch_size": 32 + } + ] + }, + "state": { + "value_types": [ + "string", + "object", + "array" + ], + "calibration_values": [ + "Mayhem calibration state", + { + "message": "Mayhem calibration state" + }, + [ + { + "role": "user", + "content": "Mayhem calibration state" + } + ] + ] + }, + "task": { + "value_types": [ + "string" + ], + "enum_values": [ + "typed_decisions" + ], + "calibration_values": [ + "typed_decisions" + ] + }, + "temperature": { + "value_types": [ + "object" + ], + "calibration_values": [ + { + "choice": 1.0, + "score": 1.0, + "noul": 1.0 + } + ] + }, + "user": { + "value_types": [ + "string" + ], + "min_length": 1, + "max_length": 512, + "calibration_values": [ + "mayhem-calibration-user" + ] + } + }, + "response_attribute_specs": { + "answers": { + "value_types": [ + "object" + ], + "calibration_values": [ + {} + ] + }, + "created": { + "value_types": [ + "integer" + ], + "minimum": 0, + "maximum": 18446744073709552000, + "calibration_values": [ + 1 + ] + }, + "id": { + "value_types": [ + "string" + ], + "min_length": 0, + "max_length": 536870912, + "calibration_values": [ + "$RESPONSE_VALUE" + ] + }, + "mayhem": { + "value_types": [ + "object" + ], + "calibration_values": [ + {} + ] + }, + "model": { + "value_types": [ + "string" + ], + "min_length": 0, + "max_length": 536870912, + "calibration_values": [ + "$RESPONSE_VALUE" + ] + }, + "object": { + "value_types": [ + "string" + ], + "min_length": 0, + "max_length": 536870912, + "calibration_values": [ + "$RESPONSE_VALUE" + ] + }, + "preprocessing": { + "value_types": [ + "object" + ], + "calibration_values": [ + {} + ] + }, + "routing": { + "value_types": [ + "object" + ], + "calibration_values": [ + {} + ] + }, + "shortlist": { + "value_types": [ + "object" + ], + "calibration_values": [ + {} + ] + }, + "usage": { + "value_types": [ + "object" + ], + "calibration_values": [ + {} + ] + } + }, + "interaction_groups": [] + } + ], + "chat_template_id": "generic_chatml", + "tool_call_strategy": "none", + "reasoning_passthrough": "strip", + "modality_set": [ + "text" + ], + "specialities": [] + }, + "modality_assessment": { + "detected": [ + "text" + ], + "evidence": [ + "Pinned upstream model snapshot, SDK, and README define structured text-state decision inference across English, multilingual, and typed-decision checkpoints." + ], + "calibrated_fingerprints": { + "safetensors-bf16": { + "text": "1977665478c278ee738983b310f7fdec25a43d5db6f415732340a5053c2a4ab1" + } + }, + "resource_profiles": {} + }, + "speciality_assessment": { + "detected": [], + "evidence": [ + "Pinned upstream card, configuration files, SDK implementation, and checkpoint inventory were reviewed; Laya exposes checkpoint and decision-question selection through its signed decision endpoint rather than text-generation speciality controls." + ], + "unsupported": {}, + "calibrated": {} + }, + "sampling": {}, + "canary": { + "set_id": "canary-laya-decision-v1", + "match_min": 1, + "verification_method": "decision_fingerprint", + "fingerprints": {}, + "token_prefixes": {}, + "perceptual_hashes": {}, + "embedding_vectors": {}, + "transcripts": {}, + "audio_fingerprints": {}, + "video_fingerprints": {}, + "decision_fingerprints": { + "safetensors-bf16": { + "english-choice-noul": "7f5037c039b1a1977b91bdbcd2d947e033578331e046bdfdd4c7c67775d8c6b7", + "multilingual-choice-score": "3f5982b302fff49f7228d66c1231d4543d6083f277cdc161c6efdb27f293a7ae", + "typed-all-question-types": "fb9b0157613d545ae22301939e8d1d8001d3f676509333e308599970f57d04b2", + "automatic-non-latin-routing": "1a0f674e7f1c2dda957b3c47fa0dcc9021939ebdcc01f1cad0888152971bfd38", + "automatic-typed-workflow-routing": "95a3c156f64aaefb191eed9e72ec4b3d3122dfd410cd1d4410ad0345d943db00", + "email-temperature-limits": "c28cb3382625208dbbb92e1010b930f3073d0f52deb85a314eca6d619d9465c4", + "high-cardinality-shortlist": "c7ec8c56b03b1f2ae00aec14700db8d088c51bb100ec543a7e2b5c1c279863b1", + "supplied-vector-shortlist": "24c76dc84e6ad1e5e170399699547208a9979225e87af6df27aca45718caa742" + } + } + }, + "price_ref_au": { + "denom": "au_usd", + "in_per_1k": "100000000000", + "out_per_1k": "100000000000", + "rate_map": [ + { + "unit": "input_token", + "per_unit_au": "100000000", + "granularity": 1 + }, + { + "unit": "output_token", + "per_unit_au": "100000000", + "granularity": 1 + } + ], + "per_req_au": "0", + "min_session_au": "0" + } } ], "schema_version": 1, @@ -37938,6 +48939,18 @@ ] ], "proof_sha256": "3fbab1e8f6fed5d8b5e393e958edebef20544346d07ae66a0d68a7c8e59114fd" + }, + "e228d4e36517c331c3ddf034753cf974a7f73d2ac3dd0be4ef145722eccdc26c": { + "schema_version": 1, + "engine": "vllm", + "independent_dispatch": true, + "request_modalities": [ + [ + "embedding" + ] + ], + "topology": "shared_worker", + "proof_sha256": "bd4bee976e32756304ef73d293eaea734aea789a2065975717ca86cb20918e10" } }, "vllm_execution_modes": { diff --git a/catalog/signatures/models.json.sig b/catalog/signatures/models.json.sig index beee9bcb..d864d5c6 100644 --- a/catalog/signatures/models.json.sig +++ b/catalog/signatures/models.json.sig @@ -4,6 +4,6 @@ "signed_path": "catalog/models.json", "key_id": "mayhem-catalog-tracnetwork-v1", "public_key": "aa0a2667d74e31c984a5e4ba34c4bd334f001a640b4ecf807d6812e14e817f24", - "blake3": "bbb2f11e65eb9dac06d8d2ffa9640a8e87e78a6ce56b73d2e51cccb14e13c29b", - "sig": "27e731cf0997eca80d7ebbdc3eb0ff75e114a51678fce535afbc76acaf44d54581b2263b3cdeab4b9cde525d13339691d477bfe5f1e1dc6e14345fd4ba7d170a" + "blake3": "d0303ff50076e0334074d7016747cf5636dc9da605fd33ecb47a941e4c8775ac", + "sig": "97681e6ff4bba641b52559838e0a92c7e638ec36d0301fc3af294d8ebbbda7395f53369de65ba0c270b19a12f6ca1664f87c60fd35d63997d5c8aab537ba0a09" } \ No newline at end of file diff --git a/contracts/scripts/tap-settlement-roller.mjs b/contracts/scripts/tap-settlement-roller.mjs index 70b2e78c..933e0b30 100644 --- a/contracts/scripts/tap-settlement-roller.mjs +++ b/contracts/scripts/tap-settlement-roller.mjs @@ -22,8 +22,8 @@ export const BPS = 10_000n; export const PROVIDER_BPS = 7_500n; export const OPERATOR_BPS = 1_500n; const PROVIDER_CAP_TOLERANCE_WEI = 0n; -const SESSION_RECEIPT_SCHEMA_VERSION = 11; -const SETTLEMENT_RECEIPT_SCHEMA_VERSIONS = new Set([10, SESSION_RECEIPT_SCHEMA_VERSION]); +const SESSION_RECEIPT_SCHEMA_VERSION = 12; +const SETTLEMENT_RECEIPT_SCHEMA_VERSIONS = new Set([10, 11, SESSION_RECEIPT_SCHEMA_VERSION]); const SIGNING_MESSAGE_VERSION = 2; const DEFAULT_TAP_CHALLENGE_EPOCHS = 6; const CONTRACT_VERSION = 19; @@ -313,6 +313,8 @@ export function canonicalReceiptBody(body) { locked_min_session_au: body.locked_min_session_au, served_ctx: body.served_ctx, }; + if (hasOwn(body, 'compute_ms')) canonical.compute_ms = body.compute_ms; + if (hasOwn(body, 'capacity_slots')) canonical.capacity_slots = body.capacity_slots; if (hasOwn(body, 'ctx_bracket')) canonical.ctx_bracket = body.ctx_bracket; if (hasOwn(body, 'ctx_bracket_table_ver')) canonical.ctx_bracket_table_ver = body.ctx_bracket_table_ver; canonical.rules_ver = body.rules_ver; @@ -356,6 +358,17 @@ export function verifyReceiptEnvelope(envelope) { `receipt schema_version must be one of ${Array.from(SETTLEMENT_RECEIPT_SCHEMA_VERSIONS).join(', ')}` ); } + if (body.schema_version === SESSION_RECEIPT_SCHEMA_VERSION) { + if (!Number.isSafeInteger(body.compute_ms) || body.compute_ms < 1) { + throw new Error('schema-12 receipt compute_ms must be a positive safe integer'); + } + if (!Number.isSafeInteger(body.capacity_slots) || body.capacity_slots < 1 || + body.capacity_slots > 1_000_000) { + throw new Error('schema-12 receipt capacity_slots must be an integer from 1 to 1000000'); + } + } else if (hasOwn(body, 'compute_ms') || hasOwn(body, 'capacity_slots')) { + throw new Error('legacy receipt schemas cannot contain utilization fields'); + } if (body.rail !== 'tap') throw new Error('TAP settlement receipt rail must be tap'); for (const field of ['session_id', 'model_id', 'prompt_hash']) { if (typeof body[field] !== 'string' || body[field].length === 0 || body[field].length > 256) { diff --git a/contracts/tests/tap-settlement-roller.test.mjs b/contracts/tests/tap-settlement-roller.test.mjs index 14e3fa58..060b7fb5 100644 --- a/contracts/tests/tap-settlement-roller.test.mjs +++ b/contracts/tests/tap-settlement-roller.test.mjs @@ -191,25 +191,48 @@ test('TAP roller reconstructs Rust receipt wire order from sorted retained value assert.doesNotThrow(() => verifyReceiptEnvelope(signed.receipt)); }); -test('TAP roller accepts current schema 11 while rejecting unknown receipt schemas', () => { +test('TAP roller accepts current schema 12 while rejecting unknown receipt schemas', () => { const current = receipt({ session: 'tap-current-schema', au: '123', epoch: 17, - extraBody: { schema_version: 11 }, + extraBody: { schema_version: 12, compute_ms: 1_000, capacity_slots: 1 }, }); assert.doesNotThrow(() => verifyReceiptEnvelope(current.receipt)); - for (const schemaVersion of [9, 12]) { + for (const schemaVersion of [9, 13]) { const unsupported = structuredClone(current.receipt); unsupported.body.schema_version = schemaVersion; assert.throws( () => verifyReceiptEnvelope(unsupported), - /receipt schema_version must be one of 10, 11/ + /receipt schema_version must be one of 10, 11, 12/ ); } }); +test('TAP roller enforces schema-specific utilization fields', () => { + const current = receipt({ + session: 'tap-schema-utilization', + au: '123', + epoch: 17, + extraBody: { schema_version: 12, compute_ms: 1_000, capacity_slots: 2 }, + }); + assert.doesNotThrow(() => verifyReceiptEnvelope(current.receipt)); + + for (const field of ['compute_ms', 'capacity_slots']) { + const missing = structuredClone(current.receipt); + delete missing.body[field]; + assert.throws(() => verifyReceiptEnvelope(missing), /schema-12 receipt/); + } + + const legacy = structuredClone(current.receipt); + legacy.body.schema_version = 11; + assert.throws( + () => verifyReceiptEnvelope(legacy), + /legacy receipt schemas cannot contain utilization fields/ + ); +}); + test('TAP roller locks payout minimum from confirmed historical parameter evidence', async () => { const calls = []; const fetchImpl = async (url) => { diff --git a/crates/mayhem-cli/Cargo.toml b/crates/mayhem-cli/Cargo.toml index 0a5a077b..4db67d58 100644 --- a/crates/mayhem-cli/Cargo.toml +++ b/crates/mayhem-cli/Cargo.toml @@ -34,7 +34,7 @@ hf-xet = { version = "=1.5.3", features = ["no-default-cache"] } mayhem-bridge = { path = "../mayhem-bridge" } mayhem-attestation = { path = "../mayhem-attestation" } mayhem-enclave = { path = "../mayhem-enclave" } -mayhem-engine = { path = "../mayhem-engine", features = ["ace-step", "chatterbox", "mlx", "needle", "sulphur", "transformers-asr", "trt-llm", "vllm"] } +mayhem-engine = { path = "../mayhem-engine", features = ["ace-step", "chatterbox", "laya", "mlx", "needle", "sulphur", "transformers-asr", "trt-llm", "vllm"] } mayhem-gateway = { path = "../mayhem-gateway" } mayhem-hwprobe = { path = "../mayhem-hwprobe" } mayhem-proto = { path = "../mayhem-proto" } diff --git a/crates/mayhem-cli/assets/docker-29.1.3-ple-io-uring.json b/crates/mayhem-cli/assets/docker-29.1.3-ple-io-uring.json new file mode 100644 index 00000000..72b24213 --- /dev/null +++ b/crates/mayhem-cli/assets/docker-29.1.3-ple-io-uring.json @@ -0,0 +1,853 @@ +{ + "defaultAction": "SCMP_ACT_ERRNO", + "defaultErrnoRet": 1, + "archMap": [ + { + "architecture": "SCMP_ARCH_X86_64", + "subArchitectures": [ + "SCMP_ARCH_X86", + "SCMP_ARCH_X32" + ] + }, + { + "architecture": "SCMP_ARCH_AARCH64", + "subArchitectures": [ + "SCMP_ARCH_ARM" + ] + }, + { + "architecture": "SCMP_ARCH_MIPS64", + "subArchitectures": [ + "SCMP_ARCH_MIPS", + "SCMP_ARCH_MIPS64N32" + ] + }, + { + "architecture": "SCMP_ARCH_MIPS64N32", + "subArchitectures": [ + "SCMP_ARCH_MIPS", + "SCMP_ARCH_MIPS64" + ] + }, + { + "architecture": "SCMP_ARCH_MIPSEL64", + "subArchitectures": [ + "SCMP_ARCH_MIPSEL", + "SCMP_ARCH_MIPSEL64N32" + ] + }, + { + "architecture": "SCMP_ARCH_MIPSEL64N32", + "subArchitectures": [ + "SCMP_ARCH_MIPSEL", + "SCMP_ARCH_MIPSEL64" + ] + }, + { + "architecture": "SCMP_ARCH_S390X", + "subArchitectures": [ + "SCMP_ARCH_S390" + ] + }, + { + "architecture": "SCMP_ARCH_RISCV64", + "subArchitectures": null + } + ], + "syscalls": [ + { + "names": [ + "accept", + "accept4", + "access", + "adjtimex", + "alarm", + "bind", + "brk", + "cachestat", + "capget", + "capset", + "chdir", + "chmod", + "chown", + "chown32", + "clock_adjtime", + "clock_adjtime64", + "clock_getres", + "clock_getres_time64", + "clock_gettime", + "clock_gettime64", + "clock_nanosleep", + "clock_nanosleep_time64", + "close", + "close_range", + "connect", + "copy_file_range", + "creat", + "dup", + "dup2", + "dup3", + "epoll_create", + "epoll_create1", + "epoll_ctl", + "epoll_ctl_old", + "epoll_pwait", + "epoll_pwait2", + "epoll_wait", + "epoll_wait_old", + "eventfd", + "eventfd2", + "execve", + "execveat", + "exit", + "exit_group", + "faccessat", + "faccessat2", + "fadvise64", + "fadvise64_64", + "fallocate", + "fanotify_mark", + "fchdir", + "fchmod", + "fchmodat", + "fchmodat2", + "fchown", + "fchown32", + "fchownat", + "fcntl", + "fcntl64", + "fdatasync", + "fgetxattr", + "flistxattr", + "flock", + "fork", + "fremovexattr", + "fsetxattr", + "fstat", + "fstat64", + "fstatat64", + "fstatfs", + "fstatfs64", + "fsync", + "ftruncate", + "ftruncate64", + "futex", + "futex_requeue", + "futex_time64", + "futex_wait", + "futex_waitv", + "futex_wake", + "futimesat", + "getcpu", + "getcwd", + "getdents", + "getdents64", + "getegid", + "getegid32", + "geteuid", + "geteuid32", + "getgid", + "getgid32", + "getgroups", + "getgroups32", + "getitimer", + "getpeername", + "getpgid", + "getpgrp", + "getpid", + "getppid", + "getpriority", + "getrandom", + "getresgid", + "getresgid32", + "getresuid", + "getresuid32", + "getrlimit", + "get_robust_list", + "getrusage", + "getsid", + "getsockname", + "getsockopt", + "get_thread_area", + "gettid", + "gettimeofday", + "getuid", + "getuid32", + "getxattr", + "getxattrat", + "inotify_add_watch", + "inotify_init", + "inotify_init1", + "inotify_rm_watch", + "io_cancel", + "ioctl", + "io_destroy", + "io_getevents", + "io_pgetevents", + "io_pgetevents_time64", + "ioprio_get", + "ioprio_set", + "io_setup", + "io_submit", + "ipc", + "kill", + "landlock_add_rule", + "landlock_create_ruleset", + "landlock_restrict_self", + "lchown", + "lchown32", + "lgetxattr", + "link", + "linkat", + "listen", + "listmount", + "listxattr", + "listxattrat", + "llistxattr", + "_llseek", + "lremovexattr", + "lseek", + "lsetxattr", + "lstat", + "lstat64", + "madvise", + "map_shadow_stack", + "membarrier", + "memfd_create", + "memfd_secret", + "mincore", + "mkdir", + "mkdirat", + "mknod", + "mknodat", + "mlock", + "mlock2", + "mlockall", + "mmap", + "mmap2", + "mprotect", + "mq_getsetattr", + "mq_notify", + "mq_open", + "mq_timedreceive", + "mq_timedreceive_time64", + "mq_timedsend", + "mq_timedsend_time64", + "mq_unlink", + "mremap", + "mseal", + "msgctl", + "msgget", + "msgrcv", + "msgsnd", + "msync", + "munlock", + "munlockall", + "munmap", + "name_to_handle_at", + "nanosleep", + "newfstatat", + "_newselect", + "open", + "openat", + "openat2", + "pause", + "pidfd_open", + "pidfd_send_signal", + "pipe", + "pipe2", + "pkey_alloc", + "pkey_free", + "pkey_mprotect", + "poll", + "ppoll", + "ppoll_time64", + "prctl", + "pread64", + "preadv", + "preadv2", + "prlimit64", + "process_mrelease", + "pselect6", + "pselect6_time64", + "pwrite64", + "pwritev", + "pwritev2", + "read", + "readahead", + "readlink", + "readlinkat", + "readv", + "recv", + "recvfrom", + "recvmmsg", + "recvmmsg_time64", + "recvmsg", + "remap_file_pages", + "removexattr", + "removexattrat", + "rename", + "renameat", + "renameat2", + "restart_syscall", + "riscv_hwprobe", + "rmdir", + "rseq", + "rt_sigaction", + "rt_sigpending", + "rt_sigprocmask", + "rt_sigqueueinfo", + "rt_sigreturn", + "rt_sigsuspend", + "rt_sigtimedwait", + "rt_sigtimedwait_time64", + "rt_tgsigqueueinfo", + "sched_getaffinity", + "sched_getattr", + "sched_getparam", + "sched_get_priority_max", + "sched_get_priority_min", + "sched_getscheduler", + "sched_rr_get_interval", + "sched_rr_get_interval_time64", + "sched_setaffinity", + "sched_setattr", + "sched_setparam", + "sched_setscheduler", + "sched_yield", + "seccomp", + "select", + "semctl", + "semget", + "semop", + "semtimedop", + "semtimedop_time64", + "send", + "sendfile", + "sendfile64", + "sendmmsg", + "sendmsg", + "sendto", + "setfsgid", + "setfsgid32", + "setfsuid", + "setfsuid32", + "setgid", + "setgid32", + "setgroups", + "setgroups32", + "setitimer", + "setpgid", + "setpriority", + "setregid", + "setregid32", + "setresgid", + "setresgid32", + "setresuid", + "setresuid32", + "setreuid", + "setreuid32", + "setrlimit", + "set_robust_list", + "setsid", + "setsockopt", + "set_thread_area", + "set_tid_address", + "setuid", + "setuid32", + "setxattr", + "setxattrat", + "shmat", + "shmctl", + "shmdt", + "shmget", + "shutdown", + "sigaltstack", + "signalfd", + "signalfd4", + "sigprocmask", + "sigreturn", + "socketcall", + "socketpair", + "splice", + "stat", + "stat64", + "statfs", + "statfs64", + "statmount", + "statx", + "symlink", + "symlinkat", + "sync", + "sync_file_range", + "syncfs", + "sysinfo", + "tee", + "tgkill", + "time", + "timer_create", + "timer_delete", + "timer_getoverrun", + "timer_gettime", + "timer_gettime64", + "timer_settime", + "timer_settime64", + "timerfd_create", + "timerfd_gettime", + "timerfd_gettime64", + "timerfd_settime", + "timerfd_settime64", + "times", + "tkill", + "truncate", + "truncate64", + "ugetrlimit", + "umask", + "uname", + "unlink", + "unlinkat", + "uretprobe", + "utime", + "utimensat", + "utimensat_time64", + "utimes", + "vfork", + "vmsplice", + "wait4", + "waitid", + "waitpid", + "write", + "writev" + ], + "action": "SCMP_ACT_ALLOW" + }, + { + "names": [ + "process_vm_readv", + "process_vm_writev", + "ptrace" + ], + "action": "SCMP_ACT_ALLOW", + "includes": { + "minKernel": "4.8" + } + }, + { + "names": [ + "socket" + ], + "action": "SCMP_ACT_ALLOW", + "args": [ + { + "index": 0, + "value": 40, + "op": "SCMP_CMP_NE" + } + ] + }, + { + "names": [ + "personality" + ], + "action": "SCMP_ACT_ALLOW", + "args": [ + { + "index": 0, + "value": 0, + "op": "SCMP_CMP_EQ" + } + ] + }, + { + "names": [ + "personality" + ], + "action": "SCMP_ACT_ALLOW", + "args": [ + { + "index": 0, + "value": 8, + "op": "SCMP_CMP_EQ" + } + ] + }, + { + "names": [ + "personality" + ], + "action": "SCMP_ACT_ALLOW", + "args": [ + { + "index": 0, + "value": 131072, + "op": "SCMP_CMP_EQ" + } + ] + }, + { + "names": [ + "personality" + ], + "action": "SCMP_ACT_ALLOW", + "args": [ + { + "index": 0, + "value": 131080, + "op": "SCMP_CMP_EQ" + } + ] + }, + { + "names": [ + "personality" + ], + "action": "SCMP_ACT_ALLOW", + "args": [ + { + "index": 0, + "value": 4294967295, + "op": "SCMP_CMP_EQ" + } + ] + }, + { + "names": [ + "sync_file_range2", + "swapcontext" + ], + "action": "SCMP_ACT_ALLOW", + "includes": { + "arches": [ + "ppc64le" + ] + } + }, + { + "names": [ + "arm_fadvise64_64", + "arm_sync_file_range", + "sync_file_range2", + "breakpoint", + "cacheflush", + "set_tls" + ], + "action": "SCMP_ACT_ALLOW", + "includes": { + "arches": [ + "arm", + "arm64" + ] + } + }, + { + "names": [ + "arch_prctl" + ], + "action": "SCMP_ACT_ALLOW", + "includes": { + "arches": [ + "amd64", + "x32" + ] + } + }, + { + "names": [ + "modify_ldt" + ], + "action": "SCMP_ACT_ALLOW", + "includes": { + "arches": [ + "amd64", + "x32", + "x86" + ] + } + }, + { + "names": [ + "s390_pci_mmio_read", + "s390_pci_mmio_write", + "s390_runtime_instr" + ], + "action": "SCMP_ACT_ALLOW", + "includes": { + "arches": [ + "s390", + "s390x" + ] + } + }, + { + "names": [ + "riscv_flush_icache" + ], + "action": "SCMP_ACT_ALLOW", + "includes": { + "arches": [ + "riscv64" + ] + } + }, + { + "names": [ + "open_by_handle_at" + ], + "action": "SCMP_ACT_ALLOW", + "includes": { + "caps": [ + "CAP_DAC_READ_SEARCH" + ] + } + }, + { + "names": [ + "bpf", + "clone", + "clone3", + "fanotify_init", + "fsconfig", + "fsmount", + "fsopen", + "fspick", + "lookup_dcookie", + "lsm_get_self_attr", + "lsm_list_modules", + "lsm_set_self_attr", + "mount", + "mount_setattr", + "move_mount", + "open_tree", + "perf_event_open", + "quotactl", + "quotactl_fd", + "setdomainname", + "sethostname", + "setns", + "syslog", + "umount", + "umount2", + "unshare" + ], + "action": "SCMP_ACT_ALLOW", + "includes": { + "caps": [ + "CAP_SYS_ADMIN" + ] + } + }, + { + "names": [ + "clone" + ], + "action": "SCMP_ACT_ALLOW", + "args": [ + { + "index": 0, + "value": 2114060288, + "op": "SCMP_CMP_MASKED_EQ" + } + ], + "excludes": { + "caps": [ + "CAP_SYS_ADMIN" + ], + "arches": [ + "s390", + "s390x" + ] + } + }, + { + "names": [ + "clone" + ], + "action": "SCMP_ACT_ALLOW", + "args": [ + { + "index": 1, + "value": 2114060288, + "op": "SCMP_CMP_MASKED_EQ" + } + ], + "comment": "s390 parameter ordering for clone is different", + "includes": { + "arches": [ + "s390", + "s390x" + ] + }, + "excludes": { + "caps": [ + "CAP_SYS_ADMIN" + ] + } + }, + { + "names": [ + "clone3" + ], + "action": "SCMP_ACT_ERRNO", + "errnoRet": 38, + "excludes": { + "caps": [ + "CAP_SYS_ADMIN" + ] + } + }, + { + "names": [ + "reboot" + ], + "action": "SCMP_ACT_ALLOW", + "includes": { + "caps": [ + "CAP_SYS_BOOT" + ] + } + }, + { + "names": [ + "chroot" + ], + "action": "SCMP_ACT_ALLOW", + "includes": { + "caps": [ + "CAP_SYS_CHROOT" + ] + } + }, + { + "names": [ + "delete_module", + "init_module", + "finit_module" + ], + "action": "SCMP_ACT_ALLOW", + "includes": { + "caps": [ + "CAP_SYS_MODULE" + ] + } + }, + { + "names": [ + "acct" + ], + "action": "SCMP_ACT_ALLOW", + "includes": { + "caps": [ + "CAP_SYS_PACCT" + ] + } + }, + { + "names": [ + "kcmp", + "pidfd_getfd", + "process_madvise", + "process_vm_readv", + "process_vm_writev", + "ptrace" + ], + "action": "SCMP_ACT_ALLOW", + "includes": { + "caps": [ + "CAP_SYS_PTRACE" + ] + } + }, + { + "names": [ + "iopl", + "ioperm" + ], + "action": "SCMP_ACT_ALLOW", + "includes": { + "caps": [ + "CAP_SYS_RAWIO" + ] + } + }, + { + "names": [ + "settimeofday", + "stime", + "clock_settime", + "clock_settime64" + ], + "action": "SCMP_ACT_ALLOW", + "includes": { + "caps": [ + "CAP_SYS_TIME" + ] + } + }, + { + "names": [ + "vhangup" + ], + "action": "SCMP_ACT_ALLOW", + "includes": { + "caps": [ + "CAP_SYS_TTY_CONFIG" + ] + } + }, + { + "names": [ + "get_mempolicy", + "mbind", + "set_mempolicy", + "set_mempolicy_home_node" + ], + "action": "SCMP_ACT_ALLOW", + "includes": { + "caps": [ + "CAP_SYS_NICE" + ] + } + }, + { + "names": [ + "syslog" + ], + "action": "SCMP_ACT_ALLOW", + "includes": { + "caps": [ + "CAP_SYSLOG" + ] + } + }, + { + "names": [ + "bpf" + ], + "action": "SCMP_ACT_ALLOW", + "includes": { + "caps": [ + "CAP_BPF" + ] + } + }, + { + "names": [ + "perf_event_open" + ], + "action": "SCMP_ACT_ALLOW", + "includes": { + "caps": [ + "CAP_PERFMON" + ] + } + }, + { + "names": [ + "io_uring_setup", + "io_uring_enter", + "io_uring_register" + ], + "action": "SCMP_ACT_ALLOW" + } + ] +} diff --git a/crates/mayhem-cli/resources/python/laya.txt b/crates/mayhem-cli/resources/python/laya.txt new file mode 100644 index 00000000..bc4e1e18 --- /dev/null +++ b/crates/mayhem-cli/resources/python/laya.txt @@ -0,0 +1,7 @@ +transformers==4.57.6 +torch==2.9.1+cu130 +tokenizers==0.22.2 +safetensors==0.8.0 +huggingface-hub==0.36.0 +numpy==2.2.6 +laya==0.3.5 diff --git a/crates/mayhem-cli/src/catalog.rs b/crates/mayhem-cli/src/catalog.rs index d9c3cadc..5671389c 100644 --- a/crates/mayhem-cli/src/catalog.rs +++ b/crates/mayhem-cli/src/catalog.rs @@ -26,6 +26,7 @@ const VERIFICATION_EMBEDDING_COSINE: &str = "embedding_cosine"; const VERIFICATION_TRANSCRIPT_MATCH: &str = "transcript_match"; const VERIFICATION_AUDIO_FINGERPRINT: &str = "audio_fingerprint"; const VERIFICATION_VIDEO_AV_FINGERPRINT: &str = "video_av_fingerprint"; +const VERIFICATION_DECISION_FINGERPRINT: &str = "decision_fingerprint"; const VERIFICATION_ATTESTATION_OF_COMPUTE: &str = "attestation_of_compute"; const MODEL_CLASS_EMBEDDING: &str = "embedding"; const MODEL_CLASS_IMAGE_GENERATION: &str = "image-generation"; @@ -35,6 +36,7 @@ const MODEL_CLASS_STT: &str = "stt"; const MODEL_CLASS_AUDIO_GENERATION: &str = "audio-generation"; const MODEL_CLASS_MUSIC_GENERATION: &str = "music-generation"; const MODEL_CLASS_WORKFLOW: &str = "workflow"; +const MODEL_CLASS_DECISION: &str = "decision"; const MAX_CATALOG_MODALITY_INFLIGHT_ITEMS: u32 = 1_024; const MAX_CATALOG_MODALITY_ITEMS_PER_REQUEST: u32 = 1_024; const MAX_VLLM_SPECULATIVE_TOKENS: u32 = 32; @@ -113,8 +115,7 @@ pub(crate) struct CatalogDocument { #[serde(default)] pub(crate) vllm_execution_profiles: BTreeMap, #[serde(default)] - pub(crate) vllm_execution_modes: - BTreeMap>, + pub(crate) vllm_execution_modes: BTreeMap>, pub(crate) models: Vec, } @@ -148,6 +149,8 @@ pub(crate) struct CatalogGenerationExecutionProfile { pub(crate) schema_version: u32, pub(crate) engine: String, pub(crate) independent_dispatch: bool, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub(crate) max_concurrent: Option, pub(crate) request_modalities: Vec>, #[serde(default, skip_serializing_if = "Option::is_none")] pub(crate) topology: Option, @@ -213,7 +216,9 @@ pub(crate) fn execution_mode_model( artifact_name: &str, mode: &CatalogVllmExecutionMode, ) -> Result { - let artifact = model.artifacts.get(artifact_name) + let artifact = model + .artifacts + .get(artifact_name) .with_context(|| format!("unknown execution mode artifact {artifact_name}"))?; if artifact.engine != "vllm" || mode.schema_version != 1 { bail!("execution mode requires a vllm artifact and schema version 1"); @@ -251,33 +256,42 @@ pub(crate) fn execution_mode_model( let mut effective = model.clone(); effective.artifacts.retain(|name, _| name == artifact_name); - effective.adapter.endpoint_families.retain(|contract| mode.requests.endpoint_families - .iter().any(|policy| policy.family == contract.family)); + effective.adapter.endpoint_families.retain(|contract| { + mode.requests + .endpoint_families + .iter() + .any(|policy| policy.family == contract.family) + }); for contract in &mut effective.adapter.endpoint_families { - let restrictions = mode.requests.endpoint_families.iter() - .find(|policy| policy.family == contract.family).expect("retained mode family"); + let restrictions = mode + .requests + .endpoint_families + .iter() + .find(|policy| policy.family == contract.family) + .expect("retained mode family"); for (path, restriction) in &restrictions.request_attribute_specs { let mut spec = restriction.clone(); let original = &contract.request_attribute_specs[path]; if let Some(default) = &original.default { mayhem_proto::validate_endpoint_attribute_value(&spec, default) .map_err(anyhow::Error::msg) - .with_context(|| format!("execution mode cannot serve inherited default for {path}"))?; + .with_context(|| { + format!("execution mode cannot serve inherited default for {path}") + })?; spec.default = Some(default.clone()); } contract.request_attribute_specs.insert(path.clone(), spec); } } effective.canary = mode.canary.clone(); - effective.modality_assessment.calibrated_fingerprints = BTreeMap::from([ - (artifact_name.to_owned(), mode.modality_fingerprints.clone()), - ]); - effective.modality_assessment.resource_profiles = BTreeMap::from([ - (artifact_name.to_owned(), mode.resource_profiles.clone()), - ]); - effective.speciality_assessment.calibrated = BTreeMap::from([ - (artifact_name.to_owned(), mode.speciality_calibrations.clone()), - ]); + effective.modality_assessment.calibrated_fingerprints = + BTreeMap::from([(artifact_name.to_owned(), mode.modality_fingerprints.clone())]); + effective.modality_assessment.resource_profiles = + BTreeMap::from([(artifact_name.to_owned(), mode.resource_profiles.clone())]); + effective.speciality_assessment.calibrated = BTreeMap::from([( + artifact_name.to_owned(), + mode.speciality_calibrations.clone(), + )]); Ok(effective) } @@ -453,6 +467,8 @@ pub(crate) struct ConversionRef { #[serde(deny_unknown_fields)] pub(crate) struct CatalogArtifact { pub(crate) engine: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub(crate) openai_compatible: Option, #[serde(default)] pub(crate) stable_diffusion_cpp: Option, #[serde(default)] @@ -633,6 +649,8 @@ pub(crate) struct CanaryRef { pub(crate) audio_fingerprints: BTreeMap>, #[serde(default)] pub(crate) video_fingerprints: BTreeMap>, + #[serde(default, skip_serializing_if = "BTreeMap::is_empty")] + pub(crate) decision_fingerprints: BTreeMap>, } #[derive(Debug, Clone, Deserialize)] @@ -644,6 +662,10 @@ pub(crate) struct PriceRef { pub(crate) out_per_1k: MoneyAu, #[serde(default)] pub(crate) rate_map: Vec, + #[serde(default, with = "mayhem_proto::decimal_u128")] + pub(crate) per_req_au: MoneyAu, + #[serde(default, with = "mayhem_proto::decimal_u128")] + pub(crate) min_session_au: MoneyAu, } #[derive(Debug, Clone, Deserialize)] @@ -695,6 +717,8 @@ struct CanarySetPrompt { #[serde(default)] temperature: Option, #[serde(default)] + decision_temperature: Option, + #[serde(default)] top_p: Option, #[serde(default)] top_k: Option, @@ -1018,9 +1042,12 @@ fn validate_generation_execution_profiles(catalog: &CatalogDocument, errors: &mu let bound_model = match primary_artifacts.get(artifact_root.as_str()) { Some(bindings) if bindings.len() == 1 => { let (model, artifact) = bindings[0]; - if model.model_class != DEFAULT_MODEL_CLASS { + if !matches!( + model.model_class.as_str(), + DEFAULT_MODEL_CLASS | MODEL_CLASS_EMBEDDING + ) { errors.push(format!( - "{label} is only valid for generation-capable text models" + "{label} is only valid for generation-capable text or embedding models" )); } if artifact.engine != profile.engine { @@ -1029,6 +1056,14 @@ fn validate_generation_execution_profiles(catalog: &CatalogDocument, errors: &mu profile.engine, artifact.engine )); } + if let Some(binding) = artifact.openai_compatible.as_ref() { + if profile.max_concurrent != Some(binding.max_concurrent) { + errors.push(format!( + "{label}.max_concurrent {:?} must equal the signed openai_compatible runtime capacity {}", + profile.max_concurrent, binding.max_concurrent + )); + } + } Some(model) } Some(bindings) => { @@ -1064,12 +1099,25 @@ fn validate_generation_execution_profile_values( if profile.schema_version != 1 { errors.push(format!("{label}.schema_version must be 1")); } - if profile.engine != "vllm" { - errors.push(format!("{label}.engine must be vllm")); + if !matches!(profile.engine.as_str(), "vllm" | "openai-compatible") { + errors.push(format!("{label}.engine must be vllm or openai-compatible")); } if !profile.independent_dispatch { errors.push(format!("{label}.independent_dispatch must be true")); } + if profile + .max_concurrent + .is_some_and(|capacity| !(1..=64).contains(&capacity)) + { + errors.push(format!( + "{label}.max_concurrent must be between 1 and 64 when present" + )); + } + if profile.engine == "openai-compatible" && profile.max_concurrent.is_none() { + errors.push(format!( + "{label}.max_concurrent is required for openai-compatible" + )); + } if !is_lower_hex_len(&profile.proof_sha256, 64) { errors.push(format!( "{label}.proof_sha256 must be exact lowercase 32-byte hex" @@ -1082,6 +1130,8 @@ fn validate_generation_execution_profile_values( return; } + let embedding_dispatch = + bound_model.is_some_and(|model| model.model_class == MODEL_CLASS_EMBEDDING); let mut seen_sets = BTreeSet::new(); for (set_index, modality_set) in profile.request_modalities.iter().enumerate() { let set_label = format!("{label}.request_modalities[{set_index}]"); @@ -1092,7 +1142,8 @@ fn validate_generation_execution_profile_values( let mut normalized = BTreeSet::new(); for modality in modality_set { - if !valid_adapter_modality(modality) || modality == "embedding" { + if !valid_adapter_modality(modality) || (modality == "embedding" && !embedding_dispatch) + { errors.push(format!( "{set_label} contains unsupported generation modality {modality:?}" )); @@ -1107,9 +1158,15 @@ fn validate_generation_execution_profile_values( } } let normalized = normalized.into_iter().collect::>(); - if !normalized.iter().any(|modality| modality == "text") { + if embedding_dispatch { + if normalized.as_slice() != ["embedding"] { + errors.push(format!( + "{set_label} must contain only embedding for embedding dispatch" + )); + } + } else if !normalized.iter().any(|modality| modality == "text") { errors.push(format!( - "{set_label} must include text for vLLM generation dispatch" + "{set_label} must include text for generation dispatch" )); } if &normalized != modality_set { @@ -1720,6 +1777,27 @@ fn validate_model(model: &CatalogModel, errors: &mut Vec) { !future_model, errors, ); + if let Some(binding) = artifact.openai_compatible.as_ref() { + if u64::from(binding.native_context) != model.caps.ctx_max { + errors.push(format!( + "{}/{} openai_compatible.native_context {} must equal caps.ctx_max {}", + model.model_id, name, binding.native_context, model.caps.ctx_max + )); + } + for (capability, advertised) in [ + ("tools", model.caps.tools), + ("json", model.caps.json), + ("image", model.caps.vision || model.caps.image), + ("video", model_has_input_modality(model, "video")), + ] { + if advertised != binding.capabilities.contains(capability) { + errors.push(format!( + "{}/{} openai_compatible capability {capability} must match model caps", + model.model_id, name + )); + } + } + } } if model.caps.ctx_max == 0 { errors.push(format!("{} caps.ctx_max must be positive", model.model_id)); @@ -2354,6 +2432,26 @@ fn validate_model_modality_assessment(model: &CatalogModel, errors: &mut Vec) { + if model.model_class == MODEL_CLASS_DECISION { + if model.price_ref_au.rate_map.is_empty() { + if model.price_ref_au.in_per_1k == 0 || model.price_ref_au.out_per_1k == 0 { + errors.push(format!( + "{} decision pricing requires positive input and output token rates", + model.model_id + )); + } + } else { + validate_price_rate_map(model, errors); + validate_required_modality_price_units(model, errors); + } + if model.price_ref_au.per_req_au != 0 || model.price_ref_au.min_session_au != 0 { + errors.push(format!( + "{} decision pricing must use token rates without a fixed or minimum fee", + model.model_id + )); + } + return; + } if !model.price_ref_au.rate_map.is_empty() { validate_price_rate_map(model, errors); validate_required_modality_price_units(model, errors); @@ -2376,7 +2474,7 @@ fn validate_price_ref(model: &CatalogModel, errors: &mut Vec) { fn validate_required_modality_price_units(model: &CatalogModel, errors: &mut Vec) { let mut required = BTreeSet::new(); match model.model_class.as_str() { - DEFAULT_MODEL_CLASS => { + DEFAULT_MODEL_CLASS | MODEL_CLASS_DECISION => { required.insert("input_token"); required.insert("output_token"); } @@ -2446,6 +2544,7 @@ fn validate_price_rate_map(model: &CatalogModel, errors: &mut Vec) { USAGE_STEP, USAGE_VIDEO_SECOND, ], + MODEL_CLASS_DECISION => &["input_token", "output_token"], _ => &["input_token", "cached_input_token", "output_token"], }; for entry in &model.price_ref_au.rate_map { @@ -2496,6 +2595,7 @@ fn validate_price_rate_map(model: &CatalogModel, errors: &mut Vec) { .and_then(|policy| policy.pricing_unit.as_deref()) .into_iter() .collect(), + MODEL_CLASS_DECISION => vec![USAGE_INPUT_TOKEN, USAGE_OUTPUT_TOKEN], _ => vec![USAGE_INPUT_TOKEN, USAGE_OUTPUT_TOKEN], }; for unit in required_units { @@ -2548,6 +2648,7 @@ fn validate_canary_verification(model: &CatalogModel, errors: &mut Vec) VERIFICATION_TRANSCRIPT_MATCH => validate_transcript_match_canary(model, errors), VERIFICATION_AUDIO_FINGERPRINT => validate_audio_fingerprint_canary(model, errors), VERIFICATION_VIDEO_AV_FINGERPRINT => validate_video_av_fingerprint_canary(model, errors), + VERIFICATION_DECISION_FINGERPRINT => validate_decision_fingerprint_canary(model, errors), VERIFICATION_ATTESTATION_OF_COMPUTE => { validate_attestation_of_compute_canary(model, errors) } @@ -2751,6 +2852,7 @@ fn valid_canary_verification_method(method: &str) -> bool { | VERIFICATION_TRANSCRIPT_MATCH | VERIFICATION_AUDIO_FINGERPRINT | VERIFICATION_VIDEO_AV_FINGERPRINT + | VERIFICATION_DECISION_FINGERPRINT | VERIFICATION_ATTESTATION_OF_COMPUTE ) } @@ -2779,6 +2881,7 @@ fn canary_verification_method_allowed_for_class(model_class: &str, method: &str) | VERIFICATION_AUDIO_FINGERPRINT | VERIFICATION_VIDEO_AV_FINGERPRINT ) + | (MODEL_CLASS_DECISION, VERIFICATION_DECISION_FINGERPRINT) | (_, VERIFICATION_ATTESTATION_OF_COMPUTE) ) } @@ -2793,10 +2896,46 @@ fn required_launch_output_canary_method(model_class: &str) -> Option<&'static st MODEL_CLASS_TTS | MODEL_CLASS_AUDIO_GENERATION | MODEL_CLASS_MUSIC_GENERATION => { Some(VERIFICATION_AUDIO_FINGERPRINT) } + MODEL_CLASS_DECISION => Some(VERIFICATION_DECISION_FINGERPRINT), _ => None, } } +fn validate_decision_fingerprint_canary(model: &CatalogModel, errors: &mut Vec) { + if model.canary.verification_tolerance_bps.is_some() { + errors.push(format!( + "{} decision_fingerprint canary must not set verification_tolerance_bps", + model.model_id + )); + } + if !model.canary.fingerprints.is_empty() + || !model.canary.token_prefixes.is_empty() + || !model.canary.perceptual_hashes.is_empty() + || !model.canary.embedding_vectors.is_empty() + || !model.canary.transcripts.is_empty() + || !model.canary.audio_fingerprints.is_empty() + || !model.canary.video_fingerprints.is_empty() + { + errors.push(format!( + "{} decision_fingerprint canary must use decision_fingerprints only", + model.model_id + )); + } + validate_prompt_map_complete( + model, + "decision_fingerprints", + &model.canary.decision_fingerprints, + errors, + |model_id, artifact, prompt_id, fingerprint, errors| { + if prompt_id.trim().is_empty() || !is_lower_hex_len(fingerprint, 64) { + errors.push(format!( + "{model_id} canary decision_fingerprints for {artifact} prompt {prompt_id} must be exact lowercase 32-byte hex" + )); + } + }, + ); +} + fn validate_token_fingerprint_canary(model: &CatalogModel, errors: &mut Vec) { if model.canary.verification_tolerance_bps.unwrap_or(0) != 0 { errors.push(format!( @@ -3189,6 +3328,7 @@ fn valid_model_class(model_class: &str) -> bool { | MODEL_CLASS_AUDIO_GENERATION | MODEL_CLASS_MUSIC_GENERATION | MODEL_CLASS_WORKFLOW + | MODEL_CLASS_DECISION ) } @@ -3209,6 +3349,7 @@ fn output_modality_allowed_for_class(model_class: &str, modality: &str) -> bool ) | (MODEL_CLASS_STT, "text") | (MODEL_CLASS_WORKFLOW, "image" | "video" | "audio") + | (MODEL_CLASS_DECISION, "text") ) } @@ -4446,6 +4587,7 @@ fn valid_endpoint_family(family: &str) -> bool { | mayhem_proto::ENDPOINT_MAYHEM_MUSIC_GENERATIONS | mayhem_proto::ENDPOINT_HF_TEXT_TO_AUDIO | mayhem_proto::ENDPOINT_MAYHEM_COMFY_WORKFLOWS + | mayhem_proto::ENDPOINT_MAYHEM_DECISIONS ) } @@ -4519,6 +4661,7 @@ fn required_endpoint_family_names( mayhem_proto::ENDPOINT_HF_TEXT_TO_AUDIO, ]), MODEL_CLASS_WORKFLOW => BTreeSet::from([mayhem_proto::ENDPOINT_MAYHEM_COMFY_WORKFLOWS]), + MODEL_CLASS_DECISION => BTreeSet::from([mayhem_proto::ENDPOINT_MAYHEM_DECISIONS]), _ => BTreeSet::new(), }; if model_class == DEFAULT_MODEL_CLASS @@ -4585,6 +4728,7 @@ fn endpoint_family_allowed_for_model(model: &CatalogModel, family: &str) -> bool | mayhem_proto::ENDPOINT_MAYHEM_COMFY_WORKFLOWS ), MODEL_CLASS_WORKFLOW => family == mayhem_proto::ENDPOINT_MAYHEM_COMFY_WORKFLOWS, + MODEL_CLASS_DECISION => family == mayhem_proto::ENDPOINT_MAYHEM_DECISIONS, _ => false, } } @@ -4601,6 +4745,19 @@ fn adapter_modality_allowed(model: &CatalogModel, modality: &str) -> bool { .any(|detected| detected == modality) } +fn model_has_input_modality(model: &CatalogModel, modality: &str) -> bool { + model + .adapter + .modality_set + .iter() + .any(|entry| entry == modality) + && model + .modality_assessment + .detected + .iter() + .any(|entry| entry == modality) +} + fn validate_artifact( model_id: &str, tier: &str, @@ -4630,12 +4787,14 @@ fn validate_artifact_with_engine_policy( | "comfyui" | "ace-step" | "chatterbox" + | "laya" | "needle-cpu" | "needle-gpu" | "sulphur" | "transformers-asr" | "whisper.cpp" | "piper" + | "openai-compatible" ) { errors.push(format!( @@ -4643,6 +4802,51 @@ fn validate_artifact_with_engine_policy( artifact.engine )); } + match (&*artifact.engine, artifact.openai_compatible.as_ref()) { + ("openai-compatible", Some(binding)) => { + if let Err(error) = binding.validate() { + errors.push(format!( + "{model_id}/{name} invalid openai_compatible runtime binding: {error}" + )); + } + match artifact.sidecars.get(&binding.runtime_recipe_sidecar) { + Some(recipe) if recipe.source_sha256 == binding.runtime_recipe_sha256 => {} + Some(_) => errors.push(format!( + "{model_id}/{name} runtime recipe sidecar hash does not match openai_compatible.runtime_recipe_sha256" + )), + None => errors.push(format!( + "{model_id}/{name} is missing openai_compatible runtime recipe sidecar {}", + binding.runtime_recipe_sidecar + )), + } + match artifact.sidecars.get(&binding.snapshot_manifest_sidecar) { + Some(manifest) if manifest.source_sha256 == binding.snapshot_manifest_sha256 => { + if artifact.path != manifest.path + || artifact.source_sha256.as_deref() + != Some(manifest.source_sha256.as_str()) + { + errors.push(format!( + "{model_id}/{name} primary path/hash must bind the same snapshot manifest object as openai_compatible.snapshot_manifest_sidecar" + )); + } + } + Some(_) => errors.push(format!( + "{model_id}/{name} snapshot manifest sidecar hash does not match openai_compatible.snapshot_manifest_sha256" + )), + None => errors.push(format!( + "{model_id}/{name} is missing openai_compatible snapshot manifest sidecar {}", + binding.snapshot_manifest_sidecar + )), + } + } + ("openai-compatible", None) => errors.push(format!( + "{model_id}/{name} openai-compatible engine requires a signed openai_compatible runtime binding" + )), + (_, Some(_)) => errors.push(format!( + "{model_id}/{name} openai_compatible runtime binding requires engine openai-compatible" + )), + _ => {} + } validate_source( model_id, &format!("artifacts.{name}.source"), @@ -4708,7 +4912,10 @@ fn validate_artifact_with_engine_policy( )); } } - if matches!(artifact.engine.as_str(), "trt-llm" | "vllm") && artifact.min_compute_cap.is_none() + if matches!( + artifact.engine.as_str(), + "trt-llm" | "vllm" | "openai-compatible" + ) && artifact.min_compute_cap.is_none() { errors.push(format!( "{model_id}/{name} {} artifact needs min_compute_cap", @@ -4989,6 +5196,41 @@ fn validate_artifact_with_engine_policy( )); } } + "openai-compatible" => { + let signed_dtype = artifact + .openai_compatible + .as_ref() + .and_then(|binding| binding.server_info_checks.get("/kv_cache_dtype")) + .and_then(Value::as_str); + if signed_dtype != Some(profile.dtype.as_str()) { + errors.push(format!( + "{model_id}/{name} OpenAI-compatible KV-cache dtype {} must match the signed /server_info /kv_cache_dtype check", + profile.dtype + )); + } + let expected_bits = vllm_kv_cache_expected_bits(&profile.dtype); + match expected_bits { + Some(bits) if bits == profile.bits => {} + Some(bits) => errors.push(format!( + "{model_id}/{name} OpenAI-compatible KV-cache dtype {} requires bits={bits}, got {}", + profile.dtype, profile.bits + )), + None => errors.push(format!( + "{model_id}/{name} has unsupported OpenAI-compatible KV-cache dtype {}", + profile.dtype + )), + } + if profile.group_size != 1 { + errors.push(format!( + "{model_id}/{name} OpenAI-compatible KV-cache group_size must be 1" + )); + } + if profile.quantized_start_tokens != 0 { + errors.push(format!( + "{model_id}/{name} OpenAI-compatible KV-cache quantized_start_tokens must be 0" + )); + } + } _ => errors.push(format!( "{model_id}/{name} declares a KV-cache profile for unsupported engine {}", artifact.engine @@ -5732,6 +5974,12 @@ fn canary_prompt_modalities<'a>( modalities.insert("image"); } } + VERIFICATION_DECISION_FINGERPRINT + if prompt.endpoint_attributes.contains_key("state") + && prompt.endpoint_attributes.contains_key("questions") => + { + modalities.insert("text"); + } VERIFICATION_SEED_PERCEPTUAL_HASH if model.model_class == MODEL_CLASS_WORKFLOW && prompt.endpoint_attributes.contains_key("workflow") => @@ -6159,6 +6407,7 @@ mod tests { schema_version: 1, engine: "vllm".to_owned(), independent_dispatch: true, + max_concurrent: None, request_modalities: vec![vec!["text".to_owned()]], topology: None, proof_sha256: "a".repeat(64), @@ -6307,6 +6556,32 @@ mod tests { ); } + #[test] + fn generation_execution_profile_accepts_embedding_dispatch_for_bound_embedding_model() { + let (mut catalog, artifact_root) = catalog_with_valid_generation_execution_profile(); + let model = catalog + .models + .iter_mut() + .find(|model| { + model + .artifacts + .values() + .any(|artifact| artifact.artifact_root == artifact_root) + }) + .expect("bound model"); + model.model_class = MODEL_CLASS_EMBEDDING.to_owned(); + model.adapter.modality_set = vec!["embedding".to_owned()]; + catalog + .generation_execution_profiles + .get_mut(&artifact_root) + .expect("profile") + .request_modalities = vec![vec!["embedding".to_owned()]]; + + let mut errors = Vec::new(); + validate_catalog(&catalog, &mut errors); + assert!(errors.is_empty(), "{errors:#?}"); + } + #[test] fn generation_execution_profile_absent_topology_preserves_serialized_bytes() { let legacy = format!( @@ -6348,8 +6623,8 @@ mod tests { #[test] fn generation_execution_profile_topology_rejects_unknown_values_in_root_and_mode() { let (catalog, root, _) = catalog_with_optional_vllm_mode(); - let mode = - serde_json::to_value(catalog.vllm_execution_mode(&root, "throughput").unwrap()).unwrap(); + let mode = serde_json::to_value(catalog.vllm_execution_mode(&root, "throughput").unwrap()) + .unwrap(); for value in [ serde_json::json!("unknown"), serde_json::json!("IsolatedWorkers"), @@ -6630,6 +6905,7 @@ mod tests { schema_version: 1, engine: "vllm".to_owned(), independent_dispatch: true, + max_concurrent: None, request_modalities: vec![vec!["text".to_owned()]], topology: None, proof_sha256: "e".repeat(64), @@ -6886,7 +7162,10 @@ mod tests { let effective = execution_mode_model(model, &artifact_name, &mode).unwrap(); assert_eq!(effective.adapter.endpoint_families.len(), 1); - assert_eq!(effective.adapter.endpoint_families[0].family, expected_family); + assert_eq!( + effective.adapter.endpoint_families[0].family, + expected_family + ); } #[test] @@ -7063,9 +7342,16 @@ mod tests { let mode = missing.vllm_execution_mode(&root, "throughput").unwrap(); assert!(mode.profile.speculative_decoding.is_some()); assert!(mode.generation_execution_profile.is_none()); - let model = missing.models.iter().find(|model| { - model.artifacts.values().any(|artifact| artifact.artifact_root == root) - }).unwrap(); + let model = missing + .models + .iter() + .find(|model| { + model + .artifacts + .values() + .any(|artifact| artifact.artifact_root == root) + }) + .unwrap(); execution_mode_model(model, &artifact_name, mode).unwrap(); let mut invalid = catalog.clone(); @@ -7130,7 +7416,10 @@ mod tests { .models .iter() .find(|model| { - model.artifacts.values().any(|artifact| artifact.artifact_root == root) + model + .artifacts + .values() + .any(|artifact| artifact.artifact_root == root) }) .unwrap(); let effective = execution_mode_model(model, &artifact_name, mode).unwrap(); @@ -7176,10 +7465,9 @@ mod tests { } let mut invalid_root = catalog.clone(); - invalid_root.generation_execution_profiles.insert( - root, - mode.generation_execution_profile.clone().unwrap(), - ); + invalid_root + .generation_execution_profiles + .insert(root, mode.generation_execution_profile.clone().unwrap()); validate_catalog(&invalid_root, &mut errors); assert_eq!(errors.len(), 1, "{errors:#?}"); assert!(errors[0].contains("isolated_workers requires an authenticated execution mode")); @@ -7291,7 +7579,10 @@ mod tests { .models .iter() .find(|model| { - model.artifacts.values().any(|artifact| artifact.artifact_root == root) + model + .artifacts + .values() + .any(|artifact| artifact.artifact_root == root) }) .unwrap(); assert!(execution_mode_model( @@ -7344,10 +7635,12 @@ mod tests { ); assert_ne!(mode.binding(&root, "different").unwrap(), binding); let mut changed_runtime = mode.clone(); - changed_runtime.profile.runtime = Some( - crate::python_runtime::VllmRuntime::FlashinferSpeculativeMetadataV1, + changed_runtime.profile.runtime = + Some(crate::python_runtime::VllmRuntime::FlashinferSpeculativeMetadataV1); + assert_ne!( + changed_runtime.binding(&root, "throughput").unwrap(), + binding ); - assert_ne!(changed_runtime.binding(&root, "throughput").unwrap(), binding); let mut changed = mode.clone(); changed .profile @@ -7487,14 +7780,21 @@ mod tests { assert_eq!(serde_json::to_value(restored).unwrap(), legacy); for mode in 0..=3 { - for graph in ["NONE", "FULL_DECODE_ONLY", "FULL", "PIECEWISE", "FULL_AND_PIECEWISE"] { + for graph in [ + "NONE", + "FULL_DECODE_ONLY", + "FULL", + "PIECEWISE", + "FULL_AND_PIECEWISE", + ] { let profile = catalog.vllm_execution_profiles.get_mut(&root).unwrap(); profile.compilation_mode = Some(mode); profile.cudagraph_mode = Some(graph.to_owned()); let encoded = serde_json::to_value(&*profile).unwrap(); assert_eq!(encoded["compilation_mode"], serde_json::json!(mode)); assert_eq!(encoded["cudagraph_mode"], serde_json::json!(graph)); - let restored: CatalogVllmExecutionProfile = serde_json::from_value(encoded).unwrap(); + let restored: CatalogVllmExecutionProfile = + serde_json::from_value(encoded).unwrap(); assert_eq!(*profile, restored); let mut errors = Vec::new(); validate_vllm_execution_profiles(&catalog, &mut errors); @@ -7804,18 +8104,24 @@ mod tests { let mut explicit_default = source.clone(); explicit_default["runtime"] = serde_json::Value::Null; - let default: CatalogVllmExecutionProfile = serde_json::from_value(explicit_default).unwrap(); + let default: CatalogVllmExecutionProfile = + serde_json::from_value(explicit_default).unwrap(); assert_eq!(default, legacy); assert_eq!(serde_json::to_value(default).unwrap(), source); let mut selected_source = source; selected_source["runtime"] = serde_json::json!("flashinfer_speculative_metadata_v1"); - let selected: CatalogVllmExecutionProfile = serde_json::from_value(selected_source.clone()).unwrap(); - assert_eq!(selected.runtime, Some(crate::python_runtime::VllmRuntime::FlashinferSpeculativeMetadataV1)); + let selected: CatalogVllmExecutionProfile = + serde_json::from_value(selected_source.clone()).unwrap(); + assert_eq!( + selected.runtime, + Some(crate::python_runtime::VllmRuntime::FlashinferSpeculativeMetadataV1) + ); assert_eq!(serde_json::to_value(selected).unwrap(), selected_source); selected_source["runtime"] = serde_json::json!("unknown_runtime"); - let error = serde_json::from_value::(selected_source).unwrap_err(); + let error = + serde_json::from_value::(selected_source).unwrap_err(); assert!(error.to_string().contains("unknown variant"), "{error}"); } @@ -7929,6 +8235,7 @@ mod tests { transcripts: BTreeMap::new(), audio_fingerprints: BTreeMap::new(), video_fingerprints: BTreeMap::new(), + decision_fingerprints: BTreeMap::new(), }, ); @@ -8132,6 +8439,7 @@ mod tests { )]), )]), video_fingerprints: BTreeMap::new(), + decision_fingerprints: BTreeMap::new(), }, ); let artifact = model.artifacts.get_mut("fixture").unwrap(); @@ -8216,6 +8524,7 @@ mod tests { transcripts: BTreeMap::new(), audio_fingerprints: BTreeMap::new(), video_fingerprints: BTreeMap::new(), + decision_fingerprints: BTreeMap::new(), }, ); { @@ -8359,6 +8668,7 @@ mod tests { transcripts: BTreeMap::new(), audio_fingerprints: BTreeMap::new(), video_fingerprints: BTreeMap::new(), + decision_fingerprints: BTreeMap::new(), }, ); let artifact = { @@ -8570,6 +8880,7 @@ mod tests { transcripts: BTreeMap::new(), audio_fingerprints: BTreeMap::new(), video_fingerprints: BTreeMap::new(), + decision_fingerprints: BTreeMap::new(), }, ); let artifact = model.artifacts.get_mut("fixture").unwrap(); @@ -8717,6 +9028,7 @@ mod tests { transcripts: BTreeMap::new(), audio_fingerprints: BTreeMap::new(), video_fingerprints: BTreeMap::new(), + decision_fingerprints: BTreeMap::new(), }, ); model.min_app_version = Some("0.1.0".to_owned()); @@ -8759,6 +9071,7 @@ mod tests { transcripts: BTreeMap::new(), audio_fingerprints: BTreeMap::new(), video_fingerprints: BTreeMap::new(), + decision_fingerprints: BTreeMap::new(), }, ); let mut errors = Vec::new(); @@ -8788,6 +9101,37 @@ mod tests { ); } + #[test] + fn laya_is_a_supported_catalog_engine() { + let model = verification_test_model( + "admin/decision@fixture", + MODEL_CLASS_DECISION, + "laya", + CanaryRef { + set_id: "canary-decision-v1".to_owned(), + match_min: 1.0, + verification_method: VERIFICATION_DECISION_FINGERPRINT.to_owned(), + verification_tolerance_bps: None, + fingerprints: BTreeMap::new(), + token_prefixes: BTreeMap::new(), + perceptual_hashes: BTreeMap::new(), + embedding_vectors: BTreeMap::new(), + transcripts: BTreeMap::new(), + audio_fingerprints: BTreeMap::new(), + video_fingerprints: BTreeMap::new(), + decision_fingerprints: BTreeMap::new(), + }, + ); + let mut errors = Vec::new(); + validate_model(&model, &mut errors); + assert!( + !errors + .iter() + .any(|error| error.contains("unsupported engine laya")), + "{errors:?}" + ); + } + #[test] fn speciality_catalog_validation_requires_complete_per_artifact_levels() { let mut model = verification_test_model( @@ -8812,6 +9156,7 @@ mod tests { transcripts: BTreeMap::new(), audio_fingerprints: BTreeMap::new(), video_fingerprints: BTreeMap::new(), + decision_fingerprints: BTreeMap::new(), }, ); let descriptor = ModelSpecialityDescriptor { @@ -9093,16 +9438,20 @@ mod tests { transcripts: BTreeMap::new(), audio_fingerprints: BTreeMap::new(), video_fingerprints: BTreeMap::new(), + decision_fingerprints: BTreeMap::new(), }, price_ref_au: PriceRef { denom: "au_usd".to_owned(), in_per_1k: 1, out_per_1k: 1, + per_req_au: 0, + min_session_au: 0, rate_map: Vec::new(), }, }; let mut artifact = CatalogArtifact { engine: "llama.cpp".to_owned(), + openai_compatible: None, stable_diffusion_cpp: None, mlx_runtime: mayhem_engine::MlxRuntimeConfig::default(), kv_cache: None, @@ -9168,6 +9517,7 @@ mod tests { transcripts: BTreeMap::new(), audio_fingerprints: BTreeMap::new(), video_fingerprints: BTreeMap::new(), + decision_fingerprints: BTreeMap::new(), }, ); let mut errors = Vec::new(); @@ -9193,6 +9543,7 @@ mod tests { transcripts: BTreeMap::new(), audio_fingerprints: BTreeMap::new(), video_fingerprints: BTreeMap::new(), + decision_fingerprints: BTreeMap::new(), }, ); let mut errors = Vec::new(); @@ -9242,6 +9593,7 @@ mod tests { transcripts: BTreeMap::new(), audio_fingerprints: BTreeMap::new(), video_fingerprints: BTreeMap::new(), + decision_fingerprints: BTreeMap::new(), }, ); let mut errors = Vec::new(); @@ -9280,6 +9632,7 @@ mod tests { )]), audio_fingerprints: BTreeMap::new(), video_fingerprints: BTreeMap::new(), + decision_fingerprints: BTreeMap::new(), }, ); let mut errors = Vec::new(); @@ -9308,6 +9661,7 @@ mod tests { )]), )]), video_fingerprints: BTreeMap::new(), + decision_fingerprints: BTreeMap::new(), }, ); let mut errors = Vec::new(); @@ -9364,6 +9718,7 @@ mod tests { "fixture".to_owned(), BTreeMap::from([("fixed-video".to_owned(), test_video_av_fingerprint())]), )]), + decision_fingerprints: BTreeMap::new(), }, ); let mut errors = Vec::new(); @@ -9392,6 +9747,7 @@ mod tests { fn artifact_kv_cache_profile_is_validated_as_signed_runtime_data() { let mut artifact = CatalogArtifact { engine: "llama.cpp".to_owned(), + openai_compatible: None, stable_diffusion_cpp: None, mlx_runtime: mayhem_engine::MlxRuntimeConfig::default(), kv_cache: Some(CatalogKvCacheProfile { @@ -9649,6 +10005,7 @@ mod tests { transcripts: BTreeMap::new(), audio_fingerprints: BTreeMap::new(), video_fingerprints: BTreeMap::new(), + decision_fingerprints: BTreeMap::new(), }, ); model.adapter.tool_call_strategy = "openai_tool_calls".to_owned(); @@ -9775,6 +10132,7 @@ mod tests { transcripts: BTreeMap::new(), audio_fingerprints: BTreeMap::new(), video_fingerprints: BTreeMap::new(), + decision_fingerprints: BTreeMap::new(), }, ); model.adapter.endpoint_families.retain(|contract| { @@ -9825,6 +10183,7 @@ mod tests { transcripts: BTreeMap::new(), audio_fingerprints: BTreeMap::new(), video_fingerprints: BTreeMap::new(), + decision_fingerprints: BTreeMap::new(), }, ); let prompt = CanarySetPrompt { @@ -9840,6 +10199,7 @@ mod tests { input: None, audio_b64: None, temperature: Some(0.0), + decision_temperature: None, top_p: None, top_k: None, min_p: None, @@ -9875,6 +10235,7 @@ mod tests { transcripts: BTreeMap::new(), audio_fingerprints: BTreeMap::new(), video_fingerprints: BTreeMap::new(), + decision_fingerprints: BTreeMap::new(), }, ); let text_to_video: CanarySetPrompt = serde_json::from_value(serde_json::json!({ @@ -9903,6 +10264,48 @@ mod tests { ); } + #[test] + fn decision_canary_counts_structured_state_and_questions_as_text() { + let model = verification_test_model( + "admin/decision@fixture", + MODEL_CLASS_DECISION, + "laya", + CanaryRef { + set_id: "canary-decision-v1".to_owned(), + match_min: 1.0, + verification_method: VERIFICATION_DECISION_FINGERPRINT.to_owned(), + verification_tolerance_bps: None, + fingerprints: BTreeMap::new(), + token_prefixes: BTreeMap::new(), + perceptual_hashes: BTreeMap::new(), + embedding_vectors: BTreeMap::new(), + transcripts: BTreeMap::new(), + audio_fingerprints: BTreeMap::new(), + video_fingerprints: BTreeMap::new(), + decision_fingerprints: BTreeMap::new(), + }, + ); + let prompt: CanarySetPrompt = serde_json::from_value(serde_json::json!({ + "id": "decision", + "state": {"message": "Please refund the duplicate charge."}, + "decision_temperature": {"choice": 1.0, "noul": 1.5}, + "questions": { + "refund_requested": { + "type": "noul", + "instructions": "Was a refund requested?" + } + } + })) + .expect("parse decision prompt"); + + assert!(prompt.decision_temperature.is_some()); + + assert_eq!( + canary_prompt_modalities(&model, &prompt), + BTreeSet::from(["text"]) + ); + } + #[test] fn embedding_adapter_and_input_only_pricing_validate() { let mut model = verification_test_model( @@ -9924,6 +10327,7 @@ mod tests { transcripts: BTreeMap::new(), audio_fingerprints: BTreeMap::new(), video_fingerprints: BTreeMap::new(), + decision_fingerprints: BTreeMap::new(), }, ); model.price_ref_au.out_per_1k = 0; @@ -9959,6 +10363,7 @@ mod tests { transcripts: BTreeMap::new(), audio_fingerprints: BTreeMap::new(), video_fingerprints: BTreeMap::new(), + decision_fingerprints: BTreeMap::new(), }, ); text_with_zero_output.price_ref_au.out_per_1k = 0; @@ -9989,6 +10394,7 @@ mod tests { transcripts: BTreeMap::new(), audio_fingerprints: BTreeMap::new(), video_fingerprints: BTreeMap::new(), + decision_fingerprints: BTreeMap::new(), }, ); model.adapter.tool_call_strategy = "none".to_owned(); @@ -10092,6 +10498,7 @@ mod tests { transcripts: BTreeMap::new(), audio_fingerprints: BTreeMap::new(), video_fingerprints: BTreeMap::new(), + decision_fingerprints: BTreeMap::new(), }, ); text_with_image_shape.adapter.endpoint_families = @@ -10127,6 +10534,7 @@ mod tests { transcripts: BTreeMap::new(), audio_fingerprints: BTreeMap::new(), video_fingerprints: BTreeMap::new(), + decision_fingerprints: BTreeMap::new(), }, ); model.family = "comfy-workflow".to_owned(); @@ -10202,6 +10610,7 @@ mod tests { transcripts: BTreeMap::new(), audio_fingerprints: BTreeMap::new(), video_fingerprints: BTreeMap::new(), + decision_fingerprints: BTreeMap::new(), }, ); model.family = "comfy-workflow".to_owned(); @@ -10259,6 +10668,7 @@ mod tests { transcripts: BTreeMap::new(), audio_fingerprints: BTreeMap::new(), video_fingerprints: BTreeMap::new(), + decision_fingerprints: BTreeMap::new(), }, ); let rate = |unit: &str, per_unit_au, granularity| CatalogRateMapEntry { @@ -10352,6 +10762,7 @@ mod tests { "fixture".to_owned(), BTreeMap::from([("fixed-video".to_owned(), test_video_av_fingerprint())]), )]), + decision_fingerprints: BTreeMap::new(), }, ); model.price_ref_au.in_per_1k = 0; @@ -10413,6 +10824,7 @@ mod tests { transcripts: BTreeMap::new(), audio_fingerprints: BTreeMap::new(), video_fingerprints: BTreeMap::new(), + decision_fingerprints: BTreeMap::new(), }, ); model.adapter.modality_set = vec!["audio".to_owned()]; @@ -10567,6 +10979,7 @@ mod tests { "fixture".to_owned(), CatalogArtifact { engine: engine.to_owned(), + openai_compatible: None, stable_diffusion_cpp: (engine == "stable-diffusion.cpp") .then_some(mayhem_engine::StableDiffusionCppConfig::default()), mlx_runtime: mayhem_engine::MlxRuntimeConfig::default(), @@ -10656,11 +11069,90 @@ mod tests { denom: "au_usd".to_owned(), in_per_1k: 1, out_per_1k: 1, + per_req_au: 0, + min_session_au: 0, rate_map: Vec::new(), }, } } + #[test] + fn openai_compatible_kv_metadata_and_video_input_validate() { + let draft_path = Path::new(env!("CARGO_MANIFEST_DIR")) + .join("../../catalog/drafts/qwen3.8-flash-next-nvfp4/artifact-binding.values.json"); + let draft: Value = serde_json::from_slice( + &fs::read(&draft_path) + .unwrap_or_else(|error| panic!("reading {}: {error}", draft_path.display())), + ) + .expect("artifact binding draft JSON"); + let artifact: CatalogArtifact = serde_json::from_value(draft["artifact"].clone()) + .expect("OpenAI-compatible artifact binding"); + + let mut artifact_errors = Vec::new(); + validate_artifact( + "Qwen/Qwen3.8-Flash-Next", + "launch", + "nvfp4", + &artifact, + &mut artifact_errors, + ); + assert!(artifact_errors.is_empty(), "{artifact_errors:#?}"); + + let mut drifted = artifact.clone(); + drifted + .openai_compatible + .as_mut() + .unwrap() + .server_info_checks + .insert( + "/kv_cache_dtype".to_owned(), + Value::String("bfloat16".to_owned()), + ); + artifact_errors.clear(); + validate_artifact( + "Qwen/Qwen3.8-Flash-Next", + "launch", + "nvfp4", + &drifted, + &mut artifact_errors, + ); + assert!(artifact_errors.iter().any(|error| error.contains( + "KV-cache dtype fp8_e4m3 must match the signed /server_info /kv_cache_dtype check" + ))); + + let catalog = repository_catalog(); + let mut model = catalog + .models + .iter() + .find(|model| model.model_id == "Qwen/Qwen3.8-27B") + .expect("Qwen3.8 multimodal fixture") + .clone(); + model.model_id = "Qwen/Qwen3.8-Flash-Next".to_owned(); + model.caps.video = false; + assert!(model_has_input_modality(&model, "video")); + model.artifacts = BTreeMap::from([("nvfp4".to_owned(), artifact)]); + let mut model_errors = Vec::new(); + validate_model(&model, &mut model_errors); + assert!( + !model_errors.iter().any(|error| { + error.contains("openai_compatible capability video must match model caps") + || error.contains("caps.video output") + || error.contains("declares a KV-cache profile for unsupported engine") + }), + "{model_errors:#?}" + ); + + model + .adapter + .modality_set + .retain(|modality| modality != "video"); + model_errors.clear(); + validate_model(&model, &mut model_errors); + assert!(model_errors.iter().any(|error| { + error.contains("openai_compatible capability video must match model caps") + })); + } + fn hex_string(bytes: &[u8]) -> String { const HEX: &[u8; 16] = b"0123456789abcdef"; let mut out = String::with_capacity(bytes.len() * 2); diff --git a/crates/mayhem-cli/src/main.rs b/crates/mayhem-cli/src/main.rs index 639b8c4e..71055026 100644 --- a/crates/mayhem-cli/src/main.rs +++ b/crates/mayhem-cli/src/main.rs @@ -4,9 +4,10 @@ mod catalog; mod endpoint_calibration; mod gemma4; mod intercom_runtime; -mod python_runtime; -mod provider_output_stream; +mod managed_openai_compatible; mod provider_failure_recovery; +mod provider_output_stream; +mod python_runtime; mod release_bundle; #[cfg(test)] @@ -70,12 +71,13 @@ use mayhem_enclave::{ use mayhem_engine::ComfyUiBackend; use mayhem_engine::{ ArtifactChunk, AudioTranscriptionRequest as EngineAudioTranscriptionRequest, CancellationToken, - ComfyUiCustomNodePackage, ComfyUiModelFile, ComponentRecovery, ConcurrentGenerationBackend, - EngineBackend, EngineError, GenerateRequest, GenerateSpecialityParameter, - GenerateSpecialityTarget, GrammarSpec, ImageGenerationRequest as EngineImageGenerationRequest, - LoadConfig, MediaGenerationRequest as EngineMediaGenerationRequest, MediaInput, ModelArtifact, - SpeechReferenceAudio, SpeechRequest, TokenChunk, ToolSpec, WorkflowGenerationRequest, - WorkflowInputFile, MTMD_MEDIA_MARKER, + ComfyUiCustomNodePackage, ComfyUiModelFile, ComponentRecovery, ConcurrentEmbeddingBackend, + ConcurrentGenerationBackend, DecisionRequest as EngineDecisionRequest, EngineBackend, + EngineError, GenerateRequest, GenerateSpecialityParameter, GenerateSpecialityTarget, + GrammarSpec, ImageGenerationRequest as EngineImageGenerationRequest, LoadConfig, + MediaGenerationRequest as EngineMediaGenerationRequest, MediaInput, ModelArtifact, + SpeechReferenceAudio, SpeechRequest, TokenChunk, Tokenization, ToolSpec, + WorkflowGenerationRequest, WorkflowInputFile, MTMD_MEDIA_MARKER, }; use mayhem_gateway::{ audio_fingerprint, cancellation_settlement_usage, embedding_vector_fingerprint, @@ -86,12 +88,12 @@ use mayhem_gateway::{ validate_gateway_bind_access, GatewayAccessControl, GatewayAttestationAuthority, GatewayAttestationCollateral, GatewayCanaryChallengeContext, GatewayCanaryProbePolicy, GatewayCanaryRegistry, GatewayExecutionModeRegistry, GatewayLocalRunBadge, - GatewayMarketInfo, GatewayModel, - GatewayReceiptSettlementPublisher, GatewayRouteCandidate, GatewayState, - GatewayTokenBudgetPeriod, GatewayTokenRecord, GatewayTokenStore, GatewayUpdateModelNotice, - MayhemModelInfo, ModelCaps, PriceRefAu, ProviderKybInfo, SamplingProfile, - ScBridgeGatewaySessionBackend, ScBridgeGatewaySessionConfig, ShapeAdapterInfo, - DEFAULT_ROUTE_MAX_WAIT_MS, MAX_PREFERRED_PROVIDERS_PER_MODEL, MAX_ROUTE_MAX_WAIT_MS, + GatewayMarketInfo, GatewayModel, GatewayReceiptSettlementPublisher, GatewayRouteCandidate, + GatewayState, GatewayTokenBudgetPeriod, GatewayTokenRecord, GatewayTokenStore, + GatewayUpdateModelNotice, MayhemModelInfo, ModelCaps, PriceRefAu, ProviderKybInfo, + SamplingProfile, ScBridgeGatewaySessionBackend, ScBridgeGatewaySessionConfig, + ShapeAdapterInfo, DEFAULT_ROUTE_MAX_WAIT_MS, MAX_PREFERRED_PROVIDERS_PER_MODEL, + MAX_ROUTE_MAX_WAIT_MS, }, rate_gate_basis_au, rate_map_cost_basis_per_1k, text_generation_rate_map, text_rate_per_1k_au, valid_video_av_fingerprint, video_av_fingerprint, video_av_fingerprint_similarity_bps, @@ -112,27 +114,32 @@ use mayhem_proto::{ artifact_generation_inline_audio_load, catalog_enclave_id, chunk_json_payload, ctx_bracket_for_tokens_in_schedule, ctx_bracket_table_at, default_ctx_bracket_schedule, metered_output_units, parse_record_usage_receipt_envelope, payload_chunk_at, - payload_chunk_manifest, reassemble_json_payload, receipt_signing_bytes, + payload_chunk_manifest, reassemble_json_payload, receipt_contract_version_is_supported, + receipt_schema_version_is_supported_for_contract, receipt_signing_bytes, record_usage_receipt_envelope, record_usage_receipt_feature_key, - record_usage_receipt_feature_key_for_contract, RECOVERABLE_RECEIPT_CONTRACT_VERSION, receipt_contract_version_is_supported, - record_usage_receipt_signing_bytes, session_accept_signing_bytes, session_frame_head, - spend_voucher_signing_bytes, stable_json_bytes, tools_only_model_input_prompt_units, - validate_ctx_bracket_schedule, validated_audio_metadata, validated_wav_audio_metadata, - AdminAttestationPolicy, AttestationRuntimeConfig, AttestationTrustDataRef, - CatalogEnclaveIdentity, CheckpointPolicy, CtxBracketSchedule, HardwareQuote, HardwareQuoteKind, - HardwareQuoteRoutePolicyBinding, MoneyAu, PayloadChunk, PayloadChunkCollector, - PayloadChunkManifest, ReceiptAck, ReceiptBody, ReceiptUsage, SessionReceipt, SpendVoucher, + record_usage_receipt_feature_key_from_envelope_for_contract, + record_usage_receipt_signing_bytes, reservation_binding_matches, session_accept_signing_bytes, + session_frame_head, spend_voucher_signing_bytes, stable_json_bytes, + tools_only_model_input_prompt_units, validate_ctx_bracket_schedule, validated_audio_metadata, + validated_wav_audio_metadata, AdminAttestationPolicy, AttestationRuntimeConfig, + AttestationTrustDataRef, CatalogEnclaveIdentity, CheckpointPolicy, CtxBracketSchedule, + HardwareQuote, HardwareQuoteKind, HardwareQuoteRoutePolicyBinding, MoneyAu, PayloadChunk, + PayloadChunkCollector, PayloadChunkManifest, ReceiptAck, ReceiptBody, ReceiptUsage, + SessionReceipt, SpendVoucher, TokenizeRequestFrame, TokenizeResponseFrame, TpmActivateCredentialChallengeFrame, TpmActivateCredentialResponseFrame, TranscriptionResult, TranscriptionResultLimits, TranscriptionTimestamp, ValidatedAudioFormat, VisibleToolCall, WorkflowBinding, WorkflowOutputBinding, CONTRACT_VERSION, DEFAULT_MODEL_CLASS, DEFAULT_SESSION_MAX_FRAME_BYTES, DEFAULT_SESSION_MAX_PAYLOAD_CHUNKS, DEFAULT_SESSION_MAX_REASSEMBLED_PAYLOAD_BYTES, DEFAULT_SESSION_PAYLOAD_CHUNK_BYTES, DEFAULT_VIDEO_GENERATION_FPS, MAX_VISIBLE_OUTPUT_UNITS_PER_REQUEST_TOKEN, - SESSION_RECEIPT_SCHEMA_VERSION, TPM_ACTIVATE_CREDENTIAL_CHALLENGE_FRAME_TYPE, - TPM_ACTIVATE_CREDENTIAL_FRAME_VERSION, TPM_ACTIVATE_CREDENTIAL_RESPONSE_FRAME_TYPE, - TRANSPORT_MAX_OUTPUT_DURATION_SECONDS, USAGE_AUDIO_SECOND, USAGE_CACHED_INPUT_TOKEN, - USAGE_FRAME, USAGE_IMAGE, USAGE_INPUT_CHARACTER, USAGE_INPUT_TOKEN, USAGE_OUTPUT_TOKEN, - USAGE_STEP, USAGE_VIDEO_SECOND, VISIBLE_OUTPUT_BYTES_PER_UNIT, + RECOVERABLE_RECEIPT_CONTRACT_VERSION, SESSION_RECEIPT_SCHEMA_VERSION, + SPEND_VOUCHER_SCHEMA_VERSION, TOKENIZE_FRAME_VERSION, TOKENIZE_REQUEST_CHUNK_FRAME_TYPE, + TOKENIZE_REQUEST_FRAME_TYPE, TOKENIZE_RESPONSE_CHUNK_FRAME_TYPE, TOKENIZE_RESPONSE_FRAME_TYPE, + TPM_ACTIVATE_CREDENTIAL_CHALLENGE_FRAME_TYPE, TPM_ACTIVATE_CREDENTIAL_FRAME_VERSION, + TPM_ACTIVATE_CREDENTIAL_RESPONSE_FRAME_TYPE, TRANSPORT_MAX_OUTPUT_DURATION_SECONDS, + USAGE_AUDIO_SECOND, USAGE_CACHED_INPUT_TOKEN, USAGE_FRAME, USAGE_IMAGE, USAGE_INPUT_CHARACTER, + USAGE_INPUT_TOKEN, USAGE_OUTPUT_TOKEN, USAGE_STEP, USAGE_VIDEO_SECOND, + VISIBLE_OUTPUT_BYTES_PER_UNIT, }; use serde::de::DeserializeOwned; use serde::{Deserialize, Deserializer, Serialize}; @@ -299,6 +306,8 @@ const MAX_TPM_ACTIVATION_COMMAND_INPUT_BYTES: usize = 64 * 1024; const MAX_TPM_ACTIVATION_COMMAND_OUTPUT_BYTES: usize = 4 * 1024; const DEFAULT_TPM_ACTIVATIONS_PER_MINUTE: usize = 60; const DEFAULT_TPM_ACTIVATIONS_PER_PEER_PER_MINUTE: usize = 12; +const DEFAULT_TOKENIZE_REQUESTS_PER_MINUTE: usize = 600; +const DEFAULT_TOKENIZE_REQUESTS_PER_PEER_PER_MINUTE: usize = 120; const DEFAULT_PROVIDER_SESSION_REQUEST_STALL_TIMEOUT_MILLIS: u64 = 300_000; const DEFAULT_PROVIDER_SESSION_REQUEST_BYTES_PER_CTX_TOKEN: usize = 256; const DEFAULT_PROVIDER_SESSION_REQUEST_JSON_OVERHEAD_BYTES: usize = 1024 * 1024; @@ -320,6 +329,7 @@ const F13_DISK_RESERVE_FLOOR_BYTES: u64 = 2 * GIB_BYTES; const PROVIDER_MACOS_MEMORY_PRESSURE_STOP_LEVEL: i32 = 2; const DEFAULT_PROVIDER_ENGINE_WATCHDOG_RESTART_AFTER_MILLIS: u64 = 0; const DEFAULT_PROVIDER_ENGINE_WATCHDOG_RESTART_COOLDOWN_MILLIS: u64 = 30_000; +const DEFAULT_PROVIDER_IDLE_MEMORY_RECLAIM_COOLDOWN_MILLIS: u64 = 30_000; const F13_MEMORY_CLAIM_TTL_SECONDS: u64 = 24 * 60 * 60; const VLLM_ADMIN_MEMORY_UTILIZATION_MAX_PCT: u32 = 90; const VLLM_MEMORY_UTILIZATION_CUSHION_PCT: u32 = 5; @@ -347,6 +357,7 @@ const PROVIDER_ACCEPTED_RAIL_ORDER: [&str; 3] = ["fiat", "tap", "tnk"]; const CANARY_VERIFICATION_TOKEN_FINGERPRINT: &str = "token_fingerprint"; const CANARY_VERIFICATION_SEED_PERCEPTUAL_HASH: &str = "seed_perceptual_hash"; const CANARY_VERIFICATION_EMBEDDING_COSINE: &str = "embedding_cosine"; +const CANARY_VERIFICATION_DECISION_FINGERPRINT: &str = "decision_fingerprint"; const CANARY_VERIFICATION_TRANSCRIPT_MATCH: &str = "transcript_match"; const CANARY_VERIFICATION_AUDIO_FINGERPRINT: &str = "audio_fingerprint"; const CANARY_VERIFICATION_VIDEO_AV_FINGERPRINT: &str = "video_av_fingerprint"; @@ -359,6 +370,7 @@ const MODEL_CLASS_STT: &str = "stt"; const MODEL_CLASS_AUDIO_GENERATION: &str = "audio-generation"; const MODEL_CLASS_MUSIC_GENERATION: &str = "music-generation"; const MODEL_CLASS_WORKFLOW: &str = "workflow"; +const MODEL_CLASS_DECISION: &str = "decision"; const DEFAULT_COMFY_OUTCOME_GRID_PATH: &str = "catalog/comfy/outcome-classes-v1.json"; #[derive(Debug, Parser)] @@ -746,7 +758,7 @@ enum AdminCommands { #[command(subcommand)] command: AdminBanCommands, }, - /// Reverse a provider, device, fingerprint, or committer ban going forward. + /// Reverse a provider, device, fingerprint, committer, or provider-KYB ban going forward. Unban(AdminUnbanArgs), /// Admin device-key operations. Device { @@ -3761,6 +3773,7 @@ enum AdminBanTargetType { Device, Fingerprint, Committer, + Kyb, } #[derive(Debug, Clone, Copy, Eq, PartialEq, ValueEnum)] @@ -3785,6 +3798,7 @@ impl AdminBanTargetType { Self::Device => "device", Self::Fingerprint => "fingerprint", Self::Committer => "committer", + Self::Kyb => "kyb", } } } @@ -5012,7 +5026,7 @@ struct AdminUnbanArgs { #[command(flatten)] tx: AdminTxArgs, - /// Ban target to clear going forward. + /// Ban target to clear going forward. For --type kyb, use the provider public key. target: String, /// Ban record type. @@ -6395,7 +6409,7 @@ struct ProviderServePlanArgs { #[arg(long, value_name = "PATH")] canaries_dir: Option, - /// Override backend selection: auto, trt-llm, mlx, llama.cpp, stable-diffusion.cpp, comfyui, ace-step, chatterbox, needle-cpu, needle-gpu, sulphur, transformers-asr, whisper.cpp, or piper. + /// Override backend selection: auto, trt-llm, mlx, llama.cpp, stable-diffusion.cpp, comfyui, ace-step, chatterbox, laya, needle-cpu, needle-gpu, sulphur, transformers-asr, whisper.cpp, or piper. #[arg(long, default_value = "auto")] engine_backend: String, @@ -6647,7 +6661,7 @@ struct ProviderStartArgs { #[arg(long, value_name = "PATH")] hf_token_file: Option, - /// Override backend selection: auto, trt-llm, mlx, llama.cpp, stable-diffusion.cpp, comfyui, ace-step, chatterbox, needle-cpu, needle-gpu, sulphur, transformers-asr, whisper.cpp, or piper. + /// Override backend selection: auto, trt-llm, mlx, llama.cpp, stable-diffusion.cpp, comfyui, ace-step, chatterbox, laya, needle-cpu, needle-gpu, sulphur, transformers-asr, whisper.cpp, or piper. #[arg(long, default_value = "auto")] engine_backend: String, @@ -7920,6 +7934,7 @@ fn resolve_doctor_provider_backend(requested: &str, report: &HardwareReport) -> | "needle-gpu" | "sulphur" | "transformers-asr" + | "laya" | "whisper.cpp" | "piper" ), @@ -8936,6 +8951,9 @@ fn backend_requirement_hint(backend: &str) -> &'static str { "TensorRT-LLM requires a compatible NVIDIA GPU; NVFP4 artifacts require Blackwell-class compute capability" } "vllm" => "vLLM launch artifacts require a compatible NVIDIA GPU", + "openai-compatible" => { + "managed OpenAI-compatible launch artifacts require the signed container runtime and compatible accelerator" + } "llama.cpp" => "llama.cpp requires enough RAM and a compatible CPU/GPU runtime", "stable-diffusion.cpp" => { "stable-diffusion.cpp requires enough local RAM and preferably a local accelerator" @@ -8961,6 +8979,9 @@ fn backend_requirement_hint(backend: &str) -> &'static str { "transformers-asr" => { "Transformers ASR requires at least 8 GiB RAM and supports CUDA, Metal/MPS, or CPU execution" } + "laya" => { + "Laya requires a CUDA host, its pinned offline Python runtime, and enough memory to preload all three checkpoints" + } "whisper.cpp" => "whisper.cpp requires enough local RAM and CPU SIMD support", "piper" => "Piper requires enough local RAM for the voice artifact", _ => "backend is not compatible with this host", @@ -16043,7 +16064,7 @@ fn catalog_calibrate_canary(mut args: CatalogCalibrateCanaryArgs) -> Result<()> validate_calibration_args_for_artifact(artifact, &args)?; let calibration_memory = calibration_memory_context(artifact, &artifact_path, &args)?; preflight_catalog_calibration_managed_runtime(artifact, &args)?; - verify_calibration_artifact_matches_catalog(artifact, &artifact_path)?; + verify_calibration_artifact_matches_catalog(artifact, &artifact_path, &artifact_sidecar_paths)?; verify_calibration_sidecars_match_catalog(artifact, &artifact_sidecar_paths)?; let prompts = load_canary_prompts_checked( Some(&canaries_dir), @@ -17326,6 +17347,7 @@ fn calibration_token_prefixes( struct CatalogEndpointCalibrationFixtures { audio: Option>, audio_by_content_type: BTreeMap<&'static str, &'static [u8]>, + video_base64: Option, workflow_input_files: Option, } @@ -17629,13 +17651,24 @@ fn catalog_endpoint_calibration_report( mut behavioral_witness: Option, ) -> EndpointCalibrationReport { let (substitutions, fixtures) = catalog_endpoint_calibration_fixtures(model, prompts); + let forced_tool_max_output_tokens = artifact + .openai_compatible + .as_ref() + .map(|binding| binding.preflight.tools_max_tokens) + .unwrap_or(ENDPOINT_CALIBRATION_FORCED_TOOL_FALLBACK_MAX_OUTPUT_TOKENS); let mut execution_cache = BTreeMap::<(String, String), EndpointCalibrationExecution>::new(); run_endpoint_calibration_matrix_with_materializer( &model.adapter.endpoint_families, &substitutions, |contract, case, request| { - catalog_endpoint_calibration_materialize_request(contract, case, request, &fixtures) - .map_err(|error| format!("{error:#}")) + catalog_endpoint_calibration_materialize_request_with_tool_budget( + contract, + case, + request, + &fixtures, + forced_tool_max_output_tokens, + ) + .map_err(|error| format!("{error:#}")) }, |contract, _case, request| { let cache_key = ( @@ -17656,6 +17689,7 @@ fn catalog_endpoint_calibration_report( request, &fixtures, &mut behavioral_witness, + forced_tool_max_output_tokens, )? } else { catalog_endpoint_calibration_execute_unsupported_artifact_case( @@ -17852,6 +17886,7 @@ fn catalog_endpoint_calibration_fixtures( let mut substitutions = BTreeMap::from([("$MODEL".to_owned(), json!(model.model_id))]); let mut fixtures = CatalogEndpointCalibrationFixtures { audio: None, + video_base64: None, workflow_input_files: None, audio_by_content_type: BTreeMap::from([ ("audio/aac", CALIBRATION_AUDIO_AAC), @@ -17911,14 +17946,34 @@ fn catalog_endpoint_calibration_fixtures( } } } + fixtures.video_base64 = substitutions + .get("$VIDEO_BASE64") + .and_then(Value::as_str) + .map(str::to_owned); (substitutions, fixtures) } fn catalog_endpoint_calibration_materialize_request( + contract: &mayhem_proto::EndpointFamilyContract, + case: &mayhem_proto::EndpointCalibrationCase, + request: Value, + fixtures: &CatalogEndpointCalibrationFixtures, +) -> Result { + catalog_endpoint_calibration_materialize_request_with_tool_budget( + contract, + case, + request, + fixtures, + ENDPOINT_CALIBRATION_FORCED_TOOL_FALLBACK_MAX_OUTPUT_TOKENS, + ) +} + +fn catalog_endpoint_calibration_materialize_request_with_tool_budget( contract: &mayhem_proto::EndpointFamilyContract, case: &mayhem_proto::EndpointCalibrationCase, mut request: Value, fixtures: &CatalogEndpointCalibrationFixtures, + forced_tool_max_output_tokens: u32, ) -> Result { if matches!( contract.family.as_str(), @@ -17926,11 +17981,53 @@ fn catalog_endpoint_calibration_materialize_request( | mayhem_proto::ENDPOINT_OPENAI_RESPONSES | mayhem_proto::ENDPOINT_HF_MULTIMODAL_CHAT ) { - return catalog_endpoint_calibration_materialize_tool_request(contract, case, request); + request = catalog_endpoint_calibration_materialize_tool_request( + contract, + case, + request, + forced_tool_max_output_tokens, + )?; + if contract.family == mayhem_proto::ENDPOINT_HF_MULTIMODAL_CHAT { + request = catalog_endpoint_calibration_materialize_hf_video_request( + contract, case, request, fixtures, + )?; + } + return Ok(request); } if contract.family == mayhem_proto::ENDPOINT_MAYHEM_COMFY_WORKFLOWS { return catalog_endpoint_calibration_materialize_workflow_request(case, request, fixtures); } + if contract.family == mayhem_proto::ENDPOINT_MAYHEM_DECISIONS && case.expect_accept { + if case + .expected_response_attributes + .iter() + .any(|path| path == "shortlist") + && request.get("shortlist").is_none() + { + request["shortlist"] = json!({ + "k": 20, + "max_length": 512, + "batch_size": 32, + }); + } + if case + .expected_response_attributes + .iter() + .any(|path| path == "preprocessing") + && request.get("email").is_none() + { + request["email"] = json!({"clean": true, "max_chars": 3000}); + } + if request.get("email").is_some() + && !request.pointer("/state/body").is_some_and(Value::is_string) + { + request["state"] = json!({ + "body": "Mayhem calibration email body", + "subject": "Calibration", + }); + } + return Ok(request); + } if contract.family != mayhem_proto::ENDPOINT_MAYHEM_MUSIC_GENERATIONS { return Ok(request); } @@ -18035,10 +18132,145 @@ fn catalog_endpoint_calibration_materialize_request( Ok(request) } +fn catalog_endpoint_calibration_materialize_hf_video_request( + contract: &mayhem_proto::EndpointFamilyContract, + case: &mayhem_proto::EndpointCalibrationCase, + mut request: Value, + fixtures: &CatalogEndpointCalibrationFixtures, +) -> Result { + let mutates_parent = case.mutations.iter().any(|mutation| { + matches!( + mutation.path.as_str(), + "messages" | "messages.content" | "messages.content.video" + ) + }); + let mutates = |path: &str| case.mutations.iter().any(|mutation| mutation.path == path); + if !case.expect_accept || mutates_parent { + return Ok(request); + } + let data_mutated = mutates("messages.content.video.data"); + let content_type_mutated = mutates("messages.content.video.content_type"); + + let needs_companion = request + .get("messages") + .and_then(Value::as_array) + .into_iter() + .flatten() + .filter_map(|message| message.get("content").and_then(Value::as_array)) + .flatten() + .any(|part| { + part.get("type").and_then(Value::as_str) == Some("video") + && part + .get("video") + .and_then(Value::as_object) + .is_some_and(|video| { + (!data_mutated && !video.contains_key("data") && !video.contains_key("url")) + || (!content_type_mutated && !video.contains_key("content_type")) + }) + }); + if !needs_companion { + return Ok(request); + } + + let signed = catalog_endpoint_calibration_signed_hf_video_fixture(contract, fixtures) + .with_context(|| { + format!( + "accepted HF video calibration case {} has no complete signed video fixture", + case.case_id + ) + })?; + for message in request + .get_mut("messages") + .and_then(Value::as_array_mut) + .into_iter() + .flatten() + { + let Some(parts) = message.get_mut("content").and_then(Value::as_array_mut) else { + continue; + }; + for part in parts { + if part.get("type").and_then(Value::as_str) != Some("video") { + continue; + } + let Some(video) = part.get_mut("video").and_then(Value::as_object_mut) else { + continue; + }; + if !data_mutated && !video.contains_key("data") && !video.contains_key("url") { + video.insert("data".to_owned(), signed["data"].clone()); + } + if !content_type_mutated && !video.contains_key("content_type") { + video.insert("content_type".to_owned(), signed["content_type"].clone()); + } + } + } + Ok(request) +} + +fn catalog_endpoint_calibration_signed_hf_video_fixture( + contract: &mayhem_proto::EndpointFamilyContract, + fixtures: &CatalogEndpointCalibrationFixtures, +) -> Option> { + let values = &contract + .request_attribute_specs + .get("messages")? + .calibration_values; + for value in values { + let Some(messages) = value.as_array() else { + continue; + }; + for message in messages { + let Some(parts) = message.get("content").and_then(Value::as_array) else { + continue; + }; + for part in parts { + if part.get("type").and_then(Value::as_str) != Some("video") { + continue; + } + let Some(video) = part.get("video").and_then(Value::as_object) else { + continue; + }; + let Some(content_type) = + video + .get("content_type") + .and_then(Value::as_str) + .filter(|content_type| { + content_type + .strip_prefix("video/") + .is_some_and(|subtype| !subtype.is_empty()) + }) + else { + continue; + }; + let Some(data) = video.get("data").and_then(Value::as_str) else { + continue; + }; + let data = if data == "$VIDEO_BASE64" { + fixtures.video_base64.as_deref()? + } else { + data + }; + if data.is_empty() + || base64::engine::general_purpose::STANDARD + .decode(data) + .is_err() + { + continue; + } + return Some(Map::from_iter([ + ("data".to_owned(), json!(data)), + ("content_type".to_owned(), json!(content_type)), + ])); + } + } + } + None +} + fn catalog_endpoint_calibration_materialize_tool_request( contract: &mayhem_proto::EndpointFamilyContract, case: &mayhem_proto::EndpointCalibrationCase, mut request: Value, + forced_tool_max_output_tokens: u32, ) -> Result { // Supply a companion fixture, never repair an explicit tools value or omission test. if !case.expect_accept @@ -18053,7 +18285,8 @@ fn catalog_endpoint_calibration_materialize_tool_request( let choice = request.get("tool_choice"); let named = choice .and_then(Value::as_object) - .and_then(provider_engine_named_tool_choice); + .and_then(provider_engine_named_tool_choice) + .map(str::to_owned); if named.is_none() && !matches!(choice.and_then(Value::as_str), Some("required" | "any")) { return Ok(request); } @@ -18072,7 +18305,7 @@ fn catalog_endpoint_calibration_materialize_tool_request( provider_engine_tool_definition(tool) .and_then(|function| function.get("name")) .and_then(Value::as_str) - .is_some_and(|name| named.is_none_or(|chosen| chosen == name)) + .is_some_and(|name| named.as_deref().is_none_or(|chosen| chosen == name)) }) }); if !matches_choice { @@ -18089,9 +18322,82 @@ fn catalog_endpoint_calibration_materialize_tool_request( ) })?; request["tools"] = tools.clone(); + let selected_tool = named + .as_deref() + .or_else(|| { + tools.as_array().and_then(|tools| { + tools.iter().find_map(|tool| { + provider_engine_tool_definition(tool) + .and_then(|function| function.get("name")) + .and_then(Value::as_str) + }) + }) + }) + .context("forced-tool calibration fixture has no named function")?; + catalog_endpoint_calibration_strengthen_tool_request( + &contract.family, + case, + &mut request, + selected_tool, + forced_tool_max_output_tokens, + )?; Ok(request) } +fn catalog_endpoint_calibration_strengthen_tool_request( + endpoint_family: &str, + case: &mayhem_proto::EndpointCalibrationCase, + request: &mut Value, + selected_tool: &str, + forced_tool_max_output_tokens: u32, +) -> Result<()> { + let instruction = format!( + "Call the {selected_tool} function now with arguments that satisfy its schema. Return the function call immediately; do not answer in prose." + ); + let (prompt_path, budget_path) = match endpoint_family { + mayhem_proto::ENDPOINT_OPENAI_CHAT_COMPLETIONS + | mayhem_proto::ENDPOINT_HF_MULTIMODAL_CHAT => ("messages", "max_tokens"), + mayhem_proto::ENDPOINT_OPENAI_RESPONSES => ("input", "max_output_tokens"), + other => bail!("endpoint family {other} has no forced-tool calibration prompt"), + }; + if !case.mutations.iter().any(|mutation| { + mutation.path == prompt_path || mutation.path.starts_with(&format!("{prompt_path}.")) + }) { + match endpoint_family { + mayhem_proto::ENDPOINT_OPENAI_CHAT_COMPLETIONS + | mayhem_proto::ENDPOINT_HF_MULTIMODAL_CHAT => request + .get_mut("messages") + .and_then(Value::as_array_mut) + .context("forced-tool calibration request has no messages array")? + .push(json!({"role": "user", "content": instruction})), + mayhem_proto::ENDPOINT_OPENAI_RESPONSES => match request.get_mut("input") { + Some(Value::String(input)) => { + input.push_str("\n\n"); + input.push_str(&instruction); + } + Some(Value::Array(input)) => { + input.push(json!({"role": "user", "content": instruction})); + } + _ => bail!("forced-tool calibration Responses request has no usable input"), + }, + _ => unreachable!(), + } + } + if !case + .mutations + .iter() + .any(|mutation| mutation.path == budget_path) + && request + .get(budget_path) + .and_then(Value::as_u64) + .unwrap_or_default() + < u64::from(forced_tool_max_output_tokens) + { + request[budget_path] = json!(forced_tool_max_output_tokens); + } + Ok(()) +} + fn catalog_endpoint_calibration_materialize_workflow_request( case: &mayhem_proto::EndpointCalibrationCase, mut request: Value, @@ -18164,6 +18470,19 @@ fn collect_endpoint_media_fixture_substitutions( .entry("$IMAGE_DATA_URL".to_owned()) .or_insert_with(|| json!(value)); } + Value::String(value) if value.starts_with("data:video/") => { + if let Some((metadata, encoded)) = value.split_once(',') { + if metadata.ends_with(";base64") + && base64::engine::general_purpose::STANDARD + .decode(encoded) + .is_ok() + { + substitutions + .entry("$VIDEO_BASE64".to_owned()) + .or_insert_with(|| json!(encoded)); + } + } + } Value::Array(items) => { for item in items { collect_endpoint_media_fixture_substitutions(item, substitutions); @@ -18203,7 +18522,10 @@ fn catalog_endpoint_calibration_execute( request: &Value, fixtures: &CatalogEndpointCalibrationFixtures, behavioral_witness: &mut Option, + forced_tool_max_output_tokens: u32, ) -> Result { + let output_token_cap = + catalog_endpoint_calibration_output_token_cap(request, forced_tool_max_output_tokens); let transport = catalog_endpoint_calibration_transport(contract, request, fixtures) .map_err(|error| format!("building provider transport: {error:#}"))?; let (translation, mut handled_request_attributes) = @@ -18254,7 +18576,7 @@ fn catalog_endpoint_calibration_execute( model.workflow.as_ref(), &sealed, None, - Some(ENDPOINT_CALIBRATION_MAX_OUTPUT_TOKENS), + Some(output_token_cap), &CancellationToken::new(), ) .map_err(|error| format!("executing provider request: {error:#}"))?; @@ -18284,7 +18606,7 @@ fn catalog_endpoint_calibration_execute( model.workflow.as_ref(), &sealed, None, - Some(ENDPOINT_CALIBRATION_MAX_OUTPUT_TOKENS), + Some(output_token_cap), &CancellationToken::new(), ) .map_err(|error| format!("executing provider request: {error:#}"))?; @@ -18356,6 +18678,29 @@ fn catalog_endpoint_calibration_execute_unsupported_artifact_case( } const ENDPOINT_CALIBRATION_MAX_OUTPUT_TOKENS: u32 = 128; +const ENDPOINT_CALIBRATION_FORCED_TOOL_FALLBACK_MAX_OUTPUT_TOKENS: u32 = 384; + +fn catalog_endpoint_calibration_output_token_cap( + request: &Value, + forced_tool_max_output_tokens: u32, +) -> u32 { + let choice = request.get("tool_choice"); + let forced = matches!(choice.and_then(Value::as_str), Some("required" | "any")) + || choice + .and_then(Value::as_object) + .and_then(provider_engine_named_tool_choice) + .is_some(); + if forced + && request + .get("tools") + .and_then(Value::as_array) + .is_some_and(|tools| !tools.is_empty()) + { + forced_tool_max_output_tokens + } else { + ENDPOINT_CALIBRATION_MAX_OUTPUT_TOKENS + } +} fn provider_engine_session_media_validation( backend: &mut dyn EngineBackend, @@ -18535,6 +18880,7 @@ fn catalog_endpoint_calibration_seal( "schema_version": 1, "endpoint_family": contract.family, "endpoint_contract_fingerprint": mayhem_proto::endpoint_contract_fingerprint(contract), + "endpoint_contract_canonical_fingerprint": mayhem_proto::endpoint_contract_canonical_fingerprint(contract), "normalized_request_fingerprint": mayhem_proto::endpoint_request_fingerprint(request), "transport_request_fingerprint": transport_fingerprint, }); @@ -18569,6 +18915,9 @@ fn catalog_endpoint_calibration_translation( .context("embedding dimensions overflowed usize")?; serde_json::to_value(mayhem_engine::EmbeddingRequest { inputs, dimensions })? } + mayhem_proto::ENDPOINT_MAYHEM_DECISIONS => { + serde_json::to_value(provider_decision_request_from_body(request)?)? + } mayhem_proto::ENDPOINT_OPENAI_IMAGE_GENERATIONS | mayhem_proto::ENDPOINT_HF_TEXT_TO_IMAGE => serde_json::to_value( provider_image_generation_request_from_body(&contract.family, request)?, @@ -18745,6 +19094,7 @@ fn calibration_endpoint_attribute_is_handled( mayhem_proto::ENDPOINT_OPENAI_EMBEDDINGS => { matches!(path, "model" | "input" | "encoding_format" | "dimensions") } + mayhem_proto::ENDPOINT_MAYHEM_DECISIONS => true, mayhem_proto::ENDPOINT_HF_FEATURE_EXTRACTION => { matches!(path, "inputs" | "dimensions") } @@ -18969,6 +19319,26 @@ fn catalog_endpoint_calibration_response( "usage": usage, "mayhem": mayhem, })), + mayhem_proto::ENDPOINT_MAYHEM_DECISIONS => { + let result: Value = serde_json::from_str(&output.content) + .context("decision backend produced invalid JSON")?; + let mut response = json!({ + "id": "decision-calibration", + "object": "decision.result", + "created": 1, + "model": model, + "answers": result.get("answers").cloned().context("decision result is missing answers")?, + "routing": result.get("routing").cloned().context("decision result is missing routing")?, + "usage": usage, + "mayhem": mayhem, + }); + for optional in ["shortlist", "preprocessing"] { + if let Some(value) = result.get(optional) { + response[optional] = value.clone(); + } + } + Ok(response) + } mayhem_proto::ENDPOINT_OPENAI_IMAGE_GENERATIONS => Ok(json!({ "id": "img-calibration", "object": "images.response", @@ -19184,7 +19554,36 @@ fn catalog_endpoint_calibration_response( fn verify_calibration_artifact_matches_catalog( artifact: &catalog::CatalogArtifact, artifact_path: &Path, + sidecar_paths: &BTreeMap, ) -> Result<()> { + if artifact.engine == "openai-compatible" { + let binding = artifact + .openai_compatible + .as_ref() + .context("openai-compatible artifact is missing its signed runtime binding")?; + let manifest_path = sidecar_paths + .get(&binding.snapshot_manifest_sidecar) + .context("openai-compatible calibration requires its snapshot manifest sidecar")?; + let manifest: OpenAiCompatibleSnapshotManifest = serde_json::from_slice( + &fs::read(manifest_path) + .with_context(|| format!("reading {}", manifest_path.display()))?, + ) + .with_context(|| format!("parsing {}", manifest_path.display()))?; + validate_openai_compatible_snapshot_manifest_for_artifact(artifact, binding, &manifest)?; + validate_openai_compatible_snapshot_directory( + artifact_path, + &manifest, + DEFAULT_CHUNK_SIZE, + )?; + let merkle = build_artifact_merkle_manifest(artifact_path, DEFAULT_CHUNK_SIZE)?; + ensure!( + merkle.root == artifact.artifact_root, + "openai-compatible calibration snapshot root mismatch; expected {}, got {}", + artifact.artifact_root, + merkle.root + ); + return Ok(()); + } if artifact.engine == "comfyui" { let metadata = fs::metadata(artifact_path) .with_context(|| format!("stat {}", artifact_path.display()))?; @@ -19367,6 +19766,22 @@ fn verify_calibration_sidecars_match_catalog( ); } } + if artifact.engine == "laya" { + for (required, filename) in LAYA_REQUIRED_SIDECARS { + let sidecar = artifact.sidecars.get(*required).with_context(|| { + format!("Laya calibration requires admin catalog sidecar {required}") + })?; + ensure!( + sidecar.path == *filename, + "Laya sidecar {required} must use path {filename}, got {}", + sidecar.path + ); + ensure!( + paths.contains_key(*required), + "Laya calibration requires --artifact-sidecar {required}=PATH" + ); + } + } if artifact.engine == "vllm" { for (required, filename) in VLLM_REQUIRED_SIDECARS { let sidecar = artifact.sidecars.get(*required).with_context(|| { @@ -19679,7 +20094,11 @@ fn calibration_memory_context( if args.vllm_generation_topology == Some(mayhem_proto::GenerationExecutionTopology::IsolatedWorkers) { - scope_vllm_execution_mode_memory_pool(&mut pool, &hardware, args.trt_tensor_parallel.unwrap_or(1))?; + scope_vllm_execution_mode_memory_pool( + &mut pool, + &hardware, + args.trt_tensor_parallel.unwrap_or(1), + )?; } let reserve_basis = pool.total_bytes.max(pool.available_bytes); let (reserve_bytes, reserve_source) = @@ -19691,11 +20110,8 @@ fn calibration_memory_context( human_bytes(pool.available_bytes), human_bytes(reserve_bytes) ); - let vllm_replica_limit_bytes = calibration_vllm_replica_allocation( - args, - pool.total_bytes, - f13_budget_bytes, - )?; + let vllm_replica_limit_bytes = + calibration_vllm_replica_allocation(args, pool.total_bytes, f13_budget_bytes)?; let chatterbox_device = (artifact.engine == "chatterbox") .then(|| { @@ -19749,23 +20165,38 @@ fn calibration_vllm_replica_allocation( if args.vllm_generation_topology != Some(mayhem_proto::GenerationExecutionTopology::IsolatedWorkers) { - ensure!(args.vllm_worker_count.is_none(), - "--vllm-worker-count requires a signed isolated-worker execution mode"); + ensure!( + args.vllm_worker_count.is_none(), + "--vllm-worker-count requires a signed isolated-worker execution mode" + ); return Ok(None); } - let count = args.vllm_worker_count.context("isolated calibration worker count is missing")?; - ensure!(count > 0, "isolated calibration worker count must be positive"); - let target = args.vllm_memory_utilization + let count = args + .vllm_worker_count + .context("isolated calibration worker count is missing")?; + ensure!( + count > 0, + "isolated calibration worker count must be positive" + ); + let target = args + .vllm_memory_utilization .context("isolated calibration requires --vllm-memory-utilization per worker")?; validate_provider_vllm_memory_utilization_pct(target)?; let per_worker = u64::try_from(u128::from(total_bytes) * u128::from(target) / 100) .context("isolated calibration worker allocation exceeds u64")?; - ensure!(per_worker > 0, "isolated calibration worker allocation is empty"); - let aggregate = per_worker.checked_mul(u64::from(count)) + ensure!( + per_worker > 0, + "isolated calibration worker allocation is empty" + ); + let aggregate = per_worker + .checked_mul(u64::from(count)) .context("isolated calibration allocation overflow")?; - ensure!(aggregate <= f13_budget_bytes, + ensure!( + aggregate <= f13_budget_bytes, "isolated calibration workers require {}, exceeding the F13 budget {}", - human_bytes(aggregate), human_bytes(f13_budget_bytes)); + human_bytes(aggregate), + human_bytes(f13_budget_bytes) + ); // This is an aggregate process containment limit, not a claim of measured usage. Ok(Some(aggregate)) } @@ -19815,17 +20246,20 @@ fn calibration_memory_bytes( match context.probe { CalibrationMemoryProbe::ProcessRss => { let mut total = 0_u64; - let mut missing = Vec::new(); + let mut live = 0_usize; for pid in process_ids { match provider_process_rss_bytes(*pid) { - Some(bytes) => total = total.saturating_add(bytes), - None => missing.push(*pid), + Some(bytes) => { + live += 1; + total = total.saturating_add(bytes); + } + None => {} } } ensure!( - missing.is_empty(), + live > 0, "failed to read calibration RSS for pid(s) {}", - missing + process_ids .iter() .map(u32::to_string) .collect::>() @@ -20251,18 +20685,25 @@ fn bind_calibration_generation_topology( ) -> Result<()> { args.vllm_generation_topology = profile.and_then(|profile| profile.topology); if generation_execution_uses_isolated_workers(profile) { - ensure!(args.execution_mode.is_some(), - "isolated calibration requires --execution-mode"); + ensure!( + args.execution_mode.is_some(), + "isolated calibration requires --execution-mode" + ); ensure!(args.vllm_max_num_seqs.is_none_or(|count| count == 1) && args.trt_max_batch_size.is_none(), "isolated calibration requires one sequence per worker; use --vllm-worker-count for concurrency"); let count = args.vllm_worker_count.unwrap_or(1); - ensure!(count > 0, "isolated calibration worker count must be positive"); + ensure!( + count > 0, + "isolated calibration worker count must be positive" + ); args.vllm_worker_count = Some(count); args.vllm_max_num_seqs = Some(1); } else { - ensure!(args.vllm_worker_count.is_none(), - "--vllm-worker-count requires a signed isolated-worker execution mode"); + ensure!( + args.vllm_worker_count.is_none(), + "--vllm-worker-count requires a signed isolated-worker execution mode" + ); } Ok(()) } @@ -20271,16 +20712,20 @@ fn validate_calibration_generation_topology( runtime: &CatalogCanaryRuntimeConfig, profile: Option<&catalog::CatalogGenerationExecutionProfile>, ) -> Result<()> { - ensure!(runtime.vllm_generation_topology == profile.and_then(|profile| profile.topology), - "report generation topology does not match the catalog execution mode"); + ensure!( + runtime.vllm_generation_topology == profile.and_then(|profile| profile.topology), + "report generation topology does not match the catalog execution mode" + ); if generation_execution_uses_isolated_workers(profile) { ensure!(runtime.execution_mode.is_some() && runtime.vllm_worker_count.is_some_and(|count| count > 0) && runtime.vllm_max_num_seqs == Some(1), "isolated calibration report requires a mode binding, positive worker count and one sequence per worker"); } else { - ensure!(runtime.vllm_worker_count.is_none(), - "report worker count requires a signed isolated-worker execution mode"); + ensure!( + runtime.vllm_worker_count.is_none(), + "report worker count requires a signed isolated-worker execution mode" + ); } Ok(()) } @@ -20622,6 +21067,7 @@ fn required_launch_output_canary_method(model: &catalog::CatalogModel) -> Option MODEL_CLASS_TTS | MODEL_CLASS_AUDIO_GENERATION | MODEL_CLASS_MUSIC_GENERATION => { Some(CANARY_VERIFICATION_AUDIO_FINGERPRINT) } + MODEL_CLASS_DECISION => Some(CANARY_VERIFICATION_DECISION_FINGERPRINT), _ => None, } } @@ -20918,7 +21364,8 @@ fn merge_token_canary_calibration_reports( ); ensure!( report.runtime_config.vllm_runtime == merged.runtime_config.vllm_runtime - && report.runtime_config.vllm_enforce_eager == merged.runtime_config.vllm_enforce_eager + && report.runtime_config.vllm_enforce_eager + == merged.runtime_config.vllm_enforce_eager && report.runtime_config.vllm_compilation_mode == merged.runtime_config.vllm_compilation_mode && report.runtime_config.vllm_cudagraph_mode @@ -21522,6 +21969,11 @@ fn catalog_canary_evidence_report( model.canary.audio_fingerprints.get(artifact_name).cloned(); let expected_video_fingerprints = model.canary.video_fingerprints.get(artifact_name).cloned(); + let expected_decision_fingerprints = model + .canary + .decision_fingerprints + .get(artifact_name) + .cloned(); if mode == CatalogCanaryReportMode::VerifyMatchesCatalog && model.canary.verification_method == "token_fingerprint" { @@ -21544,6 +21996,7 @@ fn catalog_canary_evidence_report( expected_transcripts.as_ref(), expected_audio_fingerprints.as_ref(), expected_video_fingerprints.as_ref(), + expected_decision_fingerprints.as_ref(), &mut entry_errors, ); } @@ -21574,6 +22027,7 @@ fn catalog_canary_evidence_report( expected_transcripts, expected_audio_fingerprints, expected_video_fingerprints, + expected_decision_fingerprints, expected_artifact_binding: catalog_canary_artifact_binding(artifact), report_path: None, report_fingerprint: None, @@ -21586,6 +22040,7 @@ fn catalog_canary_evidence_report( report_transcripts: None, report_audio_fingerprints: None, report_video_fingerprints: None, + report_decision_fingerprints: None, report_canary_set_sha256: None, report_artifact_binding: None, matches_catalog: None, @@ -21819,6 +22274,11 @@ fn catalog_canary_evidence_report( .map(|value| (prompt.prompt_id.clone(), value)) }) .collect::>(); + let report_decision_fingerprints = calibration + .prompts + .iter() + .map(|prompt| (prompt.prompt_id.clone(), prompt.fingerprint.clone())) + .collect::>(); if calibration.verification_method == "token_fingerprint" { entry.report_token_prefixes = Some(report_token_prefixes.clone()); } @@ -21837,6 +22297,9 @@ fn catalog_canary_evidence_report( if calibration.verification_method == CANARY_VERIFICATION_VIDEO_AV_FINGERPRINT { entry.report_video_fingerprints = Some(report_video_fingerprints.clone()); } + if calibration.verification_method == CANARY_VERIFICATION_DECISION_FINGERPRINT { + entry.report_decision_fingerprints = Some(report_decision_fingerprints.clone()); + } entry.report_canary_set_sha256 = Some(calibration.canary_set_sha256.clone()); entry.report_artifact_binding = Some(calibration.artifact_binding.clone()); let bound_report_fingerprint = aggregate_canary_fingerprint_for_method( @@ -21875,17 +22338,21 @@ fn catalog_canary_evidence_report( entry.expected_transcripts.as_ref(), entry.expected_audio_fingerprints.as_ref(), entry.expected_video_fingerprints.as_ref(), + entry.expected_decision_fingerprints.as_ref(), &report_perceptual_hashes, &report_embedding_vectors, &report_transcripts, &report_audio_fingerprints, &report_video_fingerprints, + &report_decision_fingerprints, canary_min_match_bps, ); entry.method_values_match_catalog = method_values_match_catalog; let matches_catalog = if matches!( entry.verification_method.as_str(), - CANARY_VERIFICATION_AUDIO_FINGERPRINT | CANARY_VERIFICATION_VIDEO_AV_FINGERPRINT + CANARY_VERIFICATION_AUDIO_FINGERPRINT + | CANARY_VERIFICATION_VIDEO_AV_FINGERPRINT + | CANARY_VERIFICATION_DECISION_FINGERPRINT ) { method_values_match_catalog } else { @@ -21969,12 +22436,17 @@ fn catalog_canary_evidence_report( } let generation_profile = match &entry.execution_mode { Some(binding) => catalog_doc - .vllm_execution_mode(&entry.expected_artifact_binding.artifact_root, &binding.mode_id) + .vllm_execution_mode( + &entry.expected_artifact_binding.artifact_root, + &binding.mode_id, + ) .and_then(|mode| mode.generation_execution_profile.as_ref()), - None => catalog_doc.generation_execution_profile(&entry.expected_artifact_binding.artifact_root), + None => catalog_doc + .generation_execution_profile(&entry.expected_artifact_binding.artifact_root), }; if let Err(error) = validate_calibration_generation_topology( - &calibration.runtime_config, generation_profile, + &calibration.runtime_config, + generation_profile, ) { entry.errors.push(error.to_string()); } @@ -22283,6 +22755,11 @@ fn catalog_execution_mode_evidence_entry( let expected_transcripts = model.canary.transcripts.get(artifact_name).cloned(); let expected_audio_fingerprints = model.canary.audio_fingerprints.get(artifact_name).cloned(); let expected_video_fingerprints = model.canary.video_fingerprints.get(artifact_name).cloned(); + let expected_decision_fingerprints = model + .canary + .decision_fingerprints + .get(artifact_name) + .cloned(); if report_mode == CatalogCanaryReportMode::VerifyMatchesCatalog { match expected_fingerprint.as_deref() { Some(value) if is_hex_len(value, 64) => {} @@ -22329,6 +22806,7 @@ fn catalog_execution_mode_evidence_entry( expected_transcripts.as_ref(), expected_audio_fingerprints.as_ref(), expected_video_fingerprints.as_ref(), + expected_decision_fingerprints.as_ref(), &mut errors, ); } @@ -22355,6 +22833,7 @@ fn catalog_execution_mode_evidence_entry( expected_transcripts, expected_audio_fingerprints, expected_video_fingerprints, + expected_decision_fingerprints, expected_artifact_binding: catalog_canary_artifact_binding(artifact), report_path: None, report_fingerprint: None, @@ -22367,6 +22846,7 @@ fn catalog_execution_mode_evidence_entry( report_transcripts: None, report_audio_fingerprints: None, report_video_fingerprints: None, + report_decision_fingerprints: None, report_canary_set_sha256: None, report_artifact_binding: None, matches_catalog: None, @@ -22388,6 +22868,7 @@ fn validate_expected_canary_method_values( transcripts: Option<&BTreeMap>, audio_fingerprints: Option<&BTreeMap>, video_fingerprints: Option<&BTreeMap>, + decision_fingerprints: Option<&BTreeMap>, errors: &mut Vec, ) { match method { @@ -22438,6 +22919,22 @@ fn validate_expected_canary_method_values( "canary video_fingerprints missing artifact {artifact}" )), }, + CANARY_VERIFICATION_DECISION_FINGERPRINT => match decision_fingerprints { + Some(values) + if !values.is_empty() + && values.values().all(|fingerprint| { + is_hex_len(fingerprint, 64) + && fingerprint + .bytes() + .all(|byte| !byte.is_ascii_uppercase()) + }) => {} + Some(_) => errors.push(format!( + "canary decision_fingerprints for {artifact} must contain lowercase 32-byte hex fingerprints" + )), + None => errors.push(format!( + "canary decision_fingerprints missing artifact {artifact}" + )), + }, _ => {} } } @@ -22867,11 +23364,13 @@ fn method_values_match_catalog( expected_transcripts: Option<&BTreeMap>, expected_audio_fingerprints: Option<&BTreeMap>, expected_video_fingerprints: Option<&BTreeMap>, + expected_decision_fingerprints: Option<&BTreeMap>, report_perceptual_hashes: &BTreeMap, report_embedding_vectors: &BTreeMap>, report_transcripts: &BTreeMap, report_audio_fingerprints: &BTreeMap, report_video_fingerprints: &BTreeMap, + report_decision_fingerprints: &BTreeMap, min_match_bps: u32, ) -> Option { match method { @@ -22907,6 +23406,9 @@ fn method_values_match_catalog( .is_some_and(|similarity| similarity >= min_match_bps) }) }), + CANARY_VERIFICATION_DECISION_FINGERPRINT => { + expected_decision_fingerprints.map(|expected| expected == report_decision_fingerprints) + } _ => None, } } @@ -23052,12 +23554,6 @@ fn validate_calibration_prompt_method_value( ) { match method { "token_fingerprint" => { - if prompt.token_count != prompt.completion_tokens as usize { - errors.push(format!( - "prompt {} token_count {} does not match completion_tokens {}", - prompt.prompt_id, prompt.token_count, prompt.completion_tokens - )); - } if prompt.token_count != prompt.token_ids.len() { errors.push(format!( "prompt {} token_count {} does not match token_ids length {}", @@ -23234,6 +23730,15 @@ fn apply_canary_report_fingerprints( entry.report_video_fingerprints.as_ref(), )?; } + CANARY_VERIFICATION_DECISION_FINGERPRINT => { + insert_canary_method_map( + canary, + &entry.model_id, + "decision_fingerprints", + &entry.artifact, + entry.report_decision_fingerprints.as_ref(), + )?; + } other => bail!("cannot apply unsupported canary verification_method {other}"), } let modality_fingerprints = @@ -23409,6 +23914,13 @@ fn apply_execution_mode_canary_report( &entry.artifact, entry.report_video_fingerprints.as_ref(), )?, + CANARY_VERIFICATION_DECISION_FINGERPRINT => insert_canary_method_map( + canary, + &entry.model_id, + "decision_fingerprints", + &entry.artifact, + entry.report_decision_fingerprints.as_ref(), + )?, other => bail!("cannot apply unsupported canary verification_method {other}"), } mode.insert( @@ -23562,6 +24074,7 @@ fn catalog_canary_plan_report(input: CatalogCanaryPlanInput<'_>) -> CatalogCanar | "needle-gpu" | "sulphur" | "transformers-asr" + | "laya" | "whisper.cpp" | "piper" => "ready", "trt-llm" => "requires-prebuilt-trt-engine", @@ -23958,6 +24471,7 @@ fn catalog_canary_matrix_report( let mut transcript_count = None; let mut audio_fingerprint_count = None; let mut video_fingerprint_count = None; + let mut decision_fingerprint_count = None; let modality_fingerprints = model .modality_assessment .calibrated_fingerprints @@ -24025,7 +24539,7 @@ fn catalog_canary_matrix_report( } match artifact.engine.as_str() { "llama.cpp" | "mlx" | "needle-cpu" => "token-prefix-local-calibration", - "trt-llm" | "vllm" | "needle-gpu" => { + "trt-llm" | "vllm" | "openai-compatible" | "needle-gpu" => { "token-prefix-hardware-calibration" } other => { @@ -24162,6 +24676,37 @@ fn catalog_canary_matrix_report( } "video-av-fingerprint-calibrated" } + CANARY_VERIFICATION_DECISION_FINGERPRINT => { + match model.canary.decision_fingerprints.get(artifact_name) { + Some(fingerprints) => { + decision_fingerprint_count = Some(fingerprints.len()); + if fingerprints.is_empty() { + entry_errors.push(format!( + "canary decision_fingerprints for {artifact_name} must not be empty" + )); + } + for prompt_id in prompt_ids { + match fingerprints.get(prompt_id) { + Some(fingerprint) + if is_hex_len(fingerprint, 64) + && fingerprint + .bytes() + .all(|byte| !byte.is_ascii_uppercase()) => {} + Some(_) => entry_errors.push(format!( + "canary decision_fingerprints for {artifact_name} prompt {prompt_id} must be lowercase 32-byte hex" + )), + None => entry_errors.push(format!( + "canary decision_fingerprints missing prompt {prompt_id} for artifact {artifact_name}" + )), + } + } + } + None => entry_errors.push(format!( + "canary decision_fingerprints missing artifact {artifact_name}" + )), + } + "decision-fingerprint-calibrated" + } CANARY_VERIFICATION_ATTESTATION_OF_COMPUTE => { if fingerprint.is_some() || model.canary.token_prefixes.contains_key(artifact_name) @@ -24170,6 +24715,10 @@ fn catalog_canary_matrix_report( || model.canary.transcripts.contains_key(artifact_name) || model.canary.audio_fingerprints.contains_key(artifact_name) || model.canary.video_fingerprints.contains_key(artifact_name) + || model + .canary + .decision_fingerprints + .contains_key(artifact_name) { entry_errors.push(format!( "attestation_of_compute canary for {artifact_name} must not carry output calibration blobs" @@ -24199,6 +24748,7 @@ fn catalog_canary_matrix_report( transcript_count, audio_fingerprint_count, video_fingerprint_count, + decision_fingerprint_count, modality_fingerprint_count: modality_fingerprints.map(BTreeMap::len), modality_resource_profile_count: modality_resource_profiles.map(BTreeMap::len), speciality_calibration_level_count: speciality_calibrations @@ -24604,6 +25154,14 @@ fn managed_python_backend_for_artifact(artifact: &catalog::CatalogArtifact) -> & } } +fn bind_vllm_task_for_model(config: &mut LoadConfig, model: &catalog::CatalogModel) { + config.vllm_task = if model.model_class == MODEL_CLASS_EMBEDDING { + mayhem_engine::VllmTask::Embedding + } else { + mayhem_engine::VllmTask::Generate + }; +} + fn needle_device_for_engine(engine: &str) -> Option<&'static str> { match engine { "needle-cpu" => Some("cpu"), @@ -24633,6 +25191,16 @@ fn preflight_catalog_calibration_managed_runtime( artifact: &catalog::CatalogArtifact, args: &CatalogCalibrateCanaryArgs, ) -> Result<()> { + if artifact.engine == "openai-compatible" { + artifact + .openai_compatible + .as_ref() + .context("openai-compatible calibration is missing its signed runtime binding")?; + validate_managed_openai_hardware(&probe(ProbeOptions::default()))?; + resolve_executable(Path::new("docker")) + .context("managed openai-compatible calibration requires Docker on PATH")?; + return Ok(()); + } if !matches!( artifact.engine.as_str(), "mlx" @@ -24642,6 +25210,7 @@ fn preflight_catalog_calibration_managed_runtime( | "needle-gpu" | "sulphur" | "transformers-asr" + | "laya" | "trt-llm" | "vllm" ) { @@ -24650,16 +25219,86 @@ fn preflight_catalog_calibration_managed_runtime( let home = args.home.clone().map(Ok).unwrap_or_else(default_home)?; let home = absolutize(home)?; fs::create_dir_all(&home).with_context(|| format!("creating {}", home.display()))?; - ensure_catalog_artifact_python(&home, artifact, args.vllm_runtime.clone()).with_context(|| { - format!( - "preparing the managed {} calibration runtime under {}", - artifact.engine, - home.display() - ) - })?; + ensure_catalog_artifact_python(&home, artifact, args.vllm_runtime.clone()).with_context( + || { + format!( + "preparing the managed {} calibration runtime under {}", + artifact.engine, + home.display() + ) + }, + )?; Ok(()) } +struct ProcessBoundOpenAiBackend { + backend: mayhem_engine::OpenAiCompatibleBackend, + process_id: u32, + // The runtime owner must outlive every calibration request. Dropping the + // wrapper stops only the exact container identity it created. + _runtime: Option>, +} + +impl EngineBackend for ProcessBoundOpenAiBackend { + fn backend_id(&self) -> &'static str { + self.backend.backend_id() + } + + fn load( + &mut self, + config: LoadConfig, + ) -> mayhem_engine::Result { + self.backend.load(config) + } + + fn prefix_caching_enabled(&self) -> bool { + self.backend.prefix_caching_enabled() + } + + fn loaded_backend_evidence(&self) -> Option { + self.backend.loaded_backend_evidence() + } + + fn component_healthy(&mut self) -> bool { + self.backend.component_healthy() + } + + fn process_ids(&self) -> Vec { + calibration_process_parent_rows() + .map(|parents| process_tree_ids_from_parent_rows(&[self.process_id], &parents)) + .unwrap_or_else(|_| vec![self.process_id]) + } + + fn requires_owner_restart(&self) -> bool { + self._runtime.is_some() + } + + fn recover_component(&mut self) -> mayhem_engine::Result { + Ok(if self.backend.component_healthy() { + ComponentRecovery::Recovered + } else { + ComponentRecovery::Unsupported + }) + } + + fn concurrent_generation_backend(&self) -> Option> { + self.backend.concurrent_generation_backend() + } + + fn tokenize(&self, text: &str) -> mayhem_engine::Result { + self.backend.tokenize(text) + } + + fn generate( + &mut self, + request: GenerateRequest, + sink: &mut dyn mayhem_engine::TokenSink, + cancellation: &CancellationToken, + ) -> mayhem_engine::Result { + self.backend.generate(request, sink, cancellation) + } +} + fn catalog_calibration_backend( model: &catalog::CatalogModel, artifact: &catalog::CatalogArtifact, @@ -24677,19 +25316,21 @@ fn catalog_calibration_backend( | "needle-gpu" | "sulphur" | "transformers-asr" + | "laya" | "trt-llm" | "vllm" ) { let home = args.home.clone().map(Ok).unwrap_or_else(default_home)?; let home = absolutize(home)?; fs::create_dir_all(&home).with_context(|| format!("creating {}", home.display()))?; - let runtime = ensure_catalog_artifact_python(&home, artifact, args.vllm_runtime.clone()).with_context(|| { - format!( - "preparing the managed {} calibration runtime under {}", - artifact.engine, - home.display() - ) - })?; + let runtime = ensure_catalog_artifact_python(&home, artifact, args.vllm_runtime.clone()) + .with_context(|| { + format!( + "preparing the managed {} calibration runtime under {}", + artifact.engine, + home.display() + ) + })?; Some((runtime, home.join("cache").join(&artifact.engine))) } else { None @@ -24745,6 +25386,18 @@ fn catalog_calibration_backend( &paths, )?; materialized_artifact_path.as_path() + } else if artifact.engine == "laya" { + let paths = ProviderArtifactPaths { + primary: artifact_path.to_path_buf(), + sidecars: sidecar_paths.clone(), + }; + materialized_artifact_path = materialize_laya_layout( + &format!("{}/{}", artifact.source.repo, artifact.path), + &artifact.path, + artifact, + &paths, + )?; + materialized_artifact_path.as_path() } else if artifact.engine == "ace-step" { let cache_dir = &managed_runtime .as_ref() @@ -24816,6 +25469,7 @@ fn catalog_calibration_backend( "mlx" => LoadConfig::mlx_safetensors(artifact_path), "trt-llm" => LoadConfig::trt_llm_checkpoint(artifact_path), "vllm" => LoadConfig::vllm_safetensors(artifact_path), + "openai-compatible" => LoadConfig::openai_compatible_model(artifact_path), "stable-diffusion.cpp" => LoadConfig::stable_diffusion_checkpoint(artifact_path), "comfyui" => LoadConfig::comfyui_runtime(artifact_path), "ace-step" => LoadConfig::ace_step_safetensors(artifact_path), @@ -24823,6 +25477,7 @@ fn catalog_calibration_backend( "needle-cpu" | "needle-gpu" => LoadConfig::transformers_safetensors(artifact_path), "sulphur" => sulphur_load_config(artifact_path)?, "transformers-asr" => LoadConfig::transformers_safetensors(artifact_path), + "laya" => LoadConfig::laya_safetensors(artifact_path), "whisper.cpp" => LoadConfig::whisper_ggml(artifact_path), "piper" => LoadConfig::piper_voice(artifact_path), other => bail!("unsupported canary calibration engine {other}"), @@ -24848,6 +25503,7 @@ fn catalog_calibration_backend( }; } if artifact.engine == "vllm" { + bind_vllm_task_for_model(&mut config, model); config.vllm_gpu_memory_utilization_pct = args.vllm_memory_utilization; config.vllm_gpu_memory_utilization_floor_pct = args.vllm_memory_utilization_floor; config.vllm_dtype = args.vllm_dtype.clone(); @@ -24877,17 +25533,22 @@ fn catalog_calibration_backend( if args.vllm_generation_topology == Some(mayhem_proto::GenerationExecutionTopology::IsolatedWorkers) { - config.vllm_generation_topology = Some(mayhem_engine::VllmGenerationTopology::IsolatedWorkers); + config.vllm_generation_topology = + Some(mayhem_engine::VllmGenerationTopology::IsolatedWorkers); config.vllm_concurrent_generation_capacity = args.vllm_worker_count; - config.vllm_worker_address_space_limit_bytes = Some(calibration_memory.f13_budget_bytes); - config.memory_limit_bytes = Some(calibration_memory.vllm_replica_limit_bytes - .context("isolated calibration is missing aggregate F13 admission")?); + config.vllm_worker_address_space_limit_bytes = + Some(calibration_memory.f13_budget_bytes); + config.memory_limit_bytes = Some( + calibration_memory + .vllm_replica_limit_bytes + .context("isolated calibration is missing aggregate F13 admission")?, + ); } } if artifact.engine == "sulphur" { bind_sulphur_primary_hash_path(&mut config.artifact, artifact)?; } - if artifact.engine != "comfyui" { + if !matches!(artifact.engine.as_str(), "comfyui" | "openai-compatible") { if let Some(sha256) = &artifact.source_sha256 { config.artifact = config.artifact.with_sha256(sha256.clone()); } @@ -24995,6 +25656,71 @@ fn catalog_calibration_backend( .context("loading vLLM canary calibration artifact")?; Ok(Box::new(backend)) } + "openai-compatible" => { + let binding = artifact + .openai_compatible + .clone() + .context("openai-compatible calibration is missing its signed runtime binding")?; + validate_managed_openai_hardware(&probe(ProbeOptions::default()))?; + let home = args.home.clone().map(Ok).unwrap_or_else(default_home)?; + let home = absolutize(home)?; + fs::create_dir_all(&home).with_context(|| format!("creating {}", home.display()))?; + let docker = resolve_executable(Path::new("docker")) + .context("managed openai-compatible calibration requires Docker on PATH")?; + let recipe_path = sidecar_paths + .get(&binding.runtime_recipe_sidecar) + .context("openai-compatible calibration requires its runtime recipe sidecar")?; + let upstream = artifact + .upstream_source + .as_ref() + .unwrap_or(&artifact.source); + let root_prefix = artifact + .artifact_root + .get(..16) + .unwrap_or(artifact.artifact_root.as_str()); + let enclave_id = format!("calibration-{root_prefix}"); + let managed = Arc::new( + managed_openai_compatible::prepare_managed_runtime( + managed_openai_compatible::ManagedRuntimeInputs { + home: &home, + docker: &docker, + provider_id: "catalog-calibration", + enclave_id: &enclave_id, + public_model_id: &model.model_id, + artifact_repo: &upstream.repo, + artifact_revision: &upstream.revision, + snapshot_manifest_sidecar: &binding.snapshot_manifest_sidecar, + snapshot_manifest_sha256: &binding.snapshot_manifest_sha256, + snapshot_file_count: binding.model_snapshot_file_count, + snapshot_total_bytes: artifact.weights_bytes, + runtime_revision: &binding.runtime_revision, + container_image_digest: &binding.container_image_digest, + max_concurrent: binding.max_concurrent, + recipe_sha256: &binding.runtime_recipe_sha256, + recipe_path, + snapshot_dir: artifact_path, + sidecars: sidecar_paths, + }, + ) + .context("starting the signed managed OpenAI-compatible calibration runtime")?, + ); + let mut backend = ProcessBoundOpenAiBackend { + backend: mayhem_engine::OpenAiCompatibleBackend::new( + mayhem_engine::OpenAiCompatibleBackendConfig { + base_url: managed.base_url().to_owned(), + runtime: binding, + readiness_timeout: Duration::from_secs(3_600), + }, + ) + .context("initializing the managed OpenAI-compatible calibration backend")?, + process_id: managed.process_id(), + _runtime: Some(managed), + }; + backend + .load(config) + .context("loading the managed OpenAI-compatible calibration backend")?; + Ok(Box::new(backend)) + } "stable-diffusion.cpp" => { let mut backend = mayhem_engine::StableDiffusionCppBackend::new() .context("initializing stable-diffusion.cpp backend")?; @@ -25092,6 +25818,19 @@ fn catalog_calibration_backend( .context("loading Transformers ASR canary calibration artifact")?; Ok(Box::new(backend)) } + "laya" => { + let python = &managed_runtime + .as_ref() + .context("Laya calibration runtime was not resolved")? + .0 + .python; + let mut backend = mayhem_engine::LayaBackend::with_python(python) + .context("initializing Laya backend")?; + backend + .load(config) + .context("loading Laya canary calibration artifact")?; + Ok(Box::new(backend)) + } "whisper.cpp" => { let mut backend = mayhem_engine::WhisperCppBackend::new() .context("initializing whisper.cpp backend")?; @@ -25128,6 +25867,9 @@ fn calibrate_canary_prompt( calibrate_image_perceptual_hash_prompt(backend, prompt, seed, include_output) } "embedding_cosine" => calibrate_embedding_cosine_prompt(backend, prompt), + CANARY_VERIFICATION_DECISION_FINGERPRINT => { + calibrate_decision_fingerprint_prompt(backend, prompt, include_output) + } "transcript_match" => calibrate_transcript_match_prompt(backend, prompt, include_output), "audio_fingerprint" => { calibrate_audio_fingerprint_prompt(backend, model, prompt, artifact_output_dir) @@ -25141,6 +25883,99 @@ fn calibrate_canary_prompt( } } +fn calibrate_decision_fingerprint_prompt( + backend: &mut dyn EngineBackend, + prompt: &CanaryPrompt, + include_output: bool, +) -> Result { + let state = prompt + .endpoint_attributes + .get("state") + .cloned() + .with_context(|| format!("decision canary prompt {} is missing state", prompt.id))?; + let questions = prompt + .endpoint_attributes + .get("questions") + .cloned() + .with_context(|| format!("decision canary prompt {} is missing questions", prompt.id))?; + let output = backend + .decide( + EngineDecisionRequest { + state, + questions, + checkpoint: prompt + .endpoint_attributes + .get("checkpoint") + .and_then(Value::as_str) + .map(str::to_owned), + task: prompt + .endpoint_attributes + .get("task") + .and_then(Value::as_str) + .map(str::to_owned), + lang: prompt + .endpoint_attributes + .get("lang") + .and_then(Value::as_str) + .map(str::to_owned), + auto_task_detection: prompt + .endpoint_attributes + .get("auto_task_detection") + .and_then(Value::as_bool) + .unwrap_or(false), + email: prompt.endpoint_attributes.get("email").cloned(), + shortlist: prompt.endpoint_attributes.get("shortlist").cloned(), + temperature: prompt.decision_temperature.clone(), + limits: prompt.endpoint_attributes.get("limits").cloned(), + }, + &CancellationToken::new(), + ) + .with_context(|| format!("generating decision canary prompt {}", prompt.id))?; + ensure!( + output.result.get("answers").is_some_and(Value::is_object), + "decision canary prompt {} returned no answers object", + prompt.id + ); + ensure!( + output.result.get("routing").is_some_and(Value::is_object), + "decision canary prompt {} returned no routing object", + prompt.id + ); + let stable_output = stable_json_value(&output.result); + let output_text = stable_output.to_string(); + let fingerprint = stable_value_hash(&stable_output); + let completion_tokens = + metered_output_units(&output_text, "", &[]).min(u64::from(u32::MAX)) as u32; + Ok(CanaryCalibrationPromptReport { + prompt_id: prompt.id.clone(), + max_tokens: 1, + prompt_tokens: output.usage.prompt_tokens, + completion_tokens, + reasoning_tokens: 0, + token_count: 0, + token_ids: Vec::new(), + token_prefix: Vec::new(), + reproducibility_runs: 1, + fingerprint, + perceptual_hash: None, + embedding_vector: None, + transcript: None, + detected_language: None, + transcription_duration_seconds: None, + word_timestamps: Vec::new(), + segment_timestamps: Vec::new(), + audio_fingerprint: None, + video_fingerprint: None, + retained_artifacts: Vec::new(), + resource_items: BTreeMap::new(), + calibration_baseline_memory_bytes: 0, + calibration_peak_memory_bytes: 0, + output_text: include_output.then_some(output_text), + behavioral_output_fingerprint: None, + behavioral_witness: None, + }) +} + fn calibrate_token_canary_prompt( backend: &mut dyn EngineBackend, model: &catalog::CatalogModel, @@ -25187,17 +26022,23 @@ fn calibrate_token_canary_prompt_with_speciality( )?; request.seed = Some(prompt.seed.unwrap_or(seed)); let max_tokens = request.max_new_tokens; - let mut token_ids = Vec::new(); + let use_canonical_openai_units = backend.backend_id() == "openai-compatible"; + let mut streamed_token_ids = Vec::new(); let output = backend .generate( request, &mut |chunk: mayhem_engine::TokenChunk| { - token_ids.push(chunk.token_id); + streamed_token_ids.push(chunk.token_id); Ok(()) }, &CancellationToken::new(), ) .with_context(|| format!("generating canary prompt {}", prompt.id))?; + let token_ids = if use_canonical_openai_units { + mayhem_proto::openai_compatible_canary_units(&output.text) + } else { + streamed_token_ids + }; if token_ids.is_empty() { bail!("canary prompt {} produced no tokens", prompt.id); } @@ -25257,13 +26098,27 @@ fn calibrate_image_perceptual_hash_prompt( _include_output: bool, ) -> Result { let mut request = EngineImageGenerationRequest::new(canary_prompt_text(prompt)?); - request.input_reference = prompt.endpoint_attributes.get("input_reference") - .and_then(Value::as_str).map(str::to_owned); - request.strength = prompt.endpoint_attributes.get("strength") - .and_then(Value::as_f64).map(|value| value as f32); - request.negative_prompt = prompt.negative_prompt.as_ref().and_then(Value::as_str).map(str::to_owned); - let reference = request.input_reference.as_deref().map(mayhem_proto::image_reference_metadata) - .transpose().map_err(anyhow::Error::msg)?; + request.input_reference = prompt + .endpoint_attributes + .get("input_reference") + .and_then(Value::as_str) + .map(str::to_owned); + request.strength = prompt + .endpoint_attributes + .get("strength") + .and_then(Value::as_f64) + .map(|value| value as f32); + request.negative_prompt = prompt + .negative_prompt + .as_ref() + .and_then(Value::as_str) + .map(str::to_owned); + let reference = request + .input_reference + .as_deref() + .map(mayhem_proto::image_reference_metadata) + .transpose() + .map_err(anyhow::Error::msg)?; request.seed = Some(prompt.seed.unwrap_or(seed)); request.image_count = 1; if let Some((width, height)) = prompt @@ -25308,9 +26163,11 @@ fn calibrate_image_perceptual_hash_prompt( CanaryCalibrationResourceItem { unit: "pixel".to_owned(), item_count: 1, - item_bytes: u64::try_from(artifact.bytes.len()).unwrap_or(u64::MAX) + item_bytes: u64::try_from(artifact.bytes.len()) + .unwrap_or(u64::MAX) .max(reference.map_or(0, |image| image.bytes)), - item_units: u64::from(width).saturating_mul(u64::from(height)) + item_units: u64::from(width) + .saturating_mul(u64::from(height)) .max(reference.map_or(0, |image| image.pixels)), }, )]); @@ -25350,11 +26207,19 @@ fn calibrate_embedding_cosine_prompt( ) -> Result { let input = canary_prompt_text(prompt)?; let input_bytes = u64::try_from(input.len()).unwrap_or(u64::MAX); + let mut request = mayhem_engine::EmbeddingRequest::new(input); + if let Some(dimensions) = prompt + .endpoint_attributes + .get("dimensions") + .and_then(Value::as_u64) + { + request.dimensions = Some( + usize::try_from(dimensions) + .context("embedding canary dimensions do not fit this platform")?, + ); + } let output = backend - .embed( - mayhem_engine::EmbeddingRequest::new(input), - &CancellationToken::new(), - ) + .embed(request, &CancellationToken::new()) .with_context(|| format!("generating embedding canary prompt {}", prompt.id))?; let vector = output .embeddings @@ -26759,6 +27624,17 @@ fn existing_catalog_canary_fingerprint( .map(|(id, vector)| (id.as_str(), embedding_vector_fingerprint(vector))), ) }), + CANARY_VERIFICATION_DECISION_FINGERPRINT => model + .canary + .decision_fingerprints + .get(artifact) + .map(|values| { + aggregate_prompt_fingerprint_map( + values + .iter() + .map(|(id, fingerprint)| (id.as_str(), fingerprint.clone())), + ) + }), "transcript_match" => model.canary.transcripts.get(artifact).map(|values| { aggregate_prompt_fingerprint_map(values.iter().map(|(id, transcript)| { ( @@ -30058,7 +30934,7 @@ fn provider_comfy_workflow_inventory_resident_bytes( .iter() .map(|part| (part.part_id.as_str(), part)) .collect::>(); - let mut total = 0_u64; + let mut verified = BTreeMap::::new(); for required in &policy.parts { let part = by_part_id.get(required.part_id.as_str()).with_context(|| { format!( @@ -30073,7 +30949,56 @@ fn provider_comfy_workflow_inventory_resident_bytes( model.model_id, required.part_id ); - total = total.saturating_add(part.record.size_bytes); + verified.insert( + required.name.clone(), + (required.part_id.clone(), part.record.size_bytes), + ); + } + let Some(constraints) = policy.graph_constraints.as_ref() else { + return Ok(verified + .values() + .fold(0_u64, |total, (_, size)| total.saturating_add(*size))); + }; + + // `policy.parts` is the complete selectable inventory. Optional workflow roles can expose + // many signed choices while bounding how many are resident in one request (for example, a + // catalog of LoRAs with a four-loader maximum). Memory admission must still verify every + // advertised part above, but charging the resident set for every alternative makes a bounded + // workflow impossible to serve on hardware that safely fits its signed maximum. + let mut referenced = BTreeSet::new(); + let mut total = 0_u64; + for role in constraints.roles.values() { + for input in role.inputs.values() { + if input.value_type != mayhem_proto::ComfyWorkflowInputType::Part + || input.part_names.is_empty() + { + continue; + } + let mut candidates = Vec::with_capacity(input.part_names.len()); + for name in &input.part_names { + let (part_id, size) = verified.get(name).with_context(|| { + format!( + "Comfy workflow {} graph references part {} outside its signed parts envelope", + model.model_id, name + ) + })?; + referenced.insert(part_id.clone()); + candidates.push(*size); + } + candidates.sort_unstable_by(|left, right| right.cmp(left)); + total = candidates + .into_iter() + .take(role.max_count) + .fold(total, u64::saturating_add); + } + } + // Parts that are not request-selectable graph inputs are runtime/system parts and remain + // resident requirements. This also preserves the prior conservative behavior for custom-node + // packages and fixed workflow assets. + for (part_id, size) in verified.values() { + if !referenced.contains(part_id) { + total = total.saturating_add(*size); + } } Ok(total) } @@ -33026,6 +33951,7 @@ async fn admin_ban_list(args: &AdminBanListArgs) -> Result<()> { ("device", "ban/device/"), ("fingerprint", "ban/fingerprint/"), ("committer", "committer/ban/"), + ("kyb", "ban/kyb/"), ]; for (target_type, prefix) in prefixes { if wanted.is_some_and(|wanted| wanted != target_type) { @@ -35211,7 +36137,14 @@ fn enforce_backend_caps(backend: &str, caps: &mut Value) -> Result<()> { fn backend_supports_tool_calls(backend: &str) -> bool { !matches!( backend, - "mlx" | "comfyui" | "ace-step" | "chatterbox" | "sulphur" | "transformers-asr" | "trt-llm" + "mlx" + | "comfyui" + | "ace-step" + | "chatterbox" + | "sulphur" + | "transformers-asr" + | "laya" + | "trt-llm" ) } @@ -44025,6 +44958,7 @@ fn spawn_gateway_catalog_watcher(state: GatewayState, config: GatewayCatalogWatc eprintln!("Gateway catalog watcher component panicked; restarting: {err}"); } } + state.failed_catalog_refresh(); sleep(Duration::from_secs(1)).await; } }); @@ -44043,7 +44977,10 @@ async fn run_gateway_catalog_watcher( let mut applied_snapshot = String::new(); let mut last_error = None; loop { - sleep(config.refresh_interval).await; + tokio::select! { + _ = sleep(config.refresh_interval) => {}, + _ = state.wait_for_catalog_refresh_request() => {}, + } let refresh = async { let contract = read_contract_catalog(&rpc).await?; let contract_models = gateway_models_from_contract(&contract)?; @@ -44054,70 +44991,68 @@ async fn run_gateway_catalog_watcher( canary_registry, execution_mode_registry, attestation_authority, - ) = - match config.dev_catalog.as_ref() { - Some(dev) => ( - dev.catalog_hash.clone(), - Some(dev.catalog_doc.clone()), - dev.canary_registry.clone(), - dev.execution_mode_registry.clone(), - dev.attestation_authority.clone(), - ), - None => match read_optional_catalog_release_anchor(&rpc).await? { - Some(release) => { - let files = - fetch_catalog_release_files(&client, &config.home, &release) - .await?; - let catalog_doc = catalog::load_document(&files.catalog_path) - .with_context(|| { - format!( - "loading refreshed ledger catalog {}", - files.catalog_path.display() - ) - })?; - let catalog_json = fs::read_to_string(&files.catalog_path) - .with_context(|| { - format!( - "reading refreshed ledger catalog {}", - files.catalog_path.display() - ) - })?; - let canary_json_by_set = - load_catalog_canary_json_by_set(&catalog_doc, &files.canaries_dir)?; - let canary_registry = - GatewayState::canary_registry_from_catalog_and_canary_json( - &catalog_json, - &canary_json_by_set, + ) = match config.dev_catalog.as_ref() { + Some(dev) => ( + dev.catalog_hash.clone(), + Some(dev.catalog_doc.clone()), + dev.canary_registry.clone(), + dev.execution_mode_registry.clone(), + dev.attestation_authority.clone(), + ), + None => match read_optional_catalog_release_anchor(&rpc).await? { + Some(release) => { + let files = + fetch_catalog_release_files(&client, &config.home, &release).await?; + let catalog_doc = catalog::load_document(&files.catalog_path) + .with_context(|| { + format!( + "loading refreshed ledger catalog {}", + files.catalog_path.display() ) - .map_err(anyhow::Error::msg) - .context("loading refreshed gateway canary registry")?; - let execution_mode_registry = - GatewayState::execution_mode_registry_from_catalog_and_canary_json( - &catalog_json, - &canary_json_by_set, + })?; + let catalog_json = + fs::read_to_string(&files.catalog_path).with_context(|| { + format!( + "reading refreshed ledger catalog {}", + files.catalog_path.display() ) - .map_err(anyhow::Error::msg) - .context("loading refreshed gateway execution mode registry")?; - ( - release.catalog_hash, - Some(catalog_doc), - canary_registry, - execution_mode_registry, - files.attestation_authority, + })?; + let canary_json_by_set = + load_catalog_canary_json_by_set(&catalog_doc, &files.canaries_dir)?; + let canary_registry = + GatewayState::canary_registry_from_catalog_and_canary_json( + &catalog_json, + &canary_json_by_set, ) - } - None if contract_models.is_empty() => ( - "unpublished-empty".to_owned(), - None, - GatewayCanaryRegistry::default(), - GatewayExecutionModeRegistry::default(), - catalog::CatalogAttestationAuthority::default(), - ), - None => bail!( - "catalog/current disappeared while canonical models remain published" - ), - }, - }; + .map_err(anyhow::Error::msg) + .context("loading refreshed gateway canary registry")?; + let execution_mode_registry = + GatewayState::execution_mode_registry_from_catalog_and_canary_json( + &catalog_json, + &canary_json_by_set, + ) + .map_err(anyhow::Error::msg) + .context("loading refreshed gateway execution mode registry")?; + ( + release.catalog_hash, + Some(catalog_doc), + canary_registry, + execution_mode_registry, + files.attestation_authority, + ) + } + None if contract_models.is_empty() => ( + "unpublished-empty".to_owned(), + None, + GatewayCanaryRegistry::default(), + GatewayExecutionModeRegistry::default(), + catalog::CatalogAttestationAuthority::default(), + ), + None => { + bail!("catalog/current disappeared while canonical models remain published") + } + }, + }; let (models, _version_gates) = match catalog_doc.as_ref() { Some(catalog_doc) => { filter_gateway_models_by_app_version(contract_models, catalog_doc)? @@ -44181,8 +45116,10 @@ async fn run_gateway_catalog_watcher( "Gateway authenticated catalog refreshed from contract: {model_count} model(s)" ); } + state.complete_catalog_refresh(); } Err(err) => { + state.failed_catalog_refresh(); let message = format!("{err:#}"); if last_error.as_deref() != Some(message.as_str()) { eprintln!("Gateway catalog watcher retrying: {message}"); @@ -46928,6 +47865,8 @@ struct CanaryPrompt { #[serde(default)] temperature: Option, #[serde(default)] + decision_temperature: Option, + #[serde(default)] top_p: Option, #[serde(default)] top_k: Option, @@ -47233,6 +48172,7 @@ struct CatalogCanaryMatrixEntry { transcript_count: Option, audio_fingerprint_count: Option, video_fingerprint_count: Option, + decision_fingerprint_count: Option, modality_fingerprint_count: Option, modality_resource_profile_count: Option, speciality_calibration_level_count: Option, @@ -47321,6 +48261,7 @@ struct CatalogCanaryEvidenceEntry { expected_transcripts: Option>, expected_audio_fingerprints: Option>, expected_video_fingerprints: Option>, + expected_decision_fingerprints: Option>, expected_artifact_binding: CatalogCanaryArtifactBinding, report_path: Option, report_fingerprint: Option, @@ -47334,6 +48275,7 @@ struct CatalogCanaryEvidenceEntry { report_transcripts: Option>, report_audio_fingerprints: Option>, report_video_fingerprints: Option>, + report_decision_fingerprints: Option>, report_canary_set_sha256: Option, report_artifact_binding: Option, matches_catalog: Option, @@ -48172,9 +49114,12 @@ fn load_catalog_canary_json_by_set( .models .iter() .map(|model| model.canary.set_id.as_str()) - .chain(catalog.vllm_execution_modes.values().flat_map(|modes| { - modes.values().map(|mode| mode.canary.set_id.as_str()) - })) + .chain( + catalog + .vllm_execution_modes + .values() + .flat_map(|modes| modes.values().map(|mode| mode.canary.set_id.as_str())), + ) .collect::>() { let mut components = Path::new(set_id).components(); @@ -50109,9 +51054,6 @@ fn price_derivation_summary(derivation: &Value) -> String { .or_else(|| derivation_u64(derivation, &["price_ver"])) .map(|value| format!("v{value}")) .unwrap_or_else(|| "price".to_owned()); - let momentum = derivation_u64(derivation, &["controller", "momentum_bps"]) - .map(format_bps) - .unwrap_or_else(|| "?".to_owned()); let basis = derivation_str(derivation, &["controller", "activity_basis"]) .unwrap_or("historical pricing"); let sessions = derivation_u64(derivation, &["usage", "session_count"]) @@ -50129,9 +51071,31 @@ fn price_derivation_summary(derivation: &Value) -> String { let leaf = derivation_str(derivation, &["derivation_hash"]) .map(|value| format!(" leaf={}", short_hash(value))) .unwrap_or_default(); - format!( - "price {result_ver}; activity momentum {momentum}; {basis}; epoch {epoch}; {sessions} settled sessions; {supply} providers; seed {seed_ver}; {source}{root}{leaf}" - ) + if derivation.get("controller").is_some_and(|controller| { + controller.get("activity_basis").is_some_and(|value| { + matches!( + value.as_str(), + Some("signed_slot_time_v1" | "legacy_receipt_hold_v1") + ) + }) + }) { + let utilization = derivation_u64(derivation, &["controller", "utilization_bps"]) + .map(format_bps) + .unwrap_or_else(|| "held for retained receipt recovery".to_owned()); + let multiplier = derivation_u64(derivation, &["controller", "multiplier_bps"]) + .map(format_bps) + .unwrap_or_else(|| "?".to_owned()); + format!( + "price {result_ver}; utilization {utilization}; price multiplier {multiplier}; {basis}; epoch {epoch}; {sessions} settled sessions; {supply} providers; seed {seed_ver}; {source}{root}{leaf}" + ) + } else { + let momentum = derivation_u64(derivation, &["controller", "momentum_bps"]) + .map(format_bps) + .unwrap_or_else(|| "?".to_owned()); + format!( + "price {result_ver}; activity momentum {momentum}; {basis}; epoch {epoch}; {sessions} settled sessions; {supply} providers; seed {seed_ver}; {source}{root}{leaf}" + ) + } } fn derivation_u64(value: &Value, path: &[&str]) -> Option { @@ -52477,6 +53441,7 @@ fn normalize_tnk_holdbacks(earning: &LedgerEarningRecord) -> Result Result { @@ -53188,6 +54153,7 @@ async fn canonicalize_fiat_settlement_plan( "liability_au": money_au_json(output.liability_au), }), None, + false, max_attempts, retry_ms, ) @@ -53243,6 +54209,7 @@ async fn canonicalize_fiat_settlement_plan( "liability_au": money_au_json(output.liability_au), }), None, + false, max_attempts, retry_ms, ) @@ -53959,6 +54926,7 @@ async fn stripe_create_fx_quote( lock_duration: &str, operation_identity: &Value, idempotency_key_override: Option<&str>, + allow_expired_valuation_quote: bool, max_attempts: u32, retry_ms: u64, ) -> Result { @@ -54074,20 +55042,45 @@ async fn stripe_create_fx_quote( "unlocked Stripe FX quote is not usable" ); } else { - let now = unix_epoch_seconds()?; - ensure!( - quote.lock_status == "active" - && quote - .expires_at - .is_some_and(|expires| expires > now.saturating_add(15)), - "Stripe FX quote is not active long enough to create a transfer" - ); + validate_locked_stripe_fx_quote( + "e, + lock_duration, + unix_epoch_seconds()?, + allow_expired_valuation_quote, + )?; } return Ok(quote); } unreachable!("positive max_attempts checked by caller") } +fn validate_locked_stripe_fx_quote( + quote: &StripeFxQuote, + requested_lock_duration: &str, + now: u64, + allow_expired_valuation_quote: bool, +) -> Result<()> { + let expires_at = quote + .expires_at + .context("locked Stripe FX quote is missing its expiry")?; + ensure!( + quote.lock_duration == requested_lock_duration && expires_at > quote.created, + "Stripe FX quote lock does not match its request" + ); + if allow_expired_valuation_quote { + ensure!( + matches!(quote.lock_status.as_str(), "active" | "expired"), + "Stripe FX valuation quote has an invalid lock status" + ); + } else { + ensure!( + quote.lock_status == "active" && expires_at > now.saturating_add(15), + "Stripe FX quote is not active long enough to create a transfer" + ); + } + Ok(()) +} + async fn stripe_retrieve_fx_quote( client: &reqwest::Client, api_base_url: &str, @@ -54651,6 +55644,7 @@ async fn stripe_create_transfer_verified( "operation": operation, }), Some(&journal.quote_idempotency_key), + source_currency == destination_currency, max_attempts, retry_ms, ) @@ -56103,7 +57097,8 @@ async fn create_targeted_fiat_quote( vec!["usd".to_owned(), source_currency.to_owned()] }; from_currencies.retain(|currency| currency != destination_currency); - let idempotency_key = format!("mayhem:fiat:fx-quote:v1:{economic_op_id}:{attempt_no}"); + let idempotency_key = + targeted_fiat_quote_idempotency_key(economic_op_id, attempt_no, unix_epoch_seconds()?); let quote = stripe_create_fx_quote( client, api_base_url, @@ -56118,6 +57113,7 @@ async fn create_targeted_fiat_quote( "attempt_no": attempt_no, }), Some(&idempotency_key), + false, max_attempts, retry_ms, ) @@ -56126,11 +57122,40 @@ async fn create_targeted_fiat_quote( Ok(Some(quote)) } +fn targeted_fiat_quote_idempotency_key(economic_op_id: &str, attempt_no: u64, now: u64) -> String { + format!( + "mayhem:fiat:fx-quote:v2:{economic_op_id}:{attempt_no}:{}", + now / STRIPE_FX_QUOTE_RENEWAL_BUCKET_SECONDS + ) +} + fn ensure_canonical_fiat_quote_matches_output( output: &Value, quote: Option<&StripeFxQuote>, ) -> Result { let planned = fiat_plan_output_fx(output)?; + if planned.source_currency == planned.destination_currency { + if planned.source_currency == "usd" { + ensure!( + quote.is_none(), + "USD fiat execution must not carry an FX quote" + ); + return Ok(planned); + } + let quote = quote.context("same-currency fiat execution is missing its valuation quote")?; + let destination = output + .get("to") + .and_then(Value::as_str) + .context("canonical fiat output is missing destination")?; + ensure!( + quote.to_currency == planned.destination_currency + && quote.usage_type == "transfer" + && quote.usage_destination.as_deref() == Some(destination) + && quote.rates.contains_key("usd"), + "same-currency fiat valuation quote does not match the canonical output" + ); + return Ok(planned); + } let candidate = fiat_provider_transfer_plan( planned.liability_au, &planned.source_currency, @@ -56523,6 +57548,7 @@ async fn stripe_operator_fee_evidence( "liability_au": money_au_json(output.liability_au), }), None, + false, max_attempts, retry_ms, ) @@ -59244,6 +60270,28 @@ struct ProviderArtifactPaths { sidecars: BTreeMap, } +#[derive(Debug, Deserialize)] +#[serde(deny_unknown_fields)] +struct OpenAiCompatibleSnapshotManifest { + schema: u32, + source: String, + repo: String, + source_revision: String, + canonical_hf_revision: String, + total_bytes: u64, + files: Vec, +} + +#[derive(Debug, Deserialize)] +#[serde(deny_unknown_fields)] +struct OpenAiCompatibleSnapshotFile { + path: String, + size: u64, + sha256: String, + hf_oid: String, + hf_oid_kind: String, +} + const PROVIDER_COMFY_WORKFLOW_DEFINITION_ARTIFACT: &str = "__workflow_class_definition"; #[derive(Clone, Debug)] @@ -59421,11 +60469,13 @@ struct ProviderHeartbeatLoad { modality_active_items: Arc>, modality_max_inflight_items: Arc>, changes: tokio::sync::watch::Sender, + published_changes: tokio::sync::watch::Sender, } impl Default for ProviderHeartbeatLoad { fn default() -> Self { let (changes, _) = tokio::sync::watch::channel(0); + let (published_changes, _) = tokio::sync::watch::channel(0); Self { prefix_caching: Arc::new(AtomicBool::new(false)), active_slots: Arc::new(AtomicU64::new(0)), @@ -59439,6 +60489,7 @@ impl Default for ProviderHeartbeatLoad { modality_active_items: Arc::new(BTreeMap::new()), modality_max_inflight_items: Arc::new(BTreeMap::new()), changes, + published_changes, } } } @@ -59686,6 +60737,20 @@ impl ProviderHeartbeatLoad { self.changes.subscribe() } + fn change_generation(&self) -> u64 { + *self.changes.borrow() + } + + fn mark_published(&self, generation: u64) { + if *self.published_changes.borrow() < generation { + self.published_changes.send_replace(generation); + } + } + + fn published_through(&self, generation: u64) -> bool { + *self.published_changes.borrow() >= generation + } + fn notify_change(&self) { self.changes .send_modify(|generation| *generation = generation.wrapping_add(1)); @@ -59913,6 +60978,53 @@ impl ProviderTpmActivationLimiter { } } +#[derive(Debug)] +struct ProviderTokenizeLimiter { + global_limit: usize, + per_peer_limit: usize, + accepted_at: VecDeque, + accepted_by_peer: HashMap>, +} + +impl ProviderTokenizeLimiter { + fn from_environment() -> Result { + Ok(Self { + global_limit: configured_positive_count( + "MAYHEM_PROVIDER_TOKENIZE_REQUESTS_PER_MINUTE", + DEFAULT_TOKENIZE_REQUESTS_PER_MINUTE, + "provider tokenize global rate", + )?, + per_peer_limit: configured_positive_count( + "MAYHEM_PROVIDER_TOKENIZE_REQUESTS_PER_PEER_PER_MINUTE", + DEFAULT_TOKENIZE_REQUESTS_PER_PEER_PER_MINUTE, + "provider tokenize per-peer rate", + )?, + accepted_at: VecDeque::new(), + accepted_by_peer: HashMap::new(), + }) + } + + fn admit(&mut self, remote: &str, now: Instant) -> Result<()> { + ProviderTpmActivationLimiter::prune(&mut self.accepted_at, now); + self.accepted_by_peer.retain(|_, accepted| { + ProviderTpmActivationLimiter::prune(accepted, now); + !accepted.is_empty() + }); + ensure!( + self.accepted_at.len() < self.global_limit, + "provider tokenize global rate limit reached" + ); + let peer = self.accepted_by_peer.entry(remote.to_owned()).or_default(); + ensure!( + peer.len() < self.per_peer_limit, + "provider tokenize per-peer rate limit reached" + ); + self.accepted_at.push_back(now); + peer.push_back(now); + Ok(()) + } +} + #[derive(Clone, Debug, Eq, PartialEq)] struct ProviderRuntimeFloorRejection { code: &'static str, @@ -60348,6 +61460,14 @@ fn provider_session_quality_value( }) } +fn provider_session_quality_compute_ms(quality: &Value) -> u64 { + quality + .get("compute_ms") + .and_then(Value::as_u64) + .unwrap_or(1) + .max(1) +} + struct ProviderRequestLoadGuard { load: ProviderHeartbeatLoad, modality_items: BTreeMap, @@ -60444,6 +61564,7 @@ struct ReceiptSettlementOutboxEntry { final_receipt: bool, usage: ReceiptUsage, au_owed_cum: MoneyAu, + compute_ms: u64, immutable_terms_hash: String, } @@ -60454,17 +61575,23 @@ struct ReceiptSettlementFeatureMeta { final_receipt: bool, usage: ReceiptUsage, au_owed_cum: MoneyAu, + compute_ms: u64, immutable_terms_hash: String, } fn receipt_settlement_feature_meta(feature: &Value) -> Result { validate_receipt_settlement_feature(feature)?; - let receipt = parse_record_usage_receipt_envelope( - feature - .pointer("/value/receipt") - .context("receipt settlement feature is missing receipt")?, - ) - .map_err(anyhow::Error::msg)?; + let envelope = feature + .pointer("/value/receipt") + .context("receipt settlement feature is missing receipt")?; + let receipt = parse_record_usage_receipt_envelope(envelope).map_err(anyhow::Error::msg)?; + receipt_settlement_receipt_meta(&receipt, Some(envelope)) +} + +fn receipt_settlement_receipt_meta( + receipt: &SessionReceipt, + original_envelope: Option<&Value>, +) -> Result { let body = &receipt.body; let attempt_id = stable_value_hash(&json!({ "domain": "mayhem-receipt-settlement-attempt-v1", @@ -60473,8 +61600,13 @@ fn receipt_settlement_feature_meta(feature: &Value) -> Result envelope + .get("body") + .cloned() + .context("receipt envelope is missing its body")?, + None => serde_json::to_value(body).context("serializing receipt immutable terms")?, + }; let terms = immutable_terms .as_object_mut() .context("receipt body did not serialize as an object")?; @@ -60484,6 +61616,11 @@ fn receipt_settlement_feature_meta(feature: &Value) -> Result Result= incoming.au_owed_cum - && current.usage.is_monotonic_from(&incoming.usage), + && current.usage.is_monotonic_from(&incoming.usage) + && current.compute_ms >= incoming.compute_ms, "receipt settlement attempt cannot advance or conflict with a durable final receipt" ); return Ok(false); } ensure!( - incoming.seq >= current.seq, - "receipt settlement attempt cannot downgrade its canonical sequence" + incoming.seq > current.seq, + "receipt settlement attempt must advance its canonical sequence" + ); + ensure!( + incoming.au_owed_cum >= current.au_owed_cum + && incoming.usage.is_monotonic_from(¤t.usage) + && incoming.compute_ms >= current.compute_ms, + "receipt settlement attempt high-water evidence is not monotonic" ); - if incoming.seq == current.seq { - ensure!( - incoming.final_receipt - && incoming.au_owed_cum >= current.au_owed_cum - && incoming.usage.is_monotonic_from(¤t.usage), - "receipt settlement attempt has conflicting evidence at the same sequence" - ); - } else { - ensure!( - incoming.au_owed_cum >= current.au_owed_cum - && incoming.usage.is_monotonic_from(¤t.usage), - "receipt settlement attempt high-water evidence is not monotonic" - ); - } Ok(true) } @@ -60566,16 +61697,43 @@ fn confirmed_receipt_settlement_record_matches( key: &str, entry: &ReceiptSettlementOutboxEntry, ) -> bool { - record.get("confirmed").and_then(Value::as_bool) == Some(true) + let confirmed_head = record.get("confirmed").and_then(Value::as_bool) == Some(true) && record.get("key").and_then(Value::as_str) == Some(key) - && record.pointer("/value/type").and_then(Value::as_str) - == Some("canonical_receipt_head") - && record - .pointer("/value/settlement_ready") - .and_then(Value::as_bool) - == Some(true) - && record.pointer("/value/feature_key") == entry.feature.get("key") + && record.pointer("/value/type").and_then(Value::as_str) == Some("canonical_receipt_head"); + // A confirmed checkpoint has been delivered even though the session has + // not settled. Requiring settlement here deadlocks failed-session recovery: + // recovery waits for signed evidence delivery before closing the hold. + // Finals still gate subsequent admission until settlement is ready. + let settlement_ready = record + .pointer("/value/settlement_ready") + .and_then(Value::as_bool) + == Some(true); + if !confirmed_head || (entry.final_receipt && !settlement_ready) { + return false; + } + if record.pointer("/value/feature_key") == entry.feature.get("key") && record.pointer("/value/receipt") == entry.feature.pointer("/value/receipt") + { + return true; + } + if entry.final_receipt { + return false; + } + let Some(envelope) = record.pointer("/value/receipt") else { + return false; + }; + let Some(receipt) = parse_record_usage_receipt_envelope(envelope).ok() else { + return false; + }; + let Ok(canonical) = receipt_settlement_receipt_meta(&receipt, Some(envelope)) else { + return false; + }; + canonical.attempt_id == entry.attempt_id + && canonical.immutable_terms_hash == entry.immutable_terms_hash + && canonical.seq > entry.seq + && canonical.au_owed_cum >= entry.au_owed_cum + && canonical.usage.is_monotonic_from(&entry.usage) + && canonical.compute_ms >= entry.compute_ms } async fn confirmed_receipt_settlement_entry( @@ -60698,6 +61856,7 @@ impl ReceiptSettlementOutbox { final_receipt: meta.final_receipt, usage: meta.usage, au_owed_cum: meta.au_owed_cum, + compute_ms: meta.compute_ms, immutable_terms_hash: meta.immutable_terms_hash, }) } @@ -60802,8 +61961,14 @@ impl ReceiptSettlementOutbox { } fn load_entries(&self) -> Result> { + Self::select_attempt_heads(self.load_physical_entries()?) + } + + fn select_attempt_heads( + entries: Vec, + ) -> Result> { let mut attempts = BTreeMap::::new(); - for entry in self.load_physical_entries()? { + for entry in entries { match attempts.get(&entry.attempt_id) { None => { attempts.insert(entry.attempt_id.clone(), entry); @@ -60815,6 +61980,7 @@ impl ReceiptSettlementOutbox { final_receipt: entry.final_receipt, usage: entry.usage.clone(), au_owed_cum: entry.au_owed_cum, + compute_ms: entry.compute_ms, immutable_terms_hash: entry.immutable_terms_hash.clone(), }; if receipt_settlement_entry_supersedes(current, &meta, &entry.feature)? { @@ -60831,6 +61997,89 @@ impl ReceiptSettlementOutbox { Ok(attempts.into_values().collect()) } + // Called under the cross-process outbox lock. Enumerate current filenames + // for capacity, but only read/verify signed documents for this attempt. + // Startup, admission and retry still perform complete validation; no cache + // or layout change can hide another process's durable evidence. + fn load_attempt( + &self, + attempt_id: &str, + ) -> Result<(usize, Option)> { + let mut attempts = BTreeSet::new(); + let mut paths = Vec::new(); + let mut physical_count = 0_usize; + for item in fs::read_dir(&self.directory)? { + let item = item?; + let path = item.path(); + if path.extension() != Some(OsStr::new("json")) { + continue; + } + physical_count += 1; + ensure!( + physical_count <= RECEIPT_SETTLEMENT_OUTBOX_MAX_ENTRIES.saturating_mul(2), + "receipt settlement outbox exceeded its bounded recovery file count" + ); + let filename = item.file_name(); + let parts = filename + .to_str() + .context("invalid receipt filename")? + .split('.') + .collect::>(); + let lower_hex = |s: &str| { + s.len() == 64 + && s.bytes() + .all(|b| b.is_ascii_digit() || (b'a'..=b'f').contains(&b)) + }; + ensure!( + parts.len() == 5 + && lower_hex(parts[0]) + && parts[1].len() == 20 + && parts[1].bytes().all(|b| b.is_ascii_digit()) + && parts[1].parse::().is_ok() + && matches!(parts[2], "0" | "1") + && lower_hex(parts[3]) + && parts[4] == "json", + "receipt settlement outbox entry has a non-canonical filename" + ); + let metadata = match fs::symlink_metadata(&path) { + Ok(metadata) => metadata, + // Full background validation can quarantine an obsolete entry + // between directory enumeration and this metadata check. + Err(error) if error.kind() == io::ErrorKind::NotFound => continue, + Err(error) => return Err(error.into()), + }; + ensure!( + metadata.file_type().is_file(), + "receipt settlement outbox entry must be a regular file" + ); + ensure!( + metadata.len() <= RECEIPT_SETTLEMENT_OUTBOX_MAX_ENTRY_BYTES, + "receipt settlement outbox entry exceeds its byte bound" + ); + #[cfg(unix)] + { + use std::os::unix::fs::PermissionsExt as _; + ensure!( + metadata.permissions().mode() & 0o077 == 0, + "receipt settlement outbox entry must not be group/world accessible" + ); + } + attempts.insert(parts[0].to_owned()); + if parts[0] == attempt_id { + paths.push(path); + } + } + ensure!( + attempts.len() <= RECEIPT_SETTLEMENT_OUTBOX_MAX_ENTRIES, + "receipt settlement outbox reached its attempt bound" + ); + paths.sort(); + let current = Self::select_attempt_heads(self.load_physical_entries_at_paths(paths)?)? + .into_iter() + .next(); + Ok((attempts.len(), current)) + } + fn lock_file(&self) -> Result { let path = self.directory.join(".outbox.lock"); let mut options = fs::OpenOptions::new(); @@ -60876,19 +62125,7 @@ impl ReceiptSettlementOutbox { let lock = self.lock_file()?; let meta = receipt_settlement_feature_meta(feature)?; let path = self.entry_path(feature)?; - if path.exists() { - let existing = self.load_entry(&path)?; - ensure!( - existing.feature == *feature, - "receipt settlement outbox key already contains different signed evidence" - ); - fs2::FileExt::unlock(&lock).context("unlocking receipt settlement outbox")?; - return Ok(existing); - } - let current = self - .load_entries()? - .into_iter() - .find(|entry| entry.attempt_id == meta.attempt_id); + let (attempt_count, current) = self.load_attempt(&meta.attempt_id)?; if let Some(current) = current.as_ref() { if !receipt_settlement_entry_supersedes(current, &meta, feature)? { fs2::FileExt::unlock(&lock).context("unlocking receipt settlement outbox")?; @@ -60896,7 +62133,7 @@ impl ReceiptSettlementOutbox { } } ensure!( - self.load_entries()?.len() < RECEIPT_SETTLEMENT_OUTBOX_MAX_ENTRIES, + current.is_some() || attempt_count < RECEIPT_SETTLEMENT_OUTBOX_MAX_ENTRIES, "receipt settlement outbox reached its {} attempt bound", RECEIPT_SETTLEMENT_OUTBOX_MAX_ENTRIES ); @@ -60968,10 +62205,7 @@ impl ReceiptSettlementOutbox { fn remove(&self, entry: &ReceiptSettlementOutboxEntry) -> Result<()> { let lock = self.lock_file()?; - let current = self - .load_entries()? - .into_iter() - .find(|current| current.attempt_id == entry.attempt_id); + let (_, current) = self.load_attempt(&entry.attempt_id)?; if current .as_ref() .is_some_and(|current| current.feature != entry.feature) @@ -61004,19 +62238,14 @@ impl ReceiptSettlementOutbox { rpc.submit_feature(entry.feature.clone()), ) .await; - if matches!( - &relay, - Ok(Ok(response)) if response.get("ok").and_then(Value::as_bool) == Some(true) - ) { - return self.remove(&entry); - } - // A relay acknowledgement is transport evidence, not the source of - // truth. The writer can commit the receipt and lose only its answer; - // retaining that already-canonical entry forever eventually blocks all - // new paid work. Retire it only when the confirmed ledger head proves - // the exact signed feature landed. Any missing or mismatched field - // fails closed and leaves the durable outbox entry untouched. + // truth. The writer may acknowledge an accepted append before that + // append is visible in canonical state. Removing the entry at that + // point also hides the pending final receipt from next-turn admission, + // so a newly freed provider can reject the next request while the + // previous hold is still closing. Retire only when the confirmed ledger + // head proves the exact signed feature landed. Any missing or + // mismatched field fails closed and leaves the durable entry visible. if confirmed_receipt_settlement_entry(rpc, &entry).await? { return self.remove(&entry); } @@ -61027,7 +62256,7 @@ impl ReceiptSettlementOutbox { Err(error).context("submitting receipt settlement through participant relay") } Ok(Ok(response)) => Err(anyhow!( - "receipt settlement relay did not confirm canonical evidence: {response}" + "receipt settlement relay accepted transport delivery but canonical evidence remains pending: {response}" )), } } @@ -61136,7 +62365,11 @@ impl GatewayReceiptSettlementPublisher for ReceiptSettlementOutbox { .map_err(|error| format!("{error:#}")) } - fn has_pending_final_receipts(&self, user: &str, rail: &str) -> std::result::Result { + fn has_pending_final_receipts( + &self, + user: &str, + rail: &str, + ) -> std::result::Result { self.load_entries() .map(|entries| { entries.iter().any(|entry| { @@ -61220,22 +62453,26 @@ fn validate_receipt_settlement_feature(feature: &Value) -> Result { if receipt_contract_version_is_supported(version)), "receipt settlement feature has the wrong operation or contract version" ); - let receipt = parse_record_usage_receipt_envelope( - value - .get("receipt") - .context("receipt settlement feature is missing receipt")?, - ) - .map_err(anyhow::Error::msg)?; + let receipt_envelope = value + .get("receipt") + .context("receipt settlement feature is missing receipt")?; + let receipt = + parse_record_usage_receipt_envelope(receipt_envelope).map_err(anyhow::Error::msg)?; ensure!( - receipt.body.schema_version == SESSION_RECEIPT_SCHEMA_VERSION - && value.get("epoch").and_then(Value::as_u64) == Some(receipt.body.billing_epoch) + receipt_schema_version_is_supported_for_contract( + u64::from(receipt.body.schema_version), + contract_version.expect("validated receipt contract version") + ) && value.get("epoch").and_then(Value::as_u64) == Some(receipt.body.billing_epoch) && value.get("payout_revision").and_then(Value::as_str) == Some(receipt.body.payout_revision.as_str()), "receipt settlement outer binding does not match its signed receipt" ); ensure!( - key == record_usage_receipt_feature_key_for_contract( - &receipt, contract_version.expect("validated receipt contract version") as u32), + key == record_usage_receipt_feature_key_from_envelope_for_contract( + receipt_envelope, + contract_version.expect("validated receipt contract version") as u32, + ) + .map_err(anyhow::Error::msg)?, "receipt settlement feature key is not canonical" ); let provider_signature = value @@ -61308,6 +62545,14 @@ struct ProviderBackendRuntime { cache_dir: Option, external_binary: Option, stable_diffusion_backend: Option, + #[serde(skip_serializing)] + openai_compatible_url: Option, + #[serde(skip_serializing)] + openai_compatible_pid: Option, + #[serde(skip_serializing)] + openai_compatible_docker: Option, + #[serde(skip_serializing)] + managed_openai_compatible: Option>, } #[derive(Clone)] @@ -61405,12 +62650,28 @@ fn provider_session_reject_replay_decision( Ok( if replay.remote != remote || replay.open_head != replay_head { ProviderSessionReplayDecision::Conflict + } else if replay.reject_frame.get("code").and_then(Value::as_str) + == Some("RESERVATION_PENDING") + { + ProviderSessionReplayDecision::Pending } else { ProviderSessionReplayDecision::Cached(replay.reject_frame.clone()) }, ) } +fn provider_session_reservation_timeout_decision( + admission_timeout: Duration, +) -> ProviderSessionDecision { + ProviderSessionDecision::Reject { + code: "RESERVATION_PENDING", + reason: format!( + "the signed spend reservation did not reach a canonical result within the {} ms provider admission budget; the exact reservation is retained for recovery", + admission_timeout.as_millis() + ), + } +} + fn prune_provider_session_reject_replays( replays: &mut HashMap, now: Instant, @@ -61481,6 +62742,10 @@ struct ProviderSessionTerms { model_id: String, adapter: catalog::CatalogAdapter, generation_execution_profile: Option, + /// Request modality sets that the loaded runtime can dispatch independently + /// without a catalog generation profile. This is populated only from a + /// live concurrent backend, never from advertised provider capacity. + runtime_independent_dispatch_modalities: Vec>, sampling: catalog::CatalogSamplingProfile, workflow_policy: Option, output_modalities: Vec, @@ -61494,6 +62759,7 @@ struct ProviderSessionTerms { min_session_au: MoneyAu, min_ask_au: MoneyAu, rules_ver: u64, + capacity_slots: u32, ctx: u64, ctx_bracket: Option, ctx_bracket_table_ver: Option, @@ -61514,7 +62780,9 @@ enum ProviderSessionDecision { } trait ProviderSessionResponder { - fn prefix_caching_enabled(&self) -> bool { false } + fn prefix_caching_enabled(&self) -> bool { + false + } fn mode(&self) -> &'static str; /// Independently dispatchable requests, not the engine's theoretical batch capacity. fn concurrent_session_capacity(&self) -> u32 { @@ -61526,15 +62794,27 @@ trait ProviderSessionResponder { fn recover_component(&mut self) -> Result { Ok(ComponentRecovery::Unsupported) } + fn reclaim_idle_memory(&mut self) -> Result { + Ok(false) + } fn supports_live_text_streaming(&self) -> bool { false } fn process_ids(&self) -> Vec { Vec::new() } + fn requires_owner_restart(&self) -> bool { + false + } fn concurrent_generation_backend(&self) -> Option> { None } + fn concurrent_embedding_backend(&self) -> Option> { + None + } + fn tokenize(&mut self, _terms: &ProviderSessionTerms, _body: &Value) -> Result { + bail!("provider backend does not expose exact tokenization") + } fn respond( &mut self, terms: &ProviderSessionTerms, @@ -61598,7 +62878,9 @@ struct EngineProviderSessionResponder { } impl ProviderSessionResponder for EngineProviderSessionResponder { - fn prefix_caching_enabled(&self) -> bool { self.backend.prefix_caching_enabled() } + fn prefix_caching_enabled(&self) -> bool { + self.backend.prefix_caching_enabled() + } fn mode(&self) -> &'static str { "mayhem-engine" } @@ -61608,17 +62890,27 @@ impl ProviderSessionResponder for EngineProviderSessionResponder { } fn concurrent_session_capacity(&self) -> u32 { - self.backend + let generation = self + .backend .concurrent_generation_backend() .map(|backend| u32::try_from(backend.capacity()).unwrap_or(u32::MAX)) - .unwrap_or(1) - .max(1) + .unwrap_or(1); + let embedding = self + .backend + .concurrent_embedding_backend() + .map(|backend| u32::try_from(backend.capacity()).unwrap_or(u32::MAX)) + .unwrap_or(1); + generation.max(embedding).max(1) } fn concurrent_generation_backend(&self) -> Option> { self.backend.concurrent_generation_backend() } + fn concurrent_embedding_backend(&self) -> Option> { + self.backend.concurrent_embedding_backend() + } + fn component_healthy(&mut self) -> bool { self.backend.component_healthy() } @@ -61627,10 +62919,26 @@ impl ProviderSessionResponder for EngineProviderSessionResponder { self.backend.recover_component().map_err(Into::into) } + fn reclaim_idle_memory(&mut self) -> Result { + self.backend.reclaim_idle_memory().map_err(Into::into) + } + fn process_ids(&self) -> Vec { self.backend.process_ids() } + fn requires_owner_restart(&self) -> bool { + self.backend.requires_owner_restart() + } + + fn tokenize(&mut self, terms: &ProviderSessionTerms, body: &Value) -> Result { + let prompt = provider_engine_tokenize_prompt(terms, body)?; + if let Some(backend) = self.backend.concurrent_generation_backend() { + return backend.tokenize(&prompt).map_err(Into::into); + } + self.backend.tokenize(&prompt).map_err(Into::into) + } + fn respond( &mut self, terms: &ProviderSessionTerms, @@ -61669,11 +62977,26 @@ impl ProviderSessionResponder for EngineProviderSessionResponder { } } -struct ConcurrentGenerationEngineBackend { - backend: Arc, +#[derive(Clone)] +enum ProviderConcurrentEngine { + Generation(Arc), + Embedding(Arc), +} + +impl ProviderConcurrentEngine { + fn capacity(&self) -> usize { + match self { + Self::Generation(backend) => backend.capacity(), + Self::Embedding(backend) => backend.capacity(), + } + } +} + +struct ConcurrentEngineBackend { + backend: ProviderConcurrentEngine, } -impl EngineBackend for ConcurrentGenerationEngineBackend { +impl EngineBackend for ConcurrentEngineBackend { fn backend_id(&self) -> &'static str { "vllm" } @@ -61687,10 +63010,13 @@ impl EngineBackend for ConcurrentGenerationEngineBackend { )) } - fn tokenize(&self, _text: &str) -> mayhem_engine::Result { - Err(EngineError::InvalidConfig( - "concurrent vLLM request handles cannot tokenize outside generation".to_owned(), - )) + fn tokenize(&self, text: &str) -> mayhem_engine::Result { + match &self.backend { + ProviderConcurrentEngine::Generation(backend) => backend.tokenize(text), + ProviderConcurrentEngine::Embedding(_) => Err(EngineError::InvalidConfig( + "concurrent embedding handles do not expose text tokenization".to_owned(), + )), + } } fn generate( @@ -61699,18 +63025,38 @@ impl EngineBackend for ConcurrentGenerationEngineBackend { sink: &mut dyn mayhem_engine::TokenSink, cancellation: &CancellationToken, ) -> mayhem_engine::Result { - self.backend.generate(request, sink, cancellation) + match &self.backend { + ProviderConcurrentEngine::Generation(backend) => { + backend.generate(request, sink, cancellation) + } + ProviderConcurrentEngine::Embedding(_) => Err(EngineError::InvalidConfig( + "concurrent embedding handles cannot generate text".to_owned(), + )), + } + } + + fn embed( + &mut self, + request: mayhem_engine::EmbeddingRequest, + cancellation: &CancellationToken, + ) -> mayhem_engine::Result { + match &self.backend { + ProviderConcurrentEngine::Embedding(backend) => backend.embed(request, cancellation), + ProviderConcurrentEngine::Generation(_) => Err(EngineError::InvalidConfig( + "concurrent generation handles cannot create embeddings".to_owned(), + )), + } } } struct ConcurrentEngineProviderSessionResponder { - backend: ConcurrentGenerationEngineBackend, + backend: ConcurrentEngineBackend, } impl ConcurrentEngineProviderSessionResponder { - fn new(backend: Arc) -> Self { + fn new(backend: ProviderConcurrentEngine) -> Self { Self { - backend: ConcurrentGenerationEngineBackend { backend }, + backend: ConcurrentEngineBackend { backend }, } } } @@ -61730,6 +63076,11 @@ impl ProviderSessionResponder for ConcurrentEngineProviderSessionResponder { true } + fn tokenize(&mut self, terms: &ProviderSessionTerms, body: &Value) -> Result { + let prompt = provider_engine_tokenize_prompt(terms, body)?; + self.backend.tokenize(&prompt).map_err(Into::into) + } + fn respond( &mut self, terms: &ProviderSessionTerms, @@ -62129,7 +63480,11 @@ async fn provider_serve_plan(args: ProviderServePlanArgs) -> Result<()> { "Copy/paste start command: {}", report["copy_paste"]["up"].as_str().unwrap_or("") ); - for command in report["copy_paste"]["serve"].as_array().into_iter().flatten() { + for command in report["copy_paste"]["serve"] + .as_array() + .into_iter() + .flatten() + { println!("Then: {}", command.as_str().unwrap_or("")); } } @@ -62800,10 +64155,14 @@ fn append_provider_hardware_quote_args( fn append_provider_serve_execution_mode(child: &mut Value, mode_id: &str) -> Result<()> { mayhem_proto::validate_execution_mode_id(mode_id).map_err(anyhow::Error::msg)?; - let args = child.get_mut("args").and_then(Value::as_array_mut) + let args = child + .get_mut("args") + .and_then(Value::as_array_mut) .context("supervised provider command is missing its argument list")?; ensure!( - !args.iter().any(|arg| arg.as_str() == Some("--execution-mode")), + !args + .iter() + .any(|arg| arg.as_str() == Some("--execution-mode")), "supervised provider command already selects an execution mode" ); args.extend([json!("--execution-mode"), json!(mode_id)]); @@ -62857,6 +64216,7 @@ fn provider_backend_runtime_child_env( "transformers-asr" => { insert_path("MAYHEM_TRANSFORMERS_ASR_PYTHON", runtime.python.as_deref()); } + "laya" => insert_path("MAYHEM_LAYA_PYTHON", runtime.python.as_deref()), "stable-diffusion.cpp" => { insert_path( "MAYHEM_STABLE_DIFFUSION_CPP_BIN", @@ -62871,12 +64231,22 @@ fn provider_backend_runtime_child_env( } "comfyui" => { insert_path("MAYHEM_COMFYUI_PYTHON", runtime.python.as_deref()); - if let Ok(device) = env::var("MAYHEM_COMFYUI_DEVICE") { - child_env.insert("MAYHEM_COMFYUI_DEVICE".to_owned(), device); + for name in ["MAYHEM_COMFYUI_DEVICE", "MAYHEM_COMFYUI_RESERVE_VRAM_GB"] { + if let Ok(value) = env::var(name) { + child_env.insert(name.to_owned(), value); + } } } "whisper.cpp" => insert_path("MAYHEM_WHISPER_CPP_BIN", runtime.external_binary.as_deref()), "piper" => insert_path("MAYHEM_PIPER_BIN", runtime.external_binary.as_deref()), + "openai-compatible" => { + if let Some(url) = runtime.openai_compatible_url.as_ref() { + child_env.insert("MAYHEM_OPENAI_COMPATIBLE_URL".to_owned(), url.clone()); + if let Some(pid) = runtime.openai_compatible_pid { + child_env.insert("MAYHEM_OPENAI_COMPATIBLE_PID".to_owned(), pid.to_string()); + } + } + } _ => {} } child_env @@ -63083,16 +64453,27 @@ fn provider_serve_plan_commands( gpu_layers: Option, hardware_quote_config: Option<&ProviderHardwareQuoteConfig>, ) -> Value { - if selection.candidates.iter().all(|candidate| candidate.execution_mode.is_none()) { + if selection + .candidates + .iter() + .all(|candidate| candidate.execution_mode.is_none()) + { return json!({"up": provider_auto_fit_up_command( home, selection, gpu_layers, &args.disable_modalities, hardware_quote_config, )}); } // `up` has no per-worker mode selector. Replay via the supervised serve command. - let serve = selection.candidates.iter().map(|candidate| { - provider_serve_mode_add_argv(home, candidate, args, gpu_layers, hardware_quote_config) - .iter().map(|value| shell_single_quote(value)).collect::>().join(" ") - }).collect::>(); + let serve = selection + .candidates + .iter() + .map(|candidate| { + provider_serve_mode_add_argv(home, candidate, args, gpu_layers, hardware_quote_config) + .iter() + .map(|value| shell_single_quote(value)) + .collect::>() + .join(" ") + }) + .collect::>(); json!({ "up": format!("mayhem up --yes --home {}", shell_single_quote(&home.display().to_string())), "serve": serve, @@ -63107,9 +64488,15 @@ fn provider_serve_mode_add_argv( hardware_quote_config: Option<&ProviderHardwareQuoteConfig>, ) -> Vec { let mut argv = vec![ - "mayhem".to_owned(), "provider".to_owned(), "serve".to_owned(), "add".to_owned(), - candidate.enclave.enclave_id.clone(), "--home".to_owned(), home.display().to_string(), - "--ctx".to_owned(), candidate.served_ctx.to_string(), + "mayhem".to_owned(), + "provider".to_owned(), + "serve".to_owned(), + "add".to_owned(), + candidate.enclave.enclave_id.clone(), + "--home".to_owned(), + home.display().to_string(), + "--ctx".to_owned(), + candidate.served_ctx.to_string(), ]; if let Some(mode) = &candidate.execution_mode { argv.extend(["--execution-mode".to_owned(), mode.binding.mode_id.clone()]); @@ -63125,9 +64512,12 @@ fn provider_serve_mode_add_argv( } if let Some(config) = hardware_quote_config { argv.extend([ - "--hardware-quote-kind".to_owned(), hardware_quote_kind_name(&config.kind), - "--hardware-quote-command".to_owned(), config.command.display().to_string(), - "--hardware-quote-timeout-seconds".to_owned(), config.timeout.as_secs().to_string(), + "--hardware-quote-kind".to_owned(), + hardware_quote_kind_name(&config.kind), + "--hardware-quote-command".to_owned(), + config.command.display().to_string(), + "--hardware-quote-timeout-seconds".to_owned(), + config.timeout.as_secs().to_string(), ]); } argv @@ -63185,7 +64575,10 @@ fn provider_backend_runtime_preflight( home, backend, Some(&selected.artifact), - selected.vllm_execution_profile.as_ref().and_then(|profile| profile.runtime.clone()), + selected + .vllm_execution_profile + .as_ref() + .and_then(|profile| profile.runtime.clone()), Some(&selected.verdict), hardware, gpu_layers, @@ -63224,7 +64617,7 @@ fn provider_backend_runtime_preflight_for_backend( let mut runtime = ProviderBackendRuntime::default(); match backend { "vllm" | "trt-llm" | "mlx" | "ace-step" | "chatterbox" | "needle-cpu" | "needle-gpu" - | "sulphur" | "transformers-asr" => { + | "sulphur" | "transformers-asr" | "laya" => { let chatterbox_device = if backend == "chatterbox" { let device = chatterbox_managed_device(hardware).context( "hardware probe did not select a supported Chatterbox managed device", @@ -63346,11 +64739,214 @@ fn provider_backend_runtime_preflight_for_backend( .or(gpu_layers); provider_llama_accelerator_preflight(effective_gpu_layers, hardware)?; } + "openai-compatible" => { + let binding = artifact + .and_then(|artifact| artifact.openai_compatible.as_ref()) + .context("openai-compatible artifact is missing its signed runtime binding")?; + validate_managed_openai_hardware(hardware)?; + if let Ok(url) = env::var("MAYHEM_OPENAI_COMPATIBLE_URL") { + mayhem_engine::OpenAiCompatibleBackend::new( + mayhem_engine::OpenAiCompatibleBackendConfig { + base_url: url.clone(), + runtime: binding.clone(), + readiness_timeout: Duration::ZERO, + }, + ) + .context("validating the exact-identity loopback attach runtime binding")?; + runtime.openai_compatible_pid = Some(verified_openai_attach_pid(&url)?); + runtime.openai_compatible_url = Some(url); + } else { + runtime.openai_compatible_docker = Some( + resolve_executable(Path::new("docker")) + .context("managed openai-compatible serving requires Docker on PATH")?, + ); + } + } other => bail!("unsupported local provider session backend {other}"), } Ok(runtime) } +fn validate_managed_openai_hardware(hardware: &HardwareReport) -> Result<()> { + const MIN_QUALIFIED_VRAM: u64 = 97_887 * 1024 * 1024; + const CONTAINER_MEMORY_LIMIT: u64 = 104 * 1024 * 1024 * 1024; + ensure!( + hardware.memory.total_bytes >= CONTAINER_MEMORY_LIMIT, + "managed openai-compatible resource profile requires at least 104 GiB host memory" + ); + let nvidia = hardware + .gpus + .iter() + .filter(|gpu| gpu.vendor == GpuVendor::Nvidia) + .collect::>(); + ensure!( + nvidia.len() == 1, + "managed openai-compatible profile requires exactly one NVIDIA GPU" + ); + let gpu = nvidia[0]; + ensure!( + gpu.compute_capability.as_deref() == Some("12.0") + && gpu.dedicated_memory_bytes.or(gpu.memory_bytes).unwrap_or(0) + >= MIN_QUALIFIED_VRAM + && gpu.supports_nvfp4 + && gpu.supports_fp8, + "managed openai-compatible profile requires one compute-capability 12.0 NVIDIA GPU with at least 97887 MiB usable VRAM and NVFP4/FP8 support" + ); + Ok(()) +} + +#[cfg(target_os = "linux")] +fn verified_openai_attach_pid(base_url: &str) -> Result { + let pid = env::var("MAYHEM_OPENAI_COMPATIBLE_PID") + .context("advanced OpenAI-compatible attach requires MAYHEM_OPENAI_COMPATIBLE_PID")? + .parse::() + .context("MAYHEM_OPENAI_COMPATIBLE_PID must be a positive decimal PID")?; + ensure!(pid > 0, "MAYHEM_OPENAI_COMPATIBLE_PID must be positive"); + let url = reqwest::Url::parse(base_url).context("parsing OpenAI-compatible attach URL")?; + let port = url + .port() + .context("OpenAI-compatible attach URL must include its loopback port")?; + let expected_ip = url + .host_str() + .context("OpenAI-compatible attach URL must include a host")? + .parse::() + .context("OpenAI-compatible attach URL must use an IP literal")?; + ensure!( + expected_ip.is_loopback(), + "OpenAI-compatible attach URL must use a literal loopback address" + ); + let expected_v4 = match expected_ip { + IpAddr::V4(address) => Some(address.octets()), + IpAddr::V6(_) => None, + }; + let mut socket_inodes = BTreeSet::new(); + for (path, ipv6) in [("/proc/net/tcp", false), ("/proc/net/tcp6", true)] { + let Ok(table) = fs::read_to_string(path) else { + continue; + }; + for line in table.lines().skip(1) { + let fields = line.split_whitespace().collect::>(); + if fields.len() < 10 || fields[3] != "0A" { + continue; + } + let Some((address, encoded_port)) = fields[1].split_once(':') else { + continue; + }; + let Ok(candidate_port) = u16::from_str_radix(encoded_port, 16) else { + continue; + }; + if candidate_port != port { + continue; + } + let address_matches = if ipv6 { + expected_v4.is_none() + && address.eq_ignore_ascii_case("00000000000000000000000001000000") + } else if let Some(octets) = expected_v4 { + address.eq_ignore_ascii_case(&format!( + "{:02X}{:02X}{:02X}{:02X}", + octets[3], octets[2], octets[1], octets[0] + )) + } else { + false + }; + if address_matches { + socket_inodes.insert(fields[9].to_owned()); + } + } + } + ensure!( + !socket_inodes.is_empty(), + "the attach endpoint is not listening on its exact loopback address and port" + ); + let process_ids = + process_tree_ids_from_parent_rows(&[pid], &calibration_process_parent_rows()?); + let owns_socket = process_ids.iter().any(|candidate| { + fs::read_dir(format!("/proc/{candidate}/fd")) + .ok() + .into_iter() + .flatten() + .flatten() + .filter_map(|entry| fs::read_link(entry.path()).ok()) + .filter_map(|target| target.to_str().map(str::to_owned)) + .filter_map(|target| { + target + .strip_prefix("socket:[") + .and_then(|value| value.strip_suffix(']')) + .map(str::to_owned) + }) + .any(|inode| socket_inodes.contains(&inode)) + }); + ensure!( + owns_socket, + "MAYHEM_OPENAI_COMPATIBLE_PID and its descendants do not own the exact loopback listener" + ); + Ok(pid) +} + +#[cfg(not(target_os = "linux"))] +fn verified_openai_attach_pid(_base_url: &str) -> Result { + bail!("advanced OpenAI-compatible attach PID verification is supported only on Linux") +} + +fn activate_provider_managed_openai_runtime( + home: &Path, + provider_id: &str, + selected: &ProviderCandidate, + artifact_paths: &ProviderArtifactPaths, + backend_runtime: &mut ProviderBackendRuntime, +) -> Result<()> { + if selected.artifact.engine != "openai-compatible" + || backend_runtime.openai_compatible_url.is_some() + { + return Ok(()); + } + let binding = selected + .artifact + .openai_compatible + .as_ref() + .context("openai-compatible artifact is missing its signed runtime binding")?; + let docker = backend_runtime + .openai_compatible_docker + .as_deref() + .context("managed openai-compatible runtime preflight did not resolve Docker")?; + let recipe_path = artifact_paths + .sidecars + .get(&binding.runtime_recipe_sidecar) + .context("downloaded openai-compatible runtime recipe is missing")?; + let upstream = selected + .artifact + .upstream_source + .as_ref() + .unwrap_or(&selected.artifact.source); + let managed = managed_openai_compatible::prepare_managed_runtime( + managed_openai_compatible::ManagedRuntimeInputs { + home, + docker, + provider_id, + enclave_id: &selected.enclave.enclave_id, + public_model_id: &selected.model.model_id, + artifact_repo: &upstream.repo, + artifact_revision: &upstream.revision, + snapshot_manifest_sidecar: &binding.snapshot_manifest_sidecar, + snapshot_manifest_sha256: &binding.snapshot_manifest_sha256, + snapshot_file_count: binding.model_snapshot_file_count, + snapshot_total_bytes: selected.artifact.weights_bytes, + runtime_revision: &binding.runtime_revision, + container_image_digest: &binding.container_image_digest, + max_concurrent: binding.max_concurrent, + recipe_sha256: &binding.runtime_recipe_sha256, + recipe_path, + snapshot_dir: &artifact_paths.primary, + sidecars: &artifact_paths.sidecars, + }, + ) + .context("starting the signed managed OpenAI-compatible runtime")?; + backend_runtime.openai_compatible_url = Some(managed.base_url().to_owned()); + backend_runtime.openai_compatible_pid = Some(managed.process_id()); + backend_runtime.managed_openai_compatible = Some(Arc::new(managed)); + Ok(()) +} + fn validate_chatterbox_device_request( requested: Option<&str>, selected: ChatterboxManagedDevice, @@ -63729,7 +65325,7 @@ async fn provider_start(mut args: ProviderStartArgs) -> Result<()> { if let Some(message) = &selected.feasibility.message { provider_log(&args, message); } - let backend_runtime = if args.serve_sessions { + let mut backend_runtime = if args.serve_sessions { provider_log( &args, &format!( @@ -63764,6 +65360,15 @@ async fn provider_start(mut args: ProviderStartArgs) -> Result<()> { .unwrap_or_else(|| home.join("downloads")); let downloads_dir = absolutize(downloads_dir)?; let artifact_paths = download_provider_artifact(&args, &downloads_dir, &selected).await?; + if args.serve_sessions { + activate_provider_managed_openai_runtime( + &home, + &wallet.public_key, + &selected, + &artifact_paths, + &mut backend_runtime, + )?; + } provider_log(&args, "Verifying and sealing the enclave artifact"); write_provider_load_progress_stage(&args, "seal enclave artifact", "seal", "running", 0); @@ -71032,6 +72637,100 @@ async fn read_contract_catalog(rpc: &PeerRpcClient) -> Result { }) } +// A provider admission only needs the canonical records for its own market and +// startup rooms. The general catalog reader above enumerates historical price +// versions for every market, which grows without bound as prices move. Keep +// all admission reads on one confirmed ledger snapshot instead. +async fn read_provider_session_contract_catalog( + rpc: &PeerRpcClient, + terms: &ProviderSessionTerms, +) -> Result { + let signed_length = confirmed_snapshot_signed_length(rpc, "rules/current").await?; + let enclave_key = format!("enclave/{}", terms.enclave_id); + let provider_key = format!("prov/{}", terms.provider); + let serve_key = format!("serve/{}/{}", terms.provider, terms.enclave_id); + let (enclaves, providers, serves, schedule_value, rules_value) = tokio::try_join!( + read_provider_session_record_at::(rpc, &enclave_key, signed_length), + read_provider_session_record_at::(rpc, &provider_key, signed_length), + read_provider_session_record_at::(rpc, &serve_key, signed_length), + read_state_value_at(rpc, "ctx_brackets", signed_length), + read_state_value_at(rpc, "rules/current", signed_length), + )?; + let ctx_bracket_schedule = match schedule_value { + Some(value) => serde_json::from_value(value) + .context("parsing confirmed contract ctx_brackets schedule")?, + None => default_ctx_bracket_schedule(), + }; + validate_ctx_bracket_schedule(&ctx_bracket_schedule) + .map_err(|err| anyhow::anyhow!("invalid confirmed ctx_brackets schedule: {err}"))?; + let rules = rules_value + .map(serde_json::from_value) + .transpose() + .context("parsing confirmed rules/current")?; + let ctx_bracket = match enclaves.first() { + Some(enclave) if enclave.model_class == DEFAULT_MODEL_CLASS => { + ctx_bracket_for_tokens_in_schedule( + u32::try_from(terms.ctx).unwrap_or(u32::MAX), + &ctx_bracket_schedule, + unix_epoch_seconds()?, + ) + .map(|(bracket, _)| bracket) + } + _ => None, + }; + let price_key = format!( + "price/{}", + ledger_price_market_key(&terms.enclave_id, ctx_bracket.as_deref()) + ); + let prices = + read_provider_session_record_at::(rpc, &price_key, signed_length) + .await?; + let mut rooms = Vec::with_capacity(terms.room_ids.len()); + let mut roomserve = Vec::with_capacity(terms.room_ids.len()); + for room_id in &terms.room_ids { + let room_key = format!("room/{room_id}"); + let roomserve_key = format!( + "roomserve/{room_id}/{}/{}", + terms.provider, terms.enclave_id + ); + let (mut live_rooms, mut live_roomserve) = tokio::try_join!( + read_provider_session_record_at::(rpc, &room_key, signed_length), + read_provider_session_record_at::(rpc, &roomserve_key, signed_length,), + )?; + rooms.append(&mut live_rooms); + roomserve.append(&mut live_roomserve); + } + Ok(ContractCatalog { + enclaves, + rooms, + roomserve, + serves, + providers, + reputations: Vec::new(), + kyb: Vec::new(), + prices, + price_derivations: Vec::new(), + tier3_measurements: Vec::new(), + ctx_bracket_schedule, + rules, + active_billing_epoch: 0, + active_payout_revisions: BTreeMap::new(), + }) +} + +async fn read_provider_session_record_at( + rpc: &PeerRpcClient, + key: &str, + signed_length: u64, +) -> Result> { + match read_state_value_at(rpc, key, signed_length).await? { + Some(value) => Ok(vec![serde_json::from_value(value).with_context(|| { + format!("parsing confirmed provider admission record {key}") + })?]), + None => Ok(Vec::new()), + } +} + async fn read_active_provider_payout_revisions( rpc: &PeerRpcClient, active_epoch: u64, @@ -71572,6 +73271,7 @@ fn gateway_models_from_contract(contract: &ContractCatalog) -> Result { + "vllm" | "openai-compatible" => { if let Some(total) = known_total_nvidia_vllm_memory_bytes(hardware).filter(|total| *total > 0) { @@ -72882,7 +74582,11 @@ fn provider_memory_budget( ) -> Result { let mut pool = provider_memory_pool(hardware, verdict, &enclave.backend, args.gpu_layers); if enclave.backend == "vllm" && args.execution_mode.is_some() { - scope_vllm_execution_mode_memory_pool(&mut pool, hardware, enclave_tp_degree(&enclave.caps)?)?; + scope_vllm_execution_mode_memory_pool( + &mut pool, + hardware, + enclave_tp_degree(&enclave.caps)?, + )?; } let claimed_bytes = read_provider_memory_claimed_bytes( args.home.as_deref(), @@ -72898,17 +74602,20 @@ fn provider_memory_budget( // Some unified NVIDIA probes have no dedicated-memory total and their // allocation pool falls back to available RAM. Virtual mappings must use // the machine's real total instead of inheriting that availability fallback. - let dedicated_total = hardware.gpus.iter() - .filter(|gpu| gpu.vendor == GpuVendor::Nvidia - && !nvidia_gpu_uses_host_unified_memory(hardware, gpu)) + let dedicated_total = hardware + .gpus + .iter() + .filter(|gpu| { + gpu.vendor == GpuVendor::Nvidia && !nvidia_gpu_uses_host_unified_memory(hardware, gpu) + }) .filter_map(|gpu| gpu.memory_bytes) .fold(0u64, u64::saturating_add); let address_basis = hardware.memory.total_bytes.max(dedicated_total); - let (address_reserve, _) = provider_memory_reserve_bytes( - args.memory_reserve.as_deref(), address_basis, pool.unified, - )?; + let (address_reserve, _) = + provider_memory_reserve_bytes(args.memory_reserve.as_deref(), address_basis, pool.unified)?; let worker_address_space_limit_bytes = address_basis - .saturating_sub(address_reserve).max(worker_limit_bytes); + .saturating_sub(address_reserve) + .max(worker_limit_bytes); let mut budget_bytes = worker_limit_bytes; if enclave.backend == "vllm" { let admin_max_pct = enclave_vllm_gpu_memory_utilization_pct(&enclave.caps)?; @@ -72936,31 +74643,49 @@ fn scope_vllm_execution_mode_memory_pool( hardware: &HardwareReport, tp_degree: u32, ) -> Result<()> { - let device_memories = hardware.gpus.iter() + let device_memories = hardware + .gpus + .iter() .filter(|gpu| gpu.vendor == GpuVendor::Nvidia) .map(|gpu| { - gpu.memory_bytes.or_else(|| { - nvidia_gpu_uses_host_unified_memory(hardware, gpu) - .then_some(hardware.memory.available_bytes.unwrap_or(hardware.memory.total_bytes)) - }).filter(|bytes| *bytes > 0) - .context("vLLM execution mode requires known memory for every selectable NVIDIA device") + gpu.memory_bytes + .or_else(|| { + nvidia_gpu_uses_host_unified_memory(hardware, gpu).then_some( + hardware + .memory + .available_bytes + .unwrap_or(hardware.memory.total_bytes), + ) + }) + .filter(|bytes| *bytes > 0) + .context( + "vLLM execution mode requires known memory for every selectable NVIDIA device", + ) }) .collect::>>()?; - ensure!(tp_degree > 0 && u64::from(tp_degree) <= device_memories.len() as u64, - "vLLM execution mode TP degree exceeds the probed NVIDIA device count"); + ensure!( + tp_degree > 0 && u64::from(tp_degree) <= device_memories.len() as u64, + "vLLM execution mode TP degree exceeds the probed NVIDIA device count" + ); // Without a pinned device set, budget against the smallest selectable device, // never memory on unrelated GPUs. This remains safe under device reordering. - let per_device = device_memories.iter().copied().min() + let per_device = device_memories + .iter() + .copied() + .min() .context("vLLM execution mode requires a probed NVIDIA device")?; let total = if pool.unified { per_device } else { - per_device.checked_mul(u64::from(tp_degree)) + per_device + .checked_mul(u64::from(tp_degree)) .context("vLLM TP memory budget overflow")? }; pool.total_bytes = pool.total_bytes.min(total); pool.available_bytes = pool.available_bytes.min(pool.total_bytes); - pool.source.push_str(&format!("; execution-mode TP{tp_degree} minimum-device budget")); + pool.source.push_str(&format!( + "; execution-mode TP{tp_degree} minimum-device budget" + )); Ok(()) } @@ -73120,6 +74845,19 @@ fn provider_memory_estimate( served_ctx: u64, workflow_inventory_bytes: u64, ) -> Result { + if let Some(peak) = artifact + .openai_compatible + .as_ref() + .and_then(|runtime| runtime.calibrated_peak_gpu_memory_bytes) + { + return Ok(ProviderMemoryEstimate { + required_bytes: peak, + weights_bytes: peak, + kv_bytes: 0, + overhead_bytes: 0, + media_bytes: 0, + }); + } let weights_bytes = catalog_artifact_resident_bytes(artifact).saturating_add(workflow_inventory_bytes); let kv_bytes = @@ -73154,6 +74892,13 @@ fn provider_model_memory_fit( requested_ctx: u64, workflow_inventory_bytes: u64, ) -> Result { + if let Some(peak) = artifact + .openai_compatible + .as_ref() + .and_then(|runtime| runtime.calibrated_peak_gpu_memory_bytes) + { + return Ok(model_memory_fit(usable_bytes, peak, 0, 0, requested_ctx)); + } let weights_bytes = catalog_artifact_resident_bytes(artifact).saturating_add(workflow_inventory_bytes); let overhead_bytes = (weights_bytes / 5).max(512 * 1024 * 1024).saturating_add( @@ -73382,6 +75127,30 @@ fn provider_context_feasibility( let requested_ctx = resolve_provider_served_ctx(model, args.ctx)?; let workflow_inventory_bytes = provider_comfy_workflow_inventory_resident_bytes(args.home.as_deref(), model)?; + if let Some(host_peak) = artifact + .openai_compatible + .as_ref() + .and_then(|runtime| runtime.calibrated_peak_host_memory_bytes) + { + let available = hardware + .memory + .available_bytes + .unwrap_or(hardware.memory.total_bytes); + let (reserve, _) = provider_memory_reserve_bytes( + args.memory_reserve.as_deref(), + hardware.memory.total_bytes.max(available), + false, + )?; + let usable = available.saturating_sub(reserve); + ensure!( + host_peak <= usable, + "model {} needs {} calibrated peak host memory but only {} is usable after reserve {}", + model.model_id, + human_bytes(host_peak), + human_bytes(usable), + human_bytes(reserve) + ); + } if enclave.model_class != DEFAULT_MODEL_CLASS || requested_ctx == 0 { let memory_budget = provider_memory_budget(hardware, verdict, enclave, args)?; let estimate = provider_memory_estimate( @@ -73572,25 +75341,57 @@ fn provider_vllm_generation_execution_capacity( return Ok(1); }; ensure!( - artifact.engine == "vllm" && profile.engine == artifact.engine, - "generation execution profiles only authorize their exact vLLM artifact" + matches!(artifact.engine.as_str(), "vllm" | "openai-compatible") + && profile.engine == artifact.engine, + "generation execution profiles only authorize their exact supported artifact" ); ensure!( profile.independent_dispatch, "generation execution profile does not authorize independent dispatch" ); - let provider_capacity = args - .max_sessions - .unwrap_or(verdict.max_sessions) - .min(verdict.max_sessions) - .max(1); + let shared_embedding_scheduler = + generation_execution_uses_shared_embedding_scheduler(Some(profile)); + let configured_capacity = args.max_sessions.unwrap_or(verdict.max_sessions).max(1); + // A shared embedding scheduler batches requests inside one loaded model. + // The hardware verdict's max_sessions describes independently resident + // model sessions and must not reduce that scheduler to one. The signed + // max_batch_size and the operator's explicit max_sessions remain hard caps. + let provider_capacity = if shared_embedding_scheduler { + configured_capacity + } else { + configured_capacity.min(verdict.max_sessions).max(1) + }; + if artifact.engine == "openai-compatible" { + ensure!( + profile.topology.is_none(), + "openai-compatible managed runtimes use shared continuous batching" + ); + let signed_capacity = artifact + .openai_compatible + .as_ref() + .context("openai-compatible artifact is missing its signed runtime binding")? + .max_concurrent; + ensure!( + profile.max_concurrent == Some(signed_capacity), + "generation profile capacity does not match signed openai-compatible runtime capacity" + ); + return Ok(provider_capacity.min(signed_capacity).max(1)); + } // A signed max_batch_size is an optional artifact ceiling, not a default // hardware limit. Profiled vLLM artifacts without one scale to the local // provider's proven memory and hwprobe capacity. let scheduler_capacity = enclave_max_batch_size(caps)? .unwrap_or(provider_capacity) .max(1); + if shared_embedding_scheduler { + // Embedding requests are multiplexed inside one loaded vLLM model and + // do not reserve one full decoder KV cache per provider session. The + // signed scheduler ceiling and the operator limit are the relevant + // capacity bounds; applying the text-generation KV formula here would + // collapse a proven batched embedding runtime back to one session. + return Ok(provider_capacity.min(scheduler_capacity).max(1)); + } let utilization = provider_vllm_memory_utilization_for_feasibility( caps, feasibility, @@ -73604,13 +75405,20 @@ fn provider_vllm_generation_execution_capacity( .unwrap_or(u64::MAX) .min(feasibility.memory_budget.budget_bytes); if generation_execution_uses_isolated_workers(Some(profile)) { - ensure!(allocation_bytes > 0, "isolated vLLM worker allocation is empty"); - let memory_capacity = u32::try_from( - feasibility.memory_budget.budget_bytes / allocation_bytes, - ) - .unwrap_or(u32::MAX); - ensure!(memory_capacity > 0, "no isolated vLLM worker fits the admitted memory budget"); - return Ok(provider_capacity.min(scheduler_capacity).min(memory_capacity)); + ensure!( + allocation_bytes > 0, + "isolated vLLM worker allocation is empty" + ); + let memory_capacity = + u32::try_from(feasibility.memory_budget.budget_bytes / allocation_bytes) + .unwrap_or(u32::MAX); + ensure!( + memory_capacity > 0, + "no isolated vLLM worker fits the admitted memory budget" + ); + return Ok(provider_capacity + .min(scheduler_capacity) + .min(memory_capacity)); } let static_bytes = feasibility .estimated_required_bytes @@ -73638,28 +75446,53 @@ fn generation_execution_uses_isolated_workers( }) } +fn generation_execution_uses_shared_embedding_scheduler( + profile: Option<&catalog::CatalogGenerationExecutionProfile>, +) -> bool { + profile.is_some_and(|profile| { + profile.topology != Some(mayhem_proto::GenerationExecutionTopology::IsolatedWorkers) + && !profile.request_modalities.is_empty() + && profile.request_modalities.iter().all(|modalities| { + modalities.len() == 1 && modalities.first().is_some_and(|item| item == "embedding") + }) + }) +} + fn reserve_provider_vllm_replica_memory( feasibility: &mut ProviderCtxFeasibility, capacity: u32, utilization: VllmMemoryUtilizationPlan, ) -> Result<()> { ensure!(capacity > 0, "isolated worker count must be positive"); - ensure!(feasibility.replica_allocation.is_none(), "replica memory was already reserved"); + ensure!( + feasibility.replica_allocation.is_none(), + "replica memory was already reserved" + ); let count = u64::from(capacity); let per_worker_allocation_bytes = u64::try_from( - u128::from(feasibility.memory_budget.total_bytes) * u128::from(utilization.target_pct) / 100, + u128::from(feasibility.memory_budget.total_bytes) * u128::from(utilization.target_pct) + / 100, ) .context("isolated worker allocation exceeds u64")?; let required = feasibility.estimated_required_bytes; - ensure!(per_worker_allocation_bytes >= required, - "isolated worker allocation is below its full model/context requirement"); - let aggregate = per_worker_allocation_bytes.checked_mul(count) + ensure!( + per_worker_allocation_bytes >= required, + "isolated worker allocation is below its full model/context requirement" + ); + let aggregate = per_worker_allocation_bytes + .checked_mul(count) .context("aggregate isolated worker allocation overflow")?; - ensure!(aggregate <= feasibility.memory_budget.budget_bytes, + ensure!( + aggregate <= feasibility.memory_budget.budget_bytes, "isolated workers require {}, exceeding the admitted budget {}", - human_bytes(aggregate), human_bytes(feasibility.memory_budget.budget_bytes)); - let scale = |bytes: u64| bytes.checked_mul(count) - .context("aggregate isolated worker estimate overflow"); + human_bytes(aggregate), + human_bytes(feasibility.memory_budget.budget_bytes) + ); + let scale = |bytes: u64| { + bytes + .checked_mul(count) + .context("aggregate isolated worker estimate overflow") + }; let weights = scale(feasibility.estimated_weights_bytes)?; let kv = scale(feasibility.estimated_kv_bytes)?; let overhead = scale(feasibility.estimated_overhead_bytes)?; @@ -73761,10 +75594,16 @@ fn provider_vllm_memory_utilization( local_target_pct: Option, ) -> Result { if let Some(allocation) = selected.feasibility.replica_allocation { - ensure!(generation_execution_uses_isolated_workers(selected.generation_execution_profile.as_ref()), - "replica allocation requires an isolated execution profile"); - ensure!(allocation.worker_count == selected.generation_execution_capacity, - "replica allocation count does not match admitted execution capacity"); + ensure!( + generation_execution_uses_isolated_workers( + selected.generation_execution_profile.as_ref() + ), + "replica allocation requires an isolated execution profile" + ); + ensure!( + allocation.worker_count == selected.generation_execution_capacity, + "replica allocation count does not match admitted execution capacity" + ); let target_pct = local_target_pct.unwrap_or(allocation.utilization.target_pct); validate_provider_vllm_memory_utilization_pct(target_pct)?; ensure!(target_pct >= allocation.utilization.floor_pct @@ -73895,9 +75734,12 @@ fn provider_vllm_memory_utilization_for_candidates( let reserved_bytes = u64::try_from((u128::from(budget.total_bytes) * u128::from(target_pct)) / 100) .unwrap_or(u64::MAX); - let worker_count = candidate.feasibility.replica_allocation + let worker_count = candidate + .feasibility + .replica_allocation .map_or(1, |allocation| u64::from(allocation.worker_count)); - let reserved_bytes = reserved_bytes.checked_mul(worker_count) + let reserved_bytes = reserved_bytes + .checked_mul(worker_count) .context("aggregate vLLM allocation overflow")?; let allocation = pool_allocations .entry(budget.pool.clone()) @@ -75076,7 +76918,8 @@ fn build_provider_candidates( } let execution_policy = if let Some(mode_id) = &args.execution_mode { mayhem_proto::validate_execution_mode_id(mode_id).map_err(anyhow::Error::msg)?; - let Some(policy) = catalog_doc.vllm_execution_mode(&artifact.artifact_root, mode_id) else { + let Some(policy) = catalog_doc.vllm_execution_mode(&artifact.artifact_root, mode_id) + else { rejections.push(provider_rejection( enclave, format!("signed catalog has no execution mode {mode_id} for artifact {artifact_name}"), @@ -75091,15 +76934,22 @@ fn build_provider_candidates( let generation_execution_profile = match execution_policy { Some(policy) => policy.generation_execution_profile.as_ref(), None => catalog_doc.generation_execution_profile(&artifact.artifact_root), - }.cloned(); + } + .cloned(); let execution_mode = execution_policy - .map(|policy| -> Result<_> { Ok(ProviderExecutionMode { - binding: policy.binding(&artifact.artifact_root, args.execution_mode.as_deref().unwrap())?, - requests: policy.requests.clone(), - baseline_adapter: model.adapter.clone(), - }) }) + .map(|policy| -> Result<_> { + Ok(ProviderExecutionMode { + binding: policy.binding( + &artifact.artifact_root, + args.execution_mode.as_deref().unwrap(), + )?, + requests: policy.requests.clone(), + baseline_adapter: model.adapter.clone(), + }) + }) .transpose()?; - let vllm_execution_profile = execution_policy.map(|policy| &policy.profile) + let vllm_execution_profile = execution_policy + .map(|policy| &policy.profile) .or_else(|| catalog_doc.vllm_execution_profile(&artifact.artifact_root)) .cloned(); let served_modalities = match provider_served_modalities(model, &args.disable_modalities) { @@ -75190,14 +77040,31 @@ fn build_provider_candidates( continue; } }; - let reserve_result = if generation_execution_uses_isolated_workers(generation_execution_profile.as_ref()) { + let reserve_result = if generation_execution_uses_shared_embedding_scheduler( + generation_execution_profile.as_ref(), + ) { + // One shared vLLM embedding scheduler owns the admitted allocation; + // concurrent requests do not each require a decoder KV reservation. + Ok(()) + } else if generation_execution_uses_isolated_workers(generation_execution_profile.as_ref()) + { provider_vllm_memory_utilization_for_feasibility( - &enclave.caps, &feasibility, args.vllm_memory_utilization, - ).and_then(|utilization| reserve_provider_vllm_replica_memory( - &mut feasibility, generation_execution_capacity, utilization, - )) + &enclave.caps, + &feasibility, + args.vllm_memory_utilization, + ) + .and_then(|utilization| { + reserve_provider_vllm_replica_memory( + &mut feasibility, + generation_execution_capacity, + utilization, + ) + }) } else { - reserve_provider_generation_execution_memory(&mut feasibility, generation_execution_capacity) + reserve_provider_generation_execution_memory( + &mut feasibility, + generation_execution_capacity, + ) }; if let Err(err) = reserve_result { rejections.push(provider_rejection( @@ -76081,7 +77948,7 @@ fn provider_candidate_modalities(candidate: &ProviderCandidate) -> Vec { fn backend_rank(backend: &str) -> u8 { match backend { - "vllm" => 4, + "vllm" | "openai-compatible" => 4, "trt-llm" => 3, "mlx" => 2, "comfyui" => 2, @@ -76091,6 +77958,7 @@ fn backend_rank(backend: &str) -> u8 { "needle-gpu" => 2, "sulphur" => 2, "transformers-asr" => 2, + "laya" => 2, "llama.cpp" => 1, "stable-diffusion.cpp" | "whisper.cpp" | "piper" => 0, _ => 0, @@ -76188,6 +78056,9 @@ fn download_provider_artifact_blocking( downloads_dir: &Path, selected: &ProviderCandidate, ) -> Result { + if selected.artifact.engine == "openai-compatible" { + return download_openai_compatible_snapshot(args, downloads_dir, selected); + } if selected.artifact.engine == "comfyui" { let path = args.artifact.as_ref().with_context(|| { format!( @@ -76293,6 +78164,318 @@ fn download_provider_artifact_blocking( Ok(ProviderArtifactPaths { primary, sidecars }) } +fn download_openai_compatible_snapshot( + args: &ProviderStartArgs, + downloads_dir: &Path, + selected: &ProviderCandidate, +) -> Result { + let binding = selected + .artifact + .openai_compatible + .as_ref() + .context("openai-compatible artifact is missing its signed runtime binding")?; + fs::create_dir_all(downloads_dir) + .with_context(|| format!("creating {}", downloads_dir.display()))?; + + let mut sidecars = BTreeMap::new(); + for (name, sidecar) in &selected.artifact.sidecars { + let ledger_sidecar = selected + .enclave + .artifact_sidecars + .get(name) + .with_context(|| format!("admin enclave is missing catalog sidecar {name}"))?; + let path = download_provider_sidecar_artifact( + args, + downloads_dir, + selected, + name, + sidecar, + ledger_sidecar, + )?; + sidecars.insert(name.clone(), path); + } + let manifest_path = sidecars + .get(&binding.snapshot_manifest_sidecar) + .context("downloaded openai-compatible snapshot manifest is missing")?; + let manifest: OpenAiCompatibleSnapshotManifest = serde_json::from_slice( + &fs::read(manifest_path).with_context(|| format!("reading {}", manifest_path.display()))?, + ) + .with_context(|| format!("parsing {}", manifest_path.display()))?; + validate_openai_compatible_snapshot_manifest(selected, &manifest)?; + + let snapshot_dir = if let Some(path) = args.artifact.as_ref() { + let path = absolutize(path.clone())?; + require_local_artifact_path_supported(&path, selected)?; + ensure!( + path.is_dir(), + "openai-compatible local artifact must be the complete signed snapshot directory" + ); + path + } else { + let directory = downloads_dir.join(format!( + "snapshot-{}-{}", + safe_path_component(&selected.enclave.artifact_root), + safe_path_component(&selected.artifact_name) + )); + let _download_lock = lock_openai_compatible_snapshot_download(&directory)?; + if directory.is_dir() + && validate_openai_compatible_snapshot_directory(&directory, &manifest, args.chunk_size) + .is_ok() + && build_artifact_merkle_manifest(&directory, args.chunk_size)?.root + == selected.enclave.artifact_root + { + return Ok(ProviderArtifactPaths { + primary: directory, + sidecars, + }); + } + ensure!( + !directory.exists(), + "canonical snapshot cache {} exists but failed signed validation; remove it before retrying", + directory.display() + ); + let staging = downloads_dir.join(format!( + ".{}.partial", + directory + .file_name() + .and_then(|name| name.to_str()) + .unwrap_or("openai-compatible-snapshot") + )); + fs::create_dir_all(&staging).with_context(|| { + format!("creating snapshot staging directory {}", staging.display()) + })?; + let missing_bytes = manifest.files.iter().try_fold(0u64, |total, file| { + let path = staging.join(validate_catalog_artifact_relative_path(&file.path)?); + let reusable = path.is_file() + && fs::metadata(&path)?.len() == file.size + && file_sha256_hex(&path)? == file.sha256; + Ok::<_, anyhow::Error>(if reusable { + total + } else { + total + .checked_add(file.size) + .context("snapshot missing-byte total overflowed u64")? + }) + })?; + if missing_bytes > 0 { + provider_download_disk_preflight( + args, + &staging.join(".capacity-check"), + missing_bytes, + &format!("{} complete model snapshot", selected.artifact_name), + )?; + } + for file in &manifest.files { + let relative = validate_catalog_artifact_relative_path(&file.path)?; + let destination = staging.join(&relative); + if destination.is_file() + && fs::metadata(&destination)?.len() == file.size + && file_sha256_hex(&destination)? == file.sha256 + { + continue; + } + if let Some(parent) = destination.parent() { + fs::create_dir_all(parent) + .with_context(|| format!("creating {}", parent.display()))?; + } + download_provider_huggingface_file( + args, + &selected.artifact.source, + &file.path, + destination, + file.size, + None, + Some(&file.sha256), + &format!("{}/{} snapshot file", selected.artifact_name, file.path), + )?; + } + validate_openai_compatible_snapshot_directory(&staging, &manifest, args.chunk_size)?; + let digest = build_artifact_merkle_manifest(&staging, args.chunk_size)?; + ensure!( + digest.root == selected.enclave.artifact_root + && digest.root == selected.artifact.artifact_root, + "openai-compatible staged snapshot root mismatch; expected {}, got {}", + selected.enclave.artifact_root, + digest.root + ); + fs::rename(&staging, &directory).with_context(|| { + format!( + "atomically installing verified snapshot {} at {}", + staging.display(), + directory.display() + ) + })?; + directory + }; + + validate_openai_compatible_snapshot_directory(&snapshot_dir, &manifest, args.chunk_size)?; + let digest = build_artifact_merkle_manifest(&snapshot_dir, args.chunk_size)?; + ensure!( + digest.root == selected.enclave.artifact_root + && digest.root == selected.artifact.artifact_root, + "openai-compatible snapshot directory root mismatch; expected {}, got {}", + selected.enclave.artifact_root, + digest.root + ); + Ok(ProviderArtifactPaths { + primary: snapshot_dir, + sidecars, + }) +} + +fn lock_openai_compatible_snapshot_download(directory: &Path) -> Result { + let file_name = directory + .file_name() + .and_then(OsStr::to_str) + .context("openai-compatible snapshot cache has no UTF-8 file name")?; + let lock_path = directory.with_file_name(format!(".{file_name}.download.lock")); + let mut options = fs::OpenOptions::new(); + options.create(true).read(true).write(true); + #[cfg(unix)] + { + use std::os::unix::fs::OpenOptionsExt as _; + options.mode(0o600); + } + let lock = options + .open(&lock_path) + .with_context(|| format!("opening snapshot download lock {}", lock_path.display()))?; + fs2::FileExt::lock_exclusive(&lock) + .with_context(|| format!("locking snapshot download {}", lock_path.display()))?; + Ok(lock) +} + +fn validate_openai_compatible_snapshot_manifest( + selected: &ProviderCandidate, + manifest: &OpenAiCompatibleSnapshotManifest, +) -> Result<()> { + let binding = selected + .artifact + .openai_compatible + .as_ref() + .context("openai-compatible artifact is missing its signed runtime binding")?; + validate_openai_compatible_snapshot_manifest_for_artifact(&selected.artifact, binding, manifest) +} + +fn validate_openai_compatible_snapshot_manifest_for_artifact( + artifact: &catalog::CatalogArtifact, + binding: &mayhem_engine::OpenAiCompatibleRuntimeBinding, + manifest: &OpenAiCompatibleSnapshotManifest, +) -> Result<()> { + ensure!(manifest.schema == 1, "snapshot manifest schema must be 1"); + ensure!( + manifest.source == "modelscope", + "snapshot manifest source must preserve the calibrated modelscope identity" + ); + let upstream = artifact + .upstream_source + .as_ref() + .unwrap_or(&artifact.source); + ensure!( + manifest.repo == upstream.repo && manifest.canonical_hf_revision == upstream.revision, + "snapshot manifest upstream identity does not match the signed catalog source" + ); + ensure!( + !manifest.source_revision.trim().is_empty() + && manifest.source_revision.len() <= 128 + && !manifest.source_revision.chars().any(char::is_control), + "snapshot manifest source_revision is invalid" + ); + ensure!( + manifest.files.len() == binding.model_snapshot_file_count as usize, + "snapshot manifest contains {} files; signed runtime binding requires {}", + manifest.files.len(), + binding.model_snapshot_file_count + ); + let mut prior = None::<&str>; + let mut total = 0u64; + for file in &manifest.files { + let relative = validate_catalog_artifact_relative_path(&file.path)?; + ensure!( + relative.to_string_lossy() == file.path, + "snapshot manifest path is not normalized: {}", + file.path + ); + if let Some(prior) = prior { + ensure!( + prior < file.path.as_str(), + "snapshot manifest paths must be strictly sorted and unique" + ); + } + prior = Some(&file.path); + ensure!(file.size > 0, "snapshot file {} has zero size", file.path); + ensure!( + is_lowercase_hex_len(&file.sha256, 64), + "snapshot file {} has invalid sha256", + file.path + ); + match file.hf_oid_kind.as_str() { + "git_blob_sha1" => ensure!( + is_lowercase_hex_len(&file.hf_oid, 40), + "snapshot file {} has invalid git blob oid", + file.path + ), + "sha256" => ensure!( + is_lowercase_hex_len(&file.hf_oid, 64), + "snapshot file {} has invalid LFS oid", + file.path + ), + other => bail!( + "snapshot file {} has unsupported hf_oid_kind {other}", + file.path + ), + } + total = total + .checked_add(file.size) + .context("snapshot manifest byte total overflowed u64")?; + } + ensure!( + total == manifest.total_bytes && total == artifact.weights_bytes, + "snapshot manifest total_bytes {total} does not match its header/catalog" + ); + Ok(()) +} + +fn validate_openai_compatible_snapshot_directory( + root: &Path, + manifest: &OpenAiCompatibleSnapshotManifest, + _chunk_size: usize, +) -> Result<()> { + let expected = manifest + .files + .iter() + .map(|file| file.path.as_str()) + .collect::>(); + let mut entries = Vec::new(); + collect_directory_artifact_entries(root, root, &mut entries)?; + let actual = entries + .iter() + .filter_map(|entry| match entry.kind { + DirectoryArtifactEntryKind::File { .. } => Some(entry.rel.as_str()), + DirectoryArtifactEntryKind::Directory => None, + }) + .collect::>(); + ensure!( + actual == expected, + "openai-compatible snapshot directory must contain exactly the signed manifest files" + ); + for file in &manifest.files { + let path = root.join(validate_catalog_artifact_relative_path(&file.path)?); + let metadata = fs::metadata(&path) + .with_context(|| format!("stat snapshot file {}", path.display()))?; + ensure!( + metadata.is_file() && metadata.len() == file.size, + "snapshot file {} size/type mismatch", + file.path + ); + ensure!( + file_sha256_hex(&path)? == file.sha256, + "snapshot file {} sha256 mismatch", + file.path + ); + } + Ok(()) +} + fn provider_seal_artifact_path<'a>( artifact_paths: &'a ProviderArtifactPaths, selected: &ProviderCandidate, @@ -76436,9 +78619,14 @@ fn require_local_artifact_path_supported( source: &Path, selected: &ProviderCandidate, ) -> Result<()> { - if source.is_dir() && selected.artifact.engine != "comfyui" { + if source.is_dir() + && !matches!( + selected.artifact.engine.as_str(), + "comfyui" | "openai-compatible" + ) + { bail!( - "local directory artifacts are only supported for ComfyUI runtimes; {} uses engine {}", + "local directory artifacts are only supported for directory-backed engines; {} uses engine {}", selected.model.model_id, selected.artifact.engine ); @@ -76469,7 +78657,7 @@ fn download_provider_primary_artifact( &selected.artifact.path, destination, selected.artifact.weights_bytes, - &selected.enclave.artifact_root, + Some(&selected.enclave.artifact_root), selected.artifact.source_sha256.as_deref(), &format!("{} artifact", selected.artifact_name), ) @@ -76538,7 +78726,7 @@ fn download_provider_sidecar_artifact( &sidecar.path, destination, ledger_sidecar.weights_bytes, - &ledger_sidecar.artifact_root, + Some(&ledger_sidecar.artifact_root), Some(&sidecar.source_sha256), &format!("{}/{} sidecar", selected.artifact_name, name), ) @@ -76551,7 +78739,7 @@ fn download_provider_huggingface_file( remote_path: &str, destination: PathBuf, expected_bytes: u64, - expected_root: &str, + expected_root: Option<&str>, expected_sha256: Option<&str>, label: &str, ) -> Result { @@ -76686,11 +78874,13 @@ fn download_provider_huggingface_file( downloaded.display() ) })?; - if manifest.root != expected_root { - bail!( - "downloaded {label} root mismatch; expected admin artifact root {expected_root}, got {}", - manifest.root - ); + if let Some(expected_root) = expected_root { + if manifest.root != expected_root { + bail!( + "downloaded {label} root mismatch; expected admin artifact root {expected_root}, got {}", + manifest.root + ); + } } if let Some(expected_sha256) = expected_sha256 { let actual_sha256 = file_sha256_hex(&downloaded)?; @@ -78580,6 +80770,19 @@ fn provider_session_output_error(message: impl Into) -> anyhow::Error { anyhow::Error::new(ProviderSessionOutputError(message.into())) } +fn provider_session_output_result(result: Result) -> Result { + result.map_err(|error| provider_session_output_error(format!("{error:#}"))) +} + +fn validate_streamed_tool_call_count(emitted: usize, validated: usize) -> Result<()> { + if emitted > validated { + return Err(provider_session_output_error( + "provider streamed a tool call that failed final validation", + )); + } + Ok(()) +} + fn provider_response_error_code(error: &anyhow::Error) -> &'static str { if error.chain().any(|cause| { cause.is::() @@ -78653,6 +80856,97 @@ fn provider_response_error_message(error: &anyhow::Error) -> String { .unwrap_or_else(|| error.to_string()) } +// Opt-in, bounded diagnostics for provider output-contract failures. The full +// error may contain tool arguments or model output, so never write it here. +const PROVIDER_OUTPUT_DIAGNOSTIC_LIMIT: u64 = 64; +static PROVIDER_OUTPUT_DIAGNOSTIC_EMITTED: AtomicU64 = AtomicU64::new(0); + +fn provider_output_diagnostic_reason(error: &anyhow::Error) -> &'static str { + let message = error.chain().find_map(|cause| { + cause + .downcast_ref::() + .map(|error| error.0.as_str()) + }); + match message { + Some(message) if message.contains("unadvertised tool call") => "unadvertised_tool", + Some(message) if message.contains("malformed arguments for tool") => { + "malformed_tool_arguments" + } + Some(message) if message.contains("non-object arguments for tool") => { + "non_object_tool_arguments" + } + Some(message) if message.contains("arguments that do not satisfy the schema") => { + "tool_schema_mismatch" + } + Some(message) if message.contains("tool call without a name") => "tool_name_missing", + Some(message) if message.contains("parallel_tool_calls=false") => { + "parallel_tool_calls_disabled" + } + Some(message) if message.contains("did not return a valid required tool call") => { + "required_tool_missing" + } + Some(message) if message.contains("streamed a tool call that failed final validation") => { + "streamed_tool_validation_mismatch" + } + Some(message) if message.contains("stopped without a visible answer") => { + "empty_visible_answer" + } + Some(message) if message.contains("selected session token budget") => { + "output_token_budget_exceeded" + } + Some(message) if message.contains("selected session byte-derived unit budget") => { + "output_unit_budget_exceeded" + } + Some(_) => "other_output_contract_failure", + None if error.chain().any(|cause| { + matches!( + cause.downcast_ref::(), + Some(EngineError::InvalidOutput(_)) + ) + }) => + { + "engine_invalid_output" + } + None => "other_model_output_invalid", + } +} + +fn take_provider_output_diagnostic_slot(counter: &AtomicU64) -> bool { + counter + .fetch_update(Ordering::Relaxed, Ordering::Relaxed, |count| { + (count < PROVIDER_OUTPUT_DIAGNOSTIC_LIMIT).then_some(count + 1) + }) + .is_ok() +} + +fn log_provider_output_diagnostic(error: &anyhow::Error, request_id: &str) { + // An optional file switch lets operators turn diagnostics off immediately + // by removing the file, without restarting an active provider session. + let enabled = env::var_os("MAYHEM_PROVIDER_OUTPUT_DIAGNOSTICS").as_deref() + == Some(OsStr::new("1")) + || env::var_os("MAYHEM_PROVIDER_OUTPUT_DIAGNOSTICS_FILE") + .is_some_and(|path| Path::new(&path).is_file()); + if !enabled || !take_provider_output_diagnostic_slot(&PROVIDER_OUTPUT_DIAGNOSTIC_EMITTED) { + return; + } + let safe_request_id = if request_id.len() <= 96 + && request_id + .bytes() + .all(|byte| byte.is_ascii_alphanumeric() || byte == b'-' || byte == b'_') + { + request_id.to_owned() + } else { + format!( + "hash:{}", + &blake3::hash(request_id.as_bytes()).to_hex()[..16] + ) + }; + eprintln!( + "[provider-output] code=model_output_invalid reason={} request_id={safe_request_id}", + provider_output_diagnostic_reason(error), + ); +} + async fn run_provider_session_heartbeats(ctx: ProviderSessionHeartbeatTask) -> Result<()> { let mut retry_delay = ctx.heartbeat_reconnect_initial; while !ctx.load.is_stopped() { @@ -78702,7 +80996,7 @@ async fn run_provider_session_heartbeat_connection( let mut heartbeat_cache_updated_at = None::; let mut load_changes = ctx.load.subscribe_changes(); while !ctx.load.is_stopped() { - load_changes.borrow_and_update(); + let load_generation = *load_changes.borrow_and_update(); let round_started = Instant::now(); let sent = timeout( ctx.bridge_operation_timeout, @@ -78729,6 +81023,7 @@ async fn run_provider_session_heartbeat_connection( ) .await .with_context(|| format!("timed out sending live provider heartbeat seq {seq}"))??; + ctx.load.mark_published(load_generation); let refresh_cache = heartbeat_cache_updated_at .map(|updated_at| updated_at.elapsed() >= ctx.heartbeat_ttl / 2) .unwrap_or(true); @@ -78875,7 +81170,7 @@ struct ProviderConcurrentSessionTask { protection: Arc>, engine_recovery_initial: Duration, engine_recovery_max: Duration, - backend: Arc, + backend: ProviderConcurrentEngine, cancellation: CancellationToken, } @@ -78944,8 +81239,11 @@ async fn run_provider_concurrent_session_task( let mut engine_recovery = ProviderEngineRecovery::new(task.engine_recovery_initial, task.engine_recovery_max); let runtime = task.runtime.borrowed(); - let result = match ProviderTpmActivationLimiter::from_environment() { - Ok(mut tpm_activation_limiter) => { + let result = match ( + ProviderTpmActivationLimiter::from_environment(), + ProviderTokenizeLimiter::from_environment(), + ) { + (Ok(mut tpm_activation_limiter), Ok(mut tokenize_limiter)) => { handle_provider_session_frame( &mut bridge, &mut sessions, @@ -78955,6 +81253,7 @@ async fn run_provider_concurrent_session_task( &task.heartbeat_load, &task.protection, &mut tpm_activation_limiter, + &mut tokenize_limiter, &task.terms, &runtime, &task.sc_bridge_url, @@ -78972,13 +81271,16 @@ async fn run_provider_concurrent_session_task( ) .await } - Err(error) => Err(error), + (Err(error), _) | (_, Err(error)) => Err(error), }; (result, engine_recovery.reason().map(str::to_owned)) } .await; if let Err(error) = &result { let error_code = provider_response_error_code(error); + if error_code == "model_output_invalid" { + log_provider_output_diagnostic(error, &request_id); + } let error_message = provider_response_error_message(error); let _ = send_provider_session_error( &mut bridge, @@ -79023,10 +81325,47 @@ fn provider_session_allows_independent_dispatch( terms: &ProviderSessionTerms, active: &ActiveProviderSession, ) -> bool { - generation_execution_profile_allows_modalities( + provider_request_modalities_allow_independent_dispatch(terms, &active.required_modalities) +} + +fn provider_request_modalities_allow_independent_dispatch( + terms: &ProviderSessionTerms, + requested: &[String], +) -> bool { + if generation_execution_profile_allows_modalities( terms.generation_execution_profile.as_ref(), - &active.required_modalities, - ) + requested, + ) { + return true; + } + let requested = requested.iter().cloned().collect::>(); + terms + .runtime_independent_dispatch_modalities + .iter() + .any(|allowed| allowed.iter().cloned().collect::>() == requested) +} + +fn provider_runtime_independent_dispatch_modalities( + responder: &dyn ProviderSessionResponder, +) -> Vec> { + if responder.concurrent_embedding_backend().is_some() { + vec![vec!["embedding".to_owned()]] + } else { + Vec::new() + } +} + +fn provider_execution_capacity_for_terms( + terms: &ProviderSessionTerms, + responder: &dyn ProviderSessionResponder, +) -> u32 { + if terms.generation_execution_profile.is_some() + || !terms.runtime_independent_dispatch_modalities.is_empty() + { + responder.concurrent_session_capacity() + } else { + 1 + } } fn provider_session_open_required_modalities(frame: &Value) -> Option> { @@ -79046,10 +81385,8 @@ fn provider_local_session_acceptance_decision( reason: "signed spend voucher is missing normalized required_modalities".to_owned(), }; }; - let requested_is_independent = generation_execution_profile_allows_modalities( - terms.generation_execution_profile.as_ref(), - &requested_modalities, - ); + let requested_is_independent = + provider_request_modalities_allow_independent_dispatch(terms, &requested_modalities); let active_are_independent = sessions .values() .all(|active| provider_session_allows_independent_dispatch(terms, active)); @@ -79101,14 +81438,13 @@ async fn serve_provider_sessions( runtime: ProviderSessionRuntime<'_>, mut responder: Box, ) -> Result<()> { - let terms = provider_session_terms(&ctx, runtime.min_ask.current())?; + let mut terms = provider_session_terms(&ctx, runtime.min_ask.current())?; let configured_protection = ProviderProtectionConfig::from_provider_args(ctx.args, ctx.selected)?; - let execution_capacity = if terms.generation_execution_profile.is_some() { - responder.concurrent_session_capacity() - } else { - 1 - }; + terms.runtime_independent_dispatch_modalities = + provider_runtime_independent_dispatch_modalities(responder.as_ref()); + let execution_capacity = provider_execution_capacity_for_terms(&terms, responder.as_ref()); + terms.capacity_slots = execution_capacity.max(1); let protection_config = configured_protection.limit_to_execution_capacity(execution_capacity); let (sc_bridge_url, sc_bridge_token) = resolve_cli_sc_bridge( ctx.args.home.as_ref(), @@ -79200,7 +81536,9 @@ async fn serve_provider_sessions( let heartbeat_enabled = !ctx.args.no_heartbeat && !ctx.rooms.is_empty(); let heartbeat_load = ProviderHeartbeatLoad::new(&ctx.selected.modality_capacities); - heartbeat_load.prefix_caching.store(responder.prefix_caching_enabled(), Ordering::Release); + heartbeat_load + .prefix_caching + .store(responder.prefix_caching_enabled(), Ordering::Release); let runtime_floor_monitor = ProviderRuntimeFloorMonitor::new(ctx.args, ctx.home, ctx.selected)?; let engine_watchdog_restart_after_millis = configured_nonnegative_millis( "MAYHEM_PROVIDER_ENGINE_WATCHDOG_RESTART_AFTER_MS", @@ -79259,9 +81597,12 @@ async fn serve_provider_sessions( let owned_runtime = Arc::new(OwnedProviderSessionRuntime::from_borrowed(&runtime)); let protection = Arc::new(Mutex::new(ProviderProtectionState::new(protection_config))); let mut tpm_activation_limiter = ProviderTpmActivationLimiter::from_environment()?; + let mut tokenize_limiter = ProviderTokenizeLimiter::from_environment()?; let mut draining = false; + let mut drain_heartbeat_generation = None::; let mut local_drain_request = None::; let mut runtime_floor_reject: Option = None; + let mut last_idle_memory_reclaim_at = None::; let mut engine_watchdog_reject: Option = None; let mut engine_recovery = ProviderEngineRecovery::new( provider_heartbeat_reconnect_initial, @@ -79367,6 +81708,14 @@ async fn serve_provider_sessions( heartbeat_restart_at = None; } } + if draining + && sessions.is_empty() + && (!heartbeat_enabled + || drain_heartbeat_generation + .is_some_and(|generation| heartbeat_load.published_through(generation))) + { + break; + } if !engine_recovery.pending() && !responder.component_healthy() { heartbeat_load.set_accepting_new(false); engine_recovery.mark_failed( @@ -79387,7 +81736,15 @@ async fn serve_provider_sessions( ); } Ok(ComponentRecovery::Pending) => component_recovery_pending = true, + Ok(ComponentRecovery::Unsupported) if responder.requires_owner_restart() => { + bail!("managed provider runtime failed health verification; retiring this worker so its supervisor can reconcile and restart the owned runtime") + } Ok(ComponentRecovery::Unsupported) => {} + Err(err) if responder.requires_owner_restart() => { + return Err(err).context( + "managed provider runtime recovery failed; retiring this worker for supervised restart", + ) + } Err(err) => engine_recovery.mark_reload_failed( format!("isolated provider worker recovery failed: {err:#}"), Instant::now(), @@ -79456,6 +81813,34 @@ async fn serve_provider_sessions( } runtime_floor_reject = next_runtime_floor_reject; } + if runtime_floor_monitor.memory_check().is_some() + && !engine_recovery.pending() + && sessions.is_empty() + && last_idle_memory_reclaim_at.is_none_or(|last| { + Instant::now().saturating_duration_since(last) + >= Duration::from_millis( + DEFAULT_PROVIDER_IDLE_MEMORY_RECLAIM_COOLDOWN_MILLIS, + ) + }) + { + match responder.reclaim_idle_memory() { + Ok(true) => { + last_idle_memory_reclaim_at = Some(Instant::now()); + provider_session_debug( + "provider requested idle engine memory reclamation after the runtime floor activated", + ); + } + Ok(false) => { + last_idle_memory_reclaim_at = Some(Instant::now()); + } + Err(err) => { + last_idle_memory_reclaim_at = Some(Instant::now()); + provider_session_debug(format!( + "idle provider memory reclamation failed; continuing to refuse new sessions safely: {err:#}" + )); + } + } + } let watchdog_action = if draining || engine_recovery.pending() { ProviderEngineWatchdogAction::Healthy } else { @@ -79493,6 +81878,9 @@ async fn serve_provider_sessions( heartbeat_load.set_accepting_new(false); let restart_reason = reject.reason.clone(); engine_watchdog_reject = Some(reject); + if responder.requires_owner_restart() { + bail!("managed provider runtime requires supervised restart after watchdog pressure: {restart_reason}"); + } match reload_provider_session_responder( &mut responder, restart_reason, @@ -79534,11 +81922,10 @@ async fn serve_provider_sessions( request.path.display() )); heartbeat_load.set_accepting_new(false); + drain_heartbeat_generation = heartbeat_enabled + .then(|| heartbeat_load.change_generation()); draining = true; local_drain_request = Some(request); - if sessions.is_empty() { - break; - } } Ok(None) => {} Err(err) => provider_session_debug(format!( @@ -79552,9 +81939,16 @@ async fn serve_provider_sessions( "serve window elapsed; entering graceful drain and refusing new sessions", ); heartbeat_load.set_accepting_new(false); + drain_heartbeat_generation = heartbeat_enabled + .then(|| heartbeat_load.change_generation()); draining = true; } - if sessions.is_empty() { + if sessions.is_empty() + && (!heartbeat_enabled + || drain_heartbeat_generation.is_some_and(|generation| { + heartbeat_load.published_through(generation) + })) + { break; } } @@ -79650,8 +82044,16 @@ async fn serve_provider_sessions( provider_session_allows_independent_dispatch(&terms, active) }) { + let concurrent_backend = responder + .concurrent_generation_backend() + .map(ProviderConcurrentEngine::Generation) + .or_else(|| { + responder + .concurrent_embedding_backend() + .map(ProviderConcurrentEngine::Embedding) + }); if let (Some(backend), Some(active), Some(pending_request_deadline)) = ( - responder.concurrent_generation_backend(), + concurrent_backend, sessions.get(&event_session_id).cloned(), pending_requests.get(&event_session_id).copied(), ) { @@ -79737,6 +82139,7 @@ async fn serve_provider_sessions( &heartbeat_load, &protection, &mut tpm_activation_limiter, + &mut tokenize_limiter, &terms, &runtime, &sc_bridge_url, @@ -79789,7 +82192,13 @@ async fn serve_provider_sessions( &terms, ) .await; - if draining && sessions.is_empty() { + if draining + && sessions.is_empty() + && (!heartbeat_enabled + || drain_heartbeat_generation.is_some_and(|generation| { + heartbeat_load.published_through(generation) + })) + { break; } } @@ -80450,7 +82859,7 @@ fn provider_session_request_modalities( modality_load.contains_key("image"), ), "workflow_generation" => modality_load.keys().cloned().collect::>(), - "chat" => vec!["text".to_owned()], + "chat" | "decision" => vec!["text".to_owned()], other => bail!("unsupported provider request kind {other}"), }; if provider_endpoint_transport_kind(family)? == "chat" { @@ -80994,9 +83403,13 @@ fn validate_provider_session_request_modalities( ) -> Result> { let verified = provider_verify_endpoint_request(body, Some(&terms.model_id), &terms.adapter)?; if let Some(policy) = &terms.execution_mode_requests { - policy.validate_request(verified.family, verified.request).map_err(|_| { - provider_session_request_error("request is not supported by the negotiated execution mode") - })?; + policy + .validate_request(verified.family, verified.request) + .map_err(|_| { + provider_session_request_error( + "request is not supported by the negotiated execution mode", + ) + })?; } provider_session_request_result(validate_provider_session_modalities( &active.required_modalities, @@ -81258,6 +83671,7 @@ fn provider_session_modality_load( } Ok(load) } + "decision" => Ok(BTreeMap::new()), "embedding" => { let inputs = provider_session_request_result(provider_embedding_input_texts_from_body(body))?; @@ -81287,8 +83701,12 @@ fn provider_session_modality_load( provider_image_generation_request_from_body(family, body), )?; let reference = provider_session_request_result( - request.input_reference.as_deref().map(mayhem_proto::image_reference_metadata) - .transpose().map_err(anyhow::Error::msg), + request + .input_reference + .as_deref() + .map(mayhem_proto::image_reference_metadata) + .transpose() + .map_err(anyhow::Error::msg), )?; Ok(BTreeMap::from([( "image".to_owned(), @@ -81654,6 +84072,7 @@ async fn handle_provider_session_frame( heartbeat_load: &ProviderHeartbeatLoad, protection: &Arc>, tpm_activation_limiter: &mut ProviderTpmActivationLimiter, + tokenize_limiter: &mut ProviderTokenizeLimiter, terms: &ProviderSessionTerms, runtime: &ProviderSessionRuntime<'_>, sc_bridge_url: &str, @@ -81708,6 +84127,158 @@ where return Ok(()); } match frame_type { + TOKENIZE_REQUEST_CHUNK_FRAME_TYPE => { + ensure!( + frame.get("v").and_then(Value::as_u64) == Some(u64::from(TOKENIZE_FRAME_VERSION)), + "tokenize request chunk version is unsupported" + ); + ensure!( + frame.get("session_id").and_then(Value::as_str) == Some(session_id.as_str()), + "tokenize request chunk session binding mismatch" + ); + let first_chunk = !pending_payloads + .keys() + .any(|key| key.starts_with(&format!("{session_id}:"))); + if first_chunk { + tokenize_limiter.admit(&remote, Instant::now())?; + } + if let Err(error) = provider_session_collect_request_chunk( + pending_payloads, + &session_id, + &frame, + max_request_bytes, + max_payload_chunks, + ) { + remove_provider_session_pending_payloads(pending_payloads, &session_id); + let _ = bridge.session_close(&remote, &session_id).await; + return Err(error).context("collecting tokenize request chunk"); + } + } + TOKENIZE_REQUEST_FRAME_TYPE => { + let request_frame: TokenizeRequestFrame = serde_json::from_value(frame.clone()) + .context("tokenize request frame is invalid")?; + ensure!( + request_frame.frame_type == TOKENIZE_REQUEST_FRAME_TYPE + && request_frame.version == TOKENIZE_FRAME_VERSION, + "tokenize request frame version is unsupported" + ); + ensure!( + request_frame.session_id == session_id, + "tokenize request session binding mismatch" + ); + ensure!( + request_frame.provider == terms.provider + && request_frame.enclave_id == terms.enclave_id + && terms.room_ids.contains(&request_frame.room_id) + && request_frame.model == terms.model_id, + "tokenize request targets a different provider route" + ); + ensure!( + request_frame.request.is_some() ^ request_frame.request_ref.is_some(), + "tokenize request must contain exactly one of request or request_ref" + ); + let chunked = request_frame.request_ref.is_some(); + let request = if let Some(request) = request_frame.request.clone() { + request + } else { + ensure!( + !request_frame.request_id.is_empty(), + "chunked tokenize request is missing rid" + ); + provider_session_request_body_from_frame( + pending_payloads, + &session_id, + &json!({ + "rid": request_frame.request_id, + "body_ref": request_frame.request_ref, + }), + max_request_bytes, + ) + .context("reassembling tokenize request")? + }; + let encoded_request = + stable_json_bytes(&request).context("encoding tokenize request")?; + ensure!( + encoded_request.len() <= max_request_bytes, + "tokenize request exceeds provider request byte limit" + ); + if !chunked { + tokenize_limiter.admit(&remote, Instant::now())?; + } + open_provider_direct_session(bridge, &remote, &session_id) + .await + .context("opening provider side of tokenize session")?; + let response_frame = match responder.tokenize(terms, &request) { + Ok(tokenization) if tokenization.token_ids.is_empty() => TokenizeResponseFrame { + frame_type: TOKENIZE_RESPONSE_FRAME_TYPE.to_owned(), + version: TOKENIZE_FRAME_VERSION, + session_id: session_id.clone(), + provider: terms.provider.clone(), + enclave_id: terms.enclave_id.clone(), + room_id: request_frame.room_id, + model: terms.model_id.clone(), + ok: false, + count: None, + tokens: None, + tokens_ref: None, + error_code: Some("token_count_unsupported".to_owned()), + error: Some( + "Exact tokenization is unavailable for this provider runtime.".to_owned(), + ), + }, + Ok(tokenization) => TokenizeResponseFrame { + frame_type: TOKENIZE_RESPONSE_FRAME_TYPE.to_owned(), + version: TOKENIZE_FRAME_VERSION, + session_id: session_id.clone(), + provider: terms.provider.clone(), + enclave_id: terms.enclave_id.clone(), + room_id: request_frame.room_id, + model: terms.model_id.clone(), + ok: true, + count: Some(u64::try_from(tokenization.token_ids.len()).unwrap_or(u64::MAX)), + tokens: request_frame + .return_tokens + .then_some(tokenization.token_ids), + tokens_ref: None, + error_code: None, + error: None, + }, + Err(error) => { + let detail = error.to_string().to_ascii_lowercase(); + let unsupported = detail.contains("does not expose exact tokenization") + || detail.contains("exact tokenization is unsupported") + || detail.contains("tokenize outside generation"); + TokenizeResponseFrame { + frame_type: TOKENIZE_RESPONSE_FRAME_TYPE.to_owned(), + version: TOKENIZE_FRAME_VERSION, + session_id: session_id.clone(), + provider: terms.provider.clone(), + enclave_id: terms.enclave_id.clone(), + room_id: request_frame.room_id, + model: terms.model_id.clone(), + ok: false, + count: None, + tokens: None, + tokens_ref: None, + error_code: Some(if unsupported { + "token_count_unsupported".to_owned() + } else { + "token_count_failed".to_owned() + }), + error: Some(if unsupported { + "Exact tokenization is unavailable for this provider runtime." + .to_owned() + } else { + "The provider tokenizer could not complete this request.".to_owned() + }), + } + } + }; + let send_result = + send_provider_tokenize_response(bridge, &remote, &session_id, response_frame).await; + let _ = bridge.session_close(&remote, &session_id).await; + send_result?; + } TPM_ACTIVATE_CREDENTIAL_CHALLENGE_FRAME_TYPE => { let challenge_frame: TpmActivateCredentialChallengeFrame = serde_json::from_value(frame.clone()) @@ -81765,8 +84336,12 @@ where } "s.open" => { prune_provider_session_reject_replays(rejected_sessions, Instant::now()); - if let Some(replay) = rejected_sessions.get(&session_id) { - match provider_session_reject_replay_decision(replay, &remote, &frame)? { + let rejected_replay_decision = rejected_sessions + .get(&session_id) + .map(|replay| provider_session_reject_replay_decision(replay, &remote, &frame)) + .transpose()?; + if let Some(replay_decision) = rejected_replay_decision { + match replay_decision { ProviderSessionReplayDecision::Cached(reject_frame) => { provider_session_debug(format!( "replaying cached s.reject for terminal session {session_id} after transport reconnect" @@ -81780,16 +84355,26 @@ where .context("replaying cached s.reject"); let _ = bridge.session_close(&remote, &session_id).await; send_result?; + return Ok(()); } - ProviderSessionReplayDecision::Conflict - | ProviderSessionReplayDecision::Pending => { + ProviderSessionReplayDecision::Conflict => { provider_session_debug(format!( "refusing changed s.open replay for terminal session {session_id} from {remote}" )); let _ = bridge.session_close(&remote, &session_id).await; + return Ok(()); + } + ProviderSessionReplayDecision::Pending => { + // A pending reservation is not a terminal rejection. The + // durable recovery binding below guarantees that this + // identical replay re-submits the same signed reservation + // identity instead of creating another reservation. + rejected_sessions.remove(&session_id); + provider_session_debug(format!( + "resuming identical s.open for pending reservation session {session_id}" + )); } } - return Ok(()); } if let Some(existing) = sessions.get(&session_id) { if existing.remote != remote { @@ -81846,7 +84431,7 @@ where } provider_session_debug(format!("provider side session {session_id} opened")); let session_rail = provider_session_frame_rail(&frame).unwrap_or_default(); - let contract = read_contract_catalog(runtime.rpc).await?; + let contract = read_provider_session_contract_catalog(runtime.rpc, terms).await?; let mut admission_terms = terms.clone(); let price_decision = refresh_provider_session_price_terms( &contract, @@ -81870,6 +84455,7 @@ where }, reject => reject, }; + let mut accepted_reservation_recovery = None; let decision = match static_decision { ProviderSessionDecision::Accept => { let protection_decision = if let Some(reject) = runtime_floor_reject { @@ -81923,6 +84509,7 @@ where admission_timeout, provider_session_spend_reservation_decision( runtime.rpc, + &runtime.receipt_settlement, runtime.keypair_path, runtime.password, &runtime.runtime_keypair.public_key_hex(), @@ -81933,15 +84520,14 @@ where ) .await; let reservation_decision = match reservation { - Ok(Ok(decision)) => decision, + Ok(Ok((decision, recovery))) => { + accepted_reservation_recovery = recovery; + decision + } Ok(Err(error)) => return Err(error), - Err(_) => ProviderSessionDecision::Reject { - code: "BALANCE", - reason: format!( - "spend reservation did not complete within the {} ms provider admission budget; no work was served", - admission_timeout.as_millis() + Err(_) => provider_session_reservation_timeout_decision( + admission_timeout, ), - }, }; reservation_decision } @@ -82002,9 +84588,8 @@ where max_spend_au: spend_voucher.body.max_spend_au, receipt_settlement: Some(runtime.receipt_settlement.clone()), accept_replay: None, - reservation_recovery: None, + reservation_recovery: accepted_reservation_recovery.map(Arc::new), }; - active.reservation_recovery = provider_failure_recovery::begin(&active, terms)?.map(Arc::new); let ts = unix_epoch_millis()?; let open_head = session_frame_head(&frame).context("hashing s.open frame for s.accept")?; @@ -82337,11 +84922,19 @@ where live_stream.as_mut(), &cancellation, )?; - normalize_provider_visible_output_usage(&body, &mut output)?; + provider_session_output_result(normalize_provider_visible_output_usage( + &body, + &mut output, + ))?; cancellation .check() .context("provider request cancelled before response publication")?; - validate_provider_session_output(terms, &body, &output)?; + provider_session_output_result(validate_provider_session_output( + terms, &body, &output, + ))?; + if let Some(stream) = live_stream.as_mut() { + stream.finish()?; + } Ok(output) }) }); @@ -82362,49 +84955,6 @@ where .as_ref() .and_then(ProviderSessionLiveStream::measured_generation_tok_s), ); - if let Some(stream) = live_stream.as_mut() { - if let Err(err) = stream.finish() { - let err_text = format!("{err:#}"); - if err_text.contains(PROVIDER_SESSION_CLIENT_DISCONNECT_ABORT) { - request_load.finish(); - provider_session_debug(format!( - "client disconnected during live flush after partial receipt for session {session_id} request {request_id}" - )); - sessions.remove(&session_id); - pending_requests.remove(&session_id); - remove_provider_session_pending_payloads( - pending_payloads, - &session_id, - ); - heartbeat_load.set_active_sessions(sessions, terms); - return Ok(()); - } - provider_session_debug(format!( - "flushing live response failed for session {session_id}: {err_text}" - )); - send_provider_session_error( - bridge, - &active.remote, - &active.session_id, - request_id, - "provider_response_failed", - &err.to_string(), - ) - .await?; - send_provider_session_close( - bridge, - &active.remote, - &active.session_id, - "err:provider_response_failed", - ) - .await?; - sessions.remove(&session_id); - pending_requests.remove(&session_id); - remove_provider_session_pending_payloads(pending_payloads, &session_id); - heartbeat_load.set_active_sessions(sessions, terms); - return Ok(()); - } - } (output, measured_throughput) } Err(err) => { @@ -82435,6 +84985,7 @@ where usage, usage_attribution, receipt_seq, + duration_millis_u64(request_started.elapsed()).max(1), runtime.runtime_keypair, ) .await @@ -82476,13 +85027,22 @@ where "response failed for session {session_id}: {err_text}" )); let error_code = provider_response_error_code_for_request(&err, &body); + if error_code == "model_output_invalid" { + log_provider_output_diagnostic(&err, request_id); + } let error_message = provider_response_error_message(&err); let (usage, attribution, receipt_seq) = live_stream .as_ref() .map(ProviderSessionLiveStream::cancellation_receipt_state) .unwrap_or_else(|| (ReceiptUsage::default(), BTreeMap::new(), 1)); let failed_receipt = provider_failed_session_receipt( - terms, &active, &body, usage, attribution, receipt_seq, + terms, + &active, + &body, + usage, + attribution, + receipt_seq, + duration_millis_u64(request_started.elapsed()).max(1), runtime.runtime_keypair, )?; send_provider_session_failure( @@ -82498,12 +85058,17 @@ where // Publication may fail after the buyer durably signs. Its // recovery job and our settlement outbox retain that ACK. match wait_for_provider_receipt_ack_inner( - bridge, &active, receipt, - provider_session_receipt_ack_timeout(&active), None, true, - ).await { - Ok(_) => lock_provider_protection(protection).record_usage( - &receipt.body.usage, receipt.body.au_owed_cum, - ), + bridge, + &active, + receipt, + provider_session_receipt_ack_timeout(&active), + None, + true, + ) + .await + { + Ok(_) => lock_provider_protection(protection) + .record_usage(&receipt.body.usage, receipt.body.au_owed_cum), Err(error) => provider_session_debug(format!( "failed-session receipt handoff pending for {session_id}: {error:#}" )), @@ -82662,6 +85227,75 @@ where Ok(()) } +async fn send_provider_tokenize_response( + bridge: &mut ScBridgeClient, + remote: &str, + session_id: &str, + response: TokenizeResponseFrame, +) -> Result<()> { + for frame in provider_tokenize_response_frames(response, provider_session_max_frame_bytes())? { + bridge + .session_send(remote, session_id, frame) + .await + .context("sending tokenize response")?; + } + Ok(()) +} + +fn provider_tokenize_response_frames( + mut response: TokenizeResponseFrame, + max_frame_bytes: usize, +) -> Result> { + let inline = serde_json::to_value(&response).context("serializing tokenize response")?; + if provider_session_frame_json_len(&inline)? <= max_frame_bytes { + return Ok(vec![inline]); + } + let tokens = response + .tokens + .take() + .context("oversized tokenize response has no token payload to chunk")?; + let tokens_value = + serde_json::to_value(&tokens).context("serializing tokenize response tokens")?; + let bytes = stable_json_bytes(&tokens_value).context("serializing tokenize response tokens")?; + let chunk_size = provider_session_payload_chunk_bytes(max_frame_bytes); + let manifest = payload_chunk_manifest(&bytes, chunk_size) + .map_err(|error| anyhow!(error)) + .context("planning tokenize response chunks")?; + let mut frames = Vec::new(); + for index in 0..manifest.chunk_count { + let chunk = payload_chunk_at(&bytes, chunk_size, index) + .map_err(|error| anyhow!(error)) + .context("building tokenize response chunk")? + .context("planned tokenize response chunk was missing")?; + let frame = json!({ + "t": TOKENIZE_RESPONSE_CHUNK_FRAME_TYPE, + "v": TOKENIZE_FRAME_VERSION, + "session_id": response.session_id, + "provider": response.provider, + "enclave_id": response.enclave_id, + "room_id": response.room_id, + "model": response.model, + "payload_id": manifest.blake3, + "chunk": chunk, + }); + let len = provider_session_frame_json_len(&frame)?; + ensure!( + len <= max_frame_bytes, + "chunked tokenize response frame {len} bytes exceeds session max {max_frame_bytes} bytes" + ); + frames.push(frame); + } + response.tokens_ref = Some(manifest); + let final_frame = serde_json::to_value(response).context("serializing tokenize response")?; + let final_len = provider_session_frame_json_len(&final_frame)?; + ensure!( + final_len <= max_frame_bytes, + "tokenize response manifest frame {final_len} bytes exceeds session max {max_frame_bytes} bytes" + ); + frames.push(final_frame); + Ok(frames) +} + fn provider_session_event_belongs_to_process( event: &Value, sessions: &HashMap, @@ -82684,6 +85318,16 @@ fn provider_session_event_belongs_to_process( }; match frame.get("t").and_then(Value::as_str) { Some("s.open") => provider_session_open_targets_enclave(frame, terms), + Some(TOKENIZE_REQUEST_FRAME_TYPE | TOKENIZE_REQUEST_CHUNK_FRAME_TYPE) => { + frame.get("provider").and_then(Value::as_str) == Some(terms.provider.as_str()) + && frame.get("enclave_id").and_then(Value::as_str) + == Some(terms.enclave_id.as_str()) + && frame.get("model").and_then(Value::as_str) == Some(terms.model_id.as_str()) + && frame + .get("room_id") + .and_then(Value::as_str) + .is_some_and(|room_id| terms.room_ids.iter().any(|owned| owned == room_id)) + } Some(TPM_ACTIVATE_CREDENTIAL_CHALLENGE_FRAME_TYPE) => { frame.get("provider").and_then(Value::as_str) == Some(terms.provider.as_str()) && frame.get("enclave_id").and_then(Value::as_str) @@ -82735,6 +85379,7 @@ struct ProviderSessionLiveStreamState { delivered_metered_units: u64, reasoning_units: u64, prompt_tokens: u64, + compute_ms: u64, } struct ProviderSessionLiveStream<'a> { @@ -83077,6 +85722,7 @@ impl<'a> ProviderSessionLiveStream<'a> { usage_attribution, self.receipt_seq, false, + duration_millis_u64(self.request_started.elapsed()).max(1), self.runtime_keypair, ) .context("building live provider session checkpoint receipt")?; @@ -83089,7 +85735,24 @@ impl<'a> ProviderSessionLiveStream<'a> { &receipt, "checkpoint", ) - .await?; + .await + .context("sending live checkpoint receipt") + }) + })?; + // A successfully transmitted sequence is consumed even when its + // ACK is lost. A terminal recovery receipt must use a fresh + // sequence because the buyer may already have durably signed and + // queued this checkpoint before the transport failed. + self.receipt_seq = self.receipt_seq.saturating_add(1); + // Retain the transmitted high-water usage for the same reason. + // If the ACK is lost after the buyer persisted this checkpoint, + // terminal recovery at the fresh sequence must not regress usage + // or reasoning attribution below the durable checkpoint. + self.last_checkpoint_metered_units = self.delivered_metered_units; + self.last_checkpoint_reasoning_units = + metered_output_units("", &self.hidden_reasoning, &[]); + tokio::task::block_in_place(|| { + tokio::runtime::Handle::current().block_on(async { wait_for_provider_receipt_ack( self.bridge, self.active, @@ -83101,16 +85764,16 @@ impl<'a> ProviderSessionLiveStream<'a> { .context("waiting for live checkpoint receipt ack") }) })?; - self.last_checkpoint_metered_units = self.delivered_metered_units; - self.last_checkpoint_reasoning_units = metered_output_units("", &self.hidden_reasoning, &[]); - self.receipt_seq = self.receipt_seq.saturating_add(1); } self.poll_client_disconnect()?; Ok(()) } fn tool_stream_id(&self, index: usize) -> String { - format!("call-{}", stable_value_hash(&json!({"request": self.request_id, "index": index}))) + format!( + "call-{}", + stable_value_hash(&json!({"request": self.request_id, "index": index})) + ) } fn append_tool_deltas(&mut self, deltas: Vec) { @@ -83174,7 +85837,10 @@ impl<'a> ProviderSessionLiveStream<'a> { let usage_attribution = if self.last_checkpoint_reasoning_units == 0 { BTreeMap::new() } else { - BTreeMap::from([("reasoning_output_tokens".to_owned(), self.last_checkpoint_reasoning_units)]) + BTreeMap::from([( + "reasoning_output_tokens".to_owned(), + self.last_checkpoint_reasoning_units, + )]) }; (usage, usage_attribution, self.receipt_seq) } @@ -83205,12 +85871,20 @@ impl<'a> ProviderSessionLiveStream<'a> { let max_frame_bytes = provider_session_max_frame_bytes(); let mut tool_fragments = Vec::new(); for delta in &self.pending_tool_deltas { - let args = delta.get("arguments").and_then(Value::as_str).unwrap_or_default(); + let args = delta + .get("arguments") + .and_then(Value::as_str) + .unwrap_or_default(); let parts = provider_stream_parts(args, (max_frame_bytes / 12).max(1)); - if parts.is_empty() { tool_fragments.push(delta.clone()); } + if parts.is_empty() { + tool_fragments.push(delta.clone()); + } for (part_index, part) in parts.iter().enumerate() { - let mut fragment = if part_index == 0 { delta.clone() } - else { json!({"index":delta["index"]}) }; + let mut fragment = if part_index == 0 { + delta.clone() + } else { + json!({"index":delta["index"]}) + }; fragment["arguments"] = json!(part); tool_fragments.push(fragment); } @@ -83298,10 +85972,23 @@ impl<'a> ProviderSessionLiveStream<'a> { fn send_client_disconnect_receipt(&mut self) -> Result<()> { let (usage, attribution, seq) = self.cancellation_receipt_state(); - tokio::task::block_in_place(|| tokio::runtime::Handle::current().block_on(async { - settle_cancelled_provider_session(self.bridge, self.active, self.request_id, - self.terms, self.body, usage, attribution, seq, self.runtime_keypair).await - }))?; + tokio::task::block_in_place(|| { + tokio::runtime::Handle::current().block_on(async { + settle_cancelled_provider_session( + self.bridge, + self.active, + self.request_id, + self.terms, + self.body, + usage, + attribution, + seq, + duration_millis_u64(self.request_started.elapsed()).max(1), + self.runtime_keypair, + ) + .await + }) + })?; Ok(()) } @@ -83314,6 +86001,7 @@ impl<'a> ProviderSessionLiveStream<'a> { delivered_metered_units: self.delivered_metered_units, reasoning_units: metered_output_units("", &self.hidden_reasoning, &[]), prompt_tokens: self.prompt_tokens, + compute_ms: duration_millis_u64(self.request_started.elapsed()).max(1), }) } } @@ -83325,13 +86013,22 @@ fn provider_failed_session_receipt( usage: ReceiptUsage, attribution: BTreeMap, seq: u64, + compute_ms: u64, runtime_keypair: &RuntimeKeypair, ) -> Result> { // A provider failure never invents the minimum work quantum used for a // buyer cancellation. Only the last acknowledged checkpoint is billable. // A zero-spend failure is closed through canonical reservation recovery. let receipt = provider_session_receipt_for_usage_attribution( - terms, active, body, usage, attribution, seq, true, runtime_keypair, + terms, + active, + body, + usage, + attribution, + seq, + true, + compute_ms, + runtime_keypair, )?; Ok((receipt.body.au_owed_cum > active.billing_prior_au_owed_cum).then_some(receipt)) } @@ -83354,7 +86051,9 @@ async fn send_provider_session_failure( frame["seq"] = json!(receipt.body.seq); frame["receipt"] = json!(receipt); } - bridge.session_send(&active.remote, &active.session_id, frame).await + bridge + .session_send(&active.remote, &active.session_id, frame) + .await .context("sending provider failure and terminal accounting")?; Ok(()) } @@ -83368,6 +86067,7 @@ async fn settle_cancelled_provider_session( usage: ReceiptUsage, usage_attribution: BTreeMap, receipt_seq: u64, + compute_ms: u64, runtime_keypair: &RuntimeKeypair, ) -> Result { let receipt = provider_cancelled_session_receipt( @@ -83377,6 +86077,7 @@ async fn settle_cancelled_provider_session( usage, usage_attribution, receipt_seq, + compute_ms, runtime_keypair, ) .context("building cancelled provider session receipt")?; @@ -83409,6 +86110,7 @@ fn provider_cancelled_session_receipt( metered_usage: ReceiptUsage, usage_attribution: BTreeMap, receipt_seq: u64, + compute_ms: u64, runtime_keypair: &RuntimeKeypair, ) -> Result { let usage = cancellation_settlement_usage( @@ -83428,6 +86130,7 @@ fn provider_cancelled_session_receipt( usage_attribution, receipt_seq, true, + compute_ms, runtime_keypair, ) } @@ -83541,6 +86244,7 @@ async fn send_provider_session_output( usage_attribution, receipt_seq, false, + provider_session_quality_compute_ms(&provider_quality), runtime_keypair, ) .context("building provider session checkpoint receipt")?; @@ -83552,6 +86256,7 @@ async fn send_provider_session_output( "checkpoint", ) .await?; + receipt_seq = receipt_seq.saturating_add(1); wait_for_provider_receipt_ack( bridge, active, @@ -83562,7 +86267,6 @@ async fn send_provider_session_output( .await .context("waiting for checkpoint receipt ack")?; last_checkpoint_metered_units = delivered_metered_units; - receipt_seq = receipt_seq.saturating_add(1); } } } @@ -83612,6 +86316,7 @@ async fn send_provider_session_output( output.usage_attribution.clone(), receipt_seq, true, + provider_session_quality_compute_ms(&provider_quality), runtime_keypair, ) .context("building provider session receipt")?; @@ -83631,12 +86336,32 @@ async fn send_provider_client_disconnect_receipt_if_requested( if !provider_session_client_disconnect_requested(bridge, active).await? { return Ok(()); } - let usage = if state.last_checkpoint_metered_units == 0 { ReceiptUsage::default() } - else { ReceiptUsage::text(state.prompt_tokens, state.last_checkpoint_metered_units) }; - let attribution = if state.last_checkpoint_reasoning_units == 0 { BTreeMap::new() } - else { BTreeMap::from([("reasoning_output_tokens".to_owned(), state.last_checkpoint_reasoning_units)]) }; - settle_cancelled_provider_session(bridge, active, request_id, terms, body, usage, - attribution, state.receipt_seq, runtime_keypair).await?; + let usage = if state.last_checkpoint_metered_units == 0 { + ReceiptUsage::default() + } else { + ReceiptUsage::text(state.prompt_tokens, state.last_checkpoint_metered_units) + }; + let attribution = if state.last_checkpoint_reasoning_units == 0 { + BTreeMap::new() + } else { + BTreeMap::from([( + "reasoning_output_tokens".to_owned(), + state.last_checkpoint_reasoning_units, + )]) + }; + settle_cancelled_provider_session( + bridge, + active, + request_id, + terms, + body, + usage, + attribution, + state.receipt_seq, + state.compute_ms, + runtime_keypair, + ) + .await?; bail!("{PROVIDER_SESSION_CLIENT_DISCONNECT_ABORT}"); } @@ -84520,7 +87245,9 @@ async fn wait_for_provider_receipt_ack_inner( == Some("client_disconnect") => { if !settling_cancellation && !cancellation_drain { - if let Some(token) = cancellation { token.cancel(); } + if let Some(token) = cancellation { + token.cancel(); + } cancellation_drain = true; deadline = deadline.min(Instant::now() + Duration::from_millis(500)); } @@ -84640,6 +87367,7 @@ fn provider_session_receipt( output.usage_attribution.clone(), 1, true, + 1, runtime_keypair, ) } @@ -84651,6 +87379,7 @@ fn provider_session_receipt_for_usage( usage: ReceiptUsage, seq: u64, final_receipt: bool, + compute_ms: u64, runtime_keypair: &RuntimeKeypair, ) -> Result { provider_session_receipt_for_usage_attribution( @@ -84661,6 +87390,7 @@ fn provider_session_receipt_for_usage( BTreeMap::new(), seq, final_receipt, + compute_ms, runtime_keypair, ) } @@ -84673,6 +87403,7 @@ fn provider_session_receipt_for_usage_attribution( usage_attribution: BTreeMap, seq: u64, final_receipt: bool, + compute_ms: u64, runtime_keypair: &RuntimeKeypair, ) -> Result { let usage = provider_session_logical_usage(active, &usage); @@ -84717,6 +87448,8 @@ fn provider_session_receipt_for_usage_attribution( locked_per_req_au: active.locked_per_req_au, locked_min_session_au: active.locked_min_session_au, served_ctx: active.served_ctx, + compute_ms: compute_ms.max(1), + capacity_slots: terms.capacity_slots.max(1), ctx_bracket: active.ctx_bracket.clone(), ctx_bracket_table_ver: active.ctx_bracket_table_ver, rules_ver: terms.rules_ver, @@ -84823,6 +87556,7 @@ fn provider_receipt_binds_contract_request(endpoint_family: &str) -> bool { | mayhem_proto::ENDPOINT_MAYHEM_AUDIO_GENERATIONS | mayhem_proto::ENDPOINT_MAYHEM_MUSIC_GENERATIONS | mayhem_proto::ENDPOINT_MAYHEM_COMFY_WORKFLOWS + | mayhem_proto::ENDPOINT_MAYHEM_DECISIONS | mayhem_proto::ENDPOINT_HF_TEXT_TO_AUDIO ) } @@ -84860,6 +87594,7 @@ fn provider_session_prompt_text(body: &Value, adapter: &catalog::CatalogAdapter) | mayhem_proto::ENDPOINT_MAYHEM_AUDIO_GENERATIONS | mayhem_proto::ENDPOINT_MAYHEM_MUSIC_GENERATIONS | mayhem_proto::ENDPOINT_MAYHEM_COMFY_WORKFLOWS + | mayhem_proto::ENDPOINT_MAYHEM_DECISIONS | mayhem_proto::ENDPOINT_HF_TEXT_TO_AUDIO ) ) { @@ -84937,6 +87672,33 @@ fn provider_embedding_input_texts_from_body(body: &Value) -> Result> provider_embedding_input_texts_from_value(input) } +fn provider_decision_request_from_body(body: &Value) -> Result { + Ok(EngineDecisionRequest { + state: body + .get("state") + .cloned() + .context("decision request is missing state")?, + questions: body + .get("questions") + .cloned() + .context("decision request is missing questions")?, + checkpoint: body + .get("checkpoint") + .and_then(Value::as_str) + .map(str::to_owned), + task: body.get("task").and_then(Value::as_str).map(str::to_owned), + lang: body.get("lang").and_then(Value::as_str).map(str::to_owned), + auto_task_detection: body + .get("auto_task_detection") + .and_then(Value::as_bool) + .unwrap_or(false), + email: body.get("email").cloned(), + shortlist: body.get("shortlist").cloned(), + temperature: body.get("temperature").cloned(), + limits: body.get("limits").cloned(), + }) +} + fn provider_embedding_input_texts_from_value(value: &Value) -> Result> { match value { Value::String(text) => { @@ -84963,13 +87725,6 @@ fn provider_embedding_input_texts_from_value(value: &Value) -> Result u64 { - inputs - .iter() - .map(|input| rough_text_tokens(input)) - .fold(0_u64, u64::saturating_add) -} - fn provider_session_attestation_policy_binding( frame: &Value, terms: &ProviderSessionTerms, @@ -85103,6 +87858,23 @@ fn provider_session_responder( backend: Box::new(backend), })) } + "laya" => { + let python = ctx + .backend_runtime + .python + .as_ref() + .context("Laya runtime preflight did not resolve Python")?; + let mut backend = mayhem_engine::LayaBackend::with_python(python) + .context("initializing Laya provider session engine")?; + with_provider_progress_spinner(ctx.args, "Laya engine load", || { + backend + .load(load_config) + .context("loading Laya provider session engine") + })?; + Ok(Box::new(EngineProviderSessionResponder { + backend: Box::new(backend), + })) + } "mlx" => { let python = ctx .backend_runtime @@ -85317,6 +88089,50 @@ fn provider_session_responder( backend: Box::new(backend), })) } + "openai-compatible" => { + let base_url = ctx + .backend_runtime + .openai_compatible_url + .clone() + .context("openai-compatible runtime preflight did not resolve its loopback URL")?; + let runtime = ctx + .selected + .artifact + .openai_compatible + .clone() + .context("openai-compatible artifact is missing its signed runtime binding")?; + let mut backend = mayhem_engine::OpenAiCompatibleBackend::new( + mayhem_engine::OpenAiCompatibleBackendConfig { + base_url, + runtime, + readiness_timeout: if ctx + .backend_runtime + .managed_openai_compatible + .is_some() + { + Duration::from_secs(3_600) + } else { + Duration::ZERO + }, + }, + ) + .context("initializing openai-compatible provider session engine")?; + with_provider_progress_spinner(ctx.args, "openai-compatible engine preflight", || { + backend + .load(load_config) + .context("loading openai-compatible provider session engine") + })?; + Ok(Box::new(EngineProviderSessionResponder { + backend: Box::new(ProcessBoundOpenAiBackend { + backend, + process_id: ctx + .backend_runtime + .openai_compatible_pid + .context("OpenAI-compatible provider runtime has no verified host PID")?, + _runtime: ctx.backend_runtime.managed_openai_compatible.clone(), + }), + })) + } other => bail!( "provider session engine for {other} is not wired locally yet; do not serve this enclave until its admin-approved engine adapter is available" ), @@ -85423,11 +88239,18 @@ fn provider_modality_self_test( let mut reports = Vec::new(); for case in cases { if let Some(policy) = &terms.execution_mode_requests { - let verified = - provider_verify_endpoint_request(&case.body, Some(&terms.model_id), &terms.adapter)?; - policy.validate_request(verified.family, verified.request).map_err(|_| { - provider_session_request_error("request is not supported by the negotiated execution mode") - })?; + let verified = provider_verify_endpoint_request( + &case.body, + Some(&terms.model_id), + &terms.adapter, + )?; + policy + .validate_request(verified.family, verified.request) + .map_err(|_| { + provider_session_request_error( + "request is not supported by the negotiated execution mode", + ) + })?; } let output = responder .respond(terms, &case.body, &CancellationToken::new()) @@ -85463,7 +88286,11 @@ fn provider_session_responder_with_modality_health( // The start-up self-test admits no sessions, so no price floor applies to its terms. let terms = provider_session_terms(ctx, 0)?; let mut responder = provider_session_responder(ctx)?; - if ctx.selected.model.model_class == DEFAULT_MODEL_CLASS { + if provider_requires_prefix_caching( + &ctx.selected.model.model_id, + &ctx.selected.artifact.engine, + &ctx.selected.model.model_class, + ) { ensure!(responder.prefix_caching_enabled(), "LLM provider admission requires initialized prefix caching; upgrade to a cache-capable runtime"); } @@ -85481,6 +88308,38 @@ fn provider_session_responder_with_modality_health( Ok((responder, health)) } +fn provider_requires_prefix_caching(model_id: &str, engine: &str, model_class: &str) -> bool { + model_class == DEFAULT_MODEL_CLASS + && !(model_id == NEEDLE_MODEL_REPO && matches!(engine, "needle-cpu" | "needle-gpu")) +} + +#[cfg(test)] +#[test] +fn needle_cache_exception_is_limited_to_its_two_runtimes() { + for engine in ["needle-cpu", "needle-gpu"] { + assert!(!provider_requires_prefix_caching( + NEEDLE_MODEL_REPO, + engine, + DEFAULT_MODEL_CLASS + )); + assert!(provider_requires_prefix_caching( + "another/chat-model", + engine, + DEFAULT_MODEL_CLASS + )); + } + assert!(provider_requires_prefix_caching( + NEEDLE_MODEL_REPO, + "llama.cpp", + DEFAULT_MODEL_CLASS + )); + assert!(!provider_requires_prefix_caching( + "another/embedding-model", + "needle-cpu", + "embedding" + )); +} + fn provider_workflow_admission_modality_health( selected: &ProviderCandidate, admission: Option<&ProviderComfyAdmission>, @@ -85533,6 +88392,7 @@ fn provider_canary_prompt_modalities( modalities } "embedding" => BTreeSet::from(["embedding".to_owned()]), + MODEL_CLASS_DECISION => BTreeSet::from(["text".to_owned()]), "image-generation" => BTreeSet::from(["image".to_owned()]), "video-generation" => { let mut modalities = BTreeSet::from(["audio".to_owned(), "video".to_owned()]); @@ -85656,6 +88516,21 @@ fn provider_canary_self_test_body( "kind": "embedding", "input": canary_prompt_text(prompt)?, })), + MODEL_CLASS_DECISION => { + let mut body = Value::Object(prompt.endpoint_attributes.clone().into_iter().collect()); + let object = body + .as_object_mut() + .expect("decision canary body is an object"); + object.insert("kind".to_owned(), json!("decision")); + object.insert( + "endpoint_family".to_owned(), + json!(mayhem_proto::ENDPOINT_MAYHEM_DECISIONS), + ); + if let Some(temperature) = &prompt.decision_temperature { + object.insert("temperature".to_owned(), temperature.clone()); + } + Ok(body) + } "image-generation" => { if let Some(body) = provider_comfy_workflow_canary_self_test_body(model, prompt)? { return Ok(body); @@ -85674,8 +88549,14 @@ fn provider_canary_self_test_body( ("shift", prompt.shift.map(Value::from)), ("seed", prompt.seed.map(Value::from)), ("negative_prompt", prompt.negative_prompt.clone()), - ("input_reference", prompt.endpoint_attributes.get("input_reference").cloned()), - ("strength", prompt.endpoint_attributes.get("strength").cloned()), + ( + "input_reference", + prompt.endpoint_attributes.get("input_reference").cloned(), + ), + ( + "strength", + prompt.endpoint_attributes.get("strength").cloned(), + ), ] { if let Some(value) = value { object.insert(name.to_owned(), value); @@ -86395,6 +89276,22 @@ fn validate_provider_canary_self_test_output( && embeddings.iter().all(|row| !row.is_empty())), "embedding modality canary produced no embedding vectors" ), + MODEL_CLASS_DECISION => { + let result: Value = serde_json::from_str(&output.content) + .context("decision modality canary produced invalid JSON")?; + ensure!( + result.get("answers").is_some_and(Value::is_object), + "decision modality canary produced no answers object" + ); + ensure!( + result.get("routing").is_some_and(Value::is_object), + "decision modality canary produced no routing object" + ); + ensure!( + output.prompt_tokens > 0 && output.completion_tokens > 0, + "decision modality canary produced no metered usage" + ); + } "image-generation" => ensure!( output .artifacts @@ -86500,6 +89397,8 @@ fn provider_engine_load_config( materialized_trt_engine_dir = Some(layout.engine_dir); } else if selected.artifact.engine == "vllm" { artifact_path_buf = materialize_vllm_artifacts(selected, artifact_paths)?; + } else if selected.artifact.engine == "openai-compatible" { + artifact_path_buf = materialize_openai_compatible_artifacts(selected, artifact_paths)?; } else if selected.artifact.engine == "mlx" { artifact_path_buf = materialize_mlx_artifacts(selected, artifact_paths)?; } else if selected.artifact.engine == "ace-step" { @@ -86528,6 +89427,8 @@ fn provider_engine_load_config( artifact_path_buf = materialize_needle_artifacts(selected, artifact_paths, cache_dir)?; } else if selected.artifact.engine == "transformers-asr" { artifact_path_buf = materialize_transformers_asr_artifacts(selected, artifact_paths)?; + } else if selected.artifact.engine == "laya" { + artifact_path_buf = materialize_laya_artifacts(selected, artifact_paths)?; } let artifact_path = artifact_path_buf.as_path(); let mut artifact = match selected.artifact.engine.as_str() { @@ -86535,6 +89436,7 @@ fn provider_engine_load_config( "mlx" => ModelArtifact::mlx_safetensors(artifact_path), "trt-llm" => ModelArtifact::trt_llm_checkpoint(artifact_path), "vllm" => ModelArtifact::vllm_safetensors(artifact_path), + "openai-compatible" => ModelArtifact::openai_compatible_model(artifact_path), "stable-diffusion.cpp" => ModelArtifact::stable_diffusion_checkpoint(artifact_path), "comfyui" => ModelArtifact::comfyui_runtime(artifact_path), "ace-step" => ModelArtifact::ace_step_safetensors(artifact_path), @@ -86542,6 +89444,7 @@ fn provider_engine_load_config( "needle-cpu" | "needle-gpu" => ModelArtifact::transformers_safetensors(artifact_path), "sulphur" => sulphur_load_config(artifact_path)?.artifact, "transformers-asr" => ModelArtifact::transformers_safetensors(artifact_path), + "laya" => ModelArtifact::laya_safetensors(artifact_path), "whisper.cpp" => ModelArtifact::whisper_ggml(artifact_path), "piper" => ModelArtifact::piper_voice(artifact_path), other => bail!("unsupported local provider session engine {other}"), @@ -86549,7 +89452,10 @@ fn provider_engine_load_config( if selected.artifact.engine == "sulphur" { bind_sulphur_primary_hash_path(&mut artifact, &selected.artifact)?; } - let artifact = if selected.artifact.engine != "comfyui" { + let artifact = if !matches!( + selected.artifact.engine.as_str(), + "comfyui" | "openai-compatible" + ) { if let Some(sha256) = &selected.artifact.source_sha256 { artifact.with_sha256(sha256.clone()) } else { @@ -86563,6 +89469,7 @@ fn provider_engine_load_config( "mlx" => LoadConfig::mlx_safetensors(artifact_path), "trt-llm" => LoadConfig::trt_llm_checkpoint(artifact_path), "vllm" => LoadConfig::vllm_safetensors(artifact_path), + "openai-compatible" => LoadConfig::openai_compatible_model(artifact_path), "stable-diffusion.cpp" => LoadConfig::stable_diffusion_checkpoint(artifact_path), "comfyui" => LoadConfig::comfyui_runtime(artifact_path), "ace-step" => LoadConfig::ace_step_safetensors(artifact_path), @@ -86570,6 +89477,7 @@ fn provider_engine_load_config( "needle-cpu" | "needle-gpu" => LoadConfig::transformers_safetensors(artifact_path), "sulphur" => sulphur_load_config(artifact_path)?, "transformers-asr" => LoadConfig::transformers_safetensors(artifact_path), + "laya" => LoadConfig::laya_safetensors(artifact_path), "whisper.cpp" => LoadConfig::whisper_ggml(artifact_path), "piper" => LoadConfig::piper_voice(artifact_path), other => bail!("unsupported local provider session engine {other}"), @@ -86676,32 +89584,46 @@ fn provider_engine_load_config( config.trt_require_engine_dir = true; } if selected.artifact.engine == "vllm" { + bind_vllm_task_for_model(&mut config, &selected.model); config.vllm_tensor_parallel = Some(enclave_tp_degree(&selected.enclave.caps)?); // CUDA reservations and a mapped checkpoint consume virtual addresses, // not the host's remaining resident-memory budget. Keep this finite // envelope stable when another provider is already resident at restart. let memory = &selected.feasibility.memory_budget; - config.vllm_worker_address_space_limit_bytes = Some(memory.worker_address_space_limit_bytes) - .filter(|bytes| *bytes > 0); - let isolated = generation_execution_uses_isolated_workers(selected.generation_execution_profile.as_ref()); + config.vllm_worker_address_space_limit_bytes = + Some(memory.worker_address_space_limit_bytes).filter(|bytes| *bytes > 0); + let isolated = generation_execution_uses_isolated_workers( + selected.generation_execution_profile.as_ref(), + ); if isolated { - ensure!(selected.execution_mode.is_some(), - "isolated dispatch requires authenticated execution-mode negotiation"); - let allocation = selected.feasibility.replica_allocation + ensure!( + selected.execution_mode.is_some(), + "isolated dispatch requires authenticated execution-mode negotiation" + ); + let allocation = selected + .feasibility + .replica_allocation .context("isolated dispatch has no aggregate memory admission")?; - ensure!(allocation.worker_count == selected.generation_execution_capacity, - "isolated worker count differs from its admitted allocation"); - config.vllm_generation_topology = Some(mayhem_engine::VllmGenerationTopology::IsolatedWorkers); + ensure!( + allocation.worker_count == selected.generation_execution_capacity, + "isolated worker count differs from its admitted allocation" + ); + config.vllm_generation_topology = + Some(mayhem_engine::VllmGenerationTopology::IsolatedWorkers); config.vllm_max_num_seqs = Some(1); - config.memory_limit_bytes = Some(config.memory_limit_bytes - .unwrap_or(selected.feasibility.estimated_required_bytes) - .min(selected.feasibility.estimated_required_bytes)); + config.memory_limit_bytes = Some( + config + .memory_limit_bytes + .unwrap_or(selected.feasibility.estimated_required_bytes) + .min(selected.feasibility.estimated_required_bytes), + ); } else if selected.generation_execution_profile.is_some() { config.vllm_max_num_seqs = Some(selected.generation_execution_capacity.max(1)); } else if let Some(scheduler_capacity) = enclave_max_batch_size(&selected.enclave.caps)? { config.vllm_max_num_seqs = Some(scheduler_capacity); } - config.vllm_concurrent_generation_capacity = (isolated || selected.generation_execution_capacity > 1) + config.vllm_concurrent_generation_capacity = (isolated + || selected.generation_execution_capacity > 1) .then_some(selected.generation_execution_capacity); if let Some(max_num_tokens) = enclave_max_num_tokens(&selected.enclave.caps)? { config.ubatch_size = max_num_tokens.max(1); @@ -86754,6 +89676,50 @@ const TRANSFORMERS_ASR_REQUIRED_SIDECARS: &[(&str, &str)] = &[ ("transformers_tokenizer_config", "tokenizer_config.json"), ]; +const LAYA_REQUIRED_SIDECARS: &[(&str, &str)] = &[ + ("laya_encoder_config", "encoder/config.json"), + ("laya_agent_config", "rl_agent_config.json"), + ("laya_tokenizer", "tokenizer/tokenizer.json"), + ("laya_tokenizer_config", "tokenizer/tokenizer_config.json"), + ("laya_multilingual_model", "multilingual/model.safetensors"), + ( + "laya_multilingual_encoder_config", + "multilingual/encoder/config.json", + ), + ( + "laya_multilingual_agent_config", + "multilingual/rl_agent_config.json", + ), + ( + "laya_multilingual_tokenizer", + "multilingual/tokenizer/tokenizer.json", + ), + ( + "laya_multilingual_tokenizer_config", + "multilingual/tokenizer/tokenizer_config.json", + ), + ( + "laya_typed_decisions_model", + "typed-decisions/model.safetensors", + ), + ( + "laya_typed_decisions_encoder_config", + "typed-decisions/encoder/config.json", + ), + ( + "laya_typed_decisions_agent_config", + "typed-decisions/rl_agent_config.json", + ), + ( + "laya_typed_decisions_tokenizer", + "typed-decisions/tokenizer/tokenizer.json", + ), + ( + "laya_typed_decisions_tokenizer_config", + "typed-decisions/tokenizer/tokenizer_config.json", + ), +]; + const NEEDLE_MODEL_REPO: &str = "Cactus-Compute/needle"; const NEEDLE_RUNTIME_REPO: &str = "Cactus-Compute/needle-hf"; const NEEDLE_PRIMARY_PATH: &str = "model.safetensors"; @@ -86982,6 +89948,19 @@ fn materialize_vllm_artifacts( ) } +fn materialize_openai_compatible_artifacts( + selected: &ProviderCandidate, + artifact_paths: &ProviderArtifactPaths, +) -> Result { + ensure!( + artifact_paths.primary.is_dir(), + "openai-compatible artifact {}/{} must be a verified snapshot directory", + selected.model.model_id, + selected.artifact_name + ); + Ok(artifact_paths.primary.clone()) +} + fn materialize_vllm_layout( label: &str, artifact_name: &str, @@ -87140,6 +90119,74 @@ fn materialize_transformers_asr_layout( Ok(model_dir) } +fn materialize_laya_artifacts( + selected: &ProviderCandidate, + artifact_paths: &ProviderArtifactPaths, +) -> Result { + materialize_laya_layout( + &format!("{}/{}", selected.model.model_id, selected.artifact_name), + &selected.artifact_name, + &selected.artifact, + artifact_paths, + ) +} + +fn materialize_laya_layout( + label: &str, + artifact_name: &str, + artifact: &catalog::CatalogArtifact, + artifact_paths: &ProviderArtifactPaths, +) -> Result { + let model_dir = laya_checkpoint_cache_dir(&artifact_paths.primary, artifact_name, artifact); + fs::create_dir_all(&model_dir) + .with_context(|| format!("creating Laya model layout {}", model_dir.display()))?; + let primary_relative = validate_catalog_artifact_relative_path(&artifact.path)?; + let primary = model_dir.join(&primary_relative); + link_or_copy_file(&artifact_paths.primary, &primary).with_context(|| { + format!( + "materializing Laya primary artifact {label} at {}", + primary.display() + ) + })?; + + for (sidecar_name, filename) in LAYA_REQUIRED_SIDECARS { + let sidecar = artifact.sidecars.get(*sidecar_name).with_context(|| { + format!("Laya artifact {label} requires admin catalog sidecar {sidecar_name}") + })?; + ensure!( + sidecar.path == *filename, + "Laya artifact {label} sidecar {sidecar_name} must use path {filename}, got {}", + sidecar.path + ); + } + + let mut occupied_paths = BTreeSet::from([primary_relative]); + for (sidecar_name, sidecar) in &artifact.sidecars { + let relative = validate_catalog_artifact_relative_path(&sidecar.path)?; + ensure!( + occupied_paths.insert(relative.clone()), + "Laya artifact {label} declares duplicate path {}", + sidecar.path + ); + let source = artifact_paths.sidecars.get(sidecar_name).with_context(|| { + format!("downloaded Laya artifact {label} is missing admin sidecar {sidecar_name}") + })?; + let destination = model_dir.join(relative); + let materialize = if sidecar.path.ends_with("tokenizer/tokenizer_config.json") { + copy_file_replace(source, &destination) + } else { + link_or_copy_file(source, &destination) + }; + materialize.with_context(|| { + format!( + "materializing Laya sidecar {sidecar_name} at {}", + destination.display() + ) + })?; + } + Ok(primary) +} + fn materialize_needle_artifacts( selected: &ProviderCandidate, artifact_paths: &ProviderArtifactPaths, @@ -87811,6 +90858,22 @@ fn link_or_copy_file(source: &Path, destination: &Path) -> Result<()> { Ok(()) } +fn copy_file_replace(source: &Path, destination: &Path) -> Result<()> { + if !source.is_file() { + bail!("{} is not a file", source.display()); + } + if let Some(parent) = destination.parent() { + fs::create_dir_all(parent).with_context(|| format!("creating {}", parent.display()))?; + } + if destination.exists() { + fs::remove_file(destination) + .with_context(|| format!("removing stale {}", destination.display()))?; + } + fs::copy(source, destination) + .with_context(|| format!("copying {} to {}", source.display(), destination.display()))?; + Ok(()) +} + fn same_canonical_path(left: &Path, right: &Path) -> bool { match (left.canonicalize(), right.canonicalize()) { (Ok(left), Ok(right)) => left == right, @@ -87840,7 +90903,10 @@ fn provider_attestation_runtime_config( } }); Ok(AttestationRuntimeConfig { - execution_mode: selected.execution_mode.as_ref().map(|mode| mode.binding.clone()), + execution_mode: selected + .execution_mode + .as_ref() + .map(|mode| mode.binding.clone()), model_class: selected.enclave.model_class.clone(), backend: selected.artifact.engine.clone(), ctx, @@ -87932,6 +90998,32 @@ fn transformers_asr_checkpoint_cache_dir(artifact_path: &Path, artifact_name: &s .join(safe_path_component(artifact_name)) } +fn laya_checkpoint_cache_dir( + artifact_path: &Path, + artifact_name: &str, + artifact: &catalog::CatalogArtifact, +) -> PathBuf { + let base = if artifact_path.is_dir() { + artifact_path.to_path_buf() + } else { + artifact_path + .parent() + .map(Path::to_path_buf) + .unwrap_or_else(|| PathBuf::from(".")) + }; + let mut identity = blake3::Hasher::new(); + identity.update(b"mayhem/laya/materialized-layout/v1\0"); + identity.update(artifact.artifact_root.as_bytes()); + for (name, sidecar) in &artifact.sidecars { + identity.update(name.as_bytes()); + identity.update(sidecar.artifact_root.as_bytes()); + identity.update(sidecar.path.as_bytes()); + } + base.join(".laya-models") + .join(safe_path_component(artifact_name)) + .join(identity.finalize().to_hex().as_str()) +} + fn needle_checkpoint_cache_dir( cache_dir: &Path, artifact_name: &str, @@ -88080,16 +91172,29 @@ fn provider_session_terms( (None, None) }; Ok(ProviderSessionTerms { - execution_mode: ctx.selected.execution_mode.as_ref().map(|mode| mode.binding.clone()), - execution_mode_requests: ctx.selected.execution_mode.as_ref().map(|mode| mode.requests.clone()), + execution_mode: ctx + .selected + .execution_mode + .as_ref() + .map(|mode| mode.binding.clone()), + execution_mode_requests: ctx + .selected + .execution_mode + .as_ref() + .map(|mode| mode.requests.clone()), contract_version: CONTRACT_VERSION, provider: ctx.wallet.public_key.clone(), enclave_id: ctx.selected.enclave.enclave_id.clone(), model_id: ctx.selected.enclave.model_id.clone(), - adapter: ctx.selected.execution_mode.as_ref() + adapter: ctx + .selected + .execution_mode + .as_ref() .map(|mode| &mode.baseline_adapter) - .unwrap_or(&ctx.selected.model.adapter).clone(), + .unwrap_or(&ctx.selected.model.adapter) + .clone(), generation_execution_profile: ctx.selected.generation_execution_profile.clone(), + runtime_independent_dispatch_modalities: Vec::new(), sampling: ctx.selected.model.sampling.clone(), workflow_policy: ctx.selected.model.workflow.clone(), output_modalities: if ctx.selected.model.caps.output_modalities.is_empty() { @@ -88113,6 +91218,7 @@ fn provider_session_terms( min_session_au: price.min_session_au, min_ask_au, rules_ver: ctx.rules.ver, + capacity_slots: 1, ctx: ctx.selected.served_ctx, ctx_bracket, ctx_bracket_table_ver, @@ -88257,16 +91363,12 @@ fn provider_session_contract_decision( .to_owned(), ); } - let Some(schedule) = contract - .prices - .iter() - .find(|price| { - price.enclave_id == terms.enclave_id - && price.model_id == terms.model_id - && price.ctx_bracket == terms.ctx_bracket - && price.ctx_bracket_table_ver == terms.ctx_bracket_table_ver - }) - else { + let Some(schedule) = contract.prices.iter().find(|price| { + price.enclave_id == terms.enclave_id + && price.model_id == terms.model_id + && price.ctx_bracket == terms.ctx_bracket + && price.ctx_bracket_table_ver == terms.ctx_bracket_table_ver + }) else { return reject( "PRICE_VER", "admin price schedule is no longer present for this enclave".to_owned(), @@ -88322,17 +91424,22 @@ fn provider_session_contract_decision( async fn provider_session_spend_reservation_decision( rpc: &PeerRpcClient, + settlement: &ProviderReceiptSettlement, keypair_path: &Path, password: &str, enclave_pubkey: &str, terms: &ProviderSessionTerms, frame: &Value, rail: &str, -) -> Result { +) -> Result<( + ProviderSessionDecision, + Option, +)> { let reject = |reason: String| ProviderSessionDecision::Reject { code: "BALANCE", reason, }; + let rejected = |reason: String| Ok((reject(reason), None)); let voucher: SpendVoucher = match frame .get("voucher") .cloned() @@ -88340,31 +91447,31 @@ async fn provider_session_spend_reservation_decision( .and_then(|value| serde_json::from_value(value).context("invalid spend voucher")) { Ok(voucher) => voucher, - Err(err) => return Ok(reject(format!("invalid spend reservation: {err:#}"))), + Err(err) => return rejected(format!("invalid spend reservation: {err:#}")), }; let active_epoch = active_billing_epoch(rpc).await?; if let Err(error) = validate_provider_session_voucher_epoch(&voucher, active_epoch) { - return Ok(reject(error.to_string())); + return rejected(error.to_string()); } let epoch = voucher.body.billing_epoch; let at = match provider_session_open_at(frame) { Ok(at) => at, - Err(err) => return Ok(reject(format!("invalid spend reservation: {err:#}"))), + Err(err) => return rejected(format!("invalid spend reservation: {err:#}")), }; let payout_revision = match active_provider_payout_revision(rpc, &terms.provider, rail, epoch).await { Ok(revision) => revision, Err(err) => { - return Ok(reject(format!( + return rejected(format!( "provider has no active verified {rail} payout binding: {err:#}" - ))) + )) } }; if payout_revision != voucher.body.payout_revision { - return Ok(reject( + return rejected( "signed payout revision does not match the canonical active provider binding" .to_owned(), - )); + ); } let mut value = match provider_session_spend_reservation_value( terms, @@ -88376,7 +91483,7 @@ async fn provider_session_spend_reservation_decision( enclave_pubkey, ) { Ok(value) => value, - Err(err) => return Ok(reject(format!("invalid spend reservation: {err:#}"))), + Err(err) => return rejected(format!("invalid spend reservation: {err:#}")), }; let message = targeted_spend_reservation_message(&value); let provider_sig = sign_message(keypair_path, password, &message) @@ -88384,11 +91491,10 @@ async fn provider_session_spend_reservation_decision( .context("signing provider spend reservation")?; value["provider_sig"] = json!(provider_sig); let key = targeted_spend_reservation_feature_key(&value)?; - // The local relay comes up shortly after the peer process; an admission that - // races that window should wait it out (bounded) instead of rejecting the - // session outright (observed live 2026-07-12: s.reject BALANCE on a relay that - // became ready seconds later). Only relay-readiness errors retry; every other - // failure rejects immediately as before. + // The local relay comes up shortly after the peer process, and a lost RPC + // response cannot prove whether the writer accepted the reservation. Retry + // the same signed feature and reservation identity within a bounded window; + // never construct a second reservation for this billing attempt. let retry_window = configured_nonnegative_millis( "MAYHEM_PROVIDER_ADMISSION_RELAY_RETRY_WINDOW_MS", DEFAULT_PROVIDER_ADMISSION_RELAY_RETRY_WINDOW_MILLIS, @@ -88400,35 +91506,102 @@ async fn provider_session_spend_reservation_decision( "provider admission relay retry interval", )?); let retry_deadline = Instant::now() + Duration::from_millis(retry_window); - let submitted = loop { - match rpc - .submit_feature(json!({ - "feature": "mayhem", - "key": key.clone(), - "value": value.clone(), - })) - .await - { + let feature = json!({ + "feature": "mayhem", + "key": key.clone(), + "value": value.clone(), + }); + let mut recovery_guard = Some(provider_failure_recovery::begin_binding( + settlement, + &spend_reservation_binding(&value)?, + )?); + let mut submitted = loop { + match rpc.submit_feature(feature.clone()).await { Ok(submitted) => break submitted, Err(err) => { let message = format!("{err:#}"); - let relay_not_ready = message.contains("relay is not ready"); - if relay_not_ready && Instant::now() < retry_deadline { + if Instant::now() < retry_deadline { provider_session_debug(format!( - "spend reservation waiting for local relay readiness; retrying: {message}" + "exact spend reservation did not receive a relay response; retrying: {message}" )); tokio::time::sleep(retry_interval).await; continue; } - return Ok(reject(format!( - "could not reserve user balance on contract before serving: {err}" - ))); + return Ok(( + ProviderSessionDecision::Reject { + code: "RESERVATION_PENDING", + reason: format!( + "the signed spend reservation outcome is unknown after relay failure: {err}" + ), + }, + None, + )); } } }; - if submitted.get("ok").and_then(Value::as_bool) == Some(true) { - return Ok(ProviderSessionDecision::Accept); + loop { + if submitted.get("ok").and_then(Value::as_bool) == Some(true) { + return Ok((ProviderSessionDecision::Accept, recovery_guard)); + } + if confirmed_spend_reservation_matches(rpc, &value).await? { + return Ok((ProviderSessionDecision::Accept, recovery_guard)); + } + if !spend_reservation_submission_pending(&submitted) { + break; + } + if Instant::now() >= retry_deadline { + return Ok(( + ProviderSessionDecision::Reject { + code: "RESERVATION_PENDING", + reason: "the signed spend reservation may have been accepted but its canonical result is still pending; the exact reservation is retained for recovery".to_owned(), + }, + None, + )); + } + provider_session_debug(format!( + "spend reservation append is awaiting canonical confirmation; retrying the exact signed reservation" + )); + tokio::time::sleep( + retry_interval.min(retry_deadline.saturating_duration_since(Instant::now())), + ) + .await; + submitted = match rpc.submit_feature(feature.clone()).await { + Ok(response) => response, + Err(error) if Instant::now() < retry_deadline => { + provider_session_debug(format!( + "exact spend reservation retry did not receive a relay response: {error:#}" + )); + continue; + } + Err(error) => { + return Ok(( + ProviderSessionDecision::Reject { + code: "RESERVATION_PENDING", + reason: format!( + "the signed spend reservation outcome remains unknown after relay failure: {error:#}" + ), + }, + None, + )) + } + }; } + let relay_phase = submitted + .get("phase") + .and_then(Value::as_str) + .filter(|phase| { + matches!( + *phase, + "transport_unavailable" + | "health_proof_unavailable" + | "protocol_incompatible" + | "transport_changed" + | "transport_rejoin_failed" + | "transport_recovering" + | "request_send" + | "admin_ack" + ) + }); let reason = submitted .get("message") .and_then(Value::as_str) @@ -88441,9 +91614,93 @@ async fn provider_session_spend_reservation_decision( .map(str::to_owned) }) .unwrap_or_else(|| submitted.to_string()); - Ok(reject(format!( - "contract spend reservation rejected before serving: {reason}" - ))) + let phase_marker = relay_phase + .map(|phase| format!(" [reservation_relay_phase={phase}]")) + .unwrap_or_default(); + if let Some(guard) = recovery_guard.take() { + provider_failure_recovery::discard(guard)?; + } + Ok(( + reject(format!( + "contract spend reservation rejected before serving{phase_marker}: {reason}" + )), + None, + )) +} + +fn spend_reservation_binding(value: &Value) -> Result { + let voucher: SpendVoucher = serde_json::from_value( + value + .get("voucher") + .cloned() + .context("spend reservation is missing its voucher")?, + ) + .context("spend reservation voucher is invalid")?; + Ok(json!({ + "billing_epoch": voucher.body.billing_epoch, + "reservation_id": voucher.body.reservation_id, + "reservation_expires_after_epoch": voucher.body.reservation_expires_after_epoch, + "reservation_receipt_grace_epochs": voucher.body.reservation_receipt_grace_epochs, + "billing_id": voucher.body.billing_id, + "billing_attempt": voucher.body.billing_attempt, + "session_id": voucher.body.session_id, + "user": voucher.body.user, + "rail": voucher.body.rail, + "provider": voucher.body.provider, + "payout_revision": voucher.body.payout_revision, + "model_id": value.get("model_id").cloned().unwrap_or(Value::Null), + "enclave_id": value.get("enclave_id").cloned().unwrap_or(Value::Null), + })) +} + +async fn confirmed_spend_reservation_matches(rpc: &PeerRpcClient, value: &Value) -> Result { + let binding = spend_reservation_binding(value)?; + let reservation_id = binding["reservation_id"] + .as_str() + .context("spend reservation binding has no identity")?; + let key = format!("receipt/reservation/{reservation_id}"); + let record = rpc.state(Some(&key), Some(true)).await?; + ensure!( + record["confirmed"] == true && record["key"] == key, + "spend reservation confirmation requires exact canonical state" + ); + let reservation = &record["value"]; + if reservation.is_null() { + return Ok(false); + } + ensure!( + reservation["type"] == "receipt_reservation_identity" + && reservation["status"] == "active" + && reservation_binding_matches(&binding, reservation), + "canonical spend reservation does not match the exact signed binding" + ); + Ok(true) +} + +fn spend_reservation_submission_pending(response: &Value) -> bool { + response.get("status").and_then(Value::as_str) == Some("pending") + || response + .get("phase") + .and_then(Value::as_str) + .is_some_and(|phase| { + matches!( + phase, + "transport_unavailable" + | "health_proof_unavailable" + | "protocol_incompatible" + | "transport_changed" + | "transport_rejoin_failed" + | "transport_recovering" + | "request_send" + | "admin_ack" + ) + }) + || response + .get("message") + .and_then(Value::as_str) + .is_some_and(|message| { + message.contains("accepted the append") && message.contains("canonical result") + }) } fn validate_provider_session_voucher_epoch( @@ -88791,15 +92048,18 @@ fn provider_session_open_decision( } let expected_mode = match frame.get("expected_execution_mode") { None | Some(Value::Null) => None, - Some(value) => match serde_json::from_value::(value.clone()) { - Ok(binding) => Some(binding), - Err(_) => return reject("SCHEMA", "invalid execution mode binding".to_owned()), - }, + Some(value) => { + match serde_json::from_value::(value.clone()) { + Ok(binding) => Some(binding), + Err(_) => return reject("SCHEMA", "invalid execution mode binding".to_owned()), + } + } }; if expected_mode != terms.execution_mode { return reject( "EXECUTION_MODE", - "provider execution mode changed or was not negotiated; select a compatible route".to_owned(), + "provider execution mode changed or was not negotiated; select a compatible route" + .to_owned(), ); } let session_id = frame @@ -89105,8 +92365,8 @@ fn contract_upgrade_required_reason(expected: u32, actual: Option) -> Strin fn verify_provider_session_spend_voucher(voucher: &SpendVoucher, user_pubkey: &str) -> Result<()> { ensure!( - voucher.body.schema_version == SESSION_RECEIPT_SCHEMA_VERSION, - "spend voucher schema_version must be {SESSION_RECEIPT_SCHEMA_VERSION}" + voucher.body.schema_version == SPEND_VOUCHER_SCHEMA_VERSION, + "spend voucher schema_version must be {SPEND_VOUCHER_SCHEMA_VERSION}" ); let key_bytes = hex_decode_array::<32>(user_pubkey, "spend voucher user pubkey")?; let sig_bytes = hex_decode_array::<64>(&voucher.user_sig, "spend voucher user signature")?; @@ -89171,17 +92431,17 @@ impl ProviderSessionArtifactCollector { fn push(&mut self, chunk: ArtifactChunk) -> mayhem_engine::Result<()> { let artifact_id = chunk.artifact_id.trim(); if artifact_id.is_empty() { - return Err(EngineError::InvalidConfig( + return Err(EngineError::InvalidOutput( "provider engine emitted artifact chunk with empty id".to_owned(), )); } if chunk.content_type.trim().is_empty() { - return Err(EngineError::InvalidConfig(format!( + return Err(EngineError::InvalidOutput(format!( "provider engine emitted artifact {artifact_id} with empty content type" ))); } if !self.artifacts.contains_key(artifact_id) && self.artifacts.len() >= self.max_artifacts { - return Err(EngineError::InvalidConfig(format!( + return Err(EngineError::InvalidOutput(format!( "provider engine emitted more than {} session artifacts", self.max_artifacts ))); @@ -89190,12 +92450,12 @@ impl ProviderSessionArtifactCollector { .total_bytes .checked_add(chunk.bytes.len()) .ok_or_else(|| { - EngineError::InvalidConfig( + EngineError::InvalidOutput( "provider engine artifact byte count overflow".to_owned(), ) })?; if next_total > self.max_bytes { - return Err(EngineError::InvalidConfig(format!( + return Err(EngineError::InvalidOutput(format!( "provider engine artifacts exceed the session byte budget of {} bytes", self.max_bytes ))); @@ -89211,24 +92471,24 @@ impl ProviderSessionArtifactCollector { final_seen: false, }); if builder.final_seen { - return Err(EngineError::InvalidConfig(format!( + return Err(EngineError::InvalidOutput(format!( "provider engine artifact {artifact_id} emitted data after its final chunk" ))); } if builder.content_type != chunk.content_type { - return Err(EngineError::InvalidConfig(format!( + return Err(EngineError::InvalidOutput(format!( "provider engine artifact {artifact_id} changed content type mid-stream" ))); } if builder.next_index != chunk.index { - return Err(EngineError::InvalidConfig(format!( + return Err(EngineError::InvalidOutput(format!( "provider engine artifact {artifact_id} chunk index gap: expected {}, got {}", builder.next_index, chunk.index ))); } builder.bytes.extend_from_slice(&chunk.bytes); builder.next_index = builder.next_index.checked_add(1).ok_or_else(|| { - EngineError::InvalidConfig(format!( + EngineError::InvalidOutput(format!( "provider engine artifact {artifact_id} chunk index overflow" )) })?; @@ -89296,15 +92556,12 @@ fn provider_visible_tool_calls(tools: &[Value]) -> Result> .collect() } -fn normalize_provider_visible_output_usage( - body: &Value, - output: &mut ProviderSessionOutput, -) -> Result<()> { +fn provider_session_is_text_generation(body: &Value) -> bool { let endpoint_family = body .get("mayhem_contract") .and_then(|value| value.get("endpoint_family")) .and_then(Value::as_str); - let is_text_generation = body.get("kind").is_none() + body.get("kind").is_none() || matches!( endpoint_family, Some( @@ -89313,8 +92570,14 @@ fn normalize_provider_visible_output_usage( | mayhem_proto::ENDPOINT_OPENAI_RESPONSES | mayhem_proto::ENDPOINT_HF_MULTIMODAL_CHAT ) - ); - if !is_text_generation { + ) +} + +fn normalize_provider_visible_output_usage( + body: &Value, + output: &mut ProviderSessionOutput, +) -> Result<()> { + if !provider_session_is_text_generation(body) { return Ok(()); } let tools = provider_visible_tool_calls(&output.tools)?; @@ -89358,7 +92621,7 @@ fn validate_provider_session_output( "MAYHEM_SESSION_MAX_REASSEMBLED_PAYLOAD_BYTES", DEFAULT_SESSION_MAX_REASSEMBLED_PAYLOAD_BYTES, ); - if body.get("kind").and_then(Value::as_str).is_none() { + if provider_session_is_text_generation(body) { let available_tokens = terms.ctx.saturating_sub(output.prompt_tokens); let max_output_tokens = provider_requested_max_output_tokens(body) .unwrap_or(available_tokens) @@ -89373,11 +92636,48 @@ fn validate_provider_session_output( u64::try_from(output.token_ids.len()).unwrap_or(u64::MAX) <= max_output_tokens, "provider token ids exceeded the selected session token budget" ); + if output.finish_reason == "stop" { + ensure!( + !output.content.trim().is_empty() + || !output.tools.is_empty() + || !output.artifacts.is_empty(), + "provider text generation stopped without a visible answer" + ); + } } else { ensure!( output.content.len() <= payload_limit, "provider modality text output exceeds the session payload budget" ); + let endpoint_family = body + .get("mayhem_contract") + .and_then(|value| value.get("endpoint_family")) + .and_then(Value::as_str); + if endpoint_family == Some(mayhem_proto::ENDPOINT_MAYHEM_DECISIONS) { + let result: Value = serde_json::from_str(&output.content) + .context("decision backend returned invalid JSON")?; + ensure!( + result.get("answers").is_some_and(Value::is_object), + "decision backend result is missing an answers object" + ); + ensure!( + result.get("routing").is_some_and(Value::is_object), + "decision backend result is missing a routing object" + ); + ensure!( + output.embeddings.is_none(), + "decision output included embeddings" + ); + ensure!( + output.transcription.is_none(), + "decision output included transcription" + ); + ensure!( + output.artifacts.is_empty(), + "decision output included artifacts" + ); + ensure!(output.tools.is_empty(), "decision output included tools"); + } } if !output.tools.is_empty() { ensure!( @@ -89430,14 +92730,19 @@ fn validate_provider_session_output( "provider artifacts exceed the session byte budget" ); if let Some(tokens) = output.usage_attribution.get("context_input_tokens") { - ensure!(*tokens > 0 && *tokens <= u64::from(terms.ctx), - "provider context input tokens exceed served context"); + ensure!( + *tokens > 0 && *tokens <= u64::from(terms.ctx), + "provider context input tokens exceed served context" + ); } for axis in output.usage_attribution.keys() { ensure!( matches!( axis.as_str(), - "reasoning_output_tokens" | "vision_input_tokens" | "audio_input_tokens" | "context_input_tokens" + "reasoning_output_tokens" + | "vision_input_tokens" + | "audio_input_tokens" + | "context_input_tokens" ), "provider output contains unsupported usage attribution {axis}" ); @@ -89530,13 +92835,22 @@ fn provider_verify_endpoint_request<'a>( .iter() .find(|contract| contract.family == family) .with_context(|| format!("provider model does not expose endpoint family {family}"))?; - let declared_contract_fingerprint = provider_session_request_result( + let legacy_contract_fingerprint = provider_session_request_result( metadata .get("endpoint_contract_fingerprint") .and_then(Value::as_str) .context("provider request missing endpoint contract fingerprint"), )?; - if declared_contract_fingerprint != mayhem_proto::endpoint_contract_fingerprint(contract) { + let contract_fingerprint_matches = metadata + .get("endpoint_contract_canonical_fingerprint") + .and_then(Value::as_str) + .map(|fingerprint| { + fingerprint == mayhem_proto::endpoint_contract_canonical_fingerprint(contract) + }) + .unwrap_or_else(|| { + legacy_contract_fingerprint == mayhem_proto::endpoint_contract_fingerprint(contract) + }); + if !contract_fingerprint_matches { return Err(provider_session_request_error( "provider request endpoint contract fingerprint does not match the local signed catalog", )); @@ -89642,6 +92956,7 @@ fn provider_endpoint_transport_kind(family: &str) -> Result<&'static str> { | mayhem_proto::ENDPOINT_HF_TEXT_TO_AUDIO => Ok("audio_generation"), mayhem_proto::ENDPOINT_MAYHEM_MUSIC_GENERATIONS => Ok("music_generation"), mayhem_proto::ENDPOINT_MAYHEM_COMFY_WORKFLOWS => Ok("workflow_generation"), + mayhem_proto::ENDPOINT_MAYHEM_DECISIONS => Ok("decision"), _ => bail!("unsupported provider endpoint family {family}"), } } @@ -89741,6 +93056,7 @@ fn provider_seal_local_contract_request( "schema_version": 1, "endpoint_family": family, "endpoint_contract_fingerprint": mayhem_proto::endpoint_contract_fingerprint(contract), + "endpoint_contract_canonical_fingerprint": mayhem_proto::endpoint_contract_canonical_fingerprint(contract), "normalized_request_fingerprint": mayhem_proto::endpoint_request_fingerprint(&request), "transport_request_fingerprint": transport_fingerprint, }); @@ -89757,6 +93073,7 @@ fn provider_local_endpoint_family(kind: &str) -> &'static str { "audio_generation" => mayhem_proto::ENDPOINT_MAYHEM_AUDIO_GENERATIONS, "music_generation" => mayhem_proto::ENDPOINT_MAYHEM_MUSIC_GENERATIONS, "workflow_generation" => mayhem_proto::ENDPOINT_MAYHEM_COMFY_WORKFLOWS, + "decision" => mayhem_proto::ENDPOINT_MAYHEM_DECISIONS, _ => mayhem_proto::ENDPOINT_OPENAI_CHAT_COMPLETIONS, } } @@ -90085,6 +93402,32 @@ fn provider_protocol_prompt_tokens( Ok(Some(prompt_tokens)) } +fn provider_engine_tokenize_prompt(terms: &ProviderSessionTerms, body: &Value) -> Result { + let verified = provider_verify_endpoint_request(body, Some(&terms.model_id), &terms.adapter)?; + ensure!( + matches!( + verified.family, + mayhem_proto::ENDPOINT_OPENAI_CHAT_COMPLETIONS + | mayhem_proto::ENDPOINT_OPENAI_COMPLETIONS + | mayhem_proto::ENDPOINT_OPENAI_RESPONSES + | mayhem_proto::ENDPOINT_HF_MULTIMODAL_CHAT + ), + "exact tokenization is unsupported for endpoint family {}", + verified.family + ); + let request = provider_engine_request_from_endpoint_body_with_sampling( + verified.family, + verified.request, + &terms.adapter, + &terms.sampling, + )?; + ensure!( + !request.prompt.is_empty(), + "provider tokenizer received an empty rendered prompt" + ); + Ok(request.prompt) +} + fn provider_engine_session_response_with_sampling_bounded( backend: &mut dyn EngineBackend, expected_model_id: Option<&str>, @@ -90173,10 +93516,14 @@ fn provider_engine_session_response_with_sampling_bounded( cancellation, ) .context("synthesizing provider session speech with mayhem-engine")?; - let artifacts = artifact_chunks.finish()?; - if artifacts.is_empty() { - bail!("provider speech engine produced no audio artifact"); - } + let artifacts = provider_session_output_result(artifact_chunks.finish())?; + provider_session_output_result((|| { + ensure!( + !artifacts.is_empty(), + "provider speech engine produced no audio artifact" + ); + Ok(()) + })())?; return Ok(ProviderSessionOutput { content: String::new(), reasoning_evidence: String::new(), @@ -90212,21 +93559,24 @@ fn provider_engine_session_response_with_sampling_bounded( cancellation, ) .context("generating provider session image artifact with mayhem-engine")?; - ensure!( - output.image_count == image_count && u64::from(output.steps) == steps, - "provider image engine changed the requested image count or step count" - ); - let artifacts = artifact_chunks.finish()?; - if artifacts.is_empty() { - bail!("provider image generation engine produced no image artifacts"); - } - let usage = provider_image_generation_usage(artifacts.len() as u64, steps, width, height); - if artifacts.len() as u32 != image_count { - bail!( + let artifacts = provider_session_output_result((|| { + ensure!( + output.image_count == image_count && u64::from(output.steps) == steps, + "provider image engine changed the requested image count or step count" + ); + let artifacts = artifact_chunks.finish()?; + ensure!( + !artifacts.is_empty(), + "provider image generation engine produced no image artifacts" + ); + ensure!( + artifacts.len() as u32 == image_count, "provider image generation engine produced {} artifact(s), expected {image_count}", artifacts.len() ); - } + Ok(artifacts) + })())?; + let usage = provider_image_generation_usage(artifacts.len() as u64, steps, width, height); return Ok(ProviderSessionOutput { content: String::new(), reasoning_evidence: String::new(), @@ -90250,14 +93600,15 @@ fn provider_engine_session_response_with_sampling_bounded( let mut request = provider_session_request_result( provider_media_generation_request_from_body(endpoint_family, request_body), )?; - let (expected_duration, expected_frames) = - provider_video_output_expectation(verified.contract, &request)?; + let (expected_duration, expected_frames) = provider_session_request_result( + provider_video_output_expectation(verified.contract, &request), + )?; request.frame_count = Some(expected_frames); - provider_canonicalize_video_engine_request( + provider_session_request_result(provider_canonicalize_video_engine_request( verified.contract, &mut request, expected_frames, - )?; + ))?; let mut artifact_chunks = ProviderSessionArtifactCollector::configured(); let output = backend .generate_video( @@ -90266,28 +93617,31 @@ fn provider_engine_session_response_with_sampling_bounded( cancellation, ) .context("generating provider session video artifact with mayhem-engine")?; - ensure!( - output.duration_seconds > 0 && output.frame_count > 0, - "provider video engine returned zero duration or frames" - ); - ensure!( - output.duration_seconds == expected_duration, - "provider video engine returned {} seconds, expected {expected_duration}", - output.duration_seconds - ); - ensure!( - output.frame_count == expected_frames, - "provider video engine returned {} frames, expected {expected_frames}", - output.frame_count - ); - let artifacts = artifact_chunks.finish()?; - ensure!( - !artifacts.is_empty() - && artifacts - .iter() - .all(|artifact| artifact.content_type.starts_with("video/")), - "provider video generation engine produced no valid video artifact" - ); + let artifacts = provider_session_output_result((|| { + ensure!( + output.duration_seconds > 0 && output.frame_count > 0, + "provider video engine returned zero duration or frames" + ); + ensure!( + output.duration_seconds == expected_duration, + "provider video engine returned {} seconds, expected {expected_duration}", + output.duration_seconds + ); + ensure!( + output.frame_count == expected_frames, + "provider video engine returned {} frames, expected {expected_frames}", + output.frame_count + ); + let artifacts = artifact_chunks.finish()?; + ensure!( + !artifacts.is_empty() + && artifacts + .iter() + .all(|artifact| artifact.content_type.starts_with("video/")), + "provider video generation engine produced no valid video artifact" + ); + Ok(artifacts) + })())?; return Ok(ProviderSessionOutput { content: String::new(), reasoning_evidence: String::new(), @@ -90305,10 +93659,12 @@ fn provider_engine_session_response_with_sampling_bounded( } if endpoint_family == mayhem_proto::ENDPOINT_MAYHEM_COMFY_WORKFLOWS { - let workflow_graph = request_body - .get("workflow") - .cloned() - .context("workflow request is missing workflow")?; + let workflow_graph = provider_session_request_result( + request_body + .get("workflow") + .cloned() + .context("workflow request is missing workflow"), + )?; let workflow = provider_session_request_result(provider_comfy_workflow_binding( request_body, workflow_policy, @@ -90337,17 +93693,20 @@ fn provider_engine_session_response_with_sampling_bounded( cancellation, ) .context("running provider Comfy workflow with mayhem-engine")?; - let artifacts = artifact_chunks.finish()?; - ensure!( - !artifacts.is_empty(), - "provider workflow engine produced no artifacts" - ); - ensure!( - u64::from(output.artifact_count) == expected_artifacts - && u64::try_from(artifacts.len()).unwrap_or(u64::MAX) == expected_artifacts, - "provider workflow engine produced {} artifact(s), expected {expected_artifacts}", - artifacts.len() - ); + let artifacts = provider_session_output_result((|| { + let artifacts = artifact_chunks.finish()?; + ensure!( + !artifacts.is_empty(), + "provider workflow engine produced no artifacts" + ); + ensure!( + u64::from(output.artifact_count) == expected_artifacts + && u64::try_from(artifacts.len()).unwrap_or(u64::MAX) == expected_artifacts, + "provider workflow engine produced {} artifact(s), expected {expected_artifacts}", + artifacts.len() + ); + Ok(artifacts) + })())?; return Ok(ProviderSessionOutput { content: String::new(), reasoning_evidence: String::new(), @@ -90377,8 +93736,9 @@ fn provider_engine_session_response_with_sampling_bounded( let automatic_duration_cap = provider_session_request_result( provider_automatic_audio_duration_cap(verified.contract, requested_duration, body), )?; - let input_characters = - provider_media_generation_input_characters(endpoint_family, &request.request)?; + let input_characters = provider_session_request_result( + provider_media_generation_input_characters(endpoint_family, &request.request), + )?; let mut artifact_chunks = ProviderSessionArtifactCollector::configured(); let output = if endpoint_family == mayhem_proto::ENDPOINT_MAYHEM_MUSIC_GENERATIONS { backend @@ -90397,78 +93757,84 @@ fn provider_engine_session_response_with_sampling_bounded( ) .context("generating provider session audio artifact with mayhem-engine")? }; - ensure!( - output.duration_seconds > 0, - "provider audio engine returned zero duration" - ); - if let Some(expected) = requested_duration { + let (artifacts, measured_audio_seconds) = provider_session_output_result((|| { ensure!( - output.duration_seconds.abs_diff(expected) <= 1, - "provider audio engine returned {} seconds, expected approximately {expected}", - output.duration_seconds - ); - } else if let Some(cap) = automatic_duration_cap { - ensure!( - output.duration_seconds <= cap, - "provider audio engine returned {} seconds, exceeding the negotiated automatic-duration cap of {cap} seconds", - output.duration_seconds - ); - } - let artifacts = artifact_chunks.finish()?; - ensure!( - !artifacts.is_empty() - && artifacts - .iter() - .all(|artifact| artifact.content_type.starts_with("audio/")), - "provider audio generation engine produced no valid audio artifact" - ); - let mut measured_audio_seconds = 0_u64; - for artifact in &artifacts { - let metadata = validated_audio_metadata(&artifact.bytes).with_context(|| { - format!( - "provider audio engine returned invalid {} bytes", - artifact.content_type - ) - })?; - ensure!( - provider_audio_content_type_matches_format(&artifact.content_type, metadata.format), - "provider audio engine content type {} does not match the encoded audio format", - artifact.content_type - ); - ensure!( - output - .duration_seconds - .abs_diff(metadata.duration_seconds_ceil) - <= 1, - "provider audio engine reported {} seconds but encoded artifact measures {} seconds", - output.duration_seconds, - metadata.duration_seconds_ceil + output.duration_seconds > 0, + "provider audio engine returned zero duration" ); if let Some(expected) = requested_duration { ensure!( - expected.abs_diff(metadata.duration_seconds_ceil) <= 1, - "provider audio artifact measures {} seconds, expected {expected}", - metadata.duration_seconds_ceil + output.duration_seconds.abs_diff(expected) <= 1, + "provider audio engine returned {} seconds, expected approximately {expected}", + output.duration_seconds ); } else if let Some(cap) = automatic_duration_cap { ensure!( - metadata.duration_seconds_ceil <= cap, - "provider audio artifact measures {} seconds, exceeding the negotiated automatic-duration cap of {cap} seconds", - metadata.duration_seconds_ceil + output.duration_seconds <= cap, + "provider audio engine returned {} seconds, exceeding the negotiated automatic-duration cap of {cap} seconds", + output.duration_seconds ); } - measured_audio_seconds = - measured_audio_seconds.saturating_add(metadata.duration_seconds_ceil); - } - if endpoint_family == mayhem_proto::ENDPOINT_HF_TEXT_TO_AUDIO { + let artifacts = artifact_chunks.finish()?; ensure!( - artifacts.iter().all(|artifact| { - artifact.content_type == "audio/wav" - && wav_sample_rate(&artifact.bytes).is_some() - }), - "HF text-to-audio requires a valid WAV artifact with a declared sample rate" + !artifacts.is_empty() + && artifacts + .iter() + .all(|artifact| artifact.content_type.starts_with("audio/")), + "provider audio generation engine produced no valid audio artifact" ); - } + let mut measured_audio_seconds = 0_u64; + for artifact in &artifacts { + let metadata = validated_audio_metadata(&artifact.bytes).with_context(|| { + format!( + "provider audio engine returned invalid {} bytes", + artifact.content_type + ) + })?; + ensure!( + provider_audio_content_type_matches_format( + &artifact.content_type, + metadata.format + ), + "provider audio engine content type {} does not match the encoded audio format", + artifact.content_type + ); + ensure!( + output + .duration_seconds + .abs_diff(metadata.duration_seconds_ceil) + <= 1, + "provider audio engine reported {} seconds but encoded artifact measures {} seconds", + output.duration_seconds, + metadata.duration_seconds_ceil + ); + if let Some(expected) = requested_duration { + ensure!( + expected.abs_diff(metadata.duration_seconds_ceil) <= 1, + "provider audio artifact measures {} seconds, expected {expected}", + metadata.duration_seconds_ceil + ); + } else if let Some(cap) = automatic_duration_cap { + ensure!( + metadata.duration_seconds_ceil <= cap, + "provider audio artifact measures {} seconds, exceeding the negotiated automatic-duration cap of {cap} seconds", + metadata.duration_seconds_ceil + ); + } + measured_audio_seconds = + measured_audio_seconds.saturating_add(metadata.duration_seconds_ceil); + } + if endpoint_family == mayhem_proto::ENDPOINT_HF_TEXT_TO_AUDIO { + ensure!( + artifacts.iter().all(|artifact| { + artifact.content_type == "audio/wav" + && wav_sample_rate(&artifact.bytes).is_some() + }), + "HF text-to-audio requires a valid WAV artifact with a declared sample rate" + ); + } + Ok((artifacts, measured_audio_seconds)) + })())?; return Ok(ProviderSessionOutput { content: String::new(), reasoning_evidence: String::new(), @@ -90492,7 +93858,6 @@ fn provider_engine_session_response_with_sampling_bounded( let inputs = provider_session_request_result(provider_embedding_input_texts_from_body( request_body, ))?; - let prompt_tokens = provider_embedding_input_token_count(&inputs); let dimensions = provider_session_request_result( request_body .get("dimensions") @@ -90506,6 +93871,14 @@ fn provider_engine_session_response_with_sampling_bounded( cancellation, ) .context("generating provider session embeddings with mayhem-engine")?; + let prompt_tokens = u64::from(output.usage.prompt_tokens); + provider_session_output_result((|| { + ensure!( + prompt_tokens > 0, + "embedding backend returned zero prompt tokens for a non-empty request" + ); + Ok(()) + })())?; return Ok(ProviderSessionOutput { content: String::new(), reasoning_evidence: String::new(), @@ -90522,24 +93895,56 @@ fn provider_engine_session_response_with_sampling_bounded( }); } - ensure!( - matches!( - endpoint_family, - mayhem_proto::ENDPOINT_OPENAI_CHAT_COMPLETIONS - | mayhem_proto::ENDPOINT_OPENAI_COMPLETIONS - | mayhem_proto::ENDPOINT_OPENAI_RESPONSES - | mayhem_proto::ENDPOINT_HF_MULTIMODAL_CHAT - ), - "provider endpoint family {endpoint_family} has no engine execution path" - ); + if endpoint_family == mayhem_proto::ENDPOINT_MAYHEM_DECISIONS { + let request = + provider_session_request_result(provider_decision_request_from_body(request_body))?; + let output = backend + .decide(request, cancellation) + .context("running provider typed decision with mayhem-engine")?; + let content = provider_session_output_result( + serde_json::to_string(&output.result).context("serializing provider decision result"), + )?; + let prompt_tokens = u64::from(output.usage.prompt_tokens); + let completion_tokens = metered_output_units(&content, "", &[]); + return Ok(ProviderSessionOutput { + content, + reasoning_evidence: String::new(), + tools: Vec::new(), + embeddings: None, + transcription: None, + artifacts: Vec::new(), + finish_reason: "stop".to_owned(), + prompt_tokens, + completion_tokens, + token_ids: Vec::new(), + usage: ReceiptUsage::text(prompt_tokens, completion_tokens), + usage_attribution: BTreeMap::new(), + }); + } - let tool_mode = provider_engine_tool_request(request_body, adapter)?; - let mut request = provider_engine_request_from_endpoint_body_with_sampling( - endpoint_family, - request_body, - adapter, - sampling, - )?; + provider_session_request_result((|| { + ensure!( + matches!( + endpoint_family, + mayhem_proto::ENDPOINT_OPENAI_CHAT_COMPLETIONS + | mayhem_proto::ENDPOINT_OPENAI_COMPLETIONS + | mayhem_proto::ENDPOINT_OPENAI_RESPONSES + | mayhem_proto::ENDPOINT_HF_MULTIMODAL_CHAT + ), + "provider endpoint family {endpoint_family} has no engine execution path" + ); + Ok(()) + })())?; + + let tool_mode = + provider_session_request_result(provider_engine_tool_request(request_body, adapter))?; + let mut request = + provider_session_request_result(provider_engine_request_from_endpoint_body_with_sampling( + endpoint_family, + request_body, + adapter, + sampling, + ))?; if let Some(cap) = output_token_cap { request.max_new_tokens = request.max_new_tokens.min(cap.max(1)); } @@ -90550,42 +93955,64 @@ fn provider_engine_session_response_with_sampling_bounded( request.grammar.as_ref(), Some(GrammarSpec::ToolCall { .. } | GrammarSpec::JsonSchema { .. }) ); - let reasoning_output_mode = - provider_constrained_reasoning_output_mode(reasoning_output_mode, json_grammar_enforced); + let reasoning_output_mode = provider_constrained_reasoning_output_mode( + if backend.backend_id() == "openai-compatible" + && reasoning_output_mode == ProviderReasoningOutputMode::StripPrefilled + { + // This bridge emits only when native reasoning_content is + // present. A content-only native reply has no prefilled thinking + // span; suppressing until would erase its entire answer. + ProviderReasoningOutputMode::StripTagged + } else { + reasoning_output_mode + }, + json_grammar_enforced, + ); let mut reasoning_stream_filter = ProviderReasoningOutputFilter::with_delimiters(reasoning_output_mode, reasoning_delimiters); - let mut tool_stream_filter = tool_mode.as_ref().map(|mode| - provider_output_stream::OutputStream::new(mode.strategy, mode.tools.clone())); + let mut tool_stream_filter = tool_mode + .as_ref() + .map(|mode| provider_output_stream::OutputStream::new(mode.strategy, mode.tools.clone())); let mut token_ids = Vec::new(); let mut artifact_chunks = ProviderSessionArtifactCollector::configured(); + let mut live_stream_error = None; // Existing text models keep the established request-text estimate. A signed // tools-only endpoint is metered from the model-visible query and selected // tools so buyer and provider agree without charging transport metadata. let estimated_prompt_tokens = rough_text_tokens(&provider_session_prompt_text(body, adapter)); - let output = backend - .generate_with_artifacts( - request, - &mut |chunk: mayhem_engine::TokenChunk| { - if let Some(stream) = live_stream.as_deref_mut() { - let filtered = reasoning_stream_filter.push_split(&chunk.text); - let mut visible_chunk = chunk.clone(); - visible_chunk.text = if let Some(filter) = tool_stream_filter.as_mut() { - let delta = filter.push(&filtered.visible); - stream.append_tool_deltas(delta.tools); - delta.text - } else { filtered.visible }; - stream.on_token(visible_chunk, &filtered.hidden, - protocol_prompt_tokens.unwrap_or(estimated_prompt_tokens)) - .map_err(|err| mayhem_engine::EngineError::InvalidConfig( - format!("provider live stream failed: {err:#}")))?; + let generated = backend.generate_with_artifacts( + request, + &mut |chunk: mayhem_engine::TokenChunk| { + if let Some(stream) = live_stream.as_deref_mut() { + let filtered = reasoning_stream_filter.push_split(&chunk.text); + let mut visible_chunk = chunk.clone(); + visible_chunk.text = if let Some(filter) = tool_stream_filter.as_mut() { + let delta = filter.push(&filtered.visible); + stream.append_tool_deltas(delta.tools); + delta.text + } else { + filtered.visible + }; + if let Err(error) = stream.on_token( + visible_chunk, + &filtered.hidden, + protocol_prompt_tokens.unwrap_or(estimated_prompt_tokens), + ) { + let message = format!("provider live stream failed: {error:#}"); + live_stream_error = Some(error); + return Err(mayhem_engine::EngineError::InvalidConfig(message)); } - token_ids.push(chunk.token_id); - Ok(()) - }, - &mut |chunk: ArtifactChunk| artifact_chunks.push(chunk), - cancellation, - ) - .context("generating provider session response with mayhem-engine")?; + } + token_ids.push(chunk.token_id); + Ok(()) + }, + &mut |chunk: ArtifactChunk| artifact_chunks.push(chunk), + cancellation, + ); + if let Some(error) = live_stream_error { + return Err(error.context("streaming provider session response")); + } + let output = generated.context("generating provider session response with mayhem-engine")?; if let Some(stream) = live_stream.as_deref_mut() { let mut trailing = reasoning_stream_filter.finish_split(); if let Some(filter) = tool_stream_filter.as_mut() { @@ -90595,7 +94022,7 @@ fn provider_engine_session_response_with_sampling_bounded( } stream.append_filtered_text(trailing); } - let artifacts = artifact_chunks.finish()?; + let artifacts = provider_session_output_result(artifact_chunks.finish())?; let mut tools = tool_mode .as_ref() .and_then(|mode| { @@ -90629,16 +94056,21 @@ fn provider_engine_session_response_with_sampling_bounded( ))); } let streamed_content = if let (Some(stream), Some(filter)) = - (live_stream.as_deref_mut(), tool_stream_filter.as_mut()) { - ensure!(filter.emitted_count() <= tools.len(), - "provider streamed a tool call that failed final validation"); + (live_stream.as_deref_mut(), tool_stream_filter.as_mut()) + { + validate_streamed_tool_call_count(filter.emitted_count(), tools.len())?; for (index, call) in tools.iter_mut().enumerate().take(filter.emitted_count()) { call["id"] = json!(stream.tool_stream_id(index)); } let tail = filter.finish_text(!tools.is_empty()); - stream.append_filtered_text(ProviderReasoningFilteredText { visible: tail, hidden: String::new() }); + stream.append_filtered_text(ProviderReasoningFilteredText { + visible: tail, + hidden: String::new(), + }); Some(filter.text.clone()) - } else { None }; + } else { + None + }; let completion_tokens = u64::from(output.usage.completion_tokens); let reasoning_tokens = u64::from(output.usage.reasoning_tokens).min(completion_tokens); let vision_tokens = u64::from(output.usage.vision_tokens); @@ -90651,7 +94083,10 @@ fn provider_engine_session_response_with_sampling_bounded( // Negotiated, signed context telemetry is independent of canonical billing // units. Older gateways reject unknown attribution axes, so opt in explicitly. if provider_request_supports_context_usage(body) && output.usage.prompt_tokens > 0 { - usage_attribution.insert("context_input_tokens".to_owned(), u64::from(output.usage.prompt_tokens)); + usage_attribution.insert( + "context_input_tokens".to_owned(), + u64::from(output.usage.prompt_tokens), + ); } if reasoning_tokens > 0 { usage_attribution.insert("reasoning_output_tokens".to_owned(), reasoning_tokens); @@ -90672,15 +94107,25 @@ fn provider_engine_session_response_with_sampling_bounded( reasoning_output_mode, reasoning_delimiters, ); + if tool_mode.is_none() && provider_wants_json(request_body) { + let schema = provider_response_json_schema(request_body); + mayhem_gateway::structured_schema::validate_output(&schema, &filtered_output.visible) + .map_err(|error| provider_session_output_error(error.to_string()))?; + } Ok(ProviderSessionOutput { usage: provider_chat_receipt_usage(request_body, billed_prompt_tokens, completion_tokens), - content: streamed_content.unwrap_or_else(|| if tools.is_empty() { - filtered_output.visible - } else { - // Native Qwen permits commentary preceding a tool call. Preserve it - // in both delivery modes so usage does not depend on stream=true. - filtered_output.visible.split_once("") - .map(|(text, _)| text.to_owned()).unwrap_or_default() + content: streamed_content.unwrap_or_else(|| { + if tools.is_empty() { + filtered_output.visible + } else { + provider_engine_visible_content_before_tools( + &filtered_output.visible, + tool_mode + .as_ref() + .expect("tool output requires tool mode") + .strategy, + ) + } }), reasoning_evidence: filtered_output.hidden, tools, @@ -91114,11 +94559,21 @@ fn provider_image_generation_request_from_body( let mut request = EngineImageGenerationRequest::new(prompt); request.input_reference = body .get("input_reference") - .map(|value| value.as_str().map(str::to_owned).context("input_reference must be a data URL string")) + .map(|value| { + value + .as_str() + .map(str::to_owned) + .context("input_reference must be a data URL string") + }) .transpose()?; request.strength = body .get("strength") - .map(|value| value.as_f64().map(|value| value as f32).context("strength must be a number")) + .map(|value| { + value + .as_f64() + .map(|value| value as f32) + .context("strength must be a number") + }) .transpose()?; request.image_count = image_count; request.steps = u32::try_from(steps).context("image_generation steps overflowed u32")?; @@ -91659,8 +95114,11 @@ fn provider_engine_request_from_endpoint_body_with_sampling( } } } else if provider_wants_json(body) { + let schema = provider_response_json_schema(body); + let generation_schema = mayhem_gateway::structured_schema::prepare(&schema) + .map_err(|error| provider_session_request_error(error.to_string()))?; request.grammar = Some(GrammarSpec::JsonSchema { - schema: provider_response_json_schema(body), + schema: generation_schema, }); } Ok(request) @@ -92617,6 +96075,28 @@ fn provider_engine_tool_call_outputs( Some(calls) } +fn provider_engine_visible_content_before_tools( + text: &str, + strategy: ProviderEngineToolStrategy, +) -> String { + match strategy { + // Native Qwen permits commentary preceding a tool call. Preserve it + // in both delivery modes so usage does not depend on stream=true. + ProviderEngineToolStrategy::QwenFunctionXml => text + .split_once("") + .map(|(content, _)| content.to_owned()) + .unwrap_or_default(), + ProviderEngineToolStrategy::OpenAiToolCalls => { + provider_openai_tool_call_outputs_with_prefix(text) + .map(|(prefix, _)| text[..prefix].to_owned()) + .unwrap_or_default() + } + ProviderEngineToolStrategy::MayhemJson | ProviderEngineToolStrategy::GemmaFunctionCall => { + String::new() + } + } +} + fn validate_provider_engine_tool_call_outputs(calls: &[Value], tools: &[ToolSpec]) -> Result<()> { for call in calls { let name = call.get("name").and_then(Value::as_str).ok_or_else(|| { @@ -92698,14 +96178,36 @@ fn provider_mayhem_json_tool_call_value(value: &Value) -> Option { } fn provider_openai_tool_call_outputs(text: &str) -> Option> { - let value: Value = serde_json::from_str(text.trim()).ok()?; + provider_openai_tool_call_outputs_with_prefix(text).map(|(_, calls)| calls) +} + +fn provider_openai_tool_call_outputs_with_prefix(text: &str) -> Option<(usize, Vec)> { + if let Ok(value) = serde_json::from_str::(text.trim()) { + return provider_openai_tool_call_outputs_from_value(&value).map(|calls| (0, calls)); + } + let trimmed = text.trim_end(); + for (index, _) in trimmed.rmatch_indices('{') { + let Ok(value) = serde_json::from_str::(&trimmed[index..]) else { + continue; + }; + if value.get("tool_calls").and_then(Value::as_array).is_none() { + continue; + } + if let Some(calls) = provider_openai_tool_call_outputs_from_value(&value) { + return Some((index, calls)); + } + } + None +} + +fn provider_openai_tool_call_outputs_from_value(value: &Value) -> Option> { if let Some(calls) = value.get("tool_calls").and_then(Value::as_array) { if calls.is_empty() { return None; } return calls.iter().map(provider_openai_tool_call_value).collect(); } - Some(vec![provider_openai_tool_call_value(&value)?]) + Some(vec![provider_openai_tool_call_value(value)?]) } fn provider_openai_tool_call_value(call: &Value) -> Option { @@ -93273,6 +96775,12 @@ fn is_hex_len(value: &str, len: usize) -> bool { value.len() == len && value.as_bytes().iter().all(|byte| byte.is_ascii_hexdigit()) } +fn is_lowercase_hex_len(value: &str, len: usize) -> bool { + value.len() == len + && value.bytes().all(|byte| byte.is_ascii_hexdigit()) + && value == value.to_ascii_lowercase() +} + fn is_safe_key_part(value: &str) -> bool { !value.is_empty() && !value.starts_with('-') @@ -100898,6 +104406,136 @@ status: linked let _ = fs::remove_dir_all(temp); } + #[test] + fn provider_comfy_memory_counts_bounded_optional_part_residency() { + let temp = test_temp_dir("mayhem-provider-comfy-bounded-residency"); + let source_dir = temp.join("source"); + let layout_dir = temp.join("layout"); + let payload_dir = temp.join("payloads"); + let cache_dir = temp.join("cache"); + let home = temp.join("home"); + fs::create_dir_all(&source_dir).unwrap(); + fs::create_dir_all(&payload_dir).unwrap(); + let mut records = Vec::new(); + for (index, size) in [100_usize, 10, 20, 30, 40, 50].into_iter().enumerate() { + let payload_path = payload_dir.join(format!("part-{index}.bin")); + fs::write(&payload_path, vec![index as u8; size]).unwrap(); + let mut record = test_comfy_part_record_for_payload( + if index == 0 { + "base.safetensors" + } else { + [ + "", + "lora-1.safetensors", + "lora-2.safetensors", + "lora-3.safetensors", + "lora-4.safetensors", + "lora-5.safetensors", + ][index] + }, + &payload_path, + 8, + ); + record.part_type = if index == 0 { "checkpoint" } else { "lora" }.to_owned(); + record.part_id = + mayhem_proto::derive_comfy_part_id(&record.part_type, &record.name, &record.sha256); + record.validate().unwrap(); + let record_path = source_dir.join(format!("record-{index}.json")); + write_json_file(&record_path, &record).unwrap(); + records.push((record, record_path)); + } + admin_parts_build_index(&AdminPartsBuildIndexArgs { + records: records.iter().map(|(_, path)| path.clone()).collect(), + output_dir: layout_dir.clone(), + index_ver: 25, + blessed_runtimes: vec!["comfyui-v0.30.1".to_owned()], + whitelist_ver: 1, + outcome_classes_ver: 1, + }) + .unwrap(); + provider_parts_add(ProviderPartsPullArgs { + home: Some(home.clone()), + layout_dir, + part_ids: records + .iter() + .map(|(record, _)| record.part_id.clone()) + .collect(), + all: false, + payload_dir: Some(payload_dir), + hf_token_file: None, + source_token_file: None, + cache_dir: Some(cache_dir), + disk_reserve: None, + offline: true, + require_payload: false, + chunk_size: 8, + json: true, + }) + .unwrap(); + let mut model = test_catalog(&"aa".repeat(32)).models[0].clone(); + model.adapter.endpoint_families = vec![mayhem_proto::endpoint_family_contract_template( + mayhem_proto::ENDPOINT_MAYHEM_COMFY_WORKFLOWS, + ) + .unwrap()]; + let lora_names = records[1..] + .iter() + .map(|(record, _)| record.name.clone()) + .collect::>(); + let graph_constraints = serde_json::from_value(json!({ + "output_role": "base", + "roles": { + "base": { + "class_type": "UNETLoader", + "min_count": 1, + "max_count": 1, + "inputs": { + "unet_name": { + "value_type": "part", + "required": true, + "part_type": "checkpoint", + "part_names": [records[0].0.name.clone()] + } + } + }, + "user_lora": { + "class_type": "LoraLoaderModelOnly", + "min_count": 0, + "max_count": 2, + "inputs": { + "lora_name": { + "value_type": "part", + "required": true, + "part_type": "lora", + "part_names": lora_names, + "distinct": true + } + } + } + } + })) + .unwrap(); + model.workflow = Some(mayhem_proto::ComfyWorkflowCatalogPolicy { + whitelisted_nodes: vec!["UNETLoader".to_owned(), "LoraLoaderModelOnly".to_owned()], + parts: records + .iter() + .map(|(record, _)| mayhem_proto::ComfyWorkflowPartRef { + part_id: record.part_id.clone(), + name: record.name.clone(), + part_type: record.part_type.clone(), + sha256: record.sha256.clone(), + scale: None, + }) + .collect(), + graph_constraints: Some(graph_constraints), + ..mayhem_proto::ComfyWorkflowCatalogPolicy::default() + }); + assert_eq!( + provider_comfy_workflow_inventory_resident_bytes(Some(&home), &model).unwrap(), + 100 + 50 + 40 + ); + let _ = fs::remove_dir_all(temp); + } + #[test] fn provider_comfy_inventory_feeds_custom_node_runtime_mounts() { let temp = test_temp_dir("mayhem-provider-comfy-inventory-custom-node"); @@ -103364,6 +107002,67 @@ status: linked ); } + #[test] + fn same_currency_payout_uses_fresh_quote_only_as_valuation_evidence() { + let output = json!({ + "role": "provider", + "provider": "aa".repeat(32), + "payout_revision": "bb".repeat(32), + "to": "acct_provider", + "economic_op_id": "cc".repeat(32), + "output_index": 0, + "liability_au": "2651083256300000869", + "paid_au": "2648476605508831339", + "rounding_au": "2606650791169530", + "dust_au": "2606650791169530", + "source_currency": "eur", + "source_amount_minor": "229", + "destination_currency": "eur", + "destination_amount_min_minor": "229", + "destination_amount_max_minor": "229", + }); + + let mut rates = BTreeMap::new(); + rates.insert( + "usd".to_owned(), + StripeFxRate { + exchange_rate: "0.8".to_owned(), + exchange_ratio: parse_exact_decimal_ratio("0.8").unwrap(), + base_rate: "0.8".to_owned(), + base_ratio: parse_exact_decimal_ratio("0.8").unwrap(), + }, + ); + let quote = StripeFxQuote { + id: "fxq_fresh_valuation".to_owned(), + created: 100, + expires_at: Some(800), + lock_duration: "five_minutes".to_owned(), + lock_status: "active".to_owned(), + to_currency: "eur".to_owned(), + usage_type: "transfer".to_owned(), + usage_destination: Some("acct_provider".to_owned()), + rates, + }; + + validate_locked_stripe_fx_quote("e, "five_minutes", 500, false).unwrap(); + let execution = ensure_canonical_fiat_quote_matches_output(&output, Some("e)).unwrap(); + assert_eq!(execution.source_amount_minor, 229); + assert_eq!(execution.destination_currency, "eur"); + assert!(targeted_fiat_attempt_request(&output, 338, &"dd".repeat(32), None).is_err()); + let request = + targeted_fiat_attempt_request(&output, 338, &"dd".repeat(32), Some("e)).unwrap(); + assert_eq!(request["fx_quote_id"], "fxq_fresh_valuation"); + assert!(request["fx_quote_hash"].as_str().is_some()); + assert_eq!( + targeted_fiat_quote_idempotency_key(&"cc".repeat(32), 1, 239), + targeted_fiat_quote_idempotency_key(&"cc".repeat(32), 1, 100) + ); + assert_ne!( + targeted_fiat_quote_idempotency_key(&"cc".repeat(32), 1, 240), + targeted_fiat_quote_idempotency_key(&"cc".repeat(32), 1, 239) + ); + } + #[test] fn admin_epoch_payloads_accept_recomputed_outputs() { let roots = json!({ @@ -103722,9 +107421,10 @@ status: linked #[test] fn launch_contract_versions_are_pinned_for_m1_gating() { - assert_eq!(CONTRACT_VERSION, 25); + assert_eq!(CONTRACT_VERSION, 28); assert_eq!(CONTRACT_SIGNING_MESSAGE_VERSION, 2); - assert_eq!(SESSION_RECEIPT_SCHEMA_VERSION, 11); + assert_eq!(SESSION_RECEIPT_SCHEMA_VERSION, 12); + assert_eq!(SPEND_VOUCHER_SCHEMA_VERSION, 11); } #[test] @@ -106910,11 +110610,16 @@ status: linked selected.enclave.caps = json!({"vllm_gpu_memory_utilization_pct":40}); selected.verdict.backend = "vllm".to_owned(); let args = test_provider_start_args(); - let first = provider_memory_budget(&hardware, &selected.verdict, &selected.enclave, &args).unwrap(); + let first = + provider_memory_budget(&hardware, &selected.verdict, &selected.enclave, &args).unwrap(); hardware.memory.available_bytes = Some(80 * GIB_BYTES); - let restarted = provider_memory_budget(&hardware, &selected.verdict, &selected.enclave, &args).unwrap(); + let restarted = + provider_memory_budget(&hardware, &selected.verdict, &selected.enclave, &args).unwrap(); assert!(restarted.worker_limit_bytes < first.worker_limit_bytes); - assert_eq!(restarted.worker_address_space_limit_bytes, first.worker_address_space_limit_bytes); + assert_eq!( + restarted.worker_address_space_limit_bytes, + first.worker_address_space_limit_bytes + ); assert!(restarted.worker_address_space_limit_bytes > 92 * GIB_BYTES); assert!(restarted.worker_limit_bytes < 80 * GIB_BYTES); } @@ -106991,6 +110696,7 @@ status: linked engine: "vllm".to_owned(), topology: None, independent_dispatch: true, + max_concurrent: None, request_modalities: vec![vec!["text".to_owned()]], proof_sha256: "ab".repeat(32), }; @@ -107024,16 +110730,84 @@ status: linked ); } + #[test] + fn shared_vllm_embedding_capacity_uses_scheduler_instead_of_replica_count() { + let mut selected = + test_auto_fit_candidate('e', "test/vllm-embedding", "embedding", 4, 96, 1, 6.0); + selected.enclave.backend = "vllm".to_owned(); + selected.artifact.engine = "vllm".to_owned(); + selected.enclave.caps = json!({"max_batch_size": 8}); + selected.verdict.backend = "vllm".to_owned(); + selected.verdict.max_sessions = 1; + selected.feasibility.memory_budget.total_bytes = 96 * GIB_BYTES; + // Mirror the live failure shape: the admitted allocation has room for + // one full-context decoder KV estimate, but not eight. Embeddings use a + // shared scheduler and must not be reduced by that generation-only + // estimate. + selected.feasibility.memory_budget.budget_bytes = 13 * GIB_BYTES; + selected.feasibility.estimated_required_bytes = 12 * GIB_BYTES; + selected.feasibility.estimated_kv_bytes = 2 * GIB_BYTES; + + let profile = catalog::CatalogGenerationExecutionProfile { + schema_version: 1, + engine: "vllm".to_owned(), + topology: Some(mayhem_proto::GenerationExecutionTopology::SharedWorker), + independent_dispatch: true, + max_concurrent: None, + request_modalities: vec![vec!["embedding".to_owned()]], + proof_sha256: "ab".repeat(32), + }; + let mut args = test_provider_start_args(); + args.max_sessions = Some(8); + args.vllm_memory_utilization = Some(13); + + assert_eq!( + provider_vllm_generation_execution_capacity( + &selected.artifact, + Some(&profile), + &selected.enclave.caps, + &selected.verdict, + &args, + &selected.feasibility, + ) + .unwrap(), + 8 + ); + + args.max_sessions = Some(16); + assert_eq!( + provider_vllm_generation_execution_capacity( + &selected.artifact, + Some(&profile), + &selected.enclave.caps, + &selected.verdict, + &args, + &selected.feasibility, + ) + .unwrap(), + 8, + "signed scheduler capacity remains the hard ceiling" + ); + } + #[test] fn vllm_execution_mode_budget_uses_only_a_safe_tp_device_capacity() { let mut hardware = test_hardware(FixtureProfile::LinuxNvidia); - let device = hardware.gpus.iter().find(|gpu| gpu.vendor == GpuVendor::Nvidia).unwrap().clone(); + let device = hardware + .gpus + .iter() + .find(|gpu| gpu.vendor == GpuVendor::Nvidia) + .unwrap() + .clone(); hardware.gpus = vec![device.clone(), device]; hardware.gpus[0].memory_bytes = Some(24 * GIB_BYTES); hardware.gpus[1].memory_bytes = Some(80 * GIB_BYTES); let original = ProviderMemoryPool { - pool: "nvidia_dedicated_memory".to_owned(), source: "test".to_owned(), - unified: false, total_bytes: 104 * GIB_BYTES, available_bytes: 104 * GIB_BYTES, + pool: "nvidia_dedicated_memory".to_owned(), + source: "test".to_owned(), + unified: false, + total_bytes: 104 * GIB_BYTES, + available_bytes: 104 * GIB_BYTES, }; for (tp, expected) in [(1, 24), (2, 48)] { let mut pool = original.clone(); @@ -107046,12 +110820,17 @@ status: linked assert_eq!(reordered.total_bytes, pool.total_bytes); } for tp in [0, 3] { - assert!(scope_vllm_execution_mode_memory_pool(&mut original.clone(), &hardware, tp).is_err()); + assert!( + scope_vllm_execution_mode_memory_pool(&mut original.clone(), &hardware, tp) + .is_err() + ); } hardware.gpus[0].memory_bytes = None; hardware.gpus[0].unified_memory = false; hardware.host.arch = "x86_64".to_owned(); - assert!(scope_vllm_execution_mode_memory_pool(&mut original.clone(), &hardware, 1).is_err()); + assert!( + scope_vllm_execution_mode_memory_pool(&mut original.clone(), &hardware, 1).is_err() + ); hardware.gpus[0].memory_bytes = Some(24 * GIB_BYTES); let mut unified = original; unified.unified = true; @@ -107067,24 +110846,48 @@ status: linked for (required_gib, target, expected) in [(30, 35, 2), (10, 15, 5), (60, 65, 1)] { selected.feasibility.estimated_required_bytes = required_gib * GIB_BYTES; args.vllm_memory_utilization = Some(target); - assert_eq!(provider_vllm_generation_execution_capacity( - &selected.artifact, Some(&profile), &selected.enclave.caps, - &selected.verdict, &args, &selected.feasibility, - ).unwrap(), expected); + assert_eq!( + provider_vllm_generation_execution_capacity( + &selected.artifact, + Some(&profile), + &selected.enclave.caps, + &selected.verdict, + &args, + &selected.feasibility, + ) + .unwrap(), + expected + ); } selected.feasibility.estimated_required_bytes = 30 * GIB_BYTES; args.vllm_memory_utilization = Some(35); args.max_sessions = Some(1); - assert_eq!(provider_vllm_generation_execution_capacity( - &selected.artifact, Some(&profile), &selected.enclave.caps, - &selected.verdict, &args, &selected.feasibility, - ).unwrap(), 1); + assert_eq!( + provider_vllm_generation_execution_capacity( + &selected.artifact, + Some(&profile), + &selected.enclave.caps, + &selected.verdict, + &args, + &selected.feasibility, + ) + .unwrap(), + 1 + ); args.max_sessions = None; selected.enclave.caps = json!({"max_batch_size": 1}); - assert_eq!(provider_vllm_generation_execution_capacity( - &selected.artifact, Some(&profile), &selected.enclave.caps, - &selected.verdict, &args, &selected.feasibility, - ).unwrap(), 1); + assert_eq!( + provider_vllm_generation_execution_capacity( + &selected.artifact, + Some(&profile), + &selected.enclave.caps, + &selected.verdict, + &args, + &selected.feasibility, + ) + .unwrap(), + 1 + ); } fn test_isolated_generation_profile() -> catalog::CatalogGenerationExecutionProfile { @@ -107093,13 +110896,15 @@ status: linked engine: "vllm".to_owned(), topology: Some(mayhem_proto::GenerationExecutionTopology::IsolatedWorkers), independent_dispatch: true, + max_concurrent: None, request_modalities: vec![vec!["text".to_owned()]], proof_sha256: "ab".repeat(32), } } fn test_isolated_vllm_candidate() -> ProviderCandidate { - let mut selected = test_auto_fit_candidate('c', "test/vllm-isolated", "text", 30, 80, 1, 30.0); + let mut selected = + test_auto_fit_candidate('c', "test/vllm-isolated", "text", 30, 80, 1, 30.0); selected.enclave.backend = "vllm".to_owned(); selected.artifact.engine = "vllm".to_owned(); selected.enclave.caps = json!({}); @@ -107121,21 +110926,49 @@ status: linked assert_eq!(plan.target_pct, 35); reserve_provider_vllm_replica_memory(&mut selected.feasibility, 2, plan).unwrap(); selected.generation_execution_capacity = 2; - assert_eq!(selected.feasibility.estimated_required_bytes, 70 * GIB_BYTES); + assert_eq!( + selected.feasibility.estimated_required_bytes, + 70 * GIB_BYTES + ); assert_eq!(selected.feasibility.estimated_weights_bytes, 40 * GIB_BYTES); assert_eq!(selected.feasibility.estimated_kv_bytes, 12 * GIB_BYTES); assert_eq!(selected.feasibility.estimated_overhead_bytes, 6 * GIB_BYTES); assert_eq!(selected.feasibility.estimated_media_bytes, 2 * GIB_BYTES); - assert_eq!(selected.feasibility.replica_allocation.unwrap().per_worker_required_bytes, 30 * GIB_BYTES); - assert_eq!(provider_vllm_memory_utilization(&selected, None).unwrap().target_pct, 35); + assert_eq!( + selected + .feasibility + .replica_allocation + .unwrap() + .per_worker_required_bytes, + 30 * GIB_BYTES + ); + assert_eq!( + provider_vllm_memory_utilization(&selected, None) + .unwrap() + .target_pct, + 35 + ); assert!(provider_vllm_memory_utilization(&selected, Some(36)).is_err()); assert!(provider_vllm_memory_utilization(&selected, Some(29)).is_err()); - assert_eq!(provider_vllm_memory_utilization(&selected, Some(30)).unwrap().target_pct, 30); + assert_eq!( + provider_vllm_memory_utilization(&selected, Some(30)) + .unwrap() + .target_pct, + 30 + ); let home = test_temp_dir("isolated-vllm-claim"); - let claim = write_provider_memory_claim(&home, &"ab".repeat(32), &selected).unwrap().unwrap(); - assert_eq!(read_provider_memory_claimed_bytes(Some(&home), false).unwrap(), 70 * GIB_BYTES); + let claim = write_provider_memory_claim(&home, &"ab".repeat(32), &selected) + .unwrap() + .unwrap(); + assert_eq!( + read_provider_memory_claimed_bytes(Some(&home), false).unwrap(), + 70 * GIB_BYTES + ); drop(claim); - assert_eq!(read_provider_memory_claimed_bytes(Some(&home), false).unwrap(), 0); + assert_eq!( + read_provider_memory_claimed_bytes(Some(&home), false).unwrap(), + 0 + ); fs::remove_dir_all(home).unwrap(); } @@ -107144,16 +110977,31 @@ status: linked for (count, target) in [(0, 35), (2, 20), (3, 35), (u32::MAX, 35)] { let mut selected = test_isolated_vllm_candidate(); let before = serde_json::to_value(&selected.feasibility).unwrap(); - let plan = VllmMemoryUtilizationPlan { target_pct: target, floor_pct: 30, max_pct: 80 }; - assert!(reserve_provider_vllm_replica_memory(&mut selected.feasibility, count, plan).is_err()); + let plan = VllmMemoryUtilizationPlan { + target_pct: target, + floor_pct: 30, + max_pct: 80, + }; + assert!( + reserve_provider_vllm_replica_memory(&mut selected.feasibility, count, plan) + .is_err() + ); assert_eq!(serde_json::to_value(&selected.feasibility).unwrap(), before); } let mut selected = test_isolated_vllm_candidate(); selected.feasibility.memory_budget.total_bytes = u64::MAX; selected.feasibility.memory_budget.budget_bytes = u64::MAX; let before = serde_json::to_value(&selected.feasibility).unwrap(); - assert!(reserve_provider_vllm_replica_memory(&mut selected.feasibility, 2, - VllmMemoryUtilizationPlan { target_pct: 85, floor_pct: 1, max_pct: 85 }).is_err()); + assert!(reserve_provider_vllm_replica_memory( + &mut selected.feasibility, + 2, + VllmMemoryUtilizationPlan { + target_pct: 85, + floor_pct: 1, + max_pct: 85 + } + ) + .is_err()); assert_eq!(serde_json::to_value(&selected.feasibility).unwrap(), before); } @@ -107163,13 +111011,28 @@ status: linked let plan = provider_vllm_memory_utilization(&isolated, None).unwrap(); reserve_provider_vllm_replica_memory(&mut isolated.feasibility, 2, plan).unwrap(); isolated.generation_execution_capacity = 2; - assert_eq!(provider_vllm_memory_utilization_for_candidates(std::slice::from_ref(&isolated), None) - .unwrap().unwrap().target_pct, 35); - assert!(provider_vllm_memory_utilization_for_candidates(std::slice::from_ref(&isolated), Some(36)).is_err()); + assert_eq!( + provider_vllm_memory_utilization_for_candidates(std::slice::from_ref(&isolated), None) + .unwrap() + .unwrap() + .target_pct, + 35 + ); + assert!(provider_vllm_memory_utilization_for_candidates( + std::slice::from_ref(&isolated), + Some(36) + ) + .is_err()); let mut shared = test_isolated_vllm_candidate(); shared.generation_execution_profile = None; - let error = provider_vllm_memory_utilization_for_candidates(&[isolated, shared], None).unwrap_err(); - assert!(error.to_string().contains("exceeding the admitted shared budget"), "{error:#}"); + let error = + provider_vllm_memory_utilization_for_candidates(&[isolated, shared], None).unwrap_err(); + assert!( + error + .to_string() + .contains("exceeding the admitted shared budget"), + "{error:#}" + ); } #[test] @@ -109020,16 +112883,17 @@ esac "usage": { "usage_root": "66".repeat(32), "active_demand_au": "12345", + "compute_ms": "1800000", + "capacity_slot_count": 1u64, + "legacy_receipt_count": 0u64, "session_count": 2u64 }, "controller": { - "source": "canonical_settled_work", + "source": "canonical_signed_slot_time", "active_supply": 1u64, - "momentum_bps": 10_000u64, - "activity_basis": "relative_dimension_vector_v1", + "utilization_bps": 5_000u64, + "activity_basis": "signed_slot_time_v1", "multiplier_bps": 10_000u64, - "frozen": true, - "frozen_reason": "activity_baseline_bootstrap" }, "seed_price": { "ver": 1u64, @@ -109105,6 +112969,10 @@ esac assert_eq!(models[0].mayhem.markets[0].availability, "routable"); assert_eq!(models[0].mayhem.route_candidates.len(), 1); assert_eq!(models[0].mayhem.route_candidates[0].att_tier, 1); + assert_eq!( + models[0].mayhem.route_candidates[0].enclave_att_tier, + Some(1) + ); assert_eq!(models[0].mayhem.route_candidates[0].kyb, None); assert_eq!(models[0].mayhem.route_candidates[0].reputation_bps, 10_000); assert_eq!(models[0].mayhem.route_candidates[0].probation, None); @@ -109782,6 +113650,10 @@ esac let models = gateway_models_from_contract(&contract).unwrap(); assert_eq!(models[0].mayhem.attestation_tiers["T4"], 1); assert_eq!(models[0].mayhem.route_candidates[0].att_tier, 4); + assert_eq!( + models[0].mayhem.route_candidates[0].enclave_att_tier, + Some(1) + ); assert_eq!(models[0].mayhem.kyb_identities.len(), 1); assert_eq!( models[0].mayhem.kyb_identities[0].legal_name, @@ -109800,6 +113672,10 @@ esac let models = gateway_models_from_contract(&contract).unwrap(); assert_eq!(models[0].mayhem.attestation_tiers["T1"], 1); assert_eq!(models[0].mayhem.route_candidates[0].att_tier, 1); + assert_eq!( + models[0].mayhem.route_candidates[0].enclave_att_tier, + Some(1) + ); assert!(models[0].mayhem.kyb_identities.is_empty()); assert_eq!(models[0].mayhem.route_candidates[0].kyb, None); } @@ -109836,46 +113712,108 @@ esac fn provider_execution_mode_open_negotiation_preserves_baseline_and_rejects_downgrade() { let mut terms = test_provider_session_terms(); let mut frame = test_session_open_frame(&terms); - assert_eq!(provider_session_open_decision(&frame, &terms), ProviderSessionDecision::Accept); + assert_eq!( + provider_session_open_decision(&frame, &terms), + ProviderSessionDecision::Accept + ); frame["expected_execution_mode"] = Value::Null; - assert_eq!(provider_session_open_decision(&frame, &terms), ProviderSessionDecision::Accept); + assert_eq!( + provider_session_open_decision(&frame, &terms), + ProviderSessionDecision::Accept + ); let mode = mayhem_proto::ExecutionModeBinding { mode_id: "throughput".to_owned(), policy_hash: "ab".repeat(32), }; frame["expected_execution_mode"] = serde_json::to_value(&mode).unwrap(); - assert!(matches!(provider_session_open_decision(&frame, &terms), - ProviderSessionDecision::Reject { code: "EXECUTION_MODE", .. })); + assert!(matches!( + provider_session_open_decision(&frame, &terms), + ProviderSessionDecision::Reject { + code: "EXECUTION_MODE", + .. + } + )); terms.execution_mode = Some(mode); - assert_eq!(provider_session_open_decision(&frame, &terms), ProviderSessionDecision::Accept); - for value in [Value::Null, json!({"mode_id":"throughput", "policy_hash":"cd".repeat(32)})] { + assert_eq!( + provider_session_open_decision(&frame, &terms), + ProviderSessionDecision::Accept + ); + for value in [ + Value::Null, + json!({"mode_id":"throughput", "policy_hash":"cd".repeat(32)}), + ] { frame["expected_execution_mode"] = value; - assert!(matches!(provider_session_open_decision(&frame, &terms), - ProviderSessionDecision::Reject { code: "EXECUTION_MODE", .. })); + assert!(matches!( + provider_session_open_decision(&frame, &terms), + ProviderSessionDecision::Reject { + code: "EXECUTION_MODE", + .. + } + )); } - frame.as_object_mut().unwrap().remove("expected_execution_mode"); - assert!(matches!(provider_session_open_decision(&frame, &terms), - ProviderSessionDecision::Reject { code: "EXECUTION_MODE", .. })); - frame["expected_execution_mode"] = json!({"mode_id":"../invalid", "policy_hash":"ab".repeat(32)}); - assert!(matches!(provider_session_open_decision(&frame, &terms), - ProviderSessionDecision::Reject { code: "SCHEMA", .. })); + frame + .as_object_mut() + .unwrap() + .remove("expected_execution_mode"); + assert!(matches!( + provider_session_open_decision(&frame, &terms), + ProviderSessionDecision::Reject { + code: "EXECUTION_MODE", + .. + } + )); + frame["expected_execution_mode"] = + json!({"mode_id":"../invalid", "policy_hash":"ab".repeat(32)}); + assert!(matches!( + provider_session_open_decision(&frame, &terms), + ProviderSessionDecision::Reject { code: "SCHEMA", .. } + )); } #[test] fn provider_execution_mode_cli_and_supervisor_selection_is_explicit() { let baseline = ProviderStartArgs::try_parse_from(["start"]).unwrap(); assert!(baseline.execution_mode.is_none()); - let args = ProviderStartArgs::try_parse_from(["start", "--execution-mode", "throughput"]).unwrap(); + let args = + ProviderStartArgs::try_parse_from(["start", "--execution-mode", "throughput"]).unwrap(); assert_eq!(args.execution_mode.as_deref(), Some("throughput")); - let add = ProviderServeAddArgs::try_parse_from(["add", "test/model", "--execution-mode", "throughput"]).unwrap(); + let add = ProviderServeAddArgs::try_parse_from([ + "add", + "test/model", + "--execution-mode", + "throughput", + ]) + .unwrap(); assert_eq!(add.execution_mode.as_deref(), Some("throughput")); - let switch = ProviderServeSwitchArgs::try_parse_from(["switch", "old", "new", "--execution-mode", "throughput"]).unwrap(); + let switch = ProviderServeSwitchArgs::try_parse_from([ + "switch", + "old", + "new", + "--execution-mode", + "throughput", + ]) + .unwrap(); assert_eq!(switch.execution_mode.as_deref(), Some("throughput")); - let plan = ProviderServePlanArgs::try_parse_from(["plan", "--execution-mode", "throughput"]).unwrap(); - assert_eq!(provider_start_args_for_serve_plan(&plan, Path::new(".")).execution_mode, plan.execution_mode); + let plan = + ProviderServePlanArgs::try_parse_from(["plan", "--execution-mode", "throughput"]) + .unwrap(); + assert_eq!( + provider_start_args_for_serve_plan(&plan, Path::new(".")).execution_mode, + plan.execution_mode + ); let mut child = json!({"args": ["provider", "start", "--enclave", "test"]}); append_provider_serve_execution_mode(&mut child, "throughput").unwrap(); - assert_eq!(child["args"], json!(["provider", "start", "--enclave", "test", "--execution-mode", "throughput"])); + assert_eq!( + child["args"], + json!([ + "provider", + "start", + "--enclave", + "test", + "--execution-mode", + "throughput" + ]) + ); assert!(append_provider_serve_execution_mode(&mut child, "throughput").is_err()); assert!(append_provider_serve_execution_mode(&mut json!({"args": []}), "../bad").is_err()); assert!(append_provider_serve_execution_mode(&mut json!({}), "throughput").is_err()); @@ -109885,24 +113823,42 @@ esac fn provider_execution_mode_plan_replays_supervised_mode_without_changing_baseline() { let home = Path::new("/tmp/provider home"); let args = ProviderServePlanArgs::try_parse_from([ - "plan", "--execution-mode", "throughput", "--ctx", "8192", - "--disable-modality", "image", "--speciality-levels", "reasoning_effort=medium", - ]).unwrap(); + "plan", + "--execution-mode", + "throughput", + "--ctx", + "8192", + "--disable-modality", + "image", + "--speciality-levels", + "reasoning_effort=medium", + ]) + .unwrap(); let candidate = test_auto_fit_candidate('a', "test/model", "text", 4, 16, 1, 10.0); let mut selection = ProviderAutoFitSelection { - candidates: vec![candidate], total_required_bytes: 0, total_budget_bytes: 0, - modalities: vec!["text".to_owned()], score: 0.0, rationale: String::new(), + candidates: vec![candidate], + total_required_bytes: 0, + total_budget_bytes: 0, + modalities: vec!["text".to_owned()], + score: 0.0, + rationale: String::new(), }; let baseline = provider_serve_plan_commands(home, &selection, &args, Some(12), None); - assert_eq!(baseline, json!({"up": provider_auto_fit_up_command( - home, &selection, Some(12), &args.disable_modalities, None, - )})); + assert_eq!( + baseline, + json!({"up": provider_auto_fit_up_command( + home, &selection, Some(12), &args.disable_modalities, None, + )}) + ); let candidate = &mut selection.candidates[0]; candidate.execution_mode = Some(ProviderExecutionMode { binding: mayhem_proto::ExecutionModeBinding { - mode_id: "throughput".to_owned(), policy_hash: "ab".repeat(32), + mode_id: "throughput".to_owned(), + policy_hash: "ab".repeat(32), + }, + requests: mayhem_proto::ExecutionModeRequestPolicy { + endpoint_families: Vec::new(), }, - requests: mayhem_proto::ExecutionModeRequestPolicy { endpoint_families: Vec::new() }, baseline_adapter: candidate.model.adapter.clone(), }); let argv = provider_serve_mode_add_argv(home, candidate, &args, Some(12), None); @@ -109917,7 +113873,10 @@ esac let report = provider_serve_plan_commands(home, &selection, &args, Some(12), None); assert!(!report["up"].as_str().unwrap().contains("--provider")); assert_eq!(report["serve"].as_array().unwrap().len(), 1); - assert!(report["serve"][0].as_str().unwrap().contains("'--execution-mode' 'throughput'")); + assert!(report["serve"][0] + .as_str() + .unwrap() + .contains("'--execution-mode' 'throughput'")); } #[test] @@ -110224,6 +114183,60 @@ esac assert!(rejected.is_empty()); } + #[test] + fn provider_session_resumes_only_the_identical_pending_reservation() { + let terms = test_provider_session_terms(); + let open_frame = test_session_open_frame(&terms); + let session_id = open_frame["session_id"].as_str().unwrap().to_owned(); + let remote = "22".repeat(32); + let reject_frame = json!({ + "t": "s.reject", + "session_id": session_id, + "code": "RESERVATION_PENDING", + }); + let mut rejected = HashMap::new(); + cache_provider_session_reject( + &mut rejected, + session_id.clone(), + remote.clone(), + &open_frame, + reject_frame, + Instant::now() + Duration::from_secs(30), + ) + .unwrap(); + + assert_eq!( + provider_session_reject_replay_decision( + rejected.get(&session_id).unwrap(), + &remote, + &open_frame, + ) + .unwrap(), + ProviderSessionReplayDecision::Pending + ); + + let mut changed = open_frame.clone(); + changed["nonce"] = json!("changed"); + assert_eq!( + provider_session_reject_replay_decision( + rejected.get(&session_id).unwrap(), + &remote, + &changed, + ) + .unwrap(), + ProviderSessionReplayDecision::Conflict + ); + + match provider_session_reservation_timeout_decision(Duration::from_secs(90)) { + ProviderSessionDecision::Reject { code, reason } => { + assert_eq!(code, "RESERVATION_PENDING"); + assert!(reason.contains("90000 ms")); + assert!(reason.contains("retained for recovery")); + } + other => panic!("reservation timeout must remain retryable: {other:?}"), + } + } + #[test] fn provider_admission_timeout_uses_the_gateway_open_budget() { assert_eq!( @@ -110294,6 +114307,34 @@ esac &terms )); + let tokenize_event = json!({ + "type": "session_frame", + "session_id": "cc".repeat(32), + "remote": "22".repeat(32), + "frame": { + "t": TOKENIZE_REQUEST_FRAME_TYPE, + "v": TOKENIZE_FRAME_VERSION, + "session_id": "cc".repeat(32), + "provider": terms.provider, + "enclave_id": terms.enclave_id, + "room_id": terms.room_ids[0], + "model": terms.model_id, + "request": {}, + }, + }); + assert!(provider_session_event_belongs_to_process( + &tokenize_event, + &sessions, + &terms + )); + let mut wrong_model_tokenize = tokenize_event; + wrong_model_tokenize["frame"]["model"] = json!("other/model"); + assert!(!provider_session_event_belongs_to_process( + &wrong_model_tokenize, + &sessions, + &terms + )); + for frame_type in [ "s.accept", "s.reject", @@ -110345,6 +114386,21 @@ esac )); } + #[test] + fn provider_tokenize_uses_the_signed_model_chat_template() { + let terms = test_provider_session_terms(); + let body = json!({ + "kind": "chat", + "messages": [{"role": "user", "content": "exact tokenizer marker"}], + "stream": false, + }); + let sealed = + provider_seal_local_contract_request(&body, &terms.adapter, &terms.model_id).unwrap(); + let prompt = provider_engine_tokenize_prompt(&terms, &sealed).unwrap(); + assert!(prompt.contains("exact tokenizer marker")); + assert_ne!(prompt, "exact tokenizer marker"); + } + #[test] fn provider_session_open_accepts_unbracketed_non_text_terms() { let mut terms = test_provider_session_terms(); @@ -110415,7 +114471,12 @@ esac output_tokens: u64, ) -> Value { signed_receipt_settlement_feature_for_test_version( - epoch, seq, final_receipt, output_tokens, CONTRACT_VERSION, None, + epoch, + seq, + final_receipt, + output_tokens, + CONTRACT_VERSION, + None, ) } @@ -110426,6 +114487,26 @@ esac output_tokens: u64, contract_version: u32, context_input_tokens: Option, + ) -> Value { + signed_receipt_settlement_feature_for_test_version_and_compute_ms( + epoch, + seq, + final_receipt, + output_tokens, + contract_version, + context_input_tokens, + 1, + ) + } + + fn signed_receipt_settlement_feature_for_test_version_and_compute_ms( + epoch: u64, + seq: u64, + final_receipt: bool, + output_tokens: u64, + contract_version: u32, + context_input_tokens: Option, + compute_ms: u64, ) -> Value { let provider_key = SigningKey::from_bytes(&[31_u8; 32]); let enclave_key = SigningKey::from_bytes(&[32_u8; 32]); @@ -110458,6 +114539,8 @@ esac locked_per_req_au: 0, locked_min_session_au: 0, served_ctx: 1024, + compute_ms, + capacity_slots: 1, ctx_bracket: Some("le32k".to_owned()), ctx_bracket_table_ver: Some(CTX_BRACKET_TABLE_VERSION), rules_ver: 1, @@ -110478,8 +114561,24 @@ esac enclave_pubkey: hex_encode(&enclave_key.verifying_key().to_bytes()), user_sig: hex_encode(&user_key.sign(&receipt_payload).to_bytes()), }; - let key = record_usage_receipt_feature_key_for_contract(&receipt, contract_version); - let receipt_envelope = record_usage_receipt_envelope(&receipt); + let mut receipt_envelope = record_usage_receipt_envelope(&receipt); + if contract_version != CONTRACT_VERSION { + receipt_envelope["body"]["schema_version"] = + json!(mayhem_proto::RECOVERABLE_SESSION_RECEIPT_SCHEMA_VERSION); + receipt_envelope["body"] + .as_object_mut() + .unwrap() + .remove("compute_ms"); + receipt_envelope["body"] + .as_object_mut() + .unwrap() + .remove("capacity_slots"); + } + let key = record_usage_receipt_feature_key_from_envelope_for_contract( + &receipt_envelope, + contract_version, + ) + .unwrap(); let mut value = json!({ "op": "record_usage_receipt", "contract_version": contract_version, @@ -110561,6 +114660,161 @@ esac let _ = fs::remove_dir_all(root); } + #[test] + fn receipt_outbox_confirmed_partial_can_retire_without_settlement() { + let root = test_temp_dir("mayhem-partial-delivery-before-close"); + let outbox = ReceiptSettlementOutbox::new(root.join("provider")).unwrap(); + let partial = outbox + .persist(&signed_receipt_settlement_feature_for_test_at( + 7, 1, false, 2, + )) + .unwrap(); + let key = receipt_settlement_head_key(&partial).unwrap(); + let mut record = json!({"confirmed": true, "key": key, "value": { + "type": "canonical_receipt_head", "settlement_ready": false, + "feature_key": partial.feature["key"], "receipt": partial.feature["value"]["receipt"] + }}); + assert!(confirmed_receipt_settlement_record_matches( + &record, &key, &partial + )); + record["confirmed"] = json!(false); + assert!(!confirmed_receipt_settlement_record_matches( + &record, &key, &partial + )); + record["confirmed"] = json!(true); + record["key"] = json!("another/head"); + assert!(!confirmed_receipt_settlement_record_matches( + &record, &key, &partial + )); + let final_entry = outbox + .persist(&signed_receipt_settlement_feature_for_test(7)) + .unwrap(); + record["key"] = json!(key); + record["value"]["feature_key"] = final_entry.feature["key"].clone(); + record["value"]["receipt"] = final_entry.feature["value"]["receipt"].clone(); + assert!(confirmed_receipt_settlement_record_matches( + &record, &key, &partial + )); + assert!(!confirmed_receipt_settlement_record_matches( + &record, + &key, + &final_entry + )); + record["value"]["settlement_ready"] = json!(true); + assert!(confirmed_receipt_settlement_record_matches( + &record, + &key, + &final_entry + )); + fs::remove_dir_all(root).unwrap(); + } + + #[test] + fn receipt_outbox_checkpoint_hot_path_does_not_parse_unrelated_backlog() { + let root = test_temp_dir("mayhem-receipt-backlog-hot-path"); + let outbox = ReceiptSettlementOutbox::new(root.join("provider")).unwrap(); + let other_process = ReceiptSettlementOutbox::new(root.join("provider")).unwrap(); + let first = outbox + .persist(&signed_receipt_settlement_feature_for_test_at( + 7, 1, false, 1, + )) + .unwrap(); + // These deliberately unreadable documents prove persist/remove do not + // parse unrelated attempts. Full admission/retry validation must still + // reject them; the optimization must not change that behavior. + for n in 0..RECEIPT_SETTLEMENT_OUTBOX_MAX_ENTRIES - 1 { + let path = + outbox + .directory + .join(format!("{n:064x}.{:020}.0.{}.json", 1, "ab".repeat(32))); + let mut file = fs::OpenOptions::new() + .create_new(true) + .write(true) + .open(&path) + .unwrap(); + file.write_all(b"invalid JSON probe").unwrap(); + #[cfg(unix)] + { + use std::os::unix::fs::PermissionsExt as _; + fs::set_permissions(&path, fs::Permissions::from_mode(0o600)).unwrap(); + } + } + let next = other_process + .persist(&signed_receipt_settlement_feature_for_test_at( + 7, 2, false, 2, + )) + .unwrap(); + outbox.remove(&first).unwrap(); + assert!( + next.path.exists(), + "stale removal must not erase another process's checkpoint" + ); + let final_entry = outbox + .persist(&signed_receipt_settlement_feature_for_test(7)) + .unwrap(); + other_process.remove(&next).unwrap(); + assert!(final_entry.path.exists()); + assert!( + outbox.load_entries().is_err(), + "full scans must still validate every document" + ); + other_process.remove(&final_entry).unwrap(); + assert!(!final_entry.path.exists()); + assert_eq!( + fs::read_dir(&outbox.directory) + .unwrap() + .filter_map(Result::ok) + .filter(|item| item.path().extension() == Some(OsStr::new("json"))) + .count(), + RECEIPT_SETTLEMENT_OUTBOX_MAX_ENTRIES - 1 + ); + fs::remove_dir_all(root).unwrap(); + } + + #[test] + fn receipt_outbox_attempt_scan_checks_names_and_crash_survivors() { + let root = test_temp_dir("mayhem-receipt-attempt-crash"); + let outbox = ReceiptSettlementOutbox::new(root.join("provider")).unwrap(); + let first_feature = signed_receipt_settlement_feature_for_test_at(7, 1, false, 1); + let first = outbox.persist(&first_feature).unwrap(); + let saved = fs::read(&first.path).unwrap(); + let final_entry = outbox + .persist(&signed_receipt_settlement_feature_for_test(7)) + .unwrap(); + // Simulate power loss after the new link landed but before old unlink. + fs::write(&first.path, saved).unwrap(); + #[cfg(unix)] + { + use std::os::unix::fs::PermissionsExt as _; + fs::set_permissions(&first.path, fs::Permissions::from_mode(0o600)).unwrap(); + } + assert_eq!(outbox.persist(&first_feature).unwrap(), final_entry); + outbox.remove(&first).unwrap(); + assert!(final_entry.path.exists()); + let (count, current) = outbox.load_attempt(&first.attempt_id).unwrap(); + assert_eq!(count, 1); + assert_eq!(current.unwrap(), final_entry); + let malformed = outbox.directory.join("not-an-attempt.json"); + fs::write(&malformed, b"{}").unwrap(); + assert!(outbox.load_attempt(&first.attempt_id).is_err()); + fs::remove_file(malformed).unwrap(); + #[cfg(unix)] + { + let link = outbox.directory.join(format!( + "{}.00000000000000000001.0.{}.json", + "00".repeat(32), + "ab".repeat(32) + )); + std::os::unix::fs::symlink(&final_entry.path, &link).unwrap(); + assert!(outbox.load_attempt(&first.attempt_id).is_err()); + fs::remove_file(link).unwrap(); + } + let restarted = ReceiptSettlementOutbox::new(outbox.directory.clone()).unwrap(); + assert_eq!(restarted.load_entries().unwrap(), vec![final_entry]); + assert!(!first.path.exists()); + fs::remove_dir_all(root).unwrap(); + } + #[test] fn receipt_outbox_retires_only_exact_confirmed_canonical_evidence() { let root = test_temp_dir("mayhem-receipt-outbox-canonical-proof"); @@ -110587,7 +114841,83 @@ esac assert!(!confirmed_receipt_settlement_record_matches( &record, &key, &entry )); - assert!(entry.path.exists(), "a mismatch must leave durable evidence"); + assert!( + entry.path.exists(), + "a mismatch must leave durable evidence" + ); + let _ = fs::remove_dir_all(root); + } + + #[test] + fn receipt_outbox_retires_checkpoint_proven_superseded_by_canonical_head() { + let root = test_temp_dir("mayhem-receipt-outbox-superseded-checkpoint"); + let outbox = ReceiptSettlementOutbox::new(root.join("gateway")).unwrap(); + let entry = outbox + .persist( + &signed_receipt_settlement_feature_for_test_version_and_compute_ms( + 7, + 69, + false, + 69, + CONTRACT_VERSION, + None, + 100, + ), + ) + .unwrap(); + let canonical = signed_receipt_settlement_feature_for_test_version_and_compute_ms( + 7, + 70, + true, + 70, + CONTRACT_VERSION, + None, + 200, + ); + let key = receipt_settlement_head_key(&entry).unwrap(); + let record = json!({ + "confirmed": true, + "key": key, + "value": { + "type": "canonical_receipt_head", + "settlement_ready": true, + "feature_key": canonical["key"], + "receipt": canonical["value"]["receipt"], + }, + }); + assert!(confirmed_receipt_settlement_record_matches( + &record, &key, &entry + )); + + let mut same_sequence = record.clone(); + same_sequence["value"]["receipt"]["body"]["seq"] = json!(69); + assert!(!confirmed_receipt_settlement_record_matches( + &same_sequence, + &key, + &entry, + )); + let mut changed_terms = record.clone(); + changed_terms["value"]["receipt"]["body"]["payout_revision"] = json!("47".repeat(32)); + assert!(!confirmed_receipt_settlement_record_matches( + &changed_terms, + &key, + &entry, + )); + let mut regressed_compute = record.clone(); + regressed_compute["value"]["receipt"]["body"]["compute_ms"] = json!(99); + assert!(!confirmed_receipt_settlement_record_matches( + ®ressed_compute, + &key, + &entry, + )); + let mut regressed_usage = record; + regressed_usage["value"]["receipt"]["body"]["usage"]["output_tokens"] = json!(68); + assert!(!confirmed_receipt_settlement_record_matches( + ®ressed_usage, + &key, + &entry, + )); + assert!(entry.path.exists()); let _ = fs::remove_dir_all(root); } @@ -110600,7 +114930,10 @@ esac .unwrap(); let paths = vec![entry.path.clone()]; outbox.remove(&entry).unwrap(); - assert!(outbox.load_physical_entries_at_paths(paths).unwrap().is_empty()); + assert!(outbox + .load_physical_entries_at_paths(paths) + .unwrap() + .is_empty()); assert!(outbox.admission_available().unwrap()); let entry = outbox @@ -110629,25 +114962,152 @@ esac .unwrap(); assert!(outbox.has_pending_final_receipts(user, "tnk").unwrap()); assert!(!outbox.has_pending_final_receipts(user, "fiat").unwrap()); - assert!(!outbox.has_pending_final_receipts("another buyer", "tnk").unwrap()); + assert!(!outbox + .has_pending_final_receipts("another buyer", "tnk") + .unwrap()); outbox.remove(&final_receipt).unwrap(); assert!(!outbox.has_pending_final_receipts(user, "tnk").unwrap()); let _ = fs::remove_dir_all(root); } #[test] - fn receipt_settlement_version_bridge_preserves_v23_v24_and_v25_signatures() { - for version in [23, 24, 25] { + fn receipt_outbox_accepts_advancing_compute_across_receipt_series() { + let root = test_temp_dir("mayhem-receipt-outbox-compute-checkpoints"); + for (delivery, checkpoints) in [ + ( + "streaming", + vec![(1, false, 8, 100), (2, false, 16, 225), (3, true, 21, 310)], + ), + ("single-final", vec![(1, true, 41, 525)]), + ] { + let outbox = ReceiptSettlementOutbox::new(root.join(delivery)).unwrap(); + for (seq, final_receipt, output_tokens, compute_ms) in checkpoints { + let feature = signed_receipt_settlement_feature_for_test_version_and_compute_ms( + 7, + seq, + final_receipt, + output_tokens, + CONTRACT_VERSION, + None, + compute_ms, + ); + outbox + .persist(&feature) + .expect("cumulative compute must advance with checkpoint usage"); + let entries = outbox.load_entries().unwrap(); + assert_eq!(entries.len(), 1, "{delivery}"); + assert_eq!(entries[0].seq, seq, "{delivery}"); + assert_eq!(entries[0].final_receipt, final_receipt, "{delivery}"); + } + } + let _ = fs::remove_dir_all(root); + } + + #[test] + fn receipt_outbox_rejects_regressing_compute_across_checkpoints() { + let root = test_temp_dir("mayhem-receipt-outbox-compute-regression"); + let outbox = ReceiptSettlementOutbox::new(root.join("provider")).unwrap(); + let first = signed_receipt_settlement_feature_for_test_version_and_compute_ms( + 7, + 1, + false, + 8, + CONTRACT_VERSION, + None, + 100, + ); + outbox.persist(&first).unwrap(); + let regressed = signed_receipt_settlement_feature_for_test_version_and_compute_ms( + 7, + 2, + false, + 16, + CONTRACT_VERSION, + None, + 99, + ); + let error = outbox + .persist(®ressed) + .expect_err("higher sequence must not regress cumulative compute"); + assert!(error.to_string().contains("high-water evidence")); + let entries = outbox.load_entries().unwrap(); + assert_eq!(entries.len(), 1); + assert_eq!(entries[0].seq, 1); + assert_eq!(entries[0].compute_ms, 100); + let _ = fs::remove_dir_all(root); + } + + #[test] + fn receipt_outbox_requires_fresh_sequence_for_terminal_recovery() { + let root = test_temp_dir("mayhem-receipt-outbox-terminal-sequence"); + let outbox = ReceiptSettlementOutbox::new(root.join("provider")).unwrap(); + let checkpoint = signed_receipt_settlement_feature_for_test_version_and_compute_ms( + 7, + 3, + false, + 16, + CONTRACT_VERSION, + None, + 225, + ); + outbox.persist(&checkpoint).unwrap(); + let same_sequence_final = signed_receipt_settlement_feature_for_test_version_and_compute_ms( + 7, + 3, + true, + 16, + CONTRACT_VERSION, + None, + 310, + ); + let error = outbox + .persist(&same_sequence_final) + .expect_err("a terminal receipt must not reuse a transmitted checkpoint sequence"); + assert!(error.to_string().contains("must advance")); + let final_receipt = signed_receipt_settlement_feature_for_test_version_and_compute_ms( + 7, + 4, + true, + 16, + CONTRACT_VERSION, + None, + 310, + ); + let entry = outbox.persist(&final_receipt).unwrap(); + assert!(entry.final_receipt); + assert_eq!(entry.seq, 4); + let _ = fs::remove_dir_all(root); + } + + #[test] + fn receipt_settlement_version_bridge_preserves_v23_through_v27_signatures() { + for version in [23, 24, 25, 26, 27] { let feature = signed_receipt_settlement_feature_for_test_version( - 7, 1, true, 2, version, Some(768), + 7, + 1, + true, + 2, + version, + Some(768), + ); + assert!( + validate_receipt_settlement_feature(&feature).is_ok(), + "version {version}" ); - assert!(validate_receipt_settlement_feature(&feature).is_ok(), "version {version}"); } - for version in [22, 26] { + for version in [22, CONTRACT_VERSION + 1] { let feature = signed_receipt_settlement_feature_for_test_version( - 7, 1, true, 2, version, Some(768), + 7, + 1, + true, + 2, + version, + Some(768), + ); + assert!( + validate_receipt_settlement_feature(&feature).is_err(), + "version {version}" ); - assert!(validate_receipt_settlement_feature(&feature).is_err(), "version {version}"); } } @@ -110655,7 +115115,12 @@ esac fn receipt_outbox_recovers_v191_context_receipts_without_resigning_or_rebilling() { let root = test_temp_dir("mayhem-v191-receipt-recovery"); let feature = signed_receipt_settlement_feature_for_test_version( - 7, 1, true, 2, RECOVERABLE_RECEIPT_CONTRACT_VERSION, Some(768), + 7, + 1, + true, + 2, + RECOVERABLE_RECEIPT_CONTRACT_VERSION, + Some(768), ); let receipt = parse_record_usage_receipt_envelope(&feature["value"]["receipt"]).unwrap(); assert_eq!(receipt.body.usage.input_tokens(), 1); @@ -110664,12 +115129,15 @@ esac for kind in ["provider", "gateway"] { let directory = root.join(kind); fs::create_dir_all(&directory).unwrap(); - let path = ReceiptSettlementOutbox::new(directory.clone()).unwrap() - .entry_path(&feature).unwrap(); + let path = ReceiptSettlementOutbox::new(directory.clone()) + .unwrap() + .entry_path(&feature) + .unwrap(); let bytes = serde_json::to_vec(&json!({ "schema_version": RECEIPT_SETTLEMENT_OUTBOX_SCHEMA_VERSION, "feature": feature, - })).unwrap(); + })) + .unwrap(); fs::write(&path, &bytes).unwrap(); #[cfg(unix)] { @@ -110678,7 +115146,11 @@ esac } let outbox = ReceiptSettlementOutbox::new(directory.clone()).unwrap(); let entries = outbox.load_entries().unwrap(); - assert_eq!(entries.len(), 1, "legacy signed evidence must not be quarantined"); + assert_eq!( + entries.len(), + 1, + "legacy signed evidence must not be quarantined" + ); assert_eq!(entries[0].feature, feature); let persisted = fs::read(&entries[0].path).unwrap(); drop(outbox); @@ -110693,16 +115165,20 @@ esac "feature_key": feature["key"], "receipt": feature["value"]["receipt"], }, }); - assert!(confirmed_receipt_settlement_record_matches(&record, &key, &entry)); - record["value"]["receipt"]["body"]["usage_attribution"]["context_input_tokens"] = json!(767); - assert!(!confirmed_receipt_settlement_record_matches(&record, &key, &entry)); + assert!(confirmed_receipt_settlement_record_matches( + &record, &key, &entry + )); + record["value"]["receipt"]["body"]["usage_attribution"]["context_input_tokens"] = + json!(767); + assert!(!confirmed_receipt_settlement_record_matches( + &record, &key, &entry + )); assert!(entry.path.exists()); } let mut rewritten = feature.clone(); rewritten["value"]["contract_version"] = json!(CONTRACT_VERSION); rewritten["key"] = json!(record_usage_receipt_feature_key(&receipt)); - assert!(validate_receipt_settlement_feature(&rewritten).unwrap_err() - .to_string().contains("provider signature failed")); + assert!(validate_receipt_settlement_feature(&rewritten).is_err()); let mut unsupported = feature; unsupported["value"]["contract_version"] = json!(22); assert!(validate_receipt_settlement_feature(&unsupported).is_err()); @@ -110749,6 +115225,17 @@ esac let mut feature = signed_receipt_settlement_feature_for_test(7); feature["value"]["receipt"]["body"]["schema_version"] = json!(SESSION_RECEIPT_SCHEMA_VERSION.saturating_sub(1)); + // Keep the synthetic stale envelope valid for schema 11 so recovery + // reaches the intended obsolete-schema quarantine path. Schema 11 + // predates the cumulative utilization fields introduced in schema 12. + feature["value"]["receipt"]["body"] + .as_object_mut() + .unwrap() + .remove("compute_ms"); + feature["value"]["receipt"]["body"] + .as_object_mut() + .unwrap() + .remove("capacity_slots"); let stale_path = directory.join("obsolete-receipt-schema-entry.json"); fs::write( &stale_path, @@ -110785,11 +115272,11 @@ esac outbox.persist(&feature).unwrap(); assert_eq!(outbox.load_entries().unwrap().len(), 1); } - let final_feature = signed_receipt_settlement_feature_for_test_at(7, 200, true, 200); + let final_feature = signed_receipt_settlement_feature_for_test_at(7, 201, true, 200); outbox.persist(&final_feature).unwrap(); let entries = outbox.load_entries().unwrap(); assert_eq!(entries.len(), 1); - assert_eq!(entries[0].seq, 200); + assert_eq!(entries[0].seq, 201); assert!(entries[0].final_receipt); assert_eq!( fs::read_dir(root.join("provider")) @@ -110802,10 +115289,10 @@ esac let stale = signed_receipt_settlement_feature_for_test_at(7, 199, false, 199); let retained = outbox.persist(&stale).unwrap(); assert_eq!(retained.feature, final_feature); - let conflicting_final = signed_receipt_settlement_feature_for_test_at(7, 200, true, 201); + let conflicting_final = signed_receipt_settlement_feature_for_test_at(7, 201, true, 201); assert!(outbox.persist(&conflicting_final).is_err()); let post_final_checkpoint = - signed_receipt_settlement_feature_for_test_at(7, 201, false, 201); + signed_receipt_settlement_feature_for_test_at(7, 202, false, 201); assert!(outbox.persist(&post_final_checkpoint).is_err()); let _ = fs::remove_dir_all(root); } @@ -110833,7 +115320,8 @@ esac receipt.body.billing_id = "52".repeat(32); receipt.body.reservation_id = "53".repeat(32); let payload = receipt_signing_bytes(&receipt.body).unwrap(); - receipt.enclave_sig = hex_encode(&SigningKey::from_bytes(&[32; 32]).sign(&payload).to_bytes()); + receipt.enclave_sig = + hex_encode(&SigningKey::from_bytes(&[32; 32]).sign(&payload).to_bytes()); receipt.user_sig = hex_encode(&SigningKey::from_bytes(&[33; 32]).sign(&payload).to_bytes()); let key = record_usage_receipt_feature_key(&receipt); let mut value = json!({ @@ -110850,13 +115338,15 @@ esac let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap(); let address = listener.local_addr().unwrap(); let (seen_old, mut old_seen) = tokio::sync::mpsc::channel(1); + let (seen_fresh, mut fresh_seen) = tokio::sync::mpsc::channel(1); let release_old = Arc::new(tokio::sync::Notify::new()); let server_release = release_old.clone(); let server = tokio::spawn(async move { let mut requests = tokio::task::JoinSet::new(); - for _ in 0..2 { + for _ in 0..4 { let (mut stream, _) = listener.accept().await.unwrap(); let seen_old = seen_old.clone(); + let seen_fresh = seen_fresh.clone(); let release = server_release.clone(); requests.spawn(async move { let mut bytes = Vec::new(); @@ -110867,11 +115357,14 @@ esac bytes.extend_from_slice(&chunk[..n]); if let Some(end) = bytes.windows(4).position(|x| x == b"\r\n\r\n") { let headers = String::from_utf8_lossy(&bytes[..end]); - let length = headers.lines().find_map(|line| { - let (name, value) = line.split_once(':')?; - name.eq_ignore_ascii_case("content-length") - .then(|| value.trim().parse::().unwrap()) - }).unwrap(); + let length = headers + .lines() + .find_map(|line| { + let (name, value) = line.split_once(':')?; + name.eq_ignore_ascii_case("content-length") + .then(|| value.trim().parse::().unwrap()) + }) + .unwrap_or(0); break (end + 4, length); } }; @@ -110881,12 +115374,29 @@ esac assert!(n > 0); bytes.extend_from_slice(&chunk[..n]); } - let feature: Value = serde_json::from_slice(&bytes[header_end..header_end + length]).unwrap(); - if feature["value"]["receipt"]["body"]["billing_id"] == "42".repeat(32) { - seen_old.send(()).await.unwrap(); - release.notified().await; - } - stream.write_all(b"HTTP/1.1 200 OK\r\nContent-Type: application/json\r\nContent-Length: 11\r\nConnection: close\r\n\r\n{\"ok\":true}").await.unwrap(); + let response = if length == 0 { + r#"{"confirmed":false}"# + } else { + let feature: Value = serde_json::from_slice( + &bytes[header_end..header_end + length], + ) + .unwrap(); + if feature["value"]["receipt"]["body"]["billing_id"] + == "42".repeat(32) + { + seen_old.send(()).await.unwrap(); + release.notified().await; + } else { + seen_fresh.send(()).await.unwrap(); + } + r#"{"ok":true}"# + }; + let headers = format!( + "HTTP/1.1 200 OK\r\nContent-Type: application/json\r\nContent-Length: {}\r\nConnection: close\r\n\r\n", + response.len() + ); + stream.write_all(headers.as_bytes()).await.unwrap(); + stream.write_all(response.as_bytes()).await.unwrap(); }); } while let Some(result) = requests.join_next().await { @@ -110901,26 +115411,22 @@ esac .unwrap() .unwrap(); let fresh_entry = outbox.persist(&fresh).unwrap(); - timeout(Duration::from_secs(3), async { - while fresh_entry.path.exists() { - sleep(Duration::from_millis(10)).await; - } - }) - .await - .expect("fresh receipt must bypass the stalled disk retry pass"); + timeout(Duration::from_secs(3), fresh_seen.recv()) + .await + .expect("fresh receipt must bypass the stalled disk retry pass") + .expect("fresh receipt submission signal"); assert!( old_entry.path.exists(), "older evidence must remain durable while its relay is stalled" ); + assert!( + fresh_entry.path.exists(), + "relay acceptance must not hide a receipt before canonical confirmation" + ); release_old.notify_one(); server.await.unwrap(); - timeout(Duration::from_secs(3), async { - while old_entry.path.exists() { - sleep(Duration::from_millis(10)).await; - } - }) - .await - .unwrap(); + assert!(old_entry.path.exists()); + assert!(fresh_entry.path.exists()); let _ = fs::remove_dir_all(root); } @@ -110999,12 +115505,40 @@ esac key, "provider signature is not part of the reservation feature key" ); + + let binding = spend_reservation_binding(&signed).unwrap(); + assert!(reservation_binding_matches(&binding, &binding)); + assert_eq!(binding["reservation_id"], signed["reservation_id"]); + assert_eq!(binding["billing_id"], signed["voucher"]["billing_id"]); + assert_eq!(binding["model_id"], signed["model_id"]); + assert_eq!(binding["enclave_id"], signed["enclave_id"]); + assert_eq!( + binding["billing_attempt"], + signed["voucher"]["billing_attempt"] + ); + } + + #[test] + fn provider_reservation_pending_detection_preserves_ambiguous_append_outcomes() { + for response in [ + json!({"ok": false, "accepted": true, "status": "pending"}), + json!({"ok": false, "accepted": false, "status": "rejected", "phase": "admin_ack"}), + json!({"ok": false, "message": "Mayhem feature relay accepted the append but no canonical result appeared before the relay result budget."}), + ] { + assert!(spend_reservation_submission_pending(&response)); + } + assert!(!spend_reservation_submission_pending(&json!({ + "ok": false, + "accepted": true, + "status": "rejected", + "message": "Insufficient unreserved credit balance.", + }))); } #[test] fn js_contract_targeted_spend_reservation_fixture_matches_rust() { let voucher = json!({ - "schema_version": SESSION_RECEIPT_SCHEMA_VERSION, + "schema_version": SPEND_VOUCHER_SCHEMA_VERSION, "session_id": "11".repeat(32), "billing_id": "12".repeat(32), "billing_attempt": 0, @@ -111066,7 +115600,7 @@ esac let expected_message = concat!( "mayhem-targeted-spend-reservation-v1", "{\"payout_revision\":\"9999999999999999999999999999999999999999999999999999999999999999\",", - "\"reservation\":{\"at\":25200,\"contract_version\":25,\"ctx_bracket\":\"le8k\",", + "\"reservation\":{\"at\":25200,\"contract_version\":26,\"ctx_bracket\":\"le8k\",", "\"ctx_bracket_table_ver\":1,", "\"enclave_id\":\"4444444444444444444444444444444444444444444444444444444444444444\",", "\"enclave_pubkey\":\"5555555555555555555555555555555555555555555555555555555555555555\",", @@ -111137,23 +115671,44 @@ esac let new_open = test_session_open_frame(&refreshed); assert!(matches!( provider_session_open_decision(&new_open, &startup), - ProviderSessionDecision::Reject { code: "PRICE_VER", .. } + ProviderSessionDecision::Reject { + code: "PRICE_VER", + .. + } )); - assert_eq!(provider_session_open_decision(&new_open, &refreshed), ProviderSessionDecision::Accept); + assert_eq!( + provider_session_open_decision(&new_open, &refreshed), + ProviderSessionDecision::Accept + ); assert_eq!(refreshed.price_ver, 5); assert_eq!(refreshed.rate_map, text_generation_rate_map(3, 7)); assert_eq!(refreshed.per_req_au, 11); assert_eq!(refreshed.min_session_au, 17); assert_eq!(active.price_ver, startup.price_ver); - assert_eq!(active.locked_rate_map, normalize_rate_map(startup.rate_map.clone())); - assert!(matches!(provider_session_replay_decision(&active, &original_open).unwrap(), - ProviderSessionReplayDecision::Cached(frame) if frame == accept_frame)); + assert_eq!( + active.locked_rate_map, + normalize_rate_map(startup.rate_map.clone()) + ); + assert!( + matches!(provider_session_replay_decision(&active, &original_open).unwrap(), + ProviderSessionReplayDecision::Cached(frame) if frame == accept_frame) + ); let mut forged = refreshed.clone(); forged.rate_map = text_generation_rate_map(1, 1); - assert!(matches!(provider_session_open_decision(&test_session_open_frame(&forged), &refreshed), - ProviderSessionDecision::Reject { code: "VOUCHER", .. })); - assert!(matches!(provider_session_open_decision(&original_open, &refreshed), - ProviderSessionDecision::Reject { code: "PRICE_VER", .. })); + assert!(matches!( + provider_session_open_decision(&test_session_open_frame(&forged), &refreshed), + ProviderSessionDecision::Reject { + code: "VOUCHER", + .. + } + )); + assert!(matches!( + provider_session_open_decision(&original_open, &refreshed), + ProviderSessionDecision::Reject { + code: "PRICE_VER", + .. + } + )); } #[test] @@ -111171,13 +115726,22 @@ esac other_market.pending = None; contract.prices.insert(0, other_market); let mut before = startup.clone(); - assert_eq!(refresh_provider_session_price_terms(&contract, &mut before, 199), ProviderSessionDecision::Accept); + assert_eq!( + refresh_provider_session_price_terms(&contract, &mut before, 199), + ProviderSessionDecision::Accept + ); assert_eq!(before.price_ver, 1); let mut after = startup; - assert_eq!(refresh_provider_session_price_terms(&contract, &mut after, 200), ProviderSessionDecision::Accept); + assert_eq!( + refresh_provider_session_price_terms(&contract, &mut after, 200), + ProviderSessionDecision::Accept + ); assert_eq!(after.price_ver, 9); assert_eq!(after.ctx_bracket, before.ctx_bracket); - assert_eq!(provider_session_contract_decision(&contract, &after, &contract.rooms[..1], "fiat"), ProviderSessionDecision::Accept); + assert_eq!( + provider_session_contract_decision(&contract, &after, &contract.rooms[..1], "fiat"), + ProviderSessionDecision::Accept + ); } #[test] @@ -111187,19 +115751,34 @@ esac let mut contract = test_contract(&"aa".repeat(32)); match case { "missing" => contract.prices.clear(), - "provider" => contract.prices[0].current.as_mut().unwrap().set_by_role = Some("provider".to_owned()), + "provider" => { + contract.prices[0].current.as_mut().unwrap().set_by_role = + Some("provider".to_owned()) + } "model" => contract.prices[0].model_id = "other/model".to_owned(), "denom" => contract.prices[0].denom = "tnk".to_owned(), "table" => { contract.prices[0].ctx_bracket_table_ver = Some(99); - contract.prices[0].current.as_mut().unwrap().ctx_bracket_table_ver = Some(99); + contract.prices[0] + .current + .as_mut() + .unwrap() + .ctx_bracket_table_ver = Some(99); } "future" => contract.prices[0].current.as_mut().unwrap().effective_at = 201, _ => unreachable!(), } let mut refreshed = startup.clone(); - assert!(matches!(refresh_provider_session_price_terms(&contract, &mut refreshed, 200), - ProviderSessionDecision::Reject { code: "PRICE_VER", .. }), "{case}"); + assert!( + matches!( + refresh_provider_session_price_terms(&contract, &mut refreshed, 200), + ProviderSessionDecision::Reject { + code: "PRICE_VER", + .. + } + ), + "{case}" + ); assert_eq!(refreshed.price_ver, startup.price_ver); assert_eq!(refreshed.rate_map, startup.rate_map); } @@ -111216,15 +115795,141 @@ esac price.ctx_bracket = None; price.ctx_bracket_table_ver = None; price.ver = 6; - price.rate_map = vec![RateMapEntry { unit: "image".to_owned(), per_unit_au: 10, granularity: 1 }]; + price.rate_map = vec![RateMapEntry { + unit: "image".to_owned(), + per_unit_au: 10, + granularity: 1, + }]; let mut terms = test_provider_session_terms(); - assert_eq!(refresh_provider_session_price_terms(&contract, &mut terms, 200), ProviderSessionDecision::Accept); + assert_eq!( + refresh_provider_session_price_terms(&contract, &mut terms, 200), + ProviderSessionDecision::Accept + ); assert_eq!(terms.price_ver, 6); assert_eq!(terms.ctx_bracket, None); assert_eq!(terms.ctx_bracket_table_ver, None); assert_eq!(terms.rate_map[0].unit, "image"); } + #[tokio::test] + async fn provider_session_catalog_reads_only_its_confirmed_market_and_room() { + let terms = test_provider_session_terms(); + let source = test_contract(&"aa".repeat(32)); + let price_key = format!( + "price/{}", + ledger_price_market_key(&terms.enclave_id, terms.ctx_bracket.as_deref()) + ); + let room_id = &terms.room_ids[0]; + let records = Arc::new(BTreeMap::from([ + ( + format!("enclave/{}", terms.enclave_id), + serde_json::to_value(&source.enclaves[0]).unwrap(), + ), + ( + format!("prov/{}", terms.provider), + serde_json::to_value(&source.providers[0]).unwrap(), + ), + ( + format!("serve/{}/{}", terms.provider, terms.enclave_id), + serde_json::to_value(&source.serves[0]).unwrap(), + ), + ( + format!("room/{room_id}"), + serde_json::to_value(&source.rooms[0]).unwrap(), + ), + ( + format!( + "roomserve/{room_id}/{}/{}", + terms.provider, terms.enclave_id + ), + serde_json::to_value(&source.roomserve[0]).unwrap(), + ), + ( + price_key.clone(), + serde_json::to_value(&source.prices[0]).unwrap(), + ), + ( + "ctx_brackets".to_owned(), + serde_json::to_value(&source.ctx_bracket_schedule).unwrap(), + ), + ( + "rules/current".to_owned(), + serde_json::to_value(source.rules.as_ref().unwrap()).unwrap(), + ), + ])); + let seen = Arc::new(Mutex::new(Vec::::new())); + let listener = TcpListener::bind("127.0.0.1:0").unwrap(); + let address = listener.local_addr().unwrap(); + let server_records = Arc::clone(&records); + let server_seen = Arc::clone(&seen); + let server = thread::spawn(move || { + // One snapshot query, five independent state reads, one price, and + // the selected room plus its provider binding. + for _ in 0..9 { + let (mut stream, _) = listener.accept().unwrap(); + let mut request = Vec::new(); + loop { + let mut chunk = [0_u8; 4096]; + let read = stream.read(&mut chunk).unwrap(); + assert!(read > 0); + request.extend_from_slice(&chunk[..read]); + if request.windows(4).any(|part| part == b"\r\n\r\n") { + break; + } + } + let request_line = String::from_utf8_lossy(&request); + let path = request_line.split_whitespace().nth(1).unwrap(); + let url = reqwest::Url::parse(&format!("http://{address}{path}")).unwrap(); + let query = url.query_pairs().collect::>(); + assert_eq!(query.get("confirmed").map(|v| v.as_ref()), Some("true")); + let body = if let Some(prefix) = query.get("prefix") { + assert_eq!(prefix.as_ref(), "rules/current"); + assert_eq!(query.get("limit").map(|v| v.as_ref()), Some("1")); + server_seen.lock().unwrap().push("snapshot".to_owned()); + json!({ + "prefix": "rules/current", "confirmed": true, "signed_length": 42, + "truncated": false, "next_cursor": null, + "values": [{"key": "rules/current", "value": server_records["rules/current"]}] + }) + } else { + let key = query.get("key").unwrap().to_string(); + assert_eq!(query.get("signed_length").map(|v| v.as_ref()), Some("42")); + server_seen.lock().unwrap().push(key.clone()); + json!({ + "key": key, "confirmed": true, "signed_length": 42, + "value": server_records.get(&key) + }) + } + .to_string(); + write!( + stream, + "HTTP/1.1 200 OK\r\ncontent-type: application/json\r\ncontent-length: {}\r\nconnection: close\r\n\r\n{body}", + body.len() + ) + .unwrap(); + stream.flush().unwrap(); + } + }); + let rpc = PeerRpcClient::new(format!("http://{address}/v1")).unwrap(); + let contract = read_provider_session_contract_catalog(&rpc, &terms) + .await + .unwrap(); + server.join().unwrap(); + let mut refreshed = terms.clone(); + assert_eq!( + refresh_provider_session_price_terms(&contract, &mut refreshed, 200), + ProviderSessionDecision::Accept + ); + assert_eq!( + provider_session_contract_decision(&contract, &refreshed, &source.rooms[..1], "fiat"), + ProviderSessionDecision::Accept + ); + let seen = seen.lock().unwrap(); + assert_eq!(seen.len(), 9); + assert!(seen.contains(&price_key)); + assert!(!seen.iter().any(|key| key == "price/" || key == "ev/price/")); + } + #[test] fn provider_session_open_rechecks_current_admin_contract_state() { let terms = test_provider_session_terms(); @@ -111646,7 +116351,7 @@ printf '{"kind":"nvidia_nvtrust_offline_jwt","evidence":"boot:%s:%s","platform_i checkpoint_every: CheckpointPolicy { tokens: 1, ms: 0 }, max_spend_au: MoneyAu::MAX, accept_replay: None, - reservation_recovery: None, + reservation_recovery: None, }; let body = json!({ "messages": [ @@ -111740,7 +116445,7 @@ printf '{"kind":"nvidia_nvtrust_offline_jwt","evidence":"boot:%s:%s","platform_i checkpoint_every: CheckpointPolicy { tokens: 1, ms: 0 }, max_spend_au: MoneyAu::MAX, accept_replay: None, - reservation_recovery: None, + reservation_recovery: None, }; let body = json!({ "messages": [{ "role": "user", "content": "large atto receipt" }], @@ -111754,6 +116459,7 @@ printf '{"kind":"nvidia_nvtrust_offline_jwt","evidence":"boot:%s:%s","platform_i ReceiptUsage::text(1, 1), 1, true, + 1, &runtime_keypair, ) .unwrap(); @@ -111806,6 +116512,7 @@ printf '{"kind":"nvidia_nvtrust_offline_jwt","evidence":"boot:%s:%s","platform_i ReceiptUsage::default(), BTreeMap::new(), 1, + 1, &RuntimeKeypair::from_seed([9; 32]), ) .unwrap(); @@ -111853,7 +116560,9 @@ printf '{"kind":"nvidia_nvtrust_offline_jwt","evidence":"boot:%s:%s","platform_i .await .unwrap(); let cancellation = CancellationToken::new(); - if cancellable { cancellation.cancel(); } + if cancellable { + cancellation.cancel(); + } let result = wait_for_provider_receipt_ack_inner( &mut bridge, &active, @@ -111873,78 +116582,191 @@ printf '{"kind":"nvidia_nvtrust_offline_jwt","evidence":"boot:%s:%s","platform_i #[tokio::test] async fn provider_failure_close_retries_exact_submission_until_confirmed() { - let root = test_temp_dir("provider-failure-close"); - let outbox = Arc::new(ReceiptSettlementOutbox::new(root.join("provider")).unwrap()); - let terms = test_provider_session_terms(); - let mut active = test_active_provider_session(&terms, vec!["text".into()]); - let settlement = Arc::new(ProviderReceiptSettlement { outbox, - keypair_path: root.join("unused-key"), password: String::new(), enclave_pubkey: "aa".repeat(32) }); - active.receipt_settlement = Some(settlement.clone()); - let guard = provider_failure_recovery::begin(&active, &terms).unwrap(); - let path = root.join("provider/reservation-recovery").join(format!("{}.json", active.reservation_id)); - let binding = read_private_json_optional(&path).unwrap().unwrap()["binding"].clone(); - let mut value = mayhem_proto::usage_reservation_close_value(&binding, None, false, 1234, "provider_session_ended").unwrap(); - value["actor_sig"] = json!("aa".repeat(64)); - let feature = mayhem_proto::usage_reservation_close_feature(value).unwrap(); - write_private_json_once(&path.with_extension("close"), &feature).unwrap(); - let listener = TcpListener::bind("127.0.0.1:0").unwrap(); - let address = listener.local_addr().unwrap(); - let expected_feature = feature.clone(); - let server = thread::spawn(move || { - let mut posts = 0; - for _ in 0..8 { - let (mut stream, _) = listener.accept().unwrap(); - stream.set_read_timeout(Some(Duration::from_secs(5))).unwrap(); - let mut request = Vec::new(); - loop { - let mut buffer = [0u8; 8192]; - let n = stream.read(&mut buffer).unwrap(); - assert!(n > 0); - request.extend_from_slice(&buffer[..n]); - if let Some(end) = request.windows(4).position(|w| w == b"\r\n\r\n") { - let headers = String::from_utf8_lossy(&request[..end]); - let size = headers.lines().find_map(|line| line.to_ascii_lowercase().strip_prefix("content-length:") - .and_then(|s| s.trim().parse::().ok())).unwrap_or(0); - if request.len() >= end + 4 + size { break; } + for with_partial in [false, true] { + let root = test_temp_dir("provider-failure-close"); + let outbox = Arc::new(ReceiptSettlementOutbox::new(root.join("provider")).unwrap()); + let terms = test_provider_session_terms(); + let mut active = test_active_provider_session(&terms, vec!["text".into()]); + let settlement = Arc::new(ProviderReceiptSettlement { + outbox: outbox.clone(), + keypair_path: root.join("unused-key"), + password: String::new(), + enclave_pubkey: "aa".repeat(32), + }); + active.receipt_settlement = Some(settlement.clone()); + let partial = with_partial.then(|| { + outbox + .persist(&signed_receipt_settlement_feature_for_test_at( + 7, 1, false, 2, + )) + .unwrap() + }); + let guard = if let Some(partial) = &partial { + Some( + provider_failure_recovery::begin_binding( + &settlement, + &partial.feature["value"]["receipt"]["body"], + ) + .unwrap(), + ) + } else { + provider_failure_recovery::begin(&active, &terms).unwrap() + }; + let reservation_id = partial + .as_ref() + .map(|entry| { + entry.feature["value"]["receipt"]["body"]["reservation_id"] + .as_str() + .unwrap() + }) + .unwrap_or(&active.reservation_id); + let path = root + .join("provider/reservation-recovery") + .join(format!("{reservation_id}.json")); + let binding = read_private_json_optional(&path).unwrap().unwrap()["binding"].clone(); + let provider = binding["provider"].as_str().unwrap().to_owned(); + let head = partial.as_ref().map(|entry| { + json!({"type": "canonical_receipt_head", "settlement_ready": false, + "feature_key": entry.feature["key"], "receipt": entry.feature["value"]["receipt"]}) + }); + let mut value = mayhem_proto::usage_reservation_close_value( + &binding, + head.as_ref(), + false, + 1234, + "provider_session_ended", + ) + .unwrap(); + value["actor_sig"] = json!("aa".repeat(64)); + let feature = mayhem_proto::usage_reservation_close_feature(value).unwrap(); + write_private_json_once(&path.with_extension("close"), &feature).unwrap(); + let listener = TcpListener::bind("127.0.0.1:0").unwrap(); + let address = listener.local_addr().unwrap(); + let expected_feature = feature.clone(); + let server = thread::spawn(move || { + let mut posts = 0; + for _ in 0..if with_partial { 9 } else { 8 } { + let (mut stream, _) = listener.accept().unwrap(); + stream + .set_read_timeout(Some(Duration::from_secs(5))) + .unwrap(); + let mut request = Vec::new(); + loop { + let mut buffer = [0u8; 8192]; + let n = stream.read(&mut buffer).unwrap(); + assert!(n > 0); + request.extend_from_slice(&buffer[..n]); + if let Some(end) = request.windows(4).position(|w| w == b"\r\n\r\n") { + let headers = String::from_utf8_lossy(&request[..end]); + let size = headers + .lines() + .find_map(|line| { + line.to_ascii_lowercase() + .strip_prefix("content-length:") + .and_then(|s| s.trim().parse::().ok()) + }) + .unwrap_or(0); + if request.len() >= end + 4 + size { + break; + } + } } - } - let end = request.windows(4).position(|w| w == b"\r\n\r\n").unwrap(); - let headers = String::from_utf8_lossy(&request[..end]); - let (status, body) = if headers.starts_with("POST ") { - assert_eq!(serde_json::from_slice::(&request[end + 4..]).unwrap(), expected_feature); - posts += 1; - (if posts == 1 { "503 Service Unavailable" } else { "200 OK" }, json!({"ok": posts > 1})) - } else { - let target = headers.split_whitespace().nth(1).unwrap(); - let url = reqwest::Url::parse(&format!("http://localhost{target}")).unwrap(); - let key = url.query_pairs().find(|(k, _)| k == "key").unwrap().1.into_owned(); - let mut value = binding.clone(); - if key.starts_with("receipt/head/") { value = Value::Null; } - else if key.starts_with("receipt/reservation-close/") { - value["type"] = json!("targeted_reservation_close"); + let end = request.windows(4).position(|w| w == b"\r\n\r\n").unwrap(); + let headers = String::from_utf8_lossy(&request[..end]); + let (status, body) = if headers.starts_with("POST ") { + assert_eq!( + serde_json::from_slice::(&request[end + 4..]).unwrap(), + expected_feature + ); + posts += 1; + ( + if posts == 1 { + "503 Service Unavailable" + } else { + "200 OK" + }, + json!({"ok": posts > 1}), + ) } else { - value["type"] = json!("receipt_reservation_identity"); - value["status"] = json!(if posts > 1 { "closed" } else { "active" }); - } - ("200 OK", json!({"key": key, "confirmed": true, "value": value})) - }; - let body = body.to_string(); - write!(stream, "HTTP/1.1 {status}\r\ncontent-type: application/json\r\ncontent-length: {}\r\nconnection: close\r\n\r\n{body}", body.len()).unwrap(); - stream.flush().unwrap(); + let target = headers.split_whitespace().nth(1).unwrap(); + let url = + reqwest::Url::parse(&format!("http://localhost{target}")).unwrap(); + let key = url + .query_pairs() + .find(|(k, _)| k == "key") + .unwrap() + .1 + .into_owned(); + let mut value = binding.clone(); + if key.starts_with("receipt/head/") { + value = head.clone().unwrap_or(Value::Null); + } else if key.starts_with("receipt/reservation-close/") { + value["type"] = json!("targeted_reservation_close"); + } else { + value["type"] = json!("receipt_reservation_identity"); + value["status"] = json!(if posts > 1 { "closed" } else { "active" }); + } + ( + "200 OK", + json!({"key": key, "confirmed": true, "value": value}), + ) + }; + let body = body.to_string(); + write!(stream, "HTTP/1.1 {status}\r\ncontent-type: application/json\r\ncontent-length: {}\r\nconnection: close\r\n\r\n{body}", body.len()).unwrap(); + stream.flush().unwrap(); + } + assert_eq!(posts, 2); + }); + let rpc = PeerRpcClient::new(format!("http://{address}")).unwrap(); + assert!( + !provider_failure_recovery::recover_one(&settlement, &rpc, &provider, &path) + .await + .unwrap() + ); + drop(guard); + if let Some(partial) = partial { + assert!( + !provider_failure_recovery::recover_one(&settlement, &rpc, &provider, &path) + .await + .unwrap(), + "unconfirmed signed evidence must block closing the reservation" + ); + let key = receipt_settlement_head_key(&partial).unwrap(); + let confirmed = json!({"confirmed": true, "key": key, "value": { + "type": "canonical_receipt_head", "settlement_ready": false, + "feature_key": partial.feature["key"], "receipt": partial.feature["value"]["receipt"] + }}); + assert!(confirmed_receipt_settlement_record_matches( + &confirmed, &key, &partial + )); + outbox.remove(&partial).unwrap(); } - assert_eq!(posts, 2); - }); - let rpc = PeerRpcClient::new(format!("http://{address}")).unwrap(); - assert!(!provider_failure_recovery::recover_one(&settlement, &rpc, &terms.provider, &path).await.unwrap()); - drop(guard); - assert!(provider_failure_recovery::recover_one(&settlement, &rpc, &terms.provider, &path).await.is_err()); - assert_eq!(read_private_json_optional(&path.with_extension("close")).unwrap().unwrap(), feature); - assert!(!provider_failure_recovery::recover_one(&settlement, &rpc, &terms.provider, &path).await.unwrap()); - assert!(path.exists(), "a submission response is not confirmation"); - assert!(provider_failure_recovery::recover_one(&settlement, &rpc, &terms.provider, &path).await.unwrap()); - assert!(!path.exists()); - server.join().unwrap(); - fs::remove_dir_all(root).unwrap(); + assert!( + provider_failure_recovery::recover_one(&settlement, &rpc, &provider, &path) + .await + .is_err() + ); + assert_eq!( + read_private_json_optional(&path.with_extension("close")) + .unwrap() + .unwrap(), + feature + ); + assert!( + !provider_failure_recovery::recover_one(&settlement, &rpc, &provider, &path) + .await + .unwrap() + ); + assert!(path.exists(), "a submission response is not confirmation"); + assert!( + provider_failure_recovery::recover_one(&settlement, &rpc, &provider, &path) + .await + .unwrap() + ); + assert!(!path.exists()); + server.join().unwrap(); + fs::remove_dir_all(root).unwrap(); + } } #[test] @@ -111954,20 +116776,42 @@ printf '{"kind":"nvidia_nvtrust_offline_jwt","evidence":"boot:%s:%s","platform_i let terms = test_provider_session_terms(); let mut active = test_active_provider_session(&terms, vec!["text".into()]); active.receipt_settlement = Some(Arc::new(ProviderReceiptSettlement { - outbox, keypair_path: root.join("unused-test-key"), password: String::new(), + outbox, + keypair_path: root.join("unused-test-key"), + password: String::new(), enclave_pubkey: RuntimeKeypair::from_seed([9; 32]).public_key_hex(), })); - let guard = provider_failure_recovery::begin(&active, &terms).unwrap().unwrap(); - let path = root.join("provider/reservation-recovery").join(format!("{}.json", active.reservation_id)); + let guard = provider_failure_recovery::begin(&active, &terms) + .unwrap() + .unwrap(); + let path = root + .join("provider/reservation-recovery") + .join(format!("{}.json", active.reservation_id)); let evidence = read_private_json_optional(&path).unwrap().unwrap(); assert_eq!(evidence["binding"]["session_id"], active.session_id); assert!(evidence.get("prompt").is_none()); - let lock = fs::OpenOptions::new().read(true).write(true).open(path.with_extension("lock")).unwrap(); - assert!(fs2::FileExt::try_lock_exclusive(&lock).is_err(), "running generation must retain ownership"); - assert!(provider_failure_recovery::begin(&active, &terms).is_err(), "a duplicate must not execute"); + let lock = fs::OpenOptions::new() + .read(true) + .write(true) + .open(path.with_extension("lock")) + .unwrap(); + assert!( + fs2::FileExt::try_lock_exclusive(&lock).is_err(), + "running generation must retain ownership" + ); + assert!( + provider_failure_recovery::begin(&active, &terms).is_err(), + "a duplicate must not execute" + ); drop(guard); - assert!(fs2::FileExt::try_lock_exclusive(&lock).is_ok(), "return or unwind must enable recovery"); - assert_eq!(read_private_json_optional(&path).unwrap().unwrap(), evidence); + assert!( + fs2::FileExt::try_lock_exclusive(&lock).is_ok(), + "return or unwind must enable recovery" + ); + assert_eq!( + read_private_json_optional(&path).unwrap().unwrap(), + evidence + ); drop(lock); fs::remove_dir_all(root).unwrap(); } @@ -111981,15 +116825,36 @@ printf '{"kind":"nvidia_nvtrust_offline_jwt","evidence":"boot:%s:%s","platform_i let attribution = BTreeMap::from([("reasoning_output_tokens".to_owned(), 159)]); let keypair = RuntimeKeypair::from_seed([9; 32]); let checkpoint = provider_session_receipt_for_usage_attribution( - &terms, &active, &body, usage.clone(), attribution.clone(), 18, false, &keypair, - ).unwrap(); + &terms, + &active, + &body, + usage.clone(), + attribution.clone(), + 18, + false, + 1, + &keypair, + ) + .unwrap(); let terminal = provider_failed_session_receipt( - &terms, &active, &body, usage, attribution, 19, &keypair, - ).unwrap().unwrap(); + &terms, + &active, + &body, + usage, + attribution, + 19, + 1, + &keypair, + ) + .unwrap() + .unwrap(); assert!(terminal.body.final_receipt); assert_eq!(terminal.body.seq, 19); assert_eq!(terminal.body.usage, checkpoint.body.usage); - assert_eq!(terminal.body.usage_attribution, checkpoint.body.usage_attribution); + assert_eq!( + terminal.body.usage_attribution, + checkpoint.body.usage_attribution + ); assert_eq!(terminal.body.au_owed_cum, checkpoint.body.au_owed_cum); assert_eq!(terminal.body.prompt_hash, checkpoint.body.prompt_hash); } @@ -112001,9 +116866,17 @@ printf '{"kind":"nvidia_nvtrust_offline_jwt","evidence":"boot:%s:%s","platform_i terms.min_session_au = 0; let active = test_active_provider_session(&terms, vec!["text".to_owned()]); assert!(provider_failed_session_receipt( - &terms, &active, &json!({"messages": []}), ReceiptUsage::default(), - BTreeMap::new(), 1, &RuntimeKeypair::from_seed([9; 32]), - ).unwrap().is_none()); + &terms, + &active, + &json!({"messages": []}), + ReceiptUsage::default(), + BTreeMap::new(), + 1, + 1, + &RuntimeKeypair::from_seed([9; 32]), + ) + .unwrap() + .is_none()); } #[test] @@ -112024,6 +116897,7 @@ printf '{"kind":"nvidia_nvtrust_offline_jwt","evidence":"boot:%s:%s","platform_i ReceiptUsage::default(), BTreeMap::new(), 1, + 1, &RuntimeKeypair::from_seed([9; 32]), ) .expect("cancelled receipt"); @@ -112065,6 +116939,7 @@ printf '{"kind":"nvidia_nvtrust_offline_jwt","evidence":"boot:%s:%s","platform_i ReceiptUsage::default(), BTreeMap::new(), 1, + 1, &RuntimeKeypair::from_seed([9; 32]), ) .expect("variable-only cancelled receipt"); @@ -112150,7 +117025,7 @@ printf '{"kind":"nvidia_nvtrust_offline_jwt","evidence":"boot:%s:%s","platform_i checkpoint_every: CheckpointPolicy { tokens: 2, ms: 0 }, max_spend_au: MoneyAu::MAX, accept_replay: None, - reservation_recovery: None, + reservation_recovery: None, }; let body = json!({ "messages": [{ "role": "user", "content": "hello mayhem" }], @@ -112165,6 +117040,7 @@ printf '{"kind":"nvidia_nvtrust_offline_jwt","evidence":"boot:%s:%s","platform_i usage.clone(), 7, false, + 1, &runtime_keypair, ) .unwrap(); @@ -112222,6 +117098,7 @@ printf '{"kind":"nvidia_nvtrust_offline_jwt","evidence":"boot:%s:%s","platform_i ReceiptUsage::text(99, 3), 1, true, + 1, &RuntimeKeypair::from_seed([19; 32]), ) .expect("redispatch receipt should charge only the new visible output"); @@ -112240,6 +117117,7 @@ printf '{"kind":"nvidia_nvtrust_offline_jwt","evidence":"boot:%s:%s","platform_i ReceiptUsage::text(99, 3), 1, true, + 1, &RuntimeKeypair::from_seed([19; 32]), ) .unwrap_err() @@ -112283,7 +117161,7 @@ printf '{"kind":"nvidia_nvtrust_offline_jwt","evidence":"boot:%s:%s","platform_i checkpoint_every: CheckpointPolicy { tokens: 2, ms: 0 }, max_spend_au: MoneyAu::MAX, accept_replay: None, - reservation_recovery: None, + reservation_recovery: None, }; assert_eq!( provider_session_receipt_ack_timeout(&active), @@ -112334,7 +117212,7 @@ printf '{"kind":"nvidia_nvtrust_offline_jwt","evidence":"boot:%s:%s","platform_i checkpoint_every: CheckpointPolicy { tokens: 1, ms: 0 }, max_spend_au: MoneyAu::MAX, accept_replay: None, - reservation_recovery: None, + reservation_recovery: None, }; let body = json!({ "messages": [{ "role": "user", "content": "hello mayhem" }], @@ -112398,6 +117276,7 @@ printf '{"kind":"nvidia_nvtrust_offline_jwt","evidence":"boot:%s:%s","platform_i ReceiptUsage::text(1, 1), 1, true, + 1, &RuntimeKeypair::from_seed([9; 32]), ) .unwrap(); @@ -113565,6 +118444,81 @@ printf '{"kind":"nvidia_nvtrust_offline_jwt","evidence":"boot:%s:%s","platform_i assert_eq!(covered, served); } + #[test] + fn laya_provider_modality_canary_is_transportable_and_validated() { + let catalog = catalog::load_document(&repo_path("catalog/models.json").unwrap()).unwrap(); + let model = catalog + .models + .iter() + .find(|model| model.model_id == "convaiinnovations/laya") + .expect("live Laya model"); + let prompts = load_canary_prompts_checked( + Some(&repo_path("catalog/canaries").unwrap()), + &model.canary.set_id, + None, + false, + ) + .unwrap(); + let prompt = prompts + .iter() + .find(|prompt| prompt.id == "english-choice-noul") + .expect("Laya decision canary"); + + assert_eq!( + provider_canary_prompt_modalities(model, prompt), + BTreeSet::from(["text".to_owned()]) + ); + let body = provider_canary_self_test_body(model, prompt).unwrap(); + assert_eq!(body["kind"], "decision"); + assert_eq!( + body["endpoint_family"], + mayhem_proto::ENDPOINT_MAYHEM_DECISIONS + ); + let sealed = + provider_seal_local_contract_request(&body, &model.adapter, &model.model_id).unwrap(); + let verified = + provider_verify_endpoint_request(&sealed, Some(&model.model_id), &model.adapter) + .unwrap(); + assert_eq!(verified.family, mayhem_proto::ENDPOINT_MAYHEM_DECISIONS); + let load = provider_session_modality_load( + verified.contract, + verified.family, + verified.request, + &sealed, + None, + &["text".to_owned()], + ) + .unwrap(); + assert!(load.is_empty()); + assert_eq!( + provider_session_request_modalities( + verified.family, + verified.request, + &["text".to_owned()], + &load, + ) + .unwrap(), + vec!["text".to_owned()] + ); + let request = provider_decision_request_from_body(verified.request).unwrap(); + assert_eq!(request.checkpoint.as_deref(), Some("english")); + + let mut output = test_provider_output_with_artifacts(Vec::new()); + output.content = json!({ + "answers": {"department": {"type": "choice", "choice": "billing"}}, + "routing": {"checkpoint": "english", "reason": "explicit"}, + "usage": {"input_tokens": 12} + }) + .to_string(); + output.prompt_tokens = 12; + output.completion_tokens = 24; + output.usage = ReceiptUsage::text(12, 24); + validate_provider_canary_self_test_output(model, &output).unwrap(); + + output.content = json!({"routing": {"checkpoint": "english"}}).to_string(); + assert!(validate_provider_canary_self_test_output(model, &output).is_err()); + } + #[test] fn bounded_provider_modality_probe_uses_signed_direct_answer_level() { let catalog_path = repo_path("catalog/models.json").unwrap(); @@ -113704,24 +118658,34 @@ printf '{"kind":"nvidia_nvtrust_offline_jwt","evidence":"boot:%s:%s","platform_i assert_eq!(explicit["steps"], 9); assert_eq!(explicit["cfg_scale"], 0.0); assert_eq!(explicit["seed"], 11); - let reference = format!("data:image/png;base64,{}", base64::engine::general_purpose::STANDARD.encode( - include_bytes!("../../mayhem-engine/tests/fixtures/reference.png"), - )); + let reference = format!( + "data:image/png;base64,{}", + base64::engine::general_purpose::STANDARD.encode(include_bytes!( + "../../mayhem-engine/tests/fixtures/reference.png" + ),) + ); let reference_prompt: CanaryPrompt = serde_json::from_value(json!({ "id": "reference", "prompt": "a compass on a map", "input_reference": reference, "strength": 0.6, "negative_prompt": "blur", "cfg_scale": 1.0, - })).unwrap(); + })) + .unwrap(); let body = provider_canary_self_test_body(&model, &reference_prompt).unwrap(); - let mut sealed = provider_seal_local_contract_request(&body, &model.adapter, &model.model_id).unwrap(); + let mut sealed = + provider_seal_local_contract_request(&body, &model.adapter, &model.model_id).unwrap(); provider_verify_endpoint_request(&sealed, Some(&model.model_id), &model.adapter).unwrap(); let request = provider_image_generation_request_from_body( - mayhem_proto::ENDPOINT_OPENAI_IMAGE_GENERATIONS, &sealed["contract_request"], - ).unwrap(); + mayhem_proto::ENDPOINT_OPENAI_IMAGE_GENERATIONS, + &sealed["contract_request"], + ) + .unwrap(); assert_eq!(request.input_reference.as_deref(), Some(reference.as_str())); assert_eq!(request.strength, Some(0.6)); assert_eq!(request.negative_prompt.as_deref(), Some("blur")); sealed["contract_request"]["strength"] = json!(0.7); - assert!(provider_verify_endpoint_request(&sealed, Some(&model.model_id), &model.adapter).is_err()); + assert!( + provider_verify_endpoint_request(&sealed, Some(&model.model_id), &model.adapter) + .is_err() + ); } #[test] @@ -113893,29 +118857,43 @@ printf '{"kind":"nvidia_nvtrust_offline_jwt","evidence":"boot:%s:%s","platform_i let catalog = catalog::load_document(&catalog_path)?; let model_id = std::env::var("MAYHEM_CANARY_REQUESTS_MODEL") .unwrap_or_else(|_| "Qwen/Qwen3.8-27B".to_owned()); - let artifact_name = std::env::var("MAYHEM_CANARY_REQUESTS_ARTIFACT") - .unwrap_or_else(|_| "nvfp4".to_owned()); + let artifact_name = + std::env::var("MAYHEM_CANARY_REQUESTS_ARTIFACT").unwrap_or_else(|_| "nvfp4".to_owned()); let baseline_model = catalog .models .iter() .find(|model| model.model_id == model_id) .with_context(|| format!("catalog model {model_id}"))?; - let baseline_artifact = baseline_model.artifacts.get(&artifact_name) + let baseline_artifact = baseline_model + .artifacts + .get(&artifact_name) .with_context(|| format!("catalog artifact {artifact_name}"))?; let mode_id = std::env::var("MAYHEM_CANARY_REQUESTS_EXECUTION_MODE").ok(); - let mode = mode_id.as_deref().map(|id| { - catalog.vllm_execution_mode(&baseline_artifact.artifact_root, id) - .with_context(|| format!("catalog execution mode {id}")) - }).transpose()?; - let projected = mode.map(|mode| { - catalog::execution_mode_model(baseline_model, &artifact_name, mode) - }).transpose()?; + let mode = mode_id + .as_deref() + .map(|id| { + catalog + .vllm_execution_mode(&baseline_artifact.artifact_root, id) + .with_context(|| format!("catalog execution mode {id}")) + }) + .transpose()?; + let projected = mode + .map(|mode| catalog::execution_mode_model(baseline_model, &artifact_name, mode)) + .transpose()?; let model = projected.as_ref().unwrap_or(baseline_model); - let binding = mode.map(|mode| { - mode.binding(&baseline_artifact.artifact_root, mode_id.as_deref().unwrap()) - }).transpose()?; + let binding = mode + .map(|mode| { + mode.binding( + &baseline_artifact.artifact_root, + mode_id.as_deref().unwrap(), + ) + }) + .transpose()?; let seed: u32 = std::env::var("MAYHEM_CANARY_REQUESTS_SEED") - .ok().map(|value| value.parse()).transpose()?.unwrap_or(0); + .ok() + .map(|value| value.parse()) + .transpose()? + .unwrap_or(0); ensure!( model.canary.verification_method == CANARY_VERIFICATION_TOKEN_FINGERPRINT, "{model_id} is not a token-fingerprint canary model" @@ -113931,7 +118909,10 @@ printf '{"kind":"nvidia_nvtrust_offline_jwt","evidence":"boot:%s:%s","platform_i let canary_sha256 = canary_set_file_sha256(&canaries_dir, &model.canary.set_id) .map_err(anyhow::Error::msg)?; if let Some(mode) = mode { - ensure!(mode.canary_set_sha256 == canary_sha256, "mode canary input bytes changed"); + ensure!( + mode.canary_set_sha256 == canary_sha256, + "mode canary input bytes changed" + ); } let prompts = load_canary_prompts_checked(Some(&canaries_dir), &model.canary.set_id, None, true)?; @@ -114067,6 +119048,260 @@ printf '{"kind":"nvidia_nvtrust_offline_jwt","evidence":"boot:%s:%s","platform_i catalog_endpoint_calibration_preflight(model, &prompts).unwrap(); } + #[test] + fn laya_endpoint_calibration_preflight_is_complete() { + let catalog_path = repo_path("catalog/models.json").unwrap(); + let catalog = catalog::load_document(&catalog_path).unwrap(); + let model = catalog + .models + .iter() + .find(|model| model.model_id == "convaiinnovations/laya") + .expect("Laya catalog model"); + let canaries_dir = repo_path("catalog/canaries").unwrap(); + let prompts = + load_canary_prompts_checked(Some(&canaries_dir), &model.canary.set_id, None, false) + .unwrap(); + + catalog_endpoint_calibration_preflight(model, &prompts).unwrap(); + assert!(prompts.iter().all(|prompt| { + provider_canary_prompt_modalities(model, prompt) == BTreeSet::from(["text".to_owned()]) + })); + + let (substitutions, fixtures) = catalog_endpoint_calibration_fixtures(model, &prompts); + let contract = model + .adapter + .endpoint_families + .iter() + .find(|contract| contract.family == mayhem_proto::ENDPOINT_MAYHEM_DECISIONS) + .unwrap(); + for case in mayhem_proto::generate_endpoint_calibration_cases(contract).unwrap() { + if !case.expect_accept { + continue; + } + let request = + mayhem_proto::materialize_endpoint_calibration_request(&case, &substitutions) + .unwrap(); + let request = catalog_endpoint_calibration_materialize_request( + contract, &case, request, &fixtures, + ) + .unwrap(); + if request.get("email").is_some() { + assert!(request.pointer("/state/body").is_some_and(Value::is_string)); + } + if case + .expected_response_attributes + .iter() + .any(|path| path == "shortlist") + { + assert!(request.get("shortlist").is_some_and(Value::is_object)); + } + if case + .expected_response_attributes + .iter() + .any(|path| path == "preprocessing") + { + assert!(request.get("email").is_some_and(Value::is_object)); + } + } + } + + #[test] + fn endpoint_calibration_extracts_video_fixture_from_data_url() { + let mut substitutions = BTreeMap::new(); + collect_endpoint_media_fixture_substitutions( + &json!({ + "type": "video_url", + "video_url": {"url": "data:video/mp4;base64,aGVsbG8="} + }), + &mut substitutions, + ); + assert_eq!(substitutions.get("$VIDEO_BASE64"), Some(&json!("aGVsbG8="))); + + let mut invalid = BTreeMap::new(); + collect_endpoint_media_fixture_substitutions( + &json!({"video_url": {"url": "data:video/mp4;base64,not-base64"}}), + &mut invalid, + ); + assert!(!invalid.contains_key("$VIDEO_BASE64")); + } + + #[test] + fn qwen_endpoint_calibration_materializes_partial_hf_video_rows() { + fn video_descriptor(request: &Value) -> &Map { + request["messages"] + .as_array() + .unwrap() + .iter() + .filter_map(|message| message.get("content").and_then(Value::as_array)) + .flatten() + .find(|part| part.get("type").and_then(Value::as_str) == Some("video")) + .and_then(|part| part.get("video")) + .and_then(Value::as_object) + .unwrap() + } + + let catalog_path = repo_path("catalog/models.json").unwrap(); + let catalog = catalog::load_document(&catalog_path).unwrap(); + let model = catalog + .models + .iter() + .find(|model| model.model_id == "Qwen/Qwen3.8-27B") + .expect("Qwen 3.8 catalog model"); + let canaries_dir = repo_path("catalog/canaries").unwrap(); + let prompts = + load_canary_prompts_checked(Some(&canaries_dir), &model.canary.set_id, None, false) + .unwrap(); + let (substitutions, fixtures) = catalog_endpoint_calibration_fixtures(model, &prompts); + let contract = model + .adapter + .endpoint_families + .iter() + .find(|contract| contract.family == mayhem_proto::ENDPOINT_HF_MULTIMODAL_CHAT) + .unwrap(); + let expected_attributes = vec![ + "messages.content.type".to_owned(), + "messages.content.video.fps".to_owned(), + "messages.content.video.num_frames".to_owned(), + "messages.role".to_owned(), + ]; + let cases = mayhem_proto::generate_endpoint_calibration_cases(contract).unwrap(); + let partial = cases + .iter() + .filter(|case| case.expect_accept && case.attributes == expected_attributes) + .collect::>(); + assert_eq!(partial.len(), 7); + let expected_shapes = BTreeMap::from([ + (("0.01".to_owned(), 1_u64), 1_usize), + (("8".to_owned(), 1_u64), 3_usize), + (("8".to_owned(), 16_u64), 1_usize), + (("8".to_owned(), 1024_u64), 1_usize), + (("240.0".to_owned(), 1_u64), 1_usize), + ]); + let mut observed_shapes = BTreeMap::new(); + for case in &partial { + let raw = mayhem_proto::materialize_endpoint_calibration_request(case, &substitutions) + .unwrap(); + let raw_video = video_descriptor(&raw); + assert_eq!( + raw_video.keys().cloned().collect::>(), + BTreeSet::from(["fps".to_owned(), "num_frames".to_owned()]) + ); + *observed_shapes + .entry(( + raw_video["fps"].to_string(), + raw_video["num_frames"].as_u64().unwrap(), + )) + .or_insert(0) += 1; + let fps = raw_video["fps"].clone(); + let num_frames = raw_video["num_frames"].clone(); + let materialized = + catalog_endpoint_calibration_materialize_request(contract, case, raw, &fixtures) + .unwrap(); + let video = video_descriptor(&materialized); + assert_eq!(video["fps"], fps); + assert_eq!(video["num_frames"], num_frames); + assert_eq!(video["data"], substitutions["$VIDEO_BASE64"]); + assert_eq!(video["content_type"], "video/mp4"); + assert_eq!(video.len(), 4); + mayhem_proto::validate_endpoint_request(contract, &materialized).unwrap(); + let normalized = normalize_endpoint_calibration_request(contract, &materialized) + .unwrap() + .normalized_request; + let transport = + catalog_endpoint_calibration_transport(contract, &normalized, &fixtures).unwrap(); + let (translation, handled) = + catalog_endpoint_calibration_translation(model, contract, &normalized, &transport) + .unwrap(); + assert_eq!(translation["messages"], materialized["messages"]); + assert!(handled.contains("messages.content.video.data")); + assert!(handled.contains("messages.content.video.content_type")); + } + assert_eq!(observed_shapes, expected_shapes); + + let case = partial[0]; + let mut raw = + mayhem_proto::materialize_endpoint_calibration_request(case, &substitutions).unwrap(); + let named_choice = + contract.request_attribute_specs["tool_choice"].calibration_values[2].clone(); + raw["tool_choice"] = named_choice.clone(); + let mut media_and_tool = case.clone(); + media_and_tool + .mutations + .push(mayhem_proto::EndpointCalibrationMutation { + path: "tool_choice".to_owned(), + value: mayhem_proto::EndpointCalibrationValue::Literal { + value: named_choice, + }, + }); + let composed = catalog_endpoint_calibration_materialize_request( + contract, + &media_and_tool, + raw.clone(), + &fixtures, + ) + .unwrap(); + assert_eq!( + video_descriptor(&composed)["data"], + substitutions["$VIDEO_BASE64"] + ); + assert!(composed["tools"] + .as_array() + .is_some_and(|tools| !tools.is_empty())); + assert_eq!( + composed["max_tokens"], + ENDPOINT_CALIBRATION_FORCED_TOOL_FALLBACK_MAX_OUTPUT_TOKENS + ); + + let mut rejected = case.clone(); + rejected.expect_accept = false; + assert_eq!( + catalog_endpoint_calibration_materialize_request( + contract, + &rejected, + raw.clone(), + &fixtures, + ) + .unwrap(), + raw + ); + + for (path, key, explicit) in [ + ( + "messages.content.video.data", + "data", + json!("explicit-video-data"), + ), + ( + "messages.content.video.content_type", + "content_type", + json!("video/webm"), + ), + ] { + let mut explicit_case = case.clone(); + explicit_case + .mutations + .push(mayhem_proto::EndpointCalibrationMutation { + path: path.to_owned(), + value: mayhem_proto::EndpointCalibrationValue::Literal { + value: explicit.clone(), + }, + }); + let mut explicit_request = + mayhem_proto::materialize_endpoint_calibration_request(case, &substitutions) + .unwrap(); + video_descriptor(&explicit_request); + explicit_request["messages"][0]["content"][0]["video"][key] = explicit.clone(); + let materialized = catalog_endpoint_calibration_materialize_request( + contract, + &explicit_case, + explicit_request, + &fixtures, + ) + .unwrap(); + assert_eq!(video_descriptor(&materialized)[key], explicit); + } + } + #[test] fn qwen_endpoint_calibration_preserves_reasoning_history() { let catalog_path = repo_path("catalog/models.json").unwrap(); @@ -114180,12 +119415,47 @@ printf '{"kind":"nvidia_nvtrust_offline_jwt","evidence":"boot:%s:%s","platform_i &fixtures, ) .unwrap(); - let mut expected = raw.clone(); - expected["tools"] = + let expected_tools = contract.request_attribute_specs["tools"].calibration_values[0].clone(); assert_eq!( - request, expected, - "{family}: only add the signed tools companion" + request["tools"], expected_tools, + "{family}: add the signed tools companion" + ); + let budget_path = if family == mayhem_proto::ENDPOINT_OPENAI_RESPONSES { + "max_output_tokens" + } else { + "max_tokens" + }; + assert_eq!( + request[budget_path], ENDPOINT_CALIBRATION_FORCED_TOOL_FALLBACK_MAX_OUTPUT_TOKENS, + "{family}: allow default reasoning to reach the required call" + ); + assert_eq!( + catalog_endpoint_calibration_output_token_cap( + &request, + ENDPOINT_CALIBRATION_FORCED_TOOL_FALLBACK_MAX_OUTPUT_TOKENS, + ), + ENDPOINT_CALIBRATION_FORCED_TOOL_FALLBACK_MAX_OUTPUT_TOKENS + ); + assert!( + request.to_string().contains(&format!( + "Call the {name} function now with arguments that satisfy its schema" + )), + "{family}: request the selected tool directly" + ); + let bound_budget = 512; + let bound_request = catalog_endpoint_calibration_materialize_request_with_tool_budget( + contract, + case, + raw.clone(), + &fixtures, + bound_budget, + ) + .unwrap(); + assert_eq!(bound_request[budget_path], bound_budget); + assert_eq!( + catalog_endpoint_calibration_output_token_cap(&bound_request, bound_budget), + bound_budget ); let normalized = normalize_endpoint_calibration_request(contract, &request) .unwrap() @@ -114226,9 +119496,17 @@ printf '{"kind":"nvidia_nvtrust_offline_jwt","evidence":"boot:%s:%s","platform_i .unwrap(); let forced = !matches!(choice.as_str(), Some("auto" | "none")); if forced { - variant["tools"] = request["tools"].clone(); + assert_eq!(materialized["tools"], request["tools"]); + assert_eq!( + materialized[budget_path], + ENDPOINT_CALIBRATION_FORCED_TOOL_FALLBACK_MAX_OUTPUT_TOKENS + ); + assert!(materialized.to_string().contains(&format!( + "Call the {name} function now with arguments that satisfy its schema" + ))); + } else { + assert_eq!(materialized, variant); } - assert_eq!(materialized, variant); let (translation, _) = catalog_endpoint_calibration_translation( model, contract, @@ -114488,29 +119766,63 @@ printf '{"kind":"nvidia_nvtrust_offline_jwt","evidence":"boot:%s:%s","platform_i #[test] fn candidate_z_image_reference_endpoint_calibration_is_complete() { let catalog = catalog::load_document(&repo_path("catalog/models.json").unwrap()).unwrap(); - let mut model = catalog.models.iter().find(|model| model.model_id == "tongyi/z-image-turbo").unwrap().clone(); + let mut model = catalog + .models + .iter() + .find(|model| model.model_id == "tongyi/z-image-turbo") + .unwrap() + .clone(); let family = mayhem_proto::ENDPOINT_OPENAI_IMAGE_GENERATIONS; let template = mayhem_proto::endpoint_family_contract_template(family).unwrap(); - let contract = model.adapter.endpoint_families.iter_mut().find(|contract| contract.family == family).unwrap(); + let contract = model + .adapter + .endpoint_families + .iter_mut() + .find(|contract| contract.family == family) + .unwrap(); for name in ["input_reference", "strength"] { contract.request_attributes.push(name.to_owned()); - contract.request_attribute_specs.insert(name.to_owned(), template.request_attribute_specs[name].clone()); + contract.request_attribute_specs.insert( + name.to_owned(), + template.request_attribute_specs[name].clone(), + ); } - let reference = format!("data:image/png;base64,{}", base64::engine::general_purpose::STANDARD.encode( - include_bytes!("../../mayhem-engine/tests/fixtures/reference.png"), - )); + let reference = format!( + "data:image/png;base64,{}", + base64::engine::general_purpose::STANDARD.encode(include_bytes!( + "../../mayhem-engine/tests/fixtures/reference.png" + ),) + ); let prompts: Vec = vec![serde_json::from_value(json!({ "id": "image-reference-p1", "prompt": "A sculpture", "input_reference": reference, "strength": 0.6, "size": "1024x1024", "steps": 9, "cfg_scale": 0.0, - })).unwrap()]; + })) + .unwrap()]; catalog_endpoint_calibration_preflight(&model, &prompts).unwrap(); - let mut backend = FakeEngineBackend::new("").with_artifact_chunks(vec![ArtifactChunk { - artifact_id: "image-1".to_owned(), index: 0, content_type: "image/png".to_owned(), - bytes: include_bytes!("../../mayhem-engine/tests/fixtures/reference.png").to_vec(), final_chunk: true, - }]).with_repeated_image_artifact(); + let mut backend = FakeEngineBackend::new("") + .with_artifact_chunks(vec![ArtifactChunk { + artifact_id: "image-1".to_owned(), + index: 0, + content_type: "image/png".to_owned(), + bytes: include_bytes!("../../mayhem-engine/tests/fixtures/reference.png").to_vec(), + final_chunk: true, + }]) + .with_repeated_image_artifact(); let (artifact_name, artifact) = model.artifacts.iter().next().unwrap(); - let report = catalog_endpoint_calibration_report(&mut backend, &model, artifact_name, artifact, &prompts, None); - let failures = report.families.iter().flat_map(|family| &family.cases).filter(|case| !case.ok).collect::>(); + let report = catalog_endpoint_calibration_report( + &mut backend, + &model, + artifact_name, + artifact, + &prompts, + None, + ); + let failures = report + .families + .iter() + .flat_map(|family| &family.cases) + .filter(|case| !case.ok) + .collect::>(); assert!(report.ok, "{failures:#?}"); } @@ -114987,32 +120299,44 @@ printf '{"kind":"nvidia_nvtrust_offline_jwt","evidence":"boot:%s:%s","platform_i "response_attribute_specs": {}, "interaction_groups": [] }] - })).unwrap(); + })) + .unwrap(); let mut selected = baseline.clone(); - selected.model.adapter.endpoint_families[0].request_attribute_specs.insert( - "min_p".to_owned(), requests.endpoint_families[0].request_attribute_specs["min_p"].clone(), - ); + selected.model.adapter.endpoint_families[0] + .request_attribute_specs + .insert( + "min_p".to_owned(), + requests.endpoint_families[0].request_attribute_specs["min_p"].clone(), + ); selected.execution_mode = Some(ProviderExecutionMode { binding: mayhem_proto::ExecutionModeBinding { - mode_id: "test-mode".to_owned(), policy_hash: "ab".repeat(32), + mode_id: "test-mode".to_owned(), + policy_hash: "ab".repeat(32), }, requests, baseline_adapter: baseline_adapter.clone(), }); - let canaries = test_canary_dir_with_prompts(&selected.model.canary.set_id, json!([{ - "id": "image-health", - "messages": [{"role": "user", "content": [ - {"type": "text", "text": "Describe this image."}, - {"type": "image_url", "image_url": {"url": tiny_png_data_url()}} - ]}], - "temperature": 0, "min_p": 0, "seed": 7, "max_tokens": 64 - }])); + let canaries = test_canary_dir_with_prompts( + &selected.model.canary.set_id, + json!([{ + "id": "image-health", + "messages": [{"role": "user", "content": [ + {"type": "text", "text": "Describe this image."}, + {"type": "image_url", "image_url": {"url": tiny_png_data_url()}} + ]}], + "temperature": 0, "min_p": 0, "seed": 7, "max_tokens": 64 + }]), + ); let args = test_provider_start_args(); let wallet: WalletInfo = serde_json::from_value(json!({ "created": false, "keypair_path": "unused", "public_key": "55".repeat(32) - })).unwrap(); + })) + .unwrap(); let runtime = ProviderBackendRuntime::default(); - let artifacts = ProviderArtifactPaths { primary: PathBuf::from("unused"), sidecars: BTreeMap::new() }; + let artifacts = ProviderArtifactPaths { + primary: PathBuf::from("unused"), + sidecars: BTreeMap::new(), + }; let attestation: Tier1AttestationReport = serde_json::from_value(json!({ "report_head": "aa".repeat(32), "report": { @@ -115025,27 +120349,45 @@ printf '{"kind":"nvidia_nvtrust_offline_jwt","evidence":"boot:%s:%s","platform_i "runtime_config": AttestationRuntimeConfig::default(), "sig_enclave": "77".repeat(64), "sig_provider": "88".repeat(64) } - })).unwrap(); - let rules = RulesRef { ver: 1, hash: "99".repeat(32) }; + })) + .unwrap(); + let rules = RulesRef { + ver: 1, + hash: "99".repeat(32), + }; let ctx = ProviderSessionContext { - args: &args, home: &canaries, keypair_path: Path::new("unused"), password: "", - wallet: &wallet, selected: &selected, backend_runtime: &runtime, artifact_paths: &artifacts, - canaries_dir: &canaries, rooms: &[], attestation: &attestation, - attestation_head: &attestation.report_head, identity_anchor: "test", - tpm_activation_hello: None, workflow_inventory_root: None, workflow_admission: None, + args: &args, + home: &canaries, + keypair_path: Path::new("unused"), + password: "", + wallet: &wallet, + selected: &selected, + backend_runtime: &runtime, + artifact_paths: &artifacts, + canaries_dir: &canaries, + rooms: &[], + attestation: &attestation, + attestation_head: &attestation.report_head, + identity_anchor: "test", + tpm_activation_hello: None, + workflow_inventory_root: None, + workflow_admission: None, rules: &rules, }; let terms = provider_session_terms(&ctx, 0).unwrap(); - assert_eq!(serde_json::to_value(&terms.adapter).unwrap(), adapter_before); + assert_eq!( + serde_json::to_value(&terms.adapter).unwrap(), + adapter_before + ); let mut responder = EngineProviderSessionResponder { backend: Box::new(FakeEngineBackend::new("image described").with_backend_id("vllm")), }; // Reproduce the old production failure: mode sealing, baseline verification. - let wrong_cases = provider_modality_self_test_plan( - &ctx, &selected.model.adapter, &canaries, - ).unwrap(); - let error = provider_modality_self_test(&ctx, &terms, &mut responder, wrong_cases).unwrap_err(); + let wrong_cases = + provider_modality_self_test_plan(&ctx, &selected.model.adapter, &canaries).unwrap(); + let error = + provider_modality_self_test(&ctx, &terms, &mut responder, wrong_cases).unwrap_err(); assert!(format!("{error:#}").contains("endpoint contract fingerprint does not match")); let cases = provider_modality_self_test_plan(&ctx, &terms.adapter, &canaries).unwrap(); @@ -115053,9 +120395,12 @@ printf '{"kind":"nvidia_nvtrust_offline_jwt","evidence":"boot:%s:%s","platform_i assert_eq!(cases[0].prompt_id, "image-health"); assert_eq!(cases[0].modalities, vec!["image", "text"]); assert_eq!(cases[0].body["contract_request"]["max_tokens"], 4); - assert_eq!(cases[0].body["mayhem_contract"]["endpoint_contract_fingerprint"], - mayhem_proto::endpoint_contract_fingerprint(&baseline_adapter.endpoint_families[0])); - let health = provider_modality_self_test(&ctx, &terms, &mut responder, cases.clone()).unwrap(); + assert_eq!( + cases[0].body["mayhem_contract"]["endpoint_contract_fingerprint"], + mayhem_proto::endpoint_contract_fingerprint(&baseline_adapter.endpoint_families[0]) + ); + let health = + provider_modality_self_test(&ctx, &terms, &mut responder, cases.clone()).unwrap(); assert_eq!(health.len(), 1); assert!(health[0].ok); @@ -115064,26 +120409,44 @@ printf '{"kind":"nvidia_nvtrust_offline_jwt","evidence":"boot:%s:%s","platform_i let mut body = forbidden[0].body["contract_request"].clone(); body["min_p"] = json!(0.05); body["endpoint_family"] = json!(family); - forbidden[0].body = provider_seal_local_contract_request( - &body, &terms.adapter, &terms.model_id, - ).unwrap(); - let error = provider_modality_self_test(&ctx, &terms, &mut responder, forbidden.clone()).unwrap_err(); + forbidden[0].body = + provider_seal_local_contract_request(&body, &terms.adapter, &terms.model_id).unwrap(); + let error = provider_modality_self_test(&ctx, &terms, &mut responder, forbidden.clone()) + .unwrap_err(); assert!(error.to_string().contains("negotiated execution mode")); assert_eq!(provider_response_error_code(&error), "request_invalid"); - let baseline_ctx = ProviderSessionContext { selected: &baseline, ..ctx }; + let baseline_ctx = ProviderSessionContext { + selected: &baseline, + ..ctx + }; let baseline_terms = provider_session_terms(&baseline_ctx, 0).unwrap(); - let baseline_cases = provider_modality_self_test_plan( - &baseline_ctx, &baseline_terms.adapter, &canaries, - ).unwrap(); - assert!(provider_modality_self_test( - &baseline_ctx, &baseline_terms, &mut responder, baseline_cases, - ).unwrap()[0].ok); - assert!(provider_modality_self_test( - &baseline_ctx, &baseline_terms, &mut responder, forbidden, - ).unwrap()[0].ok); - assert_eq!(serde_json::to_value(&baseline.model.adapter).unwrap(), adapter_before); - assert_eq!(serde_json::to_value(&terms.adapter).unwrap(), adapter_before); + let baseline_cases = + provider_modality_self_test_plan(&baseline_ctx, &baseline_terms.adapter, &canaries) + .unwrap(); + assert!( + provider_modality_self_test( + &baseline_ctx, + &baseline_terms, + &mut responder, + baseline_cases, + ) + .unwrap()[0] + .ok + ); + assert!( + provider_modality_self_test(&baseline_ctx, &baseline_terms, &mut responder, forbidden,) + .unwrap()[0] + .ok + ); + assert_eq!( + serde_json::to_value(&baseline.model.adapter).unwrap(), + adapter_before + ); + assert_eq!( + serde_json::to_value(&terms.adapter).unwrap(), + adapter_before + ); fs::remove_dir_all(canaries).unwrap(); } @@ -115105,13 +120468,16 @@ printf '{"kind":"nvidia_nvtrust_offline_jwt","evidence":"boot:%s:%s","platform_i "response_attribute_specs": {}, "interaction_groups": [] }] - })).unwrap(); - let body = json!({"messages": [{"role":"user", "content":"arbitrary request"}], "min_p": 0.05}); + })) + .unwrap(); + let body = + json!({"messages": [{"role":"user", "content":"arbitrary request"}], "min_p": 0.05}); let sealed = provider_test_seal_contract_request(&body, &terms.adapter).unwrap(); let active = test_active_provider_session(&terms, vec!["text".to_owned()]); validate_provider_session_request_modalities(&active, &terms, &sealed).unwrap(); terms.execution_mode_requests = Some(requests); - let error = validate_provider_session_request_modalities(&active, &terms, &sealed).unwrap_err(); + let error = + validate_provider_session_request_modalities(&active, &terms, &sealed).unwrap_err(); assert!(error.to_string().contains("negotiated execution mode")); assert!(!error.to_string().contains("0.05")); assert_eq!(provider_response_error_code(&error), "request_invalid"); @@ -115119,7 +120485,10 @@ printf '{"kind":"nvidia_nvtrust_offline_jwt","evidence":"boot:%s:%s","platform_i compatible["min_p"] = json!(0.0); let sealed = provider_test_seal_contract_request(&compatible, &terms.adapter).unwrap(); validate_provider_session_request_modalities(&active, &terms, &sealed).unwrap(); - assert_eq!(serde_json::to_value(&terms.adapter).unwrap(), adapter_before); + assert_eq!( + serde_json::to_value(&terms.adapter).unwrap(), + adapter_before + ); } #[test] @@ -115565,6 +120934,34 @@ printf '{"kind":"nvidia_nvtrust_offline_jwt","evidence":"boot:%s:%s","platform_i provider_seal_local_contract_request(&body, &model.adapter, &model.model_id).unwrap(); assert_eq!(sealed["kind"], json!("workflow_generation")); provider_verify_endpoint_request(&sealed, Some(&model.model_id), &model.adapter).unwrap(); + let mut legacy_order_drift = sealed.clone(); + legacy_order_drift["mayhem_contract"]["endpoint_contract_fingerprint"] = + json!("ff".repeat(32)); + provider_verify_endpoint_request( + &legacy_order_drift, + Some(&model.model_id), + &model.adapter, + ) + .expect("the canonical fingerprint supersedes a representation-specific legacy hash"); + legacy_order_drift["mayhem_contract"] + .as_object_mut() + .unwrap() + .remove("endpoint_contract_canonical_fingerprint"); + assert!(provider_verify_endpoint_request( + &legacy_order_drift, + Some(&model.model_id), + &model.adapter, + ) + .is_err()); + let mut bad_canonical = sealed.clone(); + bad_canonical["mayhem_contract"]["endpoint_contract_canonical_fingerprint"] = + json!("ee".repeat(32)); + assert!(provider_verify_endpoint_request( + &bad_canonical, + Some(&model.model_id), + &model.adapter, + ) + .is_err()); model.model_class = MODEL_CLASS_WORKFLOW.to_owned(); model.caps.output_modality = Some("image".to_owned()); @@ -115775,6 +121172,137 @@ printf '{"kind":"nvidia_nvtrust_offline_jwt","evidence":"boot:%s:%s","platform_i assert_eq!(schema["required"][0], "ok"); } + #[test] + fn provider_projects_nested_unique_items_and_checks_original_output() { + let body = json!({ + "messages": [{"role":"user","content":"Return evidence IDs"}], + "response_format": {"type":"json_schema","json_schema":{"name":"brief","schema":{ + "type":"object","required":["evidenceIds"],"properties":{ + "evidenceIds":{"type":"array","minItems":2,"maxItems":4, + "uniqueItems":true,"items":{"type":"string","pattern":"^E[1-9]$"}} + } + }}} + }); + let mut valid = FakeEngineBackend::new(r#"{"evidenceIds":["E1","E2"]}"#); + let output = provider_engine_session_response( + &mut valid, + &catalog::CatalogAdapter::default(), + &body, + None, + ) + .unwrap(); + assert_eq!(output.content, r#"{"evidenceIds":["E1","E2"]}"#); + let Some(GrammarSpec::JsonSchema { schema }) = valid.last_request.unwrap().grammar else { + panic!("expected projected JSON schema grammar"); + }; + assert!(schema["properties"]["evidenceIds"] + .get("uniqueItems") + .is_none()); + + let mut duplicate = FakeEngineBackend::new(r#"{"evidenceIds":["E1","E1"]}"#); + let error = provider_engine_session_response( + &mut duplicate, + &catalog::CatalogAdapter::default(), + &body, + None, + ) + .unwrap_err(); + assert_eq!(provider_response_error_code(&error), "model_output_invalid"); + } + + #[test] + fn provider_rejects_unsupported_schema_before_engine_dispatch() { + let body = json!({ + "messages": [{"role":"user","content":"Return JSON"}], + "response_format": {"type":"json_schema","json_schema":{"name":"bad","schema":{ + "type":"array","unknownConstraint":true + }}} + }); + let mut backend = FakeEngineBackend::new("[]"); + let error = provider_engine_session_response( + &mut backend, + &catalog::CatalogAdapter::default(), + &body, + None, + ) + .unwrap_err(); + assert_eq!(provider_response_error_code(&error), "request_invalid"); + assert!(backend.last_request.is_none()); + } + + #[test] + fn qwen_system_message_order_is_request_invalid_before_engine_dispatch() { + let adapter = catalog::CatalogAdapter { + chat_template_id: "qwen3.5-instruct".to_owned(), + tool_call_strategy: "none".to_owned(), + ..catalog::CatalogAdapter::default() + }; + let body = json!({ + "messages": [ + {"role": "user", "content": "first"}, + {"role": "system", "content": "too late"} + ] + }); + let mut backend = FakeEngineBackend::new("must not run"); + + let error = provider_engine_session_response(&mut backend, &adapter, &body, None) + .expect_err("Qwen must reject a late system message before engine dispatch"); + + assert_eq!(provider_response_error_code(&error), "request_invalid"); + assert_eq!( + provider_response_error_message(&error), + "qwen system message must be first" + ); + assert!(backend.last_request.is_none()); + } + + #[test] + fn reasoning_only_stop_is_model_output_invalid() { + let adapter = catalog::CatalogAdapter { + chat_template_id: "qwen3.5-instruct".to_owned(), + tool_call_strategy: "none".to_owned(), + ..catalog::CatalogAdapter::default() + }; + let body = json!({ + "messages": [{"role": "user", "content": "give a visible answer"}], + "max_tokens": 32 + }); + let sealed = provider_test_seal_contract_request(&body, &adapter).unwrap(); + let mut backend = FakeEngineBackend::new("private reasoning only"); + let mut output = provider_engine_session_response_with_sampling( + &mut backend, + None, + &adapter, + &catalog::CatalogSamplingProfile::default(), + None, + &sealed, + None, + &CancellationToken::new(), + ) + .unwrap(); + assert!(output.content.trim().is_empty()); + assert!(!output.reasoning_evidence.trim().is_empty()); + assert_eq!(output.finish_reason, "stop"); + + provider_session_output_result(normalize_provider_visible_output_usage( + &sealed, + &mut output, + )) + .unwrap(); + let error = provider_session_output_result(validate_provider_session_output( + &test_provider_session_terms(), + &sealed, + &output, + )) + .expect_err("reasoning-only stop must not publish an HTTP-200 response"); + + assert_eq!(provider_response_error_code(&error), "model_output_invalid"); + assert_eq!( + provider_response_error_message(&error), + "provider text generation stopped without a visible answer" + ); + } + #[test] fn provider_response_error_code_distinguishes_buyer_input_from_engine_failure() { let request_error = @@ -115815,6 +121343,57 @@ printf '{"kind":"nvidia_nvtrust_offline_jwt","evidence":"boot:%s:%s","platform_i provider_response_error_code(&engine_error), "provider_response_failed" ); + + let streamed_tool_error = validate_streamed_tool_call_count(1, 0) + .expect_err("a provisional tool call must match a validated final call"); + assert_eq!( + provider_response_error_code(&streamed_tool_error), + "model_output_invalid" + ); + assert_eq!( + provider_response_error_message(&streamed_tool_error), + "provider streamed a tool call that failed final validation" + ); + validate_streamed_tool_call_count(1, 1) + .expect("a validated streamed tool call must remain accepted"); + } + + #[test] + fn provider_output_diagnostics_classify_without_exposing_model_output() { + let error = provider_session_output_error( + "provider engine did not return a valid required tool call: private model output", + ); + assert_eq!( + provider_output_diagnostic_reason(&error), + "required_tool_missing" + ); + let malformed = provider_session_output_error( + "provider engine returned malformed arguments for tool private_tool_name", + ); + assert_eq!( + provider_output_diagnostic_reason(&malformed), + "malformed_tool_arguments" + ); + let engine_error = anyhow::Error::new(EngineError::InvalidOutput( + "private argument value".to_owned(), + )); + assert_eq!( + provider_output_diagnostic_reason(&engine_error), + "engine_invalid_output" + ); + } + + #[test] + fn provider_output_diagnostics_have_a_hard_per_process_limit() { + let counter = AtomicU64::new(0); + for _ in 0..PROVIDER_OUTPUT_DIAGNOSTIC_LIMIT { + assert!(take_provider_output_diagnostic_slot(&counter)); + } + assert!(!take_provider_output_diagnostic_slot(&counter)); + assert_eq!( + counter.load(Ordering::Relaxed), + PROVIDER_OUTPUT_DIAGNOSTIC_LIMIT + ); } #[test] @@ -116074,6 +121653,47 @@ printf '{"kind":"nvidia_nvtrust_offline_jwt","evidence":"boot:%s:%s","platform_i ); } + #[test] + fn openai_compatible_reasoning_mode_preserves_content_only_answer() { + let catalog_path = repo_path("catalog/models.json").unwrap(); + let catalog = catalog::load_document(&catalog_path).unwrap(); + let model = catalog + .models + .iter() + .find(|model| model.model_id == "hauhaucs/qwen3.6-35b-a3b-uncensored") + .expect("reasoning-enabled model"); + let body = json!({ + "model": model.model_id, + "messages": [{"role": "user", "content": "answer the question"}] + }); + let sealed = + provider_seal_local_contract_request(&body, &model.adapter, &model.model_id).unwrap(); + for (native_output, expected_answer, expected_hidden) in [ + ("public answer", "public answer", ""), + ( + "privatepublic answer", + "public answer", + "private", + ), + ] { + let mut backend = + FakeEngineBackend::new(native_output).with_backend_id("openai-compatible"); + let output = provider_engine_session_response_with_sampling( + &mut backend, + Some(&model.model_id), + &model.adapter, + &model.sampling, + model.workflow.as_ref(), + &sealed, + None, + &CancellationToken::new(), + ) + .unwrap(); + assert_eq!(output.content, expected_answer); + assert_eq!(output.reasoning_evidence, expected_hidden); + } + } + #[test] fn provider_engine_session_response_raw_tool_fallback_requires_request_grammar() { let document = catalog::load_document(&repo_path("catalog/models.json").unwrap()).unwrap(); @@ -116138,9 +121758,9 @@ printf '{"kind":"nvidia_nvtrust_offline_jwt","evidence":"boot:%s:%s","platform_i assert!(output.tools.is_empty()); assert_eq!(output.embeddings, Some(vec![vec![0.1, 0.2, 0.3]])); assert_eq!(output.finish_reason, "stop"); - assert_eq!(output.prompt_tokens, 4); + assert_eq!(output.prompt_tokens, 3); assert_eq!(output.completion_tokens, 0); - assert_eq!(output.usage.input_tokens(), 4); + assert_eq!(output.usage.input_tokens(), 3); assert_eq!(output.usage.output_tokens(), 0); let request = backend.last_embedding_request.expect("embedding request"); assert_eq!(request.inputs, vec!["similar phrase", "similar sentence"]); @@ -116295,9 +121915,12 @@ printf '{"kind":"nvidia_nvtrust_offline_jwt","evidence":"boot:%s:%s","platform_i fn provider_image_reference_preserves_bytes_strength_and_admission_load() { let family = mayhem_proto::ENDPOINT_OPENAI_IMAGE_GENERATIONS; let contract = mayhem_proto::endpoint_family_contract_template(family).unwrap(); - let reference = format!("data:image/png;base64,{}", base64::engine::general_purpose::STANDARD.encode( - include_bytes!("../../mayhem-engine/tests/fixtures/reference.png"), - )); + let reference = format!( + "data:image/png;base64,{}", + base64::engine::general_purpose::STANDARD.encode(include_bytes!( + "../../mayhem-engine/tests/fixtures/reference.png" + ),) + ); let mut body = json!({ "prompt": "a blue sculpture", "n": 1, "width": 64, "height": 64, "steps": 9, "cfg_scale": 0.0, "input_reference": reference, "strength": 0.6, @@ -116306,13 +121929,28 @@ printf '{"kind":"nvidia_nvtrust_offline_jwt","evidence":"boot:%s:%s","platform_i assert_eq!(request.input_reference.as_deref(), Some(reference.as_str())); assert_eq!(request.strength, Some(0.6)); let load = provider_session_modality_load( - &contract, family, &body, &json!({"kind": "image_generation"}), None, &["image".to_owned()], - ).unwrap(); + &contract, + family, + &body, + &json!({"kind": "image_generation"}), + None, + &["image".to_owned()], + ) + .unwrap(); assert_eq!(load["image"].item_count, 1); assert_eq!(load["image"].max_item_units, 64 * 64); - assert_eq!(load["image"].max_item_bytes, mayhem_proto::image_reference_metadata(&reference).unwrap().bytes); + assert_eq!( + load["image"].max_item_bytes, + mayhem_proto::image_reference_metadata(&reference) + .unwrap() + .bytes + ); - for invalid in [json!({"image_url": reference}), json!("https://example.test/image.png"), json!(null)] { + for invalid in [ + json!({"image_url": reference}), + json!("https://example.test/image.png"), + json!(null), + ] { body["input_reference"] = invalid; assert!(provider_image_generation_request_from_body(family, &body).is_err()); } @@ -117568,8 +123206,10 @@ printf '{"kind":"nvidia_nvtrust_offline_jwt","evidence":"boot:%s:%s","platform_i let output = provider_engine_session_response(&mut backend, &adapter, &body, None).unwrap(); assert_eq!(output.finish_reason, "tool_calls"); assert_eq!(output.tools[0]["id"], "call-openai"); - assert_eq!(serde_json::from_str::(output.tools[0]["arguments"].as_str().unwrap()).unwrap(), - json!({"path":"index.html","content":"not allowed"})); + assert_eq!( + serde_json::from_str::(output.tools[0]["arguments"].as_str().unwrap()).unwrap(), + json!({"path":"index.html","content":"not allowed"}) + ); assert_eq!(output.completion_tokens, 2); assert_eq!(output.usage.output_tokens(), 2); assert_eq!(output.tools.len(), 1); @@ -117579,7 +123219,10 @@ printf '{"kind":"nvidia_nvtrust_offline_jwt","evidence":"boot:%s:%s","platform_i assert!(format!("{error:#}") .contains("arguments that do not satisfy the schema for tool read_file")); - assert_eq!(backend.last_request.unwrap().tools[0]["function"]["strict"], true); + assert_eq!( + backend.last_request.unwrap().tools[0]["function"]["strict"], + true + ); } #[test] @@ -117914,6 +123557,7 @@ printf '{"kind":"nvidia_nvtrust_offline_jwt","evidence":"boot:%s:%s","platform_i engine: "vllm".to_owned(), topology: None, independent_dispatch: true, + max_concurrent: None, request_modalities: vec![vec!["text".to_owned()]], proof_sha256: "ab".repeat(32), }, @@ -117953,8 +123597,12 @@ printf '{"kind":"nvidia_nvtrust_offline_jwt","evidence":"boot:%s:%s","platform_i mode_profile.runtime = Some(python_runtime::VllmRuntime::FlashinferSpeculativeMetadataV1); let mut mode_canary = selected.model.canary.clone(); mode_canary.set_id = "test-mode-canary".to_owned(); - let request_policies = selected.model.adapter.endpoint_families.iter().map(|family| { - mayhem_proto::EndpointFamilyContract { + let request_policies = selected + .model + .adapter + .endpoint_families + .iter() + .map(|family| mayhem_proto::EndpointFamilyContract { family: family.family.clone(), request_attributes: vec![], required_request_attributes: vec![], @@ -117964,64 +123612,140 @@ printf '{"kind":"nvidia_nvtrust_offline_jwt","evidence":"boot:%s:%s","platform_i response_attribute_specs: BTreeMap::new(), interaction_groups: vec![], speciality_mappings: BTreeMap::new(), - } - }).collect(); + }) + .collect(); let mode_policy = catalog::CatalogVllmExecutionMode { schema_version: 1, profile: mode_profile, generation_execution_profile: None, - requests: mayhem_proto::ExecutionModeRequestPolicy { endpoint_families: request_policies }, + requests: mayhem_proto::ExecutionModeRequestPolicy { + endpoint_families: request_policies, + }, canary: mode_canary, canary_set_sha256: "ad".repeat(32), modality_fingerprints: BTreeMap::new(), resource_profiles: BTreeMap::new(), speciality_calibrations: BTreeMap::new(), }; - mode_catalog.vllm_execution_modes.insert(selected.artifact.artifact_root.clone(), - BTreeMap::from([("throughput".to_owned(), mode_policy)])); + mode_catalog.vllm_execution_modes.insert( + selected.artifact.artifact_root.clone(), + BTreeMap::from([("throughput".to_owned(), mode_policy)]), + ); let mut mode_args = args.clone(); mode_args.execution_mode = Some("throughput".to_owned()); - let mode_selected = build_provider_candidates(&contract, &mode_catalog, &hardware, &mode_args) - .unwrap().remove(0); - assert_eq!(mode_selected.enclave.enclave_id, selected.enclave.enclave_id); - assert_eq!(mode_selected.artifact.artifact_root, selected.artifact.artifact_root); + let mode_selected = + build_provider_candidates(&contract, &mode_catalog, &hardware, &mode_args) + .unwrap() + .remove(0); + assert_eq!( + mode_selected.enclave.enclave_id, + selected.enclave.enclave_id + ); + assert_eq!( + mode_selected.artifact.artifact_root, + selected.artifact.artifact_root + ); assert_eq!(mode_selected.model.canary.set_id, "test-mode-canary"); assert_eq!(mode_selected.generation_execution_capacity, 1); assert!(mode_selected.generation_execution_profile.is_none()); - assert!(mode_selected.vllm_execution_profile.as_ref().unwrap().enforce_eager); - assert_eq!(mode_selected.vllm_execution_profile.as_ref().unwrap().runtime, - Some(python_runtime::VllmRuntime::FlashinferSpeculativeMetadataV1)); + assert!( + mode_selected + .vllm_execution_profile + .as_ref() + .unwrap() + .enforce_eager + ); + assert_eq!( + mode_selected + .vllm_execution_profile + .as_ref() + .unwrap() + .runtime, + Some(python_runtime::VllmRuntime::FlashinferSpeculativeMetadataV1) + ); let mut calibration_args = test_calibrate_canary_args(); bind_calibration_vllm_execution_profile( - &mut calibration_args, mode_selected.vllm_execution_profile.as_ref(), - ).unwrap(); - assert_eq!(calibration_args.vllm_runtime, - Some(python_runtime::VllmRuntime::FlashinferSpeculativeMetadataV1)); + &mut calibration_args, + mode_selected.vllm_execution_profile.as_ref(), + ) + .unwrap(); + assert_eq!( + calibration_args.vllm_runtime, + Some(python_runtime::VllmRuntime::FlashinferSpeculativeMetadataV1) + ); let binding = &mode_selected.execution_mode.as_ref().unwrap().binding; - assert_eq!(provider_attestation_runtime_config(&mode_selected).unwrap().execution_mode.as_ref(), Some(binding)); - assert_eq!(serde_json::to_value(&mode_selected.execution_mode.as_ref().unwrap().baseline_adapter).unwrap(), - serde_json::to_value(&selected.model.adapter).unwrap()); + assert_eq!( + provider_attestation_runtime_config(&mode_selected) + .unwrap() + .execution_mode + .as_ref(), + Some(binding) + ); + assert_eq!( + serde_json::to_value( + &mode_selected + .execution_mode + .as_ref() + .unwrap() + .baseline_adapter + ) + .unwrap(), + serde_json::to_value(&selected.model.adapter).unwrap() + ); let baseline_again = build_provider_candidates(&contract, &mode_catalog, &hardware, &args) - .unwrap().remove(0); + .unwrap() + .remove(0); assert!(baseline_again.execution_mode.is_none()); assert_eq!(baseline_again.generation_execution_capacity, 2); - assert_eq!(baseline_again.model.canary.set_id, selected.model.canary.set_id); - assert_eq!(baseline_again.vllm_execution_profile.as_ref().unwrap().runtime, None); + assert_eq!( + baseline_again.model.canary.set_id, + selected.model.canary.set_id + ); + assert_eq!( + baseline_again + .vllm_execution_profile + .as_ref() + .unwrap() + .runtime, + None + ); let mut default_mode_catalog = mode_catalog.clone(); let root = &selected.artifact.artifact_root; - default_mode_catalog.vllm_execution_profiles.get_mut(root).unwrap().runtime = - Some(python_runtime::VllmRuntime::FlashinferSpeculativeMetadataV1); - default_mode_catalog.vllm_execution_modes.get_mut(root).unwrap() - .get_mut("throughput").unwrap().profile.runtime = None; - let default_mode = build_provider_candidates(&contract, &default_mode_catalog, &hardware, &mode_args) - .unwrap().remove(0); - assert_eq!(default_mode.vllm_execution_profile.as_ref().unwrap().runtime, None); + default_mode_catalog + .vllm_execution_profiles + .get_mut(root) + .unwrap() + .runtime = Some(python_runtime::VllmRuntime::FlashinferSpeculativeMetadataV1); + default_mode_catalog + .vllm_execution_modes + .get_mut(root) + .unwrap() + .get_mut("throughput") + .unwrap() + .profile + .runtime = None; + let default_mode = + build_provider_candidates(&contract, &default_mode_catalog, &hardware, &mode_args) + .unwrap() + .remove(0); + assert_eq!( + default_mode + .vllm_execution_profile + .as_ref() + .unwrap() + .runtime, + None + ); bind_calibration_vllm_execution_profile( - &mut calibration_args, default_mode.vllm_execution_profile.as_ref(), - ).unwrap(); + &mut calibration_args, + default_mode.vllm_execution_profile.as_ref(), + ) + .unwrap(); assert_eq!(calibration_args.vllm_runtime, None); mode_args.execution_mode = Some("missing".to_owned()); - assert!(build_provider_candidates(&contract, &mode_catalog, &hardware, &mode_args).is_err()); + assert!( + build_provider_candidates(&contract, &mode_catalog, &hardware, &mode_args).is_err() + ); let mut uncapped_contract = contract.clone(); uncapped_contract.enclaves[0] @@ -118128,6 +123852,20 @@ printf '{"kind":"nvidia_nvtrust_offline_jwt","evidence":"boot:%s:%s","platform_i assert_eq!(config.vllm_linear_backend.as_deref(), Some("cutlass")); assert_eq!(config.vllm_moe_backend.as_deref(), Some("cutlass")); assert_eq!(config.vllm_mtp_num_speculative_tokens, None); + assert_eq!(config.vllm_task, mayhem_engine::VllmTask::Generate); + let mut embedding_selected = selected.clone(); + embedding_selected.model.model_class = "embedding".to_owned(); + let embedding_config = provider_engine_load_config( + &args, + &embedding_selected, + &artifact_paths, + &ProviderBackendRuntime::default(), + ) + .unwrap(); + assert_eq!( + embedding_config.vllm_task, + mayhem_engine::VllmTask::Embedding + ); let mut legacy_selected = selected.clone(); legacy_selected.vllm_execution_profile = None; let legacy_config = provider_engine_load_config( @@ -118156,7 +123894,10 @@ printf '{"kind":"nvidia_nvtrust_offline_jwt","evidence":"boot:%s:%s","platform_i .unwrap(); assert_eq!(graph_config.vllm_enforce_eager, Some(false)); assert_eq!(graph_config.vllm_compilation_mode, Some(0)); - assert_eq!(graph_config.vllm_cudagraph_mode.as_deref(), Some("FULL_DECODE_ONLY")); + assert_eq!( + graph_config.vllm_cudagraph_mode.as_deref(), + Some("FULL_DECODE_ONLY") + ); assert_eq!( config.memory_limit_bytes, Some(selected.feasibility.memory_budget.worker_limit_bytes) @@ -118177,43 +123918,93 @@ printf '{"kind":"nvidia_nvtrust_offline_jwt","evidence":"boot:%s:%s","platform_i max_num_tokens: Some(4096), } ); - mode_catalog.vllm_execution_modes.get_mut(&selected.artifact.artifact_root) - .unwrap().get_mut("throughput").unwrap().generation_execution_profile = - Some(test_isolated_generation_profile()); + mode_catalog + .vllm_execution_modes + .get_mut(&selected.artifact.artifact_root) + .unwrap() + .get_mut("throughput") + .unwrap() + .generation_execution_profile = Some(test_isolated_generation_profile()); mode_args.execution_mode = Some("throughput".to_owned()); - let mut isolated = build_provider_candidates(&contract, &mode_catalog, &hardware, &mode_args) - .unwrap().remove(0); + let mut isolated = + build_provider_candidates(&contract, &mode_catalog, &hardware, &mode_args) + .unwrap() + .remove(0); let isolated_config = provider_engine_load_config( - &mode_args, &isolated, &artifact_paths, &ProviderBackendRuntime::default(), - ).unwrap(); - assert_eq!(isolated_config.vllm_generation_topology, - Some(mayhem_engine::VllmGenerationTopology::IsolatedWorkers)); + &mode_args, + &isolated, + &artifact_paths, + &ProviderBackendRuntime::default(), + ) + .unwrap(); + assert_eq!( + isolated_config.vllm_generation_topology, + Some(mayhem_engine::VllmGenerationTopology::IsolatedWorkers) + ); assert_eq!(isolated_config.vllm_max_num_seqs, Some(1)); - assert_eq!(isolated_config.vllm_concurrent_generation_capacity, - Some(isolated.generation_execution_capacity)); + assert_eq!( + isolated_config.vllm_concurrent_generation_capacity, + Some(isolated.generation_execution_capacity) + ); assert_eq!(isolated_config.ctx_size, 131_072); - assert_eq!(isolated_config.memory_limit_bytes, - Some(isolated.feasibility.estimated_required_bytes)); - assert_eq!(isolated_config.vllm_worker_address_space_limit_bytes, - Some(isolated.feasibility.memory_budget.worker_address_space_limit_bytes)); + assert_eq!( + isolated_config.memory_limit_bytes, + Some(isolated.feasibility.estimated_required_bytes) + ); + assert_eq!( + isolated_config.vllm_worker_address_space_limit_bytes, + Some( + isolated + .feasibility + .memory_budget + .worker_address_space_limit_bytes + ) + ); isolated.execution_mode = None; assert!(provider_engine_load_config( - &mode_args, &isolated, &artifact_paths, &ProviderBackendRuntime::default(), - ).is_err()); + &mode_args, + &isolated, + &artifact_paths, + &ProviderBackendRuntime::default(), + ) + .is_err()); assert_eq!(config.vllm_generation_topology, None); - assert_eq!(config.vllm_worker_address_space_limit_bytes, - Some(selected.feasibility.memory_budget.worker_address_space_limit_bytes)); + assert_eq!( + config.vllm_worker_address_space_limit_bytes, + Some( + selected + .feasibility + .memory_budget + .worker_address_space_limit_bytes + ) + ); let mut resident_selected = selected.clone(); resident_selected.feasibility.memory_budget.available_bytes /= 2; - resident_selected.feasibility.memory_budget.worker_limit_bytes /= 2; + resident_selected + .feasibility + .memory_budget + .worker_limit_bytes /= 2; let restarted = provider_engine_load_config( - &args, &resident_selected, &artifact_paths, &ProviderBackendRuntime::default(), - ).unwrap(); - assert_eq!(restarted.vllm_worker_address_space_limit_bytes, + &args, + &resident_selected, + &artifact_paths, + &ProviderBackendRuntime::default(), + ) + .unwrap(); + assert_eq!( + restarted.vllm_worker_address_space_limit_bytes, config.vllm_worker_address_space_limit_bytes, - "another resident model must not shrink the virtual-address envelope"); - assert_eq!(restarted.memory_limit_bytes, - Some(resident_selected.feasibility.memory_budget.worker_limit_bytes)); + "another resident model must not shrink the virtual-address envelope" + ); + assert_eq!( + restarted.memory_limit_bytes, + Some( + resident_selected + .feasibility + .memory_budget + .worker_limit_bytes + ) + ); let _ = fs::remove_dir_all(temp); } @@ -119764,7 +125555,7 @@ printf '{"kind":"nvidia_nvtrust_offline_jwt","evidence":"boot:%s:%s","platform_i checkpoint_every: CheckpointPolicy { tokens: 1, ms: 0 }, max_spend_au: MoneyAu::MAX, accept_replay: None, - reservation_recovery: None, + reservation_recovery: None, }, ); } @@ -119833,7 +125624,7 @@ printf '{"kind":"nvidia_nvtrust_offline_jwt","evidence":"boot:%s:%s","platform_i checkpoint_every: CheckpointPolicy { tokens: 1, ms: 0 }, max_spend_au: MoneyAu::MAX, accept_replay: None, - reservation_recovery: None, + reservation_recovery: None, }, ); pending_requests.insert(id.to_owned(), Instant::now() + Duration::from_secs(30)); @@ -119896,7 +125687,7 @@ printf '{"kind":"nvidia_nvtrust_offline_jwt","evidence":"boot:%s:%s","platform_i checkpoint_every: CheckpointPolicy { tokens: 1, ms: 0 }, max_spend_au: MoneyAu::MAX, accept_replay: None, - reservation_recovery: None, + reservation_recovery: None, }, ); pending_requests.insert(id.to_owned(), Instant::now() + Duration::from_secs(30)); @@ -120563,6 +126354,48 @@ printf '{"kind":"nvidia_nvtrust_offline_jwt","evidence":"boot:%s:%s","platform_i } } + #[test] + fn provider_tokenize_response_frames_chunk_large_token_lists() { + let tokens = (0..40_000).collect::>(); + let response = TokenizeResponseFrame { + frame_type: TOKENIZE_RESPONSE_FRAME_TYPE.to_owned(), + version: TOKENIZE_FRAME_VERSION, + session_id: "11".repeat(32), + provider: "22".repeat(32), + enclave_id: "33".repeat(32), + room_id: "44".repeat(16), + model: "qwen/example".to_owned(), + ok: true, + count: Some(tokens.len() as u64), + tokens: Some(tokens.clone()), + tokens_ref: None, + error_code: None, + error: None, + }; + let max_frame_bytes = 12 * 1024; + + let frames = provider_tokenize_response_frames(response, max_frame_bytes).unwrap(); + + assert!(frames.len() > 2); + assert!(frames + .iter() + .all(|frame| provider_session_frame_json_len(frame).unwrap() <= max_frame_bytes)); + assert!(frames[..frames.len() - 1].iter().all(|frame| { + frame.get("t").and_then(Value::as_str) == Some(TOKENIZE_RESPONSE_CHUNK_FRAME_TYPE) + })); + let final_frame: TokenizeResponseFrame = + serde_json::from_value(frames.last().unwrap().clone()).unwrap(); + assert!(final_frame.tokens.is_none()); + let manifest = final_frame.tokens_ref.unwrap(); + let chunks = frames[..frames.len() - 1] + .iter() + .map(|frame| serde_json::from_value::(frame["chunk"].clone()).unwrap()) + .collect::>(); + let restored: Vec = + serde_json::from_value(reassemble_json_payload(&manifest, &chunks).unwrap()).unwrap(); + assert_eq!(restored, tokens); + } + #[test] fn provider_session_final_delta_frames_chunk_transcription_metadata() { let words = (0..1_000) @@ -120657,6 +126490,10 @@ printf '{"kind":"nvidia_nvtrust_offline_jwt","evidence":"boot:%s:%s","platform_i load.set_accepting_new(false); assert!(changes.has_changed().unwrap()); assert!(!load.snapshot(4).accepting_new); + let unavailable_generation = load.change_generation(); + assert!(!load.published_through(unavailable_generation)); + load.mark_published(unavailable_generation); + assert!(load.published_through(unavailable_generation)); assert!(!load.is_stopped()); load.stop(); assert!(load.is_stopped()); @@ -120920,6 +126757,7 @@ printf '{"kind":"nvidia_nvtrust_offline_jwt","evidence":"boot:%s:%s","platform_i engine: "vllm".to_owned(), topology: None, independent_dispatch: true, + max_concurrent: None, request_modalities: vec![vec!["text".to_owned()]], proof_sha256: "aa".repeat(32), }); @@ -120952,6 +126790,98 @@ printf '{"kind":"nvidia_nvtrust_offline_jwt","evidence":"boot:%s:%s","platform_i assert!(recovered.accepting_new); } + struct TestConcurrentEmbeddingBackend { + capacity: usize, + } + + impl ConcurrentEmbeddingBackend for TestConcurrentEmbeddingBackend { + fn capacity(&self) -> usize { + self.capacity + } + + fn embed( + &self, + _request: mayhem_engine::EmbeddingRequest, + _cancellation: &CancellationToken, + ) -> mayhem_engine::Result { + unreachable!("embedding admission test does not execute inference") + } + } + + struct ConcurrentEmbeddingResponder { + backend: Arc, + } + + impl ProviderSessionResponder for ConcurrentEmbeddingResponder { + fn mode(&self) -> &'static str { + "test-concurrent-embedding" + } + + fn concurrent_session_capacity(&self) -> u32 { + u32::try_from(self.backend.capacity()).unwrap_or(u32::MAX) + } + + fn concurrent_embedding_backend(&self) -> Option> { + Some(Arc::clone(&self.backend) as Arc) + } + + fn respond( + &mut self, + _terms: &ProviderSessionTerms, + _body: &Value, + _cancellation: &CancellationToken, + ) -> Result { + unreachable!("embedding admission test does not execute inference") + } + } + + #[test] + fn concurrent_embedding_backend_admits_independent_embedding_sessions_only() { + let responder = ConcurrentEmbeddingResponder { + backend: Arc::new(TestConcurrentEmbeddingBackend { capacity: 8 }), + }; + let mut terms = test_provider_session_terms(); + terms.runtime_independent_dispatch_modalities = + provider_runtime_independent_dispatch_modalities(&responder); + assert_eq!(provider_execution_capacity_for_terms(&terms, &responder), 8); + assert!(provider_request_modalities_allow_independent_dispatch( + &terms, + &["embedding".to_owned()] + )); + assert!(!provider_request_modalities_allow_independent_dispatch( + &terms, + &["text".to_owned()] + )); + + let active = test_active_provider_session(&terms, vec!["embedding".to_owned()]); + let sessions = HashMap::from([(active.session_id.clone(), active)]); + let protection = Arc::new(Mutex::new(ProviderProtectionState::new( + ProviderProtectionConfig::unlimited_for_tests(8), + ))); + let mut frame = test_session_open_frame(&terms); + frame["voucher"]["required_modalities"] = json!(["embedding"]); + assert_eq!( + provider_local_session_acceptance_decision(&protection, &sessions, &terms, &frame,), + ProviderSessionDecision::Accept + ); + + frame["voucher"]["required_modalities"] = json!(["text"]); + assert!(matches!( + provider_local_session_acceptance_decision(&protection, &sessions, &terms, &frame,), + ProviderSessionDecision::Reject { + code: "CAPACITY", + .. + } + )); + + let load = ProviderHeartbeatLoad::default(); + load.set_active_sessions(&sessions, &terms); + let snapshot = load.snapshot(8); + assert_eq!(snapshot.active_slots, 1); + assert_eq!(snapshot.free_slots, 7); + assert!(snapshot.accepting_new); + } + #[test] fn provider_protection_rejects_capacity_rate_and_quota_cleanly() { let mut capacity = @@ -121412,6 +127342,33 @@ printf '{"kind":"nvidia_nvtrust_offline_jwt","evidence":"boot:%s:%s","platform_i } } + #[test] + fn provider_tool_parser_executes_recovered_structured_call_after_private_reasoning() { + let tools = vec![ToolSpec::new("write", json!({ "type": "object" }))]; + let recovered = serde_json::to_string(&json!({"tool_calls":[{ + "id":"call_recovered", + "type":"function", + "function":{"name":"write","arguments":"{\"path\":\"README.md\"}"} + }]})) + .unwrap(); + let output = format!( + "Attempted {recovered}" + ); + let calls = provider_engine_tool_call_outputs_after_reasoning( + &output, + ProviderReasoningOutputMode::StripPrefilled, + Some(true), + PROVIDER_QWEN_REASONING_DELIMITERS, + ProviderEngineToolStrategy::OpenAiToolCalls, + &tools, + false, + ) + .expect("recovered structured call after private reasoning"); + assert_eq!(calls.len(), 1); + assert_eq!(calls[0]["name"], "write"); + assert_eq!(calls[0]["arguments"], r#"{"path":"README.md"}"#); + } + #[test] fn provider_tool_parser_rejects_reasoning_text_even_with_json_grammar() { let tools = vec![ToolSpec::new("write", json!({ "type": "object" }))]; @@ -121532,6 +127489,29 @@ printf '{"kind":"nvidia_nvtrust_offline_jwt","evidence":"boot:%s:%s","platform_i assert_eq!(calls[1]["arguments"], r#"{"filePath":"two.txt"}"#); } + #[test] + fn provider_openai_tool_calls_after_commentary_keep_only_the_commentary_visible() { + let tools = vec![ToolSpec::new("write", json!({ "type": "object" }))]; + let text = concat!( + "Plan saved.\n\n", + r#"{"tool_calls":[{"function":{"name":"write","arguments":{"path":"app.js"}}}]}"#, + ); + let calls = provider_engine_tool_call_outputs( + text, + ProviderEngineToolStrategy::OpenAiToolCalls, + &tools, + ) + .expect("tool call after commentary"); + assert_eq!(calls[0]["name"], "write"); + assert_eq!( + provider_engine_visible_content_before_tools( + text, + ProviderEngineToolStrategy::OpenAiToolCalls, + ), + "Plan saved.\n\n" + ); + } + #[test] fn provider_engine_tool_call_outputs_preserve_all_qwen_xml_calls_in_order() { let tools = vec![ @@ -121800,26 +127780,56 @@ printf '{"kind":"nvidia_nvtrust_offline_jwt","evidence":"boot:%s:%s","platform_i #[test] fn provider_tool_strict_setting_survives_chat_and_responses_definitions() { for nested in [false, true] { - for strict in [None, Some(Value::Null), Some(json!(false)), Some(json!(true))] { + for strict in [ + None, + Some(Value::Null), + Some(json!(false)), + Some(json!(true)), + ] { let mut function = json!({"name":"edit_file", "parameters":{ "type":"object", "properties":{"path":{"type":"string"}}, "required":["path"] }}); - if let Some(value) = &strict { function["strict"] = value.clone(); } - let tool = if nested { json!({"type":"function", "function":function}) } - else { function["type"] = json!("function"); function }; + if let Some(value) = &strict { + function["strict"] = value.clone(); + } + let tool = if nested { + json!({"type":"function", "function":function}) + } else { + function["type"] = json!("function"); + function + }; let specs = provider_engine_tool_specs(&json!({"tools":[tool]})).unwrap(); let required = strict == Some(json!(true)); assert_eq!(specs[0].strict, required); let template = provider_engine_template_tools(&specs); - assert_eq!(template[0]["function"]["strict"].as_bool().unwrap_or(false), required); + assert_eq!( + template[0]["function"]["strict"].as_bool().unwrap_or(false), + required + ); let reparsed = provider_engine_tool_specs(&json!({"tools":template})).unwrap(); assert_eq!(reparsed, specs); - let calls = vec![provider_normalized_tool_call(None, "edit_file".to_owned(), "{}".to_owned())]; - assert_eq!(validate_provider_engine_tool_call_outputs(&calls, &specs).is_err(), required); + let calls = vec![provider_normalized_tool_call( + None, + "edit_file".to_owned(), + "{}".to_owned(), + )]; + assert_eq!( + validate_provider_engine_tool_call_outputs(&calls, &specs).is_err(), + required + ); // Required-tool grammars remain schema-constrained in either mode. - assert!(mayhem_engine::tool_call_json_schema(&specs).unwrap()["$defs"]["tool_0_parameters"]["required"] - .as_array().unwrap().contains(&json!("path"))); - assert_eq!(provider_openai_tool_calls_json_schema(&specs, false)["$defs"]["tool_0_parameters"]["required"], json!(["path"])); + assert!( + mayhem_engine::tool_call_json_schema(&specs).unwrap()["$defs"] + ["tool_0_parameters"]["required"] + .as_array() + .unwrap() + .contains(&json!("path")) + ); + assert_eq!( + provider_openai_tool_calls_json_schema(&specs, false)["$defs"] + ["tool_0_parameters"]["required"], + json!(["path"]) + ); } } for invalid in [json!("true"), json!(1), json!({})] { @@ -121830,11 +127840,14 @@ printf '{"kind":"nvidia_nvtrust_offline_jwt","evidence":"boot:%s:%s","platform_i #[test] fn provider_tool_nonstrict_schema_errors_preserve_edit_arguments_in_all_formats() { - let mut tools = vec![ToolSpec::new("edit_file", json!({ - "type":"object", "additionalProperties":false, - "properties":{"path":{"type":"string"}, "old_text":{"type":"string", "minLength":1}, - "new_text":{"type":"string"}}, "required":["path","old_text","new_text"] - }))]; + let mut tools = vec![ToolSpec::new( + "edit_file", + json!({ + "type":"object", "additionalProperties":false, + "properties":{"path":{"type":"string"}, "old_text":{"type":"string", "minLength":1}, + "new_text":{"type":"string"}}, "required":["path","old_text","new_text"] + }), + )]; let expected = json!({"path":"app.js", "old_text":"", "new_text":"private source"}); for (strategy, raw) in [ (ProviderEngineToolStrategy::MayhemJson, @@ -121864,9 +127877,18 @@ printf '{"kind":"nvidia_nvtrust_offline_jwt","evidence":"boot:%s:%s","platform_i let mut tools = vec![ToolSpec::new("edit_file", json!({"type":"object"}))]; for strict in [false, true] { tools[0].strict = strict; - for (name, arguments) in [("edit_file", "not JSON"), ("edit_file", "[]"), - ("edit_file", "null"), ("edit_file", "7"), ("unknown_tool", "{}")] { - let calls = vec![provider_normalized_tool_call(None, name.to_owned(), arguments.to_owned())]; + for (name, arguments) in [ + ("edit_file", "not JSON"), + ("edit_file", "[]"), + ("edit_file", "null"), + ("edit_file", "7"), + ("unknown_tool", "{}"), + ] { + let calls = vec![provider_normalized_tool_call( + None, + name.to_owned(), + arguments.to_owned(), + )]; assert!(validate_provider_engine_tool_call_outputs(&calls, &tools).is_err()); } } @@ -123973,6 +129995,7 @@ State initialization... })]), specialities: BTreeMap::from([("thinking_mode".to_owned(), "disabled".to_owned())]), temperature: Some(0.2), + decision_temperature: None, top_p: Some(0.9), top_k: Some(20), min_p: Some(0.05), @@ -124478,7 +130501,9 @@ State initialization... report.artifact_binding = catalog_canary_artifact_binding(artifact); report.canary_set_sha256 = canary_sha.clone(); report.prompts[0].max_tokens = 8; - report.prompts[0].completion_tokens = 8; + // OpenAI-compatible backends can report tokenizer usage while the + // reproducibility witness uses content-derived canonical units. + report.prompts[0].completion_tokens = 3; report.prompts[0].token_count = tokens.len(); report.prompts[0].token_ids = tokens.clone(); report.prompts[0].token_prefix = tokens.clone(); @@ -124528,11 +130553,10 @@ State initialization... assert!(error.to_string().contains("runtime configuration")); let mut mismatched_mode = report; - mismatched_mode.runtime_config.execution_mode = - Some(mayhem_proto::ExecutionModeBinding { - mode_id: "throughput".to_owned(), - policy_hash: "ab".repeat(32), - }); + mismatched_mode.runtime_config.execution_mode = Some(mayhem_proto::ExecutionModeBinding { + mode_id: "throughput".to_owned(), + policy_hash: "ab".repeat(32), + }); let error = validate_resumed_canary_core( &mismatched_mode, model, @@ -124558,7 +130582,8 @@ State initialization... let mut artifact = test_catalog(&merkle.root).models[0].artifacts["gguf-q4_k_m"].clone(); artifact.weights_bytes = merkle.total_bytes; - verify_calibration_artifact_matches_catalog(&artifact, &artifact_path).unwrap(); + verify_calibration_artifact_matches_catalog(&artifact, &artifact_path, &BTreeMap::new()) + .unwrap(); } #[test] @@ -124571,8 +130596,12 @@ State initialization... test_catalog(&"aa".repeat(32)).models[0].artifacts["gguf-q4_k_m"].clone(); artifact.weights_bytes = merkle.total_bytes; - let err = - verify_calibration_artifact_matches_catalog(&artifact, &artifact_path).unwrap_err(); + let err = verify_calibration_artifact_matches_catalog( + &artifact, + &artifact_path, + &BTreeMap::new(), + ) + .unwrap_err(); assert!(err.to_string().contains("local artifact root mismatch")); } @@ -124593,9 +130622,11 @@ State initialization... artifact.weights_bytes = fs::metadata(&artifact_path).unwrap().len(); artifact.source_sha256 = Some(sha256_bytes_hex(&stable_definition)); - verify_calibration_artifact_matches_catalog(&artifact, &artifact_path).unwrap(); + verify_calibration_artifact_matches_catalog(&artifact, &artifact_path, &BTreeMap::new()) + .unwrap(); artifact.weights_bytes = stable_definition.len() as u64; - verify_calibration_artifact_matches_catalog(&artifact, &artifact_path).unwrap(); + verify_calibration_artifact_matches_catalog(&artifact, &artifact_path, &BTreeMap::new()) + .unwrap(); let wrong_path = dir.join("wrong-workflow-class.json"); let wrong = json!({ @@ -124610,8 +130641,12 @@ State initialization... write_json_file(&wrong_path, &wrong).unwrap(); let mut wrong_sized_artifact = artifact.clone(); wrong_sized_artifact.weights_bytes = stable_json_bytes(&wrong).unwrap().len() as u64; - let err = verify_calibration_artifact_matches_catalog(&wrong_sized_artifact, &wrong_path) - .unwrap_err(); + let err = verify_calibration_artifact_matches_catalog( + &wrong_sized_artifact, + &wrong_path, + &BTreeMap::new(), + ) + .unwrap_err(); assert!( err.to_string() .contains("local Comfy workflow-class hash mismatch"), @@ -124694,7 +130729,8 @@ State initialization... "linear_backend": "auto", "moe_backend": "cutlass", "proof_sha256": "ab".repeat(32), - })).unwrap(); + })) + .unwrap(); let artifact = test_vllm_artifact(); let mut args = test_calibrate_canary_args(); bind_calibration_vllm_execution_profile(&mut args, Some(&profile)).unwrap(); @@ -124712,8 +130748,14 @@ State initialization... validate_calibration_vllm_execution_profile(&runtime, Some(&profile)).unwrap(); assert!(validate_calibration_vllm_execution_profile(&legacy, Some(&profile)).is_err()); let encoded = serde_json::to_value(&runtime).unwrap(); - assert_eq!(encoded["vllm_runtime"], "flashinfer_speculative_metadata_v1"); - assert_eq!(serde_json::from_value::(encoded.clone()).unwrap(), runtime); + assert_eq!( + encoded["vllm_runtime"], + "flashinfer_speculative_metadata_v1" + ); + assert_eq!( + serde_json::from_value::(encoded.clone()).unwrap(), + runtime + ); let mut unknown = encoded; unknown["vllm_runtime"] = json!("unknown_runtime"); assert!(serde_json::from_value::(unknown).is_err()); @@ -124785,7 +130827,8 @@ State initialization... let mut args = test_calibrate_canary_args(); bind_calibration_vllm_execution_profile(&mut args, Some(&profile)).unwrap(); validate_calibration_args_for_artifact(&artifact, &args).unwrap(); - let runtime = catalog_canary_runtime_config(&artifact, Path::new("model"), &args).unwrap(); + let runtime = + catalog_canary_runtime_config(&artifact, Path::new("model"), &args).unwrap(); assert_eq!(runtime.vllm_compilation_mode, mode); assert_eq!(runtime.vllm_cudagraph_mode.as_deref(), graph); validate_calibration_vllm_execution_profile(&runtime, Some(&profile)).unwrap(); @@ -124799,7 +130842,9 @@ State initialization... let mut changed = serde_json::to_value(&runtime).unwrap(); changed[field] = value; let changed = serde_json::from_value(changed).unwrap(); - assert!(validate_calibration_vllm_execution_profile(&changed, Some(&profile)).is_err()); + assert!( + validate_calibration_vllm_execution_profile(&changed, Some(&profile)).is_err() + ); } args.vllm_compilation_mode = Some(3); assert!(bind_calibration_vllm_execution_profile(&mut args, Some(&profile)).is_err()); @@ -124811,9 +130856,22 @@ State initialization... #[test] fn calibration_vllm_execution_profile_compilation_cli_validation() { - let calibration = ["mayhem", "--model", "test/model", "--artifact", "vllm", - "--artifact-path", "/tmp/checkpoint"]; - let plan = ["mayhem", "--artifact-base", "/tmp/artifacts", "--report-dir", "/tmp/reports"]; + let calibration = [ + "mayhem", + "--model", + "test/model", + "--artifact", + "vllm", + "--artifact-path", + "/tmp/checkpoint", + ]; + let plan = [ + "mayhem", + "--artifact-base", + "/tmp/artifacts", + "--report-dir", + "/tmp/reports", + ]; for (flag, value) in [ ("--vllm-compilation-mode", "-1"), ("--vllm-compilation-mode", "4"), @@ -124823,15 +130881,27 @@ State initialization... ("--vllm-cudagraph-mode", "UNKNOWN"), ] { assert!(CatalogCalibrateCanaryArgs::try_parse_from( - calibration.into_iter().chain([flag, value])).is_err()); - assert!(CatalogCanaryPlanArgs::try_parse_from( - plan.into_iter().chain([flag, value])).is_err()); - } - let controls = ["--vllm-enforce-eager", "false", "--vllm-compilation-mode", "0", - "--vllm-cudagraph-mode", "FULL_DECODE_ONLY"]; - let mut args = CatalogCalibrateCanaryArgs::try_parse_from( - calibration.into_iter().chain(controls)).unwrap(); - let plan_args = CatalogCanaryPlanArgs::try_parse_from(plan.into_iter().chain(controls)).unwrap(); + calibration.into_iter().chain([flag, value]) + ) + .is_err()); + assert!( + CatalogCanaryPlanArgs::try_parse_from(plan.into_iter().chain([flag, value])) + .is_err() + ); + } + let controls = [ + "--vllm-enforce-eager", + "false", + "--vllm-compilation-mode", + "0", + "--vllm-cudagraph-mode", + "FULL_DECODE_ONLY", + ]; + let mut args = + CatalogCalibrateCanaryArgs::try_parse_from(calibration.into_iter().chain(controls)) + .unwrap(); + let plan_args = + CatalogCanaryPlanArgs::try_parse_from(plan.into_iter().chain(controls)).unwrap(); assert_eq!(plan_args.vllm_compilation_mode, args.vllm_compilation_mode); assert_eq!(plan_args.vllm_cudagraph_mode, args.vllm_cudagraph_mode); let artifact = test_vllm_artifact(); @@ -124914,7 +130984,10 @@ State initialization... assert_eq!(runtime.vllm_max_num_batched_tokens, Some(2048)); assert_eq!(runtime.vllm_enforce_eager, Some(false)); assert_eq!(runtime.vllm_compilation_mode, Some(0)); - assert_eq!(runtime.vllm_cudagraph_mode.as_deref(), Some("FULL_DECODE_ONLY")); + assert_eq!( + runtime.vllm_cudagraph_mode.as_deref(), + Some("FULL_DECODE_ONLY") + ); assert_eq!(runtime.vllm_linear_backend.as_deref(), Some("auto")); assert_eq!(runtime.vllm_moe_backend.as_deref(), Some("cutlass")); assert_eq!(runtime.vllm_mtp_num_speculative_tokens, Some(3)); @@ -125404,7 +131477,8 @@ State initialization... fn non_text_canary_calibration_helpers_emit_typed_values() { let embedding_prompt: CanaryPrompt = serde_json::from_value(json!({ "id": "embed-p1", - "input": "embedding canary" + "input": "embedding canary", + "dimensions": 1536 })) .unwrap(); let mut embedding_backend = FakeEngineBackend::new("unused"); @@ -125415,6 +131489,13 @@ State initialization... embedding.fingerprint, embedding_vector_fingerprint(&[0.1, 0.2, 0.3]) ); + assert_eq!( + embedding_backend + .last_embedding_request + .as_ref() + .and_then(|request| request.dimensions), + Some(1536) + ); let wav = tiny_wav_bytes(16_000); let stt_prompt: CanaryPrompt = serde_json::from_value(json!({ @@ -125545,23 +131626,34 @@ State initialization... #[test] fn image_reference_canary_calibration_preserves_input_and_negative_prompt() { let bytes = include_bytes!("../../mayhem-engine/tests/fixtures/reference.png").to_vec(); - let reference = format!("data:image/png;base64,{}", base64::engine::general_purpose::STANDARD.encode(&bytes)); + let reference = format!( + "data:image/png;base64,{}", + base64::engine::general_purpose::STANDARD.encode(&bytes) + ); let prompt: CanaryPrompt = serde_json::from_value(json!({ "id": "image-reference-p1", "prompt": "A blue sculpture", "input_reference": reference, "strength": 0.6, "negative_prompt": "blur", "cfg_scale": 1.0, "steps": 9, "seed": 7, "size": "64x64", - })).unwrap(); + })) + .unwrap(); let mut backend = FakeEngineBackend::new("").with_artifact_chunks(vec![ArtifactChunk { - artifact_id: "image-1".to_owned(), index: 0, content_type: "image/png".to_owned(), - bytes: bytes.clone(), final_chunk: true, + artifact_id: "image-1".to_owned(), + index: 0, + content_type: "image/png".to_owned(), + bytes: bytes.clone(), + final_chunk: true, }]); - let report = calibrate_image_perceptual_hash_prompt(&mut backend, &prompt, 42, false).unwrap(); + let report = + calibrate_image_perceptual_hash_prompt(&mut backend, &prompt, 42, false).unwrap(); let request = backend.last_image_request.unwrap(); assert_eq!(request.input_reference.as_deref(), Some(reference.as_str())); assert_eq!(request.strength, Some(0.6)); assert_eq!(request.negative_prompt.as_deref(), Some("blur")); assert_eq!(request.seed, Some(7)); - assert_eq!(report.resource_items["image"].item_bytes, bytes.len() as u64); + assert_eq!( + report.resource_items["image"].item_bytes, + bytes.len() as u64 + ); assert_eq!(report.resource_items["image"].item_units, 48); } @@ -125718,6 +131810,33 @@ State initialization... assert!(peak >= baseline); } + #[test] + fn calibration_process_rss_tolerates_exited_descendants() { + let context = CalibrationMemoryContext { + f13_budget_bytes: u64::MAX, + source: "test-process-rss".to_owned(), + probe: CalibrationMemoryProbe::ProcessRss, + chatterbox_device: None, + vllm_replica_limit_bytes: None, + }; + let missing_pid = u32::MAX; + + assert!( + calibration_memory_bytes(&context, &[std::process::id(), missing_pid]).unwrap() > 0 + ); + let error = calibration_memory_bytes(&context, &[missing_pid]) + .expect_err("an all-exited process set must not produce an RSS measurement"); + assert!(error.to_string().contains("failed to read calibration RSS")); + + let error = measure_calibration_memory(&context, &[missing_pid], || { + Err::<(), _>(anyhow!("operation failure remains authoritative")) + }) + .expect_err("the operation failure must be returned"); + assert!(error + .to_string() + .contains("operation failure remains authoritative")); + } + #[test] fn nvidia_calibration_memory_is_scoped_to_the_worker_process_tree() { let bytes = parse_nvidia_compute_process_memory_bytes( @@ -126581,11 +132700,13 @@ State initialization... None, Some(&expected), None, + None, &empty_hashes, &empty_vectors, &empty_hashes, observed, &empty_hashes, + &empty_hashes, 9_000, ) }; @@ -126945,6 +133066,7 @@ State initialization... engine: "vllm".to_owned(), topology: None, independent_dispatch: true, + max_concurrent: None, request_modalities: vec![vec!["text".to_owned()]], proof_sha256: "ab".repeat(32), }, @@ -127044,7 +133166,9 @@ State initialization... calibration.model_id = catalog.models[0].model_id.clone(); stamp_test_calibration_report(&mut calibration, &canaries_dir); - for (engine, independent_dispatch) in [("llama.cpp", false), ("vllm", false), ("vllm", true)] { + for (engine, independent_dispatch) in + [("llama.cpp", false), ("vllm", false), ("vllm", true)] + { catalog.models[0] .artifacts .get_mut("gguf-q4_k_m") @@ -127062,6 +133186,7 @@ State initialization... engine: "vllm".to_owned(), topology: None, independent_dispatch: true, + max_concurrent: None, request_modalities: vec![vec!["text".to_owned()]], proof_sha256: independent_proof, }, @@ -127094,18 +133219,19 @@ State initialization... fn canary_evidence_binds_execution_mode_policy_report_and_input_bytes() { let mut catalog = test_catalog(&"aa".repeat(32)); catalog.models[0].tier = "launch".to_owned(); - catalog.models[0].artifacts.get_mut("gguf-q4_k_m").unwrap().engine = - "vllm".to_owned(); - insert_test_canary_expectation( - &mut catalog.models[0], - "gguf-q4_k_m", - "aa".repeat(32), - ); + catalog.models[0] + .artifacts + .get_mut("gguf-q4_k_m") + .unwrap() + .engine = "vllm".to_owned(); + insert_test_canary_expectation(&mut catalog.models[0], "gguf-q4_k_m", "aa".repeat(32)); let root = catalog.models[0].artifacts["gguf-q4_k_m"] .artifact_root .clone(); let mut restricted = catalog.models[0].adapter.endpoint_families[0].clone(); - restricted.request_attribute_specs.retain(|path, _| path == "min_p"); + restricted + .request_attribute_specs + .retain(|path, _| path == "min_p"); let min_p = restricted.request_attribute_specs.get_mut("min_p").unwrap(); min_p.default = None; min_p.minimum = Some(0.0); @@ -127157,12 +133283,9 @@ State initialization... speciality_calibrations: BTreeMap::new(), }; let binding = execution_mode.binding(&root, "throughput").unwrap(); - let effective = catalog::execution_mode_model( - &catalog.models[0], - "gguf-q4_k_m", - &execution_mode, - ) - .unwrap(); + let effective = + catalog::execution_mode_model(&catalog.models[0], "gguf-q4_k_m", &execution_mode) + .unwrap(); let mut calibration = test_calibration_report("aa".repeat(32), Some("aa".repeat(32))); calibration.model_id = catalog.models[0].model_id.clone(); calibration.engine = "vllm".to_owned(); @@ -127212,7 +133335,12 @@ State initialization... Some("throughput") ); - calibration.runtime_config.execution_mode.as_mut().unwrap().policy_hash = "ff".repeat(32); + calibration + .runtime_config + .execution_mode + .as_mut() + .unwrap() + .policy_hash = "ff".repeat(32); write_json_file(&report_path, &calibration).unwrap(); catalog .vllm_execution_modes @@ -127231,7 +133359,10 @@ State initialization... CatalogCanaryReportMode::ApplyToCatalog, ); assert!(!forged.ok); - assert!(forged.errors.iter().any(|error| error.contains("mode binding"))); + assert!(forged + .errors + .iter() + .any(|error| error.contains("mode binding"))); fs::write( mode_dir.join("test-mode-canary.json"), @@ -127920,8 +134051,8 @@ State initialization... "baseline" ); assert_eq!( - mode_catalog["vllm_execution_modes"][&root]["throughput"]["canary"] - ["fingerprints"]["gguf-q4_k_m"], + mode_catalog["vllm_execution_modes"][&root]["throughput"]["canary"]["fingerprints"] + ["gguf-q4_k_m"], json!(test_canary_aggregate_for_label(&"aa".repeat(32))) ); assert_eq!( @@ -128399,6 +134530,7 @@ State initialization... "mlx-4bit".to_owned(), catalog::CatalogArtifact { engine: "mlx".to_owned(), + openai_compatible: None, stable_diffusion_cpp: None, mlx_runtime: mayhem_engine::MlxRuntimeConfig::default(), kv_cache: None, @@ -128514,6 +134646,7 @@ State initialization... "nvfp4".to_owned(), catalog::CatalogArtifact { engine: "trt-llm".to_owned(), + openai_compatible: None, stable_diffusion_cpp: None, mlx_runtime: mayhem_engine::MlxRuntimeConfig::default(), kv_cache: None, @@ -128717,6 +134850,7 @@ State initialization... "mlx-4bit".to_owned(), catalog::CatalogArtifact { engine: "mlx".to_owned(), + openai_compatible: None, stable_diffusion_cpp: None, mlx_runtime: mayhem_engine::MlxRuntimeConfig::default(), kv_cache: None, @@ -131663,6 +137797,7 @@ State initialization... model_id: "test/model@4bit".to_owned(), adapter: catalog::CatalogAdapter::default(), generation_execution_profile: None, + runtime_independent_dispatch_modalities: Vec::new(), sampling: catalog::CatalogSamplingProfile::default(), workflow_policy: None, output_modalities: vec!["text".to_owned()], @@ -131676,6 +137811,7 @@ State initialization... min_session_au: 0, min_ask_au: 0, rules_ver: 3, + capacity_slots: 1, ctx: 8192, ctx_bracket: Some(ctx_bracket_for_tokens(8192).to_owned()), ctx_bracket_table_ver: Some(CTX_BRACKET_TABLE_VERSION), @@ -131899,7 +138035,7 @@ State initialization... checkpoint_every: CheckpointPolicy { tokens: 1, ms: 0 }, max_spend_au: MoneyAu::MAX, accept_replay: None, - reservation_recovery: None, + reservation_recovery: None, } } @@ -131908,7 +138044,7 @@ State initialization... let user_key = SigningKey::from_bytes(&[6_u8; 32]); let user = hex_encode(&user_key.verifying_key().to_bytes()); let voucher_body = mayhem_proto::SpendVoucherBody { - schema_version: SESSION_RECEIPT_SCHEMA_VERSION, + schema_version: SPEND_VOUCHER_SCHEMA_VERSION, session_id: session_id.clone(), billing_id: "bb".repeat(32), billing_attempt: 0, @@ -132320,11 +138456,15 @@ State initialization... bind_calibration_generation_topology(&mut args, Some(&profile)).unwrap(); assert_eq!(args.vllm_worker_count, Some(3)); assert_eq!(args.vllm_max_num_seqs, Some(1)); - let mut artifact = test_catalog(&"aa".repeat(32)).models[0].artifacts["gguf-q4_k_m"].clone(); + let mut artifact = + test_catalog(&"aa".repeat(32)).models[0].artifacts["gguf-q4_k_m"].clone(); artifact.engine = "vllm".to_owned(); - let mut runtime = catalog_canary_runtime_config(&artifact, Path::new("model.safetensors"), &args).unwrap(); + let mut runtime = + catalog_canary_runtime_config(&artifact, Path::new("model.safetensors"), &args) + .unwrap(); runtime.execution_mode = Some(mayhem_proto::ExecutionModeBinding { - mode_id: "isolated".to_owned(), policy_hash: "ab".repeat(32), + mode_id: "isolated".to_owned(), + policy_hash: "ab".repeat(32), }); validate_calibration_generation_topology(&runtime, Some(&profile)).unwrap(); assert!(validate_calibration_generation_topology(&runtime, None).is_err()); @@ -132345,16 +138485,29 @@ State initialization... #[test] fn calibration_isolated_memory_is_checked_before_loading_any_worker() { let mut args = test_calibrate_canary_args(); - assert_eq!(calibration_vllm_replica_allocation(&args, 100 * GIB_BYTES, 80 * GIB_BYTES).unwrap(), None); + assert_eq!( + calibration_vllm_replica_allocation(&args, 100 * GIB_BYTES, 80 * GIB_BYTES).unwrap(), + None + ); args.execution_mode = Some("isolated".to_owned()); args.vllm_worker_count = Some(2); - bind_calibration_generation_topology(&mut args, Some(&test_isolated_generation_profile())).unwrap(); - assert!(calibration_vllm_replica_allocation(&args, 100 * GIB_BYTES, 80 * GIB_BYTES).is_err()); + bind_calibration_generation_topology(&mut args, Some(&test_isolated_generation_profile())) + .unwrap(); + assert!( + calibration_vllm_replica_allocation(&args, 100 * GIB_BYTES, 80 * GIB_BYTES).is_err() + ); args.vllm_memory_utilization = Some(35); - assert_eq!(calibration_vllm_replica_allocation(&args, 100 * GIB_BYTES, 80 * GIB_BYTES).unwrap(), Some(70 * GIB_BYTES)); - assert!(calibration_vllm_replica_allocation(&args, 100 * GIB_BYTES, 69 * GIB_BYTES).is_err()); + assert_eq!( + calibration_vllm_replica_allocation(&args, 100 * GIB_BYTES, 80 * GIB_BYTES).unwrap(), + Some(70 * GIB_BYTES) + ); + assert!( + calibration_vllm_replica_allocation(&args, 100 * GIB_BYTES, 69 * GIB_BYTES).is_err() + ); args.vllm_worker_count = Some(3); - assert!(calibration_vllm_replica_allocation(&args, 100 * GIB_BYTES, 80 * GIB_BYTES).is_err()); + assert!( + calibration_vllm_replica_allocation(&args, 100 * GIB_BYTES, 80 * GIB_BYTES).is_err() + ); args.vllm_memory_utilization = Some(85); assert!(calibration_vllm_replica_allocation(&args, u64::MAX, u64::MAX).is_err()); } @@ -132363,7 +138516,8 @@ State initialization... fn calibration_legacy_runtime_omits_optional_topology_and_count() { let args = test_calibrate_canary_args(); let artifact = &test_catalog(&"aa".repeat(32)).models[0].artifacts["gguf-q4_k_m"]; - let runtime = catalog_canary_runtime_config(artifact, Path::new("model.gguf"), &args).unwrap(); + let runtime = + catalog_canary_runtime_config(artifact, Path::new("model.gguf"), &args).unwrap(); validate_calibration_generation_topology(&runtime, None).unwrap(); let json = serde_json::to_value(&runtime).unwrap(); assert!(json.get("vllm_generation_topology").is_none()); @@ -134177,6 +140331,7 @@ State initialization... "gguf-q4_k_m".to_owned(), catalog::CatalogArtifact { engine: "llama.cpp".to_owned(), + openai_compatible: None, stable_diffusion_cpp: None, mlx_runtime: mayhem_engine::MlxRuntimeConfig::default(), kv_cache: None, @@ -134270,11 +140425,14 @@ State initialization... transcripts: BTreeMap::new(), audio_fingerprints: BTreeMap::new(), video_fingerprints: BTreeMap::new(), + decision_fingerprints: BTreeMap::new(), }, price_ref_au: catalog::PriceRef { denom: "au_usd".to_owned(), in_per_1k: 1, out_per_1k: 2, + per_req_au: 0, + min_session_au: 0, rate_map: Vec::new(), }, }], @@ -134665,6 +140823,7 @@ State initialization... ); catalog::CatalogArtifact { engine: "vllm".to_owned(), + openai_compatible: None, stable_diffusion_cpp: None, mlx_runtime: mayhem_engine::MlxRuntimeConfig::default(), kv_cache: None, diff --git a/crates/mayhem-cli/src/managed_openai_compatible.rs b/crates/mayhem-cli/src/managed_openai_compatible.rs new file mode 100644 index 00000000..21613a20 --- /dev/null +++ b/crates/mayhem-cli/src/managed_openai_compatible.rs @@ -0,0 +1,2305 @@ +use std::collections::{BTreeMap, BTreeSet}; +use std::fs::{self, File, OpenOptions}; +use std::io::{Read, Write}; +use std::net::{Ipv4Addr, TcpListener}; +use std::path::{Component, Path, PathBuf}; +use std::process::{Command, Output, Stdio}; +use std::sync::Mutex; + +use anyhow::{bail, ensure, Context, Result}; +use fs2::FileExt as _; +use serde::{Deserialize, Serialize}; +use sha2::{Digest as _, Sha256}; + +const IMAGE: &str = + "lmsysorg/sglang@sha256:12d3392bdc8be8d35e9a95f191df6aef99c5114bdbefd41bfdc7e760e6d25ec1"; +const SOURCE_FORMAT: &str = "pennyroyal_source_bundle_tar_gzip_v1"; +const SOURCE_LAYOUT: &str = "source"; +const MATERIALIZATION: &str = "derive_from_signed_snapshot_v1"; +const SECURITY_PROFILE: &str = "docker_29_1_3_default_plus_io_uring_v1"; +const RESOURCE_PROFILE: &str = "single_sm120_96g_hostnet_hostipc_v1"; +const LAUNCH_PROFILE: &str = "pennyroyal_flash_next_frspec_524k_nvme_deterministic_v1"; +const SECCOMP: &[u8] = include_bytes!("../assets/docker-29.1.3-ple-io-uring.json"); +const SECCOMP_SHA256: &str = "c7a33fb8ae1f8346356a61ce833d579c45acf2bc94967c6763634e81010ff816"; + +const LAUNCHER: &str = "configs/pennyroyal/serve-flash-next-frspec.sh"; +const CHAT_TEMPLATE: &str = "configs/pennyroyal/templates/froggeric-v22.5.jinja"; +const FRSPEC_MAP: &str = "configs/pennyroyal/frspec/flash-next-64k.pt"; +const FRSPEC_MANIFEST: &str = "configs/pennyroyal/frspec/flash-next-64k.manifest.json"; +const NIXL_CONFIG: &str = "configs/pennyroyal/nixl-posix-frspec.toml"; +const PLE_PREPARER: &str = "scripts/pennyroyal/prepare_ple_nvme.py"; +const PLE_CHECKER: &str = "scripts/pennyroyal/check_ple_nvme.py"; +const PLE_PLUGIN_SOURCE: &str = "tools/ple_nvme/ssd_stream"; +const PLE_READER_WHEEL_FILENAME: &str = + "sglang_ssd_stream-0.2.0+pennyroyal2-cp312-cp312-linux_x86_64.whl"; +const PLE_READER_WHEEL_BYTES: u64 = 292_987; +const PLE_READER_WHEEL_SHA256: &str = + "5fd3bf79524aec7068729e99823a8278e4f3bd7dcacd222f2c55f4395454112f"; + +#[derive(Debug, Deserialize)] +#[serde(tag = "kind", rename_all = "snake_case", deny_unknown_fields)] +pub(crate) enum ManagedRuntimeRecipeV1 { + PennyroyalFlashNextFrspecV1 { + schema_version: u32, + profile_version: u32, + public_model_id: String, + artifact: RecipeArtifact, + source: RecipeSource, + proofs: RecipeProofs, + ple: RecipePle, + runtime: RecipeRuntime, + }, +} + +#[derive(Debug, Deserialize)] +#[serde(deny_unknown_fields)] +pub(crate) struct RecipeArtifact { + repo: String, + revision: String, + snapshot_manifest_sidecar: String, + snapshot_manifest_sha256: String, + file_count: u32, + total_bytes: u64, +} + +#[derive(Debug, Deserialize)] +#[serde(deny_unknown_fields)] +pub(crate) struct RecipeSource { + sidecar: String, + format: String, + root_layout: String, + revision: String, + archive_bytes: u64, + archive_sha256: String, +} + +#[derive(Debug, Deserialize)] +#[serde(deny_unknown_fields)] +pub(crate) struct RecipeProofs { + launcher_sha256: String, + chat_template_sha256: String, + frspec_map_sha256: String, + frspec_manifest_sha256: String, + tokenizer_sha256: String, + ple_plugin_source_inventory_sha256: String, + seccomp_sha256: String, +} + +#[derive(Debug, Deserialize)] +#[serde(deny_unknown_fields)] +pub(crate) struct RecipePle { + materialization: String, + source_config_sha256: String, + source_index_sha256: String, + table_relpath: String, + table_bytes: u64, + table_sha256: String, + table_rows: u64, + table_columns: u32, + table_dtype: String, + portable_manifest_bytes: u64, + portable_manifest_sha256: String, + reader_version: String, + reader_wheel: RecipeReaderWheel, +} + +#[derive(Debug, Deserialize)] +#[serde(deny_unknown_fields)] +pub(crate) struct RecipeReaderWheel { + sidecar: String, + filename: String, + bytes: u64, + sha256: String, + python_tag: String, + abi_tag: String, + platform_tag: String, +} + +#[derive(Debug, Deserialize)] +#[serde(deny_unknown_fields)] +pub(crate) struct RecipeRuntime { + image: String, + security_profile: RecipeSecurityProfile, + resource_profile: String, + launch_profile: String, + deterministic_inference: bool, + attention_backend: String, + linear_attn_prefill_backend: String, + linear_attn_decode_backend: String, + provider_max_concurrent: u32, + scheduler_max_running_requests: u32, + reasoning_default: String, + reasoning_overrides: Vec, +} + +#[derive(Debug, Deserialize)] +#[serde(deny_unknown_fields)] +struct RecipeSecurityProfile { + kind: String, + sidecar: String, + bytes: u64, + sha256: String, +} + +pub(crate) struct ManagedRuntimeInputs<'a> { + pub home: &'a Path, + pub docker: &'a Path, + pub provider_id: &'a str, + pub enclave_id: &'a str, + pub public_model_id: &'a str, + pub artifact_repo: &'a str, + pub artifact_revision: &'a str, + pub snapshot_manifest_sidecar: &'a str, + pub snapshot_manifest_sha256: &'a str, + pub snapshot_file_count: u32, + pub snapshot_total_bytes: u64, + pub runtime_revision: &'a str, + pub container_image_digest: &'a str, + pub max_concurrent: u32, + pub recipe_sha256: &'a str, + pub recipe_path: &'a Path, + pub snapshot_dir: &'a Path, + pub sidecars: &'a BTreeMap, +} + +pub(crate) struct ManagedOpenAiRuntime { + base_url: String, + process_id: u32, + _owner: ManagedContainerOwner, +} + +impl std::fmt::Debug for ManagedOpenAiRuntime { + fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter + .debug_struct("ManagedOpenAiRuntime") + .field("base_url", &self.base_url) + .field("lifecycle", &"managed") + .finish() + } +} + +impl ManagedOpenAiRuntime { + pub(crate) fn base_url(&self) -> &str { + &self.base_url + } + + pub(crate) fn process_id(&self) -> u32 { + self.process_id + } +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +struct ManagedContainerState { + schema_version: u32, + nonce: String, + container_name: String, + container_id: Option, + labels: BTreeMap, +} + +struct ManagedContainerOwner { + docker: PathBuf, + state_path: PathBuf, + container_id: String, + labels: BTreeMap, + lock: File, + stopped: Mutex, +} + +impl std::fmt::Debug for ManagedContainerOwner { + fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter + .debug_struct("ManagedContainerOwner") + .field("container_id", &self.container_id) + .finish_non_exhaustive() + } +} + +impl Drop for ManagedContainerOwner { + fn drop(&mut self) { + let Ok(mut stopped) = self.stopped.lock() else { + return; + }; + if *stopped { + return; + } + *stopped = true; + let mut removed = false; + if container_has_exact_labels(&self.docker, &self.container_id, &self.labels) + .unwrap_or(false) + { + let stopped_ok = Command::new(&self.docker) + .args(["stop", "--time", "180", &self.container_id]) + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .status() + .is_ok_and(|status| status.success()); + if stopped_ok + && container_has_exact_labels(&self.docker, &self.container_id, &self.labels) + .unwrap_or(false) + { + removed = Command::new(&self.docker) + .args(["rm", &self.container_id]) + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .status() + .is_ok_and(|status| status.success()); + } + } + // Keep recovery identity when Docker is unavailable or cleanup cannot + // be proven. A later owner reconciles only this exact ID and labels. + if removed { + let _ = fs::remove_file(&self.state_path); + } + let _ = self.lock.unlock(); + } +} + +pub(crate) fn prepare_managed_runtime( + inputs: ManagedRuntimeInputs<'_>, +) -> Result { + platform_preflight()?; + let recipe_bytes = fs::read(inputs.recipe_path) + .with_context(|| format!("reading runtime recipe {}", inputs.recipe_path.display()))?; + ensure!( + sha256_bytes(&recipe_bytes) == inputs.recipe_sha256, + "managed runtime recipe hash differs from its signed binding" + ); + let recipe: ManagedRuntimeRecipeV1 = + serde_json::from_slice(&recipe_bytes).context("parsing strict managed runtime recipe")?; + let ManagedRuntimeRecipeV1::PennyroyalFlashNextFrspecV1 { + schema_version, + profile_version, + public_model_id, + artifact, + source, + proofs, + ple, + runtime, + } = recipe; + validate_recipe( + &inputs, + schema_version, + profile_version, + &public_model_id, + &artifact, + &source, + &proofs, + &ple, + &runtime, + )?; + docker_preflight(inputs.docker, &runtime)?; + + let managed_root = inputs + .home + .join("runtime") + .join("managed") + .join("openai-compatible") + .join(safe_component(inputs.enclave_id)); + fs::create_dir_all(&managed_root) + .with_context(|| format!("creating managed runtime {}", managed_root.display()))?; + set_private_directory(&managed_root)?; + let lock_path = managed_root.join("lock"); + let lock = private_open(&lock_path)?; + lock.try_lock_exclusive().with_context(|| { + format!( + "managed runtime for enclave {} is already owned by another process", + inputs.enclave_id + ) + })?; + reconcile_previous_runtime(inputs.docker, &managed_root, &lock)?; + + let source_archive = inputs.sidecars.get(&source.sidecar).with_context(|| { + format!( + "runtime recipe source sidecar {} was not downloaded", + source.sidecar + ) + })?; + verify_file(source_archive, source.archive_bytes, &source.archive_sha256)?; + aggregate_disk_preflight( + &managed_root, + source.archive_bytes, + ple.reader_wheel.bytes, + ple.table_bytes, + )?; + let source_dir = materialize_source_archive(&managed_root, source_archive, &source)?; + verify_source_proofs(&source_dir, inputs.snapshot_dir, &proofs, &ple)?; + let seccomp_sidecar = inputs + .sidecars + .get(&runtime.security_profile.sidecar) + .context("typed runtime seccomp sidecar was not downloaded")?; + let seccomp_path = write_seccomp( + &managed_root, + seccomp_sidecar, + &runtime.security_profile, + &proofs.seccomp_sha256, + )?; + verify_source_git( + inputs.docker, + &managed_root, + &source_dir, + inputs.recipe_sha256, + inputs.provider_id, + inputs.enclave_id, + inputs.runtime_revision, + )?; + let plugin_wheel = inputs + .sidecars + .get(&ple.reader_wheel.sidecar) + .context("typed runtime PLE reader wheel sidecar was not downloaded")?; + verify_file( + plugin_wheel, + ple.reader_wheel.bytes, + &ple.reader_wheel.sha256, + )?; + let plugin_dir = materialize_plugin( + inputs.docker, + &managed_root, + plugin_wheel, + inputs.recipe_sha256, + inputs.provider_id, + inputs.enclave_id, + &ple.reader_wheel, + &ple.reader_version, + )?; + let prepared_model = materialize_ple( + inputs.docker, + &managed_root, + inputs.snapshot_dir, + &source_dir, + &plugin_dir, + inputs.recipe_sha256, + inputs.provider_id, + inputs.enclave_id, + &ple, + )?; + + let port = reserve_loopback_port()?; + let wrapper = write_launch_wrapper(&managed_root, inputs.public_model_id, port)?; + let nonce = random_hex(16)?; + let home_hash = sha256_bytes(inputs.home.as_os_str().as_encoded_bytes()); + let labels = BTreeMap::from([ + ("mayhem.managed".to_owned(), "true".to_owned()), + ("mayhem.owner-home".to_owned(), home_hash), + ("mayhem.provider".to_owned(), inputs.provider_id.to_owned()), + ("mayhem.enclave".to_owned(), inputs.enclave_id.to_owned()), + ("mayhem.recipe".to_owned(), inputs.recipe_sha256.to_owned()), + ("mayhem.nonce".to_owned(), nonce.clone()), + ]); + let container_name = format!( + "mayhem-openai-{}-{}", + safe_component(inputs.enclave_id), + &nonce[..12] + ); + let state_path = managed_root.join("state.json"); + let mut state = ManagedContainerState { + schema_version: 1, + nonce: nonce.clone(), + container_name: container_name.clone(), + container_id: None, + labels: labels.clone(), + }; + atomic_write_private_json(&state_path, &state)?; + let create_args = service_create_args(ServiceCreateInputs { + name: &container_name, + port, + model_id: inputs.public_model_id, + snapshot: inputs.snapshot_dir, + prepared_model: &prepared_model, + source: &source_dir, + plugin: &plugin_dir, + managed_root: &managed_root, + seccomp: &seccomp_path, + wrapper: &wrapper, + labels: &labels, + })?; + let output = run_docker(inputs.docker, &create_args)?; + let container_id = String::from_utf8(output.stdout) + .context("Docker returned a non-UTF8 container ID")? + .trim() + .to_owned(); + ensure!( + is_hex(&container_id, 12, 64), + "Docker returned an invalid managed container ID" + ); + ensure!( + container_has_exact_labels(inputs.docker, &container_id, &labels)?, + "created managed container labels do not match Core ownership" + ); + state.container_id = Some(container_id.clone()); + atomic_write_private_json(&state_path, &state)?; + if let Err(error) = run_docker(inputs.docker, &["start".to_owned(), container_id.clone()]) { + if container_has_exact_labels(inputs.docker, &container_id, &labels).unwrap_or(false) { + let removed = Command::new(inputs.docker) + .args(["rm", &container_id]) + .status() + .is_ok_and(|status| status.success()); + if removed { + fs::remove_file(&state_path)?; + } + } + return Err(error).context("starting managed OpenAI-compatible runtime"); + } + let owner = ManagedContainerOwner { + docker: inputs.docker.to_path_buf(), + state_path, + container_id: container_id.clone(), + labels: labels.clone(), + lock, + stopped: Mutex::new(false), + }; + let process_id = inspect_running_container_pid(inputs.docker, &container_id, &labels) + .context("resolving the owned managed runtime host PID")?; + Ok(ManagedOpenAiRuntime { + base_url: format!("http://127.0.0.1:{port}/"), + process_id, + _owner: owner, + }) +} + +fn inspect_running_container_pid( + docker: &Path, + container_id: &str, + labels: &BTreeMap, +) -> Result { + ensure!( + container_has_exact_labels(docker, container_id, labels)?, + "managed runtime labels changed before PID inspection" + ); + let output = run_docker( + docker, + &[ + "inspect".to_owned(), + "--format".to_owned(), + "{{.State.Running}} {{.State.Pid}}".to_owned(), + container_id.to_owned(), + ], + )?; + let text = String::from_utf8(output.stdout).context("Docker returned a non-UTF8 state")?; + let mut fields = text.split_whitespace(); + ensure!( + fields.next() == Some("true"), + "managed runtime is not running" + ); + let pid = fields + .next() + .context("Docker omitted the managed runtime PID")? + .parse::() + .context("Docker returned an invalid managed runtime PID")?; + ensure!( + pid > 0 && fields.next().is_none(), + "Docker returned an invalid managed runtime state" + ); + Ok(pid) +} + +#[allow(clippy::too_many_arguments)] +fn validate_recipe( + inputs: &ManagedRuntimeInputs<'_>, + schema_version: u32, + profile_version: u32, + public_model_id: &str, + artifact: &RecipeArtifact, + source: &RecipeSource, + proofs: &RecipeProofs, + ple: &RecipePle, + runtime: &RecipeRuntime, +) -> Result<()> { + ensure!( + schema_version == 1 && profile_version == 1, + "unsupported managed runtime recipe version" + ); + ensure!( + public_model_id == inputs.public_model_id, + "runtime recipe public model ID differs from the selected catalog model" + ); + ensure!( + artifact.repo == inputs.artifact_repo && artifact.revision == inputs.artifact_revision, + "runtime recipe artifact identity differs from the selected catalog artifact" + ); + ensure!( + artifact.snapshot_manifest_sidecar == inputs.snapshot_manifest_sidecar + && artifact.snapshot_manifest_sha256 == inputs.snapshot_manifest_sha256, + "runtime recipe snapshot manifest differs from the signed runtime binding" + ); + ensure!( + artifact.file_count == inputs.snapshot_file_count + && artifact.total_bytes == inputs.snapshot_total_bytes, + "runtime recipe snapshot dimensions differ from the signed snapshot" + ); + ensure!( + source.format == SOURCE_FORMAT && source.root_layout == SOURCE_LAYOUT, + "runtime recipe source archive profile is unsupported" + ); + ensure!( + source.revision == inputs.runtime_revision, + "runtime recipe source revision differs from the signed runtime revision" + ); + ensure!( + safe_sidecar_name(&source.sidecar), + "runtime recipe has an invalid source sidecar name" + ); + ensure!( + is_lower_sha(&source.archive_sha256), + "runtime recipe has an invalid source archive SHA-256" + ); + ensure!( + source.archive_bytes > 0, + "runtime recipe source archive size must be positive" + ); + ensure!( + proofs.seccomp_sha256 == SECCOMP_SHA256 && sha256_bytes(SECCOMP) == SECCOMP_SHA256, + "runtime recipe seccomp profile is not the Core-embedded profile" + ); + for digest in [ + &proofs.launcher_sha256, + &proofs.chat_template_sha256, + &proofs.frspec_map_sha256, + &proofs.frspec_manifest_sha256, + &proofs.tokenizer_sha256, + &proofs.ple_plugin_source_inventory_sha256, + &ple.source_config_sha256, + &ple.source_index_sha256, + &ple.table_sha256, + &ple.portable_manifest_sha256, + ] { + ensure!( + is_lower_sha(digest), + "runtime recipe contains an invalid SHA-256 proof" + ); + } + ensure!( + ple.materialization == MATERIALIZATION, + "runtime recipe PLE materialization is unsupported" + ); + ensure!( + ple.table_relpath == "ple/layer-0.bin" && ple.table_bytes > 0, + "runtime recipe PLE table profile is unsupported" + ); + ensure!( + ple.table_rows == 320_001_536 + && ple.table_columns == 160 + && ple.table_dtype == "float8_e4m3fn", + "runtime recipe PLE tensor shape/dtype is unsupported" + ); + ensure!( + ple.portable_manifest_bytes > 0 && ple.reader_version == "0.2.0+pennyroyal2", + "runtime recipe PLE reader profile is unsupported" + ); + validate_reader_wheel(&ple.reader_wheel, &ple.reader_version)?; + ensure!( + runtime.image == IMAGE + && inputs.container_image_digest + == IMAGE + .split_once('@') + .map(|(_, digest)| digest) + .unwrap_or(""), + "runtime recipe image differs from the compiled digest-only image profile" + ); + ensure!( + runtime.security_profile.kind == SECURITY_PROFILE + && runtime.security_profile.sidecar == "seccomp_profile" + && runtime.security_profile.bytes == SECCOMP.len() as u64 + && runtime.security_profile.sha256 == SECCOMP_SHA256 + && runtime.resource_profile == RESOURCE_PROFILE + && runtime.launch_profile == LAUNCH_PROFILE + && runtime.deterministic_inference + && runtime.attention_backend == "triton" + && runtime.linear_attn_prefill_backend == "triton" + && runtime.linear_attn_decode_backend == "flashinfer", + "runtime recipe selects an unsupported launch/security/resource profile" + ); + ensure!( + runtime.provider_max_concurrent == inputs.max_concurrent + && runtime.provider_max_concurrent == 2 + && runtime.scheduler_max_running_requests == 4, + "runtime recipe concurrency differs from the proven two-request provider envelope" + ); + ensure!( + runtime.reasoning_default == "xhigh" + && runtime.reasoning_overrides == ["low", "medium", "xhigh"], + "runtime recipe reasoning controls are unsupported" + ); + Ok(()) +} + +fn validate_reader_wheel(wheel: &RecipeReaderWheel, version: &str) -> Result<()> { + ensure!( + wheel.sidecar == "ple_plugin_wheel", + "runtime recipe selects an unsupported PLE reader wheel sidecar" + ); + ensure!( + wheel.bytes == PLE_READER_WHEEL_BYTES && wheel.sha256 == PLE_READER_WHEEL_SHA256, + "runtime recipe PLE reader wheel differs from the qualified fixed artifact" + ); + ensure!( + wheel.python_tag == "cp312" + && wheel.abi_tag == "cp312" + && matches!( + wheel.platform_tag.as_str(), + "linux_x86_64" | "manylinux_2_28_x86_64" + ), + "runtime recipe PLE reader wheel is not compatible with the fixed CPython 3.12 Linux x86_64 image" + ); + let expected = format!( + "sglang_ssd_stream-{version}-{}-{}-{}.whl", + wheel.python_tag, wheel.abi_tag, wheel.platform_tag + ); + ensure!( + wheel.filename == expected && wheel.filename == PLE_READER_WHEEL_FILENAME, + "runtime recipe PLE reader wheel filename does not match its signed version/tags" + ); + Ok(()) +} + +fn platform_preflight() -> Result<()> { + ensure!( + cfg!(target_os = "linux") && cfg!(target_arch = "x86_64"), + "managed Pennyroyal runtime requires Linux x86_64" + ); + Ok(()) +} + +fn docker_preflight(docker: &Path, runtime: &RecipeRuntime) -> Result<()> { + let version = docker_text(docker, &["version", "--format", "{{.Server.Version}}"])?; + ensure!( + version.trim() == "29.1.3", + "managed runtime security profile requires Docker server 29.1.3" + ); + let runtimes = docker_text(docker, &["info", "--format", "{{json .Runtimes}}"])?; + let runtimes: serde_json::Value = + serde_json::from_str(runtimes.trim()).context("parsing Docker runtime inventory")?; + ensure!( + runtimes.get("nvidia").is_some(), + "managed runtime requires Docker's NVIDIA runtime" + ); + let cgroup = docker_text( + docker, + &["info", "--format", "{{.CgroupVersion}} {{.CgroupDriver}}"], + )?; + ensure!( + cgroup.split_whitespace().eq(["2", "systemd"]), + "managed runtime resource profile requires Docker cgroup v2 with the systemd driver" + ); + let limit_support = docker_text( + docker, + &[ + "info", + "--format", + "{{json .MemoryLimit}} {{json .SwapLimit}}", + ], + )?; + ensure!( + limit_support.split_whitespace().eq(["true", "true"]), + "managed runtime requires Docker cgroup memory and swap limit support" + ); + run_docker(docker, &["pull".to_owned(), runtime.image.clone()]) + .context("pulling exact managed runtime image")?; + let digests = docker_text( + docker, + &[ + "image", + "inspect", + "--format", + "{{json .RepoDigests}}", + &runtime.image, + ], + )?; + let digests: Vec = + serde_json::from_str(digests.trim()).context("parsing Docker image RepoDigests")?; + ensure!( + digests.iter().any(|digest| digest == &runtime.image), + "Docker image does not expose the signed repository digest" + ); + Ok(()) +} + +fn materialize_source_archive( + root: &Path, + archive: &Path, + source: &RecipeSource, +) -> Result { + let destination = root.join(format!("source-{}", &source.archive_sha256[..16])); + if destination.is_dir() { + return Ok(destination); + } + ensure!( + !destination.exists(), + "managed source cache exists with the wrong type" + ); + let staging = root.join(format!( + ".source-{}.partial-{}", + &source.archive_sha256[..16], + random_hex(8)? + )); + fs::create_dir(&staging).with_context(|| format!("creating {}", staging.display()))?; + let result = (|| -> Result<()> { + let file = File::open(archive).with_context(|| format!("opening {}", archive.display()))?; + let mut tar = tar::Archive::new(flate2::read::GzDecoder::new(file)); + let mut seen = BTreeSet::new(); + let mut directories = Vec::new(); + for entry in tar.entries().context("reading runtime source archive")? { + let mut entry = entry.context("reading runtime source archive entry")?; + let path = entry + .path() + .context("reading runtime source archive path")? + .into_owned(); + let relative = source_bundle_relative(&path)?; + if relative.as_os_str().is_empty() { + continue; + } + ensure!( + seen.insert(relative.clone()), + "runtime source archive contains a duplicate path" + ); + let destination_path = staging.join(&relative); + let kind = entry.header().entry_type(); + if kind.is_dir() { + fs::create_dir_all(&destination_path)?; + directories.push(destination_path); + } else if kind.is_file() { + if let Some(parent) = destination_path.parent() { + fs::create_dir_all(parent)?; + } + let mut output = OpenOptions::new() + .create_new(true) + .write(true) + .open(&destination_path)?; + std::io::copy(&mut entry, &mut output)?; + output.sync_all()?; + set_archive_mode(&destination_path, entry.header().mode().unwrap_or(0))?; + } else if kind.is_symlink() { + let target = entry + .link_name() + .context("reading runtime source symlink")? + .context("runtime source symlink has no target")? + .into_owned(); + validate_archive_symlink(&relative, &target)?; + if let Some(parent) = destination_path.parent() { + fs::create_dir_all(parent)?; + } + create_symlink(&target, &destination_path)?; + } else { + bail!("runtime source archive contains a hardlink or special file"); + } + } + ensure!(!seen.is_empty(), "runtime source archive is empty"); + directories.sort_by_key(|path| std::cmp::Reverse(path.components().count())); + for directory in directories { + set_directory_readonly(&directory)?; + } + fs::rename(&staging, &destination).context("atomically installing runtime source")?; + Ok(()) + })(); + if result.is_err() { + let _ = fs::remove_dir_all(&staging); + } + result?; + Ok(destination) +} + +fn source_bundle_relative(path: &Path) -> Result { + let safe = safe_relative(path)?; + let mut components = safe.components(); + ensure!( + components.next().and_then(|part| part.as_os_str().to_str()) == Some(SOURCE_LAYOUT), + "runtime source archive entry is outside its fixed top-level source directory" + ); + Ok(components.collect()) +} + +fn validate_archive_symlink(path: &Path, target: &Path) -> Result<()> { + ensure!( + !target.as_os_str().is_empty() && !target.is_absolute(), + "runtime source symlink target must be relative" + ); + let mut depth = path + .parent() + .map_or(0usize, |parent| parent.components().count()); + for component in target.components() { + match component { + Component::Normal(_) => depth = depth.saturating_add(1), + Component::ParentDir if depth > 0 => depth -= 1, + _ => bail!("runtime source symlink target escapes the extracted source"), + } + } + Ok(()) +} + +#[cfg(unix)] +fn create_symlink(target: &Path, path: &Path) -> Result<()> { + std::os::unix::fs::symlink(target, path).map_err(Into::into) +} + +#[cfg(not(unix))] +fn create_symlink(_target: &Path, _path: &Path) -> Result<()> { + bail!("managed runtime source symlinks require a Unix host") +} + +fn set_archive_mode(path: &Path, archive_mode: u32) -> Result<()> { + #[cfg(unix)] + { + use std::os::unix::fs::PermissionsExt as _; + let mode = if archive_mode & 0o111 != 0 { + 0o555 + } else { + 0o444 + }; + fs::set_permissions(path, fs::Permissions::from_mode(mode))?; + } + Ok(()) +} + +fn set_directory_readonly(path: &Path) -> Result<()> { + #[cfg(unix)] + { + use std::os::unix::fs::PermissionsExt as _; + fs::set_permissions(path, fs::Permissions::from_mode(0o555))?; + } + Ok(()) +} + +fn verify_source_proofs( + source: &Path, + snapshot: &Path, + proofs: &RecipeProofs, + ple: &RecipePle, +) -> Result<()> { + for (relative, expected) in [ + (LAUNCHER, proofs.launcher_sha256.as_str()), + (CHAT_TEMPLATE, proofs.chat_template_sha256.as_str()), + (FRSPEC_MAP, proofs.frspec_map_sha256.as_str()), + (FRSPEC_MANIFEST, proofs.frspec_manifest_sha256.as_str()), + ] { + verify_file_sha(&source.join(relative), expected)?; + } + verify_file_sha(&snapshot.join("tokenizer.json"), &proofs.tokenizer_sha256)?; + verify_file_sha(&snapshot.join("config.json"), &ple.source_config_sha256)?; + verify_file_sha( + &snapshot.join("model.safetensors.index.json"), + &ple.source_index_sha256, + )?; + ensure!( + source.join(NIXL_CONFIG).is_file() + && source.join(PLE_PREPARER).is_file() + && source.join(PLE_CHECKER).is_file() + && source.join(PLE_PLUGIN_SOURCE).is_dir(), + "runtime source archive is missing fixed profile inputs" + ); + ensure!( + source_inventory_sha256(&source.join(PLE_PLUGIN_SOURCE))? + == proofs.ple_plugin_source_inventory_sha256, + "runtime PLE plugin source inventory differs from the signed recipe" + ); + Ok(()) +} + +#[allow(clippy::too_many_arguments)] +fn verify_source_git( + docker: &Path, + root: &Path, + source: &Path, + recipe: &str, + provider: &str, + enclave: &str, + revision: &str, +) -> Result<()> { + let common = [ + "-c", + "safe.directory=/mayhem/source", + "-C", + "/mayhem/source", + ]; + let mut command = vec!["git".to_owned()]; + command.extend(common.iter().map(|value| (*value).to_owned())); + command.extend(["rev-parse".to_owned(), "HEAD".to_owned()]); + let output = run_owned_one_shot_capture( + docker, + root, + "source-head", + recipe, + provider, + enclave, + &[(source, "/mayhem/source", true)], + &[], + &command, + )?; + ensure!( + String::from_utf8_lossy(&output.stdout).trim() == revision, + "managed runtime source bundle HEAD differs from the signed revision" + ); + let mut command = vec!["git".to_owned()]; + command.extend(common.iter().map(|value| (*value).to_owned())); + command.extend([ + "status".to_owned(), + "--porcelain".to_owned(), + "--untracked-files=all".to_owned(), + ]); + let output = run_owned_one_shot_capture( + docker, + root, + "source-clean", + recipe, + provider, + enclave, + &[(source, "/mayhem/source", true)], + &[], + &command, + )?; + ensure!( + output.stdout.iter().all(u8::is_ascii_whitespace), + "managed runtime source bundle does not match its tracked HEAD" + ); + Ok(()) +} + +fn write_seccomp( + root: &Path, + sidecar: &Path, + profile: &RecipeSecurityProfile, + expected: &str, +) -> Result { + ensure!( + expected == SECCOMP_SHA256 + && profile.sha256 == SECCOMP_SHA256 + && profile.bytes == SECCOMP.len() as u64, + "runtime recipe seccomp hash/size mismatch" + ); + verify_file(sidecar, profile.bytes, &profile.sha256)?; + ensure!( + fs::read(sidecar)? == SECCOMP, + "typed seccomp sidecar differs from the Core-compiled security profile" + ); + let path = root.join(format!("seccomp-{SECCOMP_SHA256}.json")); + if path.exists() { + verify_file(&path, SECCOMP.len() as u64, SECCOMP_SHA256)?; + return Ok(path); + } + atomic_write_private(&path, &fs::read(sidecar)?)?; + Ok(path) +} + +fn aggregate_disk_preflight( + root: &Path, + source_bytes: u64, + wheel_bytes: u64, + table_bytes: u64, +) -> Result<()> { + const CACHE_RESERVE: u64 = 64 * 1024 * 1024 * 1024; + let required = source_bytes + .checked_add(wheel_bytes) + .and_then(|bytes| bytes.checked_add(table_bytes)) + .and_then(|bytes| bytes.checked_add(CACHE_RESERVE)) + .context("managed runtime disk requirement overflowed u64")?; + let available = fs2::available_space(root) + .with_context(|| format!("checking free space under {}", root.display()))?; + ensure!( + available >= required, + "managed runtime requires {required} free bytes for source, PLE derivation, and cache reserve; only {available} are available" + ); + Ok(()) +} + +fn source_inventory_sha256(root: &Path) -> Result { + let mut entries = Vec::::new(); + collect_inventory_entries(root, root, &mut entries)?; + entries.sort_by(|left, right| path_to_posix(left).cmp(&path_to_posix(right))); + let mut digest = Sha256::new(); + for relative in entries { + let path = root.join(&relative); + let metadata = fs::symlink_metadata(&path)?; + let relative = path_to_posix(&relative); + let row = if metadata.file_type().is_file() { + format!( + "{{\"bytes\":{},\"path\":{},\"sha256\":{},\"type\":\"file\"}}\n", + metadata.len(), + serde_json::to_string(&relative)?, + serde_json::to_string(&file_sha256(&path)?)?, + ) + } else if metadata.file_type().is_dir() { + format!( + "{{\"path\":{},\"type\":\"directory\"}}\n", + serde_json::to_string(&relative)?, + ) + } else if metadata.file_type().is_symlink() { + let target = fs::read_link(&path)?; + format!( + "{{\"path\":{},\"target\":{},\"type\":\"symlink\"}}\n", + serde_json::to_string(&relative)?, + serde_json::to_string(&target.to_string_lossy())?, + ) + } else { + bail!("plugin source inventory contains a special file") + }; + digest.update(row.as_bytes()); + } + Ok(format!("{:x}", digest.finalize())) +} + +fn collect_inventory_entries( + root: &Path, + directory: &Path, + entries: &mut Vec, +) -> Result<()> { + for entry in fs::read_dir(directory)? { + let entry = entry?; + let path = entry.path(); + let relative = path.strip_prefix(root)?.to_path_buf(); + entries.push(relative); + if fs::symlink_metadata(&path)?.file_type().is_dir() { + collect_inventory_entries(root, &path, entries)?; + } + } + Ok(()) +} + +fn path_to_posix(path: &Path) -> String { + path.components() + .filter_map(|component| match component { + Component::Normal(part) => part.to_str(), + _ => None, + }) + .collect::>() + .join("/") +} + +fn materialize_plugin( + docker: &Path, + root: &Path, + wheel_path: &Path, + recipe_sha: &str, + provider: &str, + enclave: &str, + wheel: &RecipeReaderWheel, + version: &str, +) -> Result { + let destination = root.join(format!( + "plugin-{}-{}", + safe_component(version), + &wheel.sha256[..16] + )); + if destination.exists() { + ensure!( + fs::symlink_metadata(&destination)?.file_type().is_dir(), + "managed plugin cache has an unsafe type" + ); + fs::remove_dir_all(&destination) + .context("removing the prior Core-owned PLE plugin cache")?; + } + let staging = root.join(format!(".plugin.partial-{}", random_hex(8)?)); + fs::create_dir(&staging)?; + let wheel_container_path = format!("/mayhem/wheel/{}", wheel.filename); + let command = plugin_install_command(&wheel_container_path); + let result = (|| -> Result<()> { + run_owned_one_shot_capture( + docker, + root, + "plugin", + recipe_sha, + provider, + enclave, + &[ + (wheel_path, wheel_container_path.as_str(), true), + (&staging, "/mayhem/plugin", false), + ], + &[], + &command, + )?; + ensure!( + plugin_version(&staging).as_deref() == Some(version), + "managed PLE plugin wheel installed the wrong version" + ); + fs::rename(&staging, &destination).context("atomically installing managed PLE plugin")?; + Ok(()) + })(); + if result.is_err() && !one_shot_state_exists(root, "plugin")? { + let _ = fs::remove_dir_all(&staging); + } + result?; + Ok(destination) +} + +fn plugin_install_command(wheel_container_path: &str) -> Vec { + vec![ + "uv".to_owned(), + "pip".to_owned(), + "install".to_owned(), + "--offline".to_owned(), + "--no-cache".to_owned(), + "--python".to_owned(), + "/usr/bin/python3".to_owned(), + "--target".to_owned(), + "/mayhem/plugin".to_owned(), + "--no-deps".to_owned(), + wheel_container_path.to_owned(), + ] +} + +fn materialize_ple( + docker: &Path, + root: &Path, + snapshot: &Path, + source: &Path, + plugin: &Path, + recipe_sha: &str, + provider: &str, + enclave: &str, + ple: &RecipePle, +) -> Result { + let destination = root.join(format!( + "model-nvme-ple-{}-{}", + &recipe_sha[..16], + &ple.portable_manifest_sha256[..16] + )); + if verify_prepared_ple(&destination, ple).is_ok() { + verify_ple_with_checker( + docker, + root, + snapshot, + source, + plugin, + &destination, + recipe_sha, + provider, + enclave, + ple, + )?; + return Ok(destination); + } + ensure!( + !destination.exists(), + "managed PLE cache failed signed validation" + ); + let staging = root.join(format!(".ple.partial-{}", random_hex(8)?)); + fs::create_dir(&staging)?; + let command = vec![ + "/usr/bin/python3".to_owned(), + format!("/mayhem/source/{PLE_PREPARER}"), + "--source".to_owned(), + "/mayhem/model-source".to_owned(), + "--output".to_owned(), + "/mayhem/materialize/model-nvme-ple".to_owned(), + ]; + let result = (|| -> Result<()> { + run_owned_one_shot( + docker, + root, + "ple", + recipe_sha, + provider, + enclave, + &[ + (snapshot, "/mayhem/model-source", true), + (source, "/mayhem/source", true), + (&staging, "/mayhem/materialize", false), + ], + &command, + )?; + let output = staging.join("model-nvme-ple"); + verify_prepared_ple(&output, ple)?; + verify_ple_with_checker( + docker, root, snapshot, source, plugin, &output, recipe_sha, provider, enclave, ple, + )?; + fs::rename(&output, &destination).context("atomically installing managed PLE overlay")?; + let _ = fs::remove_dir(&staging); + Ok(()) + })(); + if result.is_err() + && !one_shot_state_exists(root, "ple")? + && !one_shot_state_exists(root, "ple-check")? + { + let _ = fs::remove_dir_all(&staging); + } + result?; + Ok(destination) +} + +#[allow(clippy::too_many_arguments)] +fn verify_ple_with_checker( + docker: &Path, + root: &Path, + snapshot: &Path, + source: &Path, + plugin: &Path, + prepared: &Path, + recipe_sha: &str, + provider: &str, + enclave: &str, + ple: &RecipePle, +) -> Result<()> { + let check = vec![ + "/usr/bin/python3".to_owned(), + format!("/mayhem/source/{PLE_CHECKER}"), + "--source".to_owned(), + "/mayhem/model-source".to_owned(), + "--prepared".to_owned(), + "/mayhem/model".to_owned(), + ]; + let checked = run_owned_one_shot_capture( + docker, + root, + "ple-check", + recipe_sha, + provider, + enclave, + &[ + (snapshot, "/mayhem/model-source", true), + (source, "/mayhem/source", true), + (prepared, "/mayhem/model", true), + (plugin, "/mayhem/plugin", true), + ], + &[("PYTHONPATH", "/mayhem/plugin:/mayhem/source/python")], + &check, + )?; + ensure!( + String::from_utf8_lossy(&checked.stdout).trim() == ple.portable_manifest_sha256, + "managed PLE checker returned a different identity" + ); + Ok(()) +} + +fn verify_prepared_ple(path: &Path, ple: &RecipePle) -> Result<()> { + ensure!(path.is_dir(), "prepared PLE overlay is missing"); + verify_file( + &path.join(&ple.table_relpath), + ple.table_bytes, + &ple.table_sha256, + )?; + verify_file( + &path.join("ssd-stream.json"), + ple.portable_manifest_bytes, + &ple.portable_manifest_sha256, + )?; + Ok(()) +} + +fn plugin_version(root: &Path) -> Option { + let entries = fs::read_dir(root).ok()?; + let mut versions = Vec::new(); + for entry in entries.flatten() { + let name = entry.file_name(); + let name = name.to_str()?; + if name.starts_with("sglang_ssd_stream-") && name.ends_with(".dist-info") { + let metadata = fs::read_to_string(entry.path().join("METADATA")).ok()?; + versions.extend( + metadata + .lines() + .filter_map(|line| line.strip_prefix("Version: ")) + .map(str::to_owned), + ); + } + } + (versions.len() == 1 && root.join("sglang_ssd_stream/plugin.py").is_file()) + .then(|| versions.remove(0)) +} + +fn write_launch_wrapper(root: &Path, model_id: &str, port: u16) -> Result { + let model = serde_json::to_string(model_id)?; + let expected = serde_json::to_string(&qualified_launcher_args())?; + let effective = serde_json::to_string(&effective_launcher_args(model_id, port))?; + let script = format!( + r#"#!/usr/bin/python3 +import os +import sys + +args = sys.argv[1:] +expected = {expected} +effective = {effective} +if args != expected: + raise SystemExit("qualified launcher argument vector mismatch") + +def replace(flag, old, new): + if args.count(flag) != 1: + raise SystemExit("qualified launcher flag mismatch: " + flag) + index = args.index(flag) + 1 + if index >= len(args) or args[index] != old: + raise SystemExit("qualified launcher value mismatch: " + flag) + args[index] = new + +replace("--host", "0.0.0.0", "127.0.0.1") +replace("--port", "8001", "{port}") +replace("--served-model-name", "pennyroyal", {model}) +replace("--default-chat-template-kwargs", + '{{"enable_thinking":true,"preserve_thinking":true,"reasoning_effort":"medium"}}', + '{{"enable_thinking":true,"preserve_thinking":true,"reasoning_effort":"xhigh"}}') +replace("--linear-attn-prefill-backend", "flashinfer", "triton") +args.extend(["--attention-backend", "triton"]) +args.append("--enable-deterministic-inference") +if args != effective: + raise SystemExit("deterministic launcher argument vector mismatch") +os.execv("/usr/local/bin/sglang", ["sglang", *args]) +"#, + ); + let digest = sha256_bytes(script.as_bytes()); + let path = root.join(format!("sglang-wrapper-{digest}")); + if path.exists() { + verify_file(&path, script.len() as u64, &digest)?; + return Ok(path); + } + atomic_write_private(&path, script.as_bytes())?; + #[cfg(unix)] + { + use std::os::unix::fs::PermissionsExt as _; + fs::set_permissions(&path, fs::Permissions::from_mode(0o500))?; + } + Ok(path) +} + +fn effective_launcher_args(model_id: &str, port: u16) -> Vec { + let mut args = qualified_launcher_args() + .into_iter() + .map(str::to_owned) + .collect::>(); + for (flag, old, new) in [ + ("--host", "0.0.0.0", "127.0.0.1".to_owned()), + ("--port", "8001", port.to_string()), + ("--served-model-name", "pennyroyal", model_id.to_owned()), + ( + "--default-chat-template-kwargs", + r#"{"enable_thinking":true,"preserve_thinking":true,"reasoning_effort":"medium"}"#, + r#"{"enable_thinking":true,"preserve_thinking":true,"reasoning_effort":"xhigh"}"# + .to_owned(), + ), + ( + "--linear-attn-prefill-backend", + "flashinfer", + "triton".to_owned(), + ), + ] { + let positions = args + .iter() + .enumerate() + .filter_map(|(index, value)| (value == flag).then_some(index)) + .collect::>(); + assert_eq!( + positions.len(), + 1, + "qualified launcher flag mismatch: {flag}" + ); + let value = positions[0] + 1; + assert_eq!(args.get(value).map(String::as_str), Some(old)); + args[value] = new; + } + args.extend(["--attention-backend".to_owned(), "triton".to_owned()]); + args.push("--enable-deterministic-inference".to_owned()); + args +} + +fn qualified_launcher_args() -> Vec<&'static str> { + vec![ + "serve", + "--model-path", + "/mayhem/model", + "--load-format", + "safetensors", + "--served-model-name", + "pennyroyal", + "--host", + "0.0.0.0", + "--port", + "8001", + "--tp", + "1", + "--dtype", + "bfloat16", + "--quantization", + "modelopt_fp4", + "--kv-cache-dtype", + "fp8_e4m3", + "--mem-fraction-static", + "0.981", + "--max-total-tokens", + "824384", + "--warmups=structured_output", + "--context-length", + "524288", + "--json-model-override-args", + r#"{"text_config":{"rope_parameters":{"mrope_interleaved":true,"mrope_section":[11,11,10],"rope_type":"yarn","rope_theta":10000000,"partial_rotary_factor":0.25,"factor":2.0,"original_max_position_embeddings":262144}}}"#, + "--page-size", + "64", + "--max-running-requests", + "4", + "--sleep-on-idle", + "--chunked-prefill-size", + "4096", + "--mamba-radix-cache-strategy", + "extra_buffer", + "--mamba-ssm-dtype", + "bfloat16", + "--max-mamba-cache-size", + "24", + "--gdn-mtp-cache-mode", + "none", + "--linear-attn-decode-backend", + "flashinfer", + "--linear-attn-prefill-backend", + "flashinfer", + "--mamba-track-interval", + "64", + "--enable-hierarchical-cache", + "--hicache-size", + "32", + "--hicache-host-memory-mode", + "cache", + "--hicache-write-policy", + "write_through", + "--hicache-io-backend", + "kernel", + "--hicache-mem-layout", + "page_first", + "--hicache-storage-backend", + "nixl", + "--hicache-storage-prefetch-policy", + "timeout", + "--hicache-storage-backend-extra-config", + "@/mayhem/source/configs/pennyroyal/nixl-posix-frspec.toml", + "--trust-remote-code", + "--chat-template", + "/mayhem/source/configs/pennyroyal/templates/froggeric-v22.5.jinja", + "--image-processor-backend", + "pil", + "--reasoning-parser", + "qwen3", + "--tool-call-parser", + "qwen3_coder", + "--enable-request-time-stats-logging", + "--enable-metrics", + "--default-chat-template-kwargs", + r#"{"enable_thinking":true,"preserve_thinking":true,"reasoning_effort":"medium"}"#, + "--speculative-algorithm", + "NEXTN", + "--speculative-num-steps", + "3", + "--speculative-eagle-topk", + "1", + "--speculative-num-draft-tokens", + "4", + "--speculative-draft-model-quantization", + "unquant", + "--speculative-token-map", + "/mayhem/source/configs/pennyroyal/frspec/flash-next-64k.pt", + "--watchdog-timeout", + "1800", + ] +} + +struct ServiceCreateInputs<'a> { + name: &'a str, + port: u16, + model_id: &'a str, + snapshot: &'a Path, + prepared_model: &'a Path, + source: &'a Path, + plugin: &'a Path, + managed_root: &'a Path, + seccomp: &'a Path, + wrapper: &'a Path, + labels: &'a BTreeMap, +} + +fn service_create_args(inputs: ServiceCreateInputs<'_>) -> Result> { + let cache = inputs.managed_root.join("cache"); + let nixl = inputs.managed_root.join("nixl"); + let runtime_home = inputs.managed_root.join("home"); + let tmp = inputs.managed_root.join("tmp"); + for path in [&cache, &nixl, &runtime_home, &tmp] { + fs::create_dir_all(path)?; + } + let mut args = vec![ + "create".to_owned(), + "--name".to_owned(), + inputs.name.to_owned(), + "--restart=no".to_owned(), + "--runtime=nvidia".to_owned(), + "--gpus=device=0".to_owned(), + "--network=host".to_owned(), + "--ipc=host".to_owned(), + "--shm-size=32g".to_owned(), + "--memory=104g".to_owned(), + "--memory-swap=104g".to_owned(), + "--pids-limit=32768".to_owned(), + "--ulimit=memlock=-1:-1".to_owned(), + "--security-opt=no-new-privileges:true".to_owned(), + format!("--security-opt=seccomp={}", inputs.seccomp.display()), + "--entrypoint=/usr/bin/bash".to_owned(), + ]; + args.push(owned_container_user_arg(inputs.managed_root)?); + for (name, value) in inputs.labels { + args.push(format!("--label={name}={value}")); + } + for (host, container, read_only) in [ + (inputs.snapshot, "/mayhem/model-source", true), + (inputs.prepared_model, "/mayhem/model", true), + (inputs.source, "/mayhem/source", true), + (inputs.plugin, "/mayhem/plugin", true), + (&cache, "/mayhem/cache", false), + (&nixl, "/mayhem/nixl", false), + (&runtime_home, "/mayhem/home", false), + (&tmp, "/mayhem/tmp", false), + (inputs.wrapper, "/mayhem/bin/sglang-wrapper", true), + ] { + args.extend(["--mount".to_owned(), mount_arg(host, container, read_only)?]); + } + for (name, value) in [ + ("HOME", "/mayhem/home"), + ("TMPDIR", "/mayhem/tmp"), + ("REPO_ROOT", "/mayhem/source"), + ("SGLANG_EXE", "/mayhem/bin/sglang-wrapper"), + ("PYTHON", "/usr/bin/python3"), + ("TARGET_MODEL", "/mayhem/model-source"), + ("CACHE_BASE", "/mayhem/cache"), + ("NIXL_STORAGE_BASE", "/mayhem/nixl"), + ("PENNY_PLE_BACKEND", "nvme"), + ("PENNY_PLE_NVME_MODEL", "/mayhem/model"), + ("PENNY_PLE_PLUGIN_DIR", "/mayhem/plugin"), + ("PYTHONPATH", "/mayhem/source/python"), + ("SGLANG_SM120_ONLINE_MXFP8", "true"), + ("SGLANG_MM_PREPROCESS_DEVICE", "cpu"), + // Constrain native XML tool calls even for tool_choice=auto. Without + // this, SGLang can stream an unfinished qwen3_coder call as tool_calls + // with arguments that are not valid JSON. + ("SGLANG_TOOL_STRICT_LEVEL", "1"), + ("MAX_TOTAL_TOKENS", "824384"), + ("CUDA_HOME", "/usr/local/cuda"), + ("CC", "/usr/bin/gcc"), + ("CXX", "/usr/bin/g++"), + ("CUDAHOSTCXX", "/usr/bin/g++"), + ("TORCH_CUDA_ARCH_LIST", "12.0"), + ("PENNY_BUILD_JOBS", "2"), + ("MAX_JOBS", "2"), + ("CMAKE_BUILD_PARALLEL_LEVEL", "2"), + ("CARGO_BUILD_JOBS", "2"), + ("FLASHINFER_NINJA_JOBS", "2"), + ("FLASHINFER_NVCC_THREADS", "1"), + ("TORCHINDUCTOR_COMPILE_THREADS", "2"), + ("OMP_NUM_THREADS", "4"), + ("MKL_NUM_THREADS", "4"), + ("CUDA_VISIBLE_DEVICES", "0"), + ("NUMPY_MADVISE_HUGEPAGE", "0"), + ] { + args.extend(["--env".to_owned(), format!("{name}={value}")]); + } + args.push(IMAGE.to_owned()); + args.extend(service_command()); + let _ = (inputs.model_id, inputs.port); + Ok(args) +} + +fn service_command() -> Vec { + vec!["/mayhem/source/configs/pennyroyal/serve-flash-next-frspec.sh".to_owned()] +} + +fn run_owned_one_shot( + docker: &Path, + root: &Path, + purpose: &str, + recipe: &str, + provider: &str, + enclave: &str, + mounts: &[(&Path, &str, bool)], + command: &[String], +) -> Result<()> { + run_owned_one_shot_capture( + docker, + root, + purpose, + recipe, + provider, + enclave, + mounts, + &[], + command, + ) + .map(|_| ()) +} + +#[allow(clippy::too_many_arguments)] +fn run_owned_one_shot_capture( + docker: &Path, + root: &Path, + purpose: &str, + recipe: &str, + provider: &str, + enclave: &str, + mounts: &[(&Path, &str, bool)], + envs: &[(&str, &str)], + command: &[String], +) -> Result { + let nonce = random_hex(12)?; + let container_name = format!("mayhem-{purpose}-{}", &nonce[..12]); + let labels = BTreeMap::from([ + ("mayhem.managed".to_owned(), "true".to_owned()), + ("mayhem.purpose".to_owned(), purpose.to_owned()), + ("mayhem.provider".to_owned(), provider.to_owned()), + ("mayhem.enclave".to_owned(), enclave.to_owned()), + ("mayhem.recipe".to_owned(), recipe.to_owned()), + ("mayhem.nonce".to_owned(), nonce.clone()), + ]); + let state_path = root.join(format!("oneshot-{purpose}-{nonce}.json")); + let mut state = ManagedContainerState { + schema_version: 1, + nonce: nonce.clone(), + container_name: container_name.clone(), + container_id: None, + labels: labels.clone(), + }; + atomic_write_private_json(&state_path, &state)?; + let mut args = vec![ + "create".to_owned(), + "--name".to_owned(), + container_name, + "--restart=no".to_owned(), + "--network=none".to_owned(), + "--pids-limit=32768".to_owned(), + "--security-opt=no-new-privileges:true".to_owned(), + format!( + "--security-opt=seccomp={}", + root.join(format!("seccomp-{SECCOMP_SHA256}.json")) + .display() + ), + format!( + "--entrypoint={}", + command + .first() + .context("managed one-shot command is empty")? + ), + ]; + args.extend(owned_container_identity_args(root)?); + for (name, value) in &labels { + args.push(format!("--label={name}={value}")); + } + for (host, container, read_only) in mounts { + args.extend([ + "--mount".to_owned(), + mount_arg(host, container, *read_only)?, + ]); + } + for (name, value) in envs { + args.extend(["--env".to_owned(), format!("{name}={value}")]); + } + args.push(IMAGE.to_owned()); + args.extend_from_slice(&command[1..]); + let created = run_docker(docker, &args)?; + let id = String::from_utf8(created.stdout)?.trim().to_owned(); + ensure!( + is_hex(&id, 12, 64) && container_has_exact_labels(docker, &id, &labels)?, + "Docker one-shot container ownership mismatch" + ); + state.container_id = Some(id.clone()); + atomic_write_private_json(&state_path, &state)?; + let output = Command::new(docker) + .args(["start", "--attach", &id]) + .output() + .context("running managed preparation container")?; + ensure!( + container_has_exact_labels(docker, &id, &labels)?, + "managed one-shot container changed identity before cleanup" + ); + let removed = Command::new(docker) + .args(["rm", &id]) + .status() + .context("removing managed one-shot container")?; + ensure!( + removed.success(), + "could not remove managed one-shot container" + ); + fs::remove_file(&state_path)?; + ensure!( + output.status.success(), + "managed {purpose} container failed: {}", + bounded(&output.stderr) + ); + Ok(output) +} + +#[cfg(unix)] +fn owned_container_user_arg(root: &Path) -> Result { + use std::os::unix::fs::MetadataExt as _; + + let metadata = fs::metadata(root) + .with_context(|| format!("reading managed runtime owner for {}", root.display()))?; + ensure!( + metadata.is_dir(), + "managed runtime owner path is not a directory" + ); + Ok(format!("--user={}:{}", metadata.uid(), metadata.gid())) +} + +#[cfg(not(unix))] +fn owned_container_user_arg(_root: &Path) -> Result { + bail!("managed containers require a Unix host") +} + +fn owned_container_identity_args(root: &Path) -> Result<[String; 3]> { + Ok([ + owned_container_user_arg(root)?, + "--env=HOME=/tmp".to_owned(), + "--env=TMPDIR=/tmp".to_owned(), + ]) +} + +fn reconcile_previous_runtime(docker: &Path, root: &Path, _lock: &File) -> Result<()> { + let mut states = fs::read_dir(root)? + .filter_map(|entry| entry.ok().map(|entry| entry.path())) + .filter(|path| { + path.file_name() + .and_then(|name| name.to_str()) + .is_some_and(|name| { + name == "state.json" + || (name.starts_with("oneshot-") && name.ends_with(".json")) + }) + }) + .collect::>(); + states.sort(); + for state_path in states { + reconcile_state_path(docker, &state_path)?; + } + cleanup_abandoned_partial_directories(root)?; + Ok(()) +} + +fn cleanup_abandoned_partial_directories(root: &Path) -> Result<()> { + for entry in fs::read_dir(root)? { + let entry = entry?; + let name = entry.file_name(); + let Some(name) = name.to_str() else { + continue; + }; + let managed_partial = (name.starts_with(".source-") && name.contains(".partial-")) + || name.starts_with(".plugin.partial-") + || name.starts_with(".ple.partial-"); + if !managed_partial { + continue; + } + let metadata = fs::symlink_metadata(entry.path())?; + ensure!( + metadata.file_type().is_dir() && !metadata.file_type().is_symlink(), + "managed partial cache {} has an unsafe type", + entry.path().display() + ); + fs::remove_dir_all(entry.path()) + .with_context(|| format!("removing abandoned managed partial {name}"))?; + } + File::open(root)?.sync_all()?; + Ok(()) +} + +fn one_shot_state_exists(root: &Path, purpose: &str) -> Result { + let prefix = format!("oneshot-{purpose}-"); + Ok(fs::read_dir(root)? + .filter_map(|entry| entry.ok()) + .any(|entry| { + entry + .file_name() + .to_str() + .is_some_and(|name| name.starts_with(&prefix) && name.ends_with(".json")) + })) +} + +fn reconcile_state_path(docker: &Path, state_path: &Path) -> Result<()> { + let state: ManagedContainerState = serde_json::from_slice(&fs::read(&state_path)?) + .context("parsing prior managed runtime state")?; + ensure!( + state.schema_version == 1, + "prior managed runtime state has an unsupported schema" + ); + let inspected = inspect_container( + docker, + state + .container_id + .as_deref() + .unwrap_or(&state.container_name), + )?; + let Some((id, labels)) = inspected else { + fs::remove_file(state_path)?; + return Ok(()); + }; + ensure!( + state + .labels + .iter() + .all(|(name, value)| labels.get(name) == Some(value)), + "prior managed runtime container identity differs from its Core ownership state" + ); + if let Some(expected_id) = state.container_id.as_deref() { + ensure!( + id == expected_id, + "prior managed runtime name resolved to a different container ID" + ); + } + let stopped = Command::new(docker) + .args(["stop", "--time", "180", &id]) + .status() + .context("stopping prior managed runtime container")?; + ensure!( + stopped.success(), + "could not stop prior managed runtime container" + ); + ensure!( + container_has_exact_labels(docker, &id, &state.labels)?, + "prior managed runtime container changed identity before cleanup" + ); + run_docker(docker, &["rm".to_owned(), id])?; + fs::remove_file(state_path)?; + Ok(()) +} + +fn inspect_container( + docker: &Path, + reference: &str, +) -> Result)>> { + let output = Command::new(docker) + .args([ + "inspect", + "--type", + "container", + "--format", + "{{.Id}}\n{{json .Config.Labels}}", + reference, + ]) + .output() + .context("inspecting managed Docker container")?; + if !output.status.success() { + let error = String::from_utf8_lossy(&output.stderr); + if error.contains("No such object") || error.contains("No such container") { + return Ok(None); + } + bail!( + "Docker container inspection failed: {}", + bounded(&output.stderr) + ); + } + let text = + String::from_utf8(output.stdout).context("Docker inspect returned non-UTF8 output")?; + let (id, labels) = text + .trim_end() + .split_once('\n') + .context("Docker inspect omitted container ID or labels")?; + ensure!( + is_hex(id, 12, 64), + "Docker inspect returned an invalid container ID" + ); + let labels = serde_json::from_str(labels).context("parsing Docker inspect labels")?; + Ok(Some((id.to_owned(), labels))) +} + +fn container_has_exact_labels( + docker: &Path, + id: &str, + expected: &BTreeMap, +) -> Result { + let output = Command::new(docker) + .args(["inspect", "--format", "{{json .Config.Labels}}", id]) + .output()?; + if !output.status.success() { + return Ok(false); + } + let actual: BTreeMap = + serde_json::from_slice(&output.stdout).context("parsing Docker container labels")?; + Ok(expected + .iter() + .all(|(name, value)| actual.get(name) == Some(value))) +} + +fn reserve_loopback_port() -> Result { + let listener = + TcpListener::bind((Ipv4Addr::LOCALHOST, 0)).context("reserving loopback runtime port")?; + Ok(listener.local_addr()?.port()) +} + +fn run_docker(docker: &Path, args: &[String]) -> Result { + let output = Command::new(docker) + .args(args) + .output() + .context("running Docker")?; + ensure!( + output.status.success(), + "Docker command failed: {}", + bounded(&output.stderr) + ); + Ok(output) +} + +fn docker_text(docker: &Path, args: &[&str]) -> Result { + let output = Command::new(docker) + .args(args) + .output() + .context("running Docker preflight")?; + ensure!( + output.status.success(), + "Docker preflight failed: {}", + bounded(&output.stderr) + ); + String::from_utf8(output.stdout).context("Docker preflight returned non-UTF8 output") +} + +fn mount_arg(host: &Path, container: &str, read_only: bool) -> Result { + ensure!( + host.is_absolute(), + "managed runtime bind source must be absolute" + ); + let host = host + .to_str() + .context("managed runtime bind source must be UTF-8")?; + ensure!( + !host.contains(',') && !host.chars().any(char::is_control), + "managed runtime bind source contains a Docker mount separator/control character" + ); + ensure!( + container.starts_with('/') + && !container.contains(',') + && !container.chars().any(char::is_control), + "managed runtime bind destination is invalid" + ); + Ok(format!( + "type=bind,src={host},dst={container}{}", + if read_only { ",readonly" } else { "" } + )) +} + +fn safe_relative(path: &Path) -> Result { + ensure!( + !path.as_os_str().is_empty() && !path.is_absolute(), + "archive path must be a non-empty relative path" + ); + let mut result = PathBuf::new(); + for component in path.components() { + match component { + Component::Normal(part) => result.push(part), + _ => bail!("archive path contains a traversal component"), + } + } + Ok(result) +} + +fn safe_component(value: &str) -> String { + let value: String = value + .chars() + .map(|c| { + if c.is_ascii_alphanumeric() || c == '-' || c == '_' { + c + } else { + '-' + } + }) + .collect(); + value.trim_matches('-').chars().take(48).collect::() +} + +fn safe_sidecar_name(value: &str) -> bool { + !value.is_empty() + && value.len() <= 128 + && value + .bytes() + .all(|b| b.is_ascii_alphanumeric() || matches!(b, b'_' | b'-' | b'.')) +} + +fn verify_file(path: &Path, bytes: u64, sha: &str) -> Result<()> { + let metadata = + fs::symlink_metadata(path).with_context(|| format!("stat {}", path.display()))?; + ensure!( + metadata.file_type().is_file() && metadata.len() == bytes, + "managed runtime input {} size/type mismatch", + path.display() + ); + verify_file_sha(path, sha) +} + +fn verify_file_sha(path: &Path, sha: &str) -> Result<()> { + ensure!( + file_sha256(path)? == sha, + "managed runtime input {} SHA-256 mismatch", + path.display() + ); + Ok(()) +} + +fn file_sha256(path: &Path) -> Result { + let mut file = File::open(path).with_context(|| format!("opening {}", path.display()))?; + let mut digest = Sha256::new(); + let mut buffer = vec![0u8; 8 * 1024 * 1024]; + loop { + let read = file.read(&mut buffer)?; + if read == 0 { + break; + } + digest.update(&buffer[..read]); + } + Ok(format!("{:x}", digest.finalize())) +} + +fn sha256_bytes(bytes: &[u8]) -> String { + format!("{:x}", Sha256::digest(bytes)) +} +fn is_lower_sha(value: &str) -> bool { + value.len() == 64 + && value + .bytes() + .all(|b| b.is_ascii_digit() || (b'a'..=b'f').contains(&b)) +} +fn is_hex(value: &str, min: usize, max: usize) -> bool { + (min..=max).contains(&value.len()) && value.bytes().all(|b| b.is_ascii_hexdigit()) +} + +fn random_hex(bytes: usize) -> Result { + let mut value = vec![0u8; bytes]; + getrandom::fill(&mut value).context("generating managed runtime nonce")?; + Ok(value.into_iter().map(|b| format!("{b:02x}")).collect()) +} + +fn private_open(path: &Path) -> Result { + #[cfg(unix)] + { + use std::os::unix::fs::OpenOptionsExt as _; + return OpenOptions::new() + .create(true) + .truncate(false) + .read(true) + .write(true) + .mode(0o600) + .open(path) + .map_err(Into::into); + } + #[cfg(not(unix))] + { + OpenOptions::new() + .create(true) + .truncate(false) + .read(true) + .write(true) + .open(path) + .map_err(Into::into) + } +} + +fn atomic_write_private_json(path: &Path, value: &impl Serialize) -> Result<()> { + atomic_write_private(path, &serde_json::to_vec_pretty(value)?) +} +fn atomic_write_private(path: &Path, bytes: &[u8]) -> Result<()> { + let temporary = path.with_extension(format!("tmp-{}", random_hex(8)?)); + let mut file = private_open(&temporary)?; + file.set_len(0)?; + file.write_all(bytes)?; + file.sync_all()?; + fs::rename(&temporary, path)?; + if let Some(parent) = path.parent() { + File::open(parent)?.sync_all()?; + } + Ok(()) +} + +fn set_private_directory(path: &Path) -> Result<()> { + #[cfg(unix)] + { + use std::os::unix::fs::PermissionsExt as _; + fs::set_permissions(path, fs::Permissions::from_mode(0o700))?; + } + Ok(()) +} + +fn bounded(bytes: &[u8]) -> String { + String::from_utf8_lossy(bytes).chars().take(512).collect() +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn service_profile_is_digest_only_loopback_and_has_no_catalog_command_surface() { + let root = std::env::temp_dir().join(format!("mayhem-wrapper-{}", random_hex(8).unwrap())); + fs::create_dir(&root).unwrap(); + let wrapper_path = write_launch_wrapper(&root, "Qwen/Qwen3.8-Flash-Next", 32123).unwrap(); + let wrapper = fs::read_to_string(&wrapper_path).unwrap(); + assert!(wrapper.contains("if args != expected:")); + assert!(wrapper.contains("replace(\"--host\", \"0.0.0.0\", \"127.0.0.1\")")); + assert!(wrapper.contains("replace(\"--port\", \"8001\", \"32123\")")); + assert!(wrapper.contains("Qwen/Qwen3.8-Flash-Next")); + assert!(wrapper.contains("\"reasoning_effort\":\"xhigh\"")); + assert!(wrapper + .contains("replace(\"--linear-attn-prefill-backend\", \"flashinfer\", \"triton\")")); + assert!(wrapper.contains("args.append(\"--enable-deterministic-inference\")")); + assert!(wrapper.contains("/usr/local/bin/sglang")); + let rejected = Command::new(&wrapper_path) + .args(["serve", "--unexpected"]) + .output() + .unwrap(); + assert!(!rejected.status.success()); + assert!(String::from_utf8_lossy(&rejected.stderr) + .contains("qualified launcher argument vector mismatch")); + assert_eq!( + service_command(), + ["/mayhem/source/configs/pennyroyal/serve-flash-next-frspec.sh"] + ); + assert!(IMAGE.contains("@sha256:")); + let args = service_create_args(ServiceCreateInputs { + name: "mayhem-test", + port: 32123, + model_id: "Qwen/Qwen3.8-Flash-Next", + snapshot: &root, + prepared_model: &root, + source: &root, + plugin: &root, + managed_root: &root, + seccomp: &root.join("seccomp.json"), + wrapper: &root.join("wrapper"), + labels: &BTreeMap::new(), + }) + .unwrap(); + for required in [ + "--restart=no", + "--network=host", + "--ipc=host", + "--memory=104g", + "--memory-swap=104g", + "CARGO_BUILD_JOBS=2", + "SGLANG_TOOL_STRICT_LEVEL=1", + "MAX_TOTAL_TOKENS=824384", + ] { + assert!( + args.iter().any(|argument| argument == required), + "{required}" + ); + } + #[cfg(unix)] + assert!(args + .iter() + .any(|argument| argument == &owned_container_user_arg(&root).unwrap())); + assert_eq!(args.last().unwrap(), &format!("/mayhem/source/{LAUNCHER}")); + fs::remove_dir_all(root).unwrap(); + } + + #[test] + fn deterministic_flash_next_profile_changes_only_the_signed_runtime_controls() { + let source = qualified_launcher_args() + .into_iter() + .map(str::to_owned) + .collect::>(); + let effective = effective_launcher_args("Qwen/Qwen3.8-Flash-Next", 32123); + + assert_eq!( + effective.last().map(String::as_str), + Some("--enable-deterministic-inference") + ); + for (flag, expected) in [ + ("--attention-backend", "triton"), + ("--linear-attn-prefill-backend", "triton"), + ("--linear-attn-decode-backend", "flashinfer"), + ("--mamba-radix-cache-strategy", "extra_buffer"), + ("--hicache-storage-backend", "nixl"), + ] { + let index = effective.iter().position(|value| value == flag).unwrap(); + assert_eq!(effective[index + 1], expected); + } + assert!(!effective + .iter() + .any(|value| value == "--disable-radix-cache")); + + let changed = source + .iter() + .zip(&effective) + .filter(|(before, after)| before != after) + .map(|(before, after)| (before.as_str(), after.as_str())) + .collect::>(); + assert_eq!( + changed, + [ + ("pennyroyal", "Qwen/Qwen3.8-Flash-Next"), + ("0.0.0.0", "127.0.0.1"), + ("8001", "32123"), + ("flashinfer", "triton"), + ( + r#"{"enable_thinking":true,"preserve_thinking":true,"reasoning_effort":"medium"}"#, + r#"{"enable_thinking":true,"preserve_thinking":true,"reasoning_effort":"xhigh"}"#, + ), + ] + ); + assert_eq!(effective.len(), source.len() + 3); + } + + #[test] + fn safe_archive_paths_reject_escape_and_absolute_paths() { + assert!(safe_relative(Path::new("configs/runtime.json")).is_ok()); + assert!(safe_relative(Path::new("../escape")).is_err()); + assert!(safe_relative(Path::new("/absolute")).is_err()); + assert!(safe_relative(Path::new("./dot")).is_err()); + } + + #[test] + fn embedded_seccomp_identity_is_stable() { + assert_eq!(SECCOMP.len(), 16_286); + assert_eq!(sha256_bytes(SECCOMP), SECCOMP_SHA256); + } + + #[test] + fn docker_mount_encoding_rejects_separator_injection() { + assert!(mount_arg(Path::new("/safe/path"), "/mayhem/model", true).is_ok()); + assert!(mount_arg(Path::new("/unsafe,path"), "/mayhem/model", true).is_err()); + assert!(mount_arg(Path::new("relative"), "/mayhem/model", true).is_err()); + } + + #[test] + fn reader_wheel_is_exact_and_installs_without_network_or_dependency_resolution() { + let wheel = RecipeReaderWheel { + sidecar: "ple_plugin_wheel".to_owned(), + filename: PLE_READER_WHEEL_FILENAME.to_owned(), + bytes: PLE_READER_WHEEL_BYTES, + sha256: PLE_READER_WHEEL_SHA256.to_owned(), + python_tag: "cp312".to_owned(), + abi_tag: "cp312".to_owned(), + platform_tag: "linux_x86_64".to_owned(), + }; + validate_reader_wheel(&wheel, "0.2.0+pennyroyal2").unwrap(); + let wheel_container_path = format!("/mayhem/wheel/{}", wheel.filename); + let command = plugin_install_command(&wheel_container_path); + assert!(command.iter().any(|argument| argument == "--offline")); + assert!(command.iter().any(|argument| argument == "--no-cache")); + assert!(command.iter().any(|argument| argument == "--no-deps")); + assert_eq!(command.last().unwrap(), &wheel_container_path); + assert!(wheel_container_path.ends_with(PLE_READER_WHEEL_FILENAME)); + + let mut wrong_platform = wheel; + wrong_platform.platform_tag = "linux_aarch64".to_owned(); + assert!(validate_reader_wheel(&wrong_platform, "0.2.0+pennyroyal2").is_err()); + } + + #[cfg(unix)] + #[test] + fn one_shot_containers_write_as_the_managed_runtime_owner() { + use std::os::unix::fs::MetadataExt as _; + + let root = + std::env::temp_dir().join(format!("mayhem-one-shot-owner-{}", random_hex(8).unwrap())); + fs::create_dir(&root).unwrap(); + let metadata = fs::metadata(&root).unwrap(); + let args = owned_container_identity_args(&root).unwrap(); + assert_eq!( + args[0], + format!("--user={}:{}", metadata.uid(), metadata.gid()) + ); + assert_eq!(args[1], "--env=HOME=/tmp"); + assert_eq!(args[2], "--env=TMPDIR=/tmp"); + fs::remove_dir(&root).unwrap(); + } + + #[test] + fn abandoned_partial_cleanup_is_limited_to_owned_prefixes() { + let root = std::env::temp_dir().join(format!("mayhem-partials-{}", random_hex(8).unwrap())); + fs::create_dir(&root).unwrap(); + for name in [ + ".source-abcd.partial-1", + ".plugin.partial-2", + ".ple.partial-3", + "operator-data", + ] { + fs::create_dir(root.join(name)).unwrap(); + } + cleanup_abandoned_partial_directories(&root).unwrap(); + assert!(!root.join(".source-abcd.partial-1").exists()); + assert!(!root.join(".plugin.partial-2").exists()); + assert!(!root.join(".ple.partial-3").exists()); + assert!(root.join("operator-data").is_dir()); + fs::remove_dir_all(root).unwrap(); + } +} diff --git a/crates/mayhem-cli/src/provider_failure_recovery.rs b/crates/mayhem-cli/src/provider_failure_recovery.rs index f7b867cd..d31ff1dc 100644 --- a/crates/mayhem-cli/src/provider_failure_recovery.rs +++ b/crates/mayhem-cli/src/provider_failure_recovery.rs @@ -9,6 +9,7 @@ use mayhem_proto::{ #[derive(Debug)] pub(super) struct AttemptGuard { _lock: fs::File, + path: PathBuf, } fn directory(settlement: &ProviderReceiptSettlement) -> PathBuf { @@ -55,13 +56,41 @@ pub(super) fn begin( let Some(settlement) = active.receipt_settlement.as_ref() else { return Ok(None); }; + let binding = json!({ + "billing_epoch": active.billing_epoch, "reservation_id": active.reservation_id, + "reservation_expires_after_epoch": active.reservation_expires_after_epoch, + "reservation_receipt_grace_epochs": active.reservation_receipt_grace_epochs, + "billing_id": active.billing_id, "billing_attempt": active.billing_attempt, + "session_id": active.session_id, "user": active.user_pubkey, "rail": active.rail, + "provider": terms.provider, "payout_revision": active.payout_revision, + "model_id": terms.model_id, "enclave_id": terms.enclave_id, + }); + begin_binding(settlement, &binding).map(Some) +} + +pub(super) fn begin_binding( + settlement: &ProviderReceiptSettlement, + binding: &Value, +) -> Result { let root = directory(settlement); ensure_private_directory(&root, "provider reservation recovery")?; + let reservation_id = binding["reservation_id"] + .as_str() + .context("reservation recovery binding has no identity")?; ensure!( - is_hex_len(&active.reservation_id, 64), + is_hex_len(reservation_id, 64) && reservation_binding_matches(binding, binding), "invalid reservation identity" ); - let path = root.join(format!("{}.json", active.reservation_id)); + ensure!( + binding["model_id"] + .as_str() + .is_some_and(|model| !model.is_empty()) + && binding["enclave_id"] + .as_str() + .is_some_and(|enclave| is_hex_len(enclave, 64)), + "reservation recovery binding is missing model evidence" + ); + let path = root.join(format!("{reservation_id}.json")); ensure!( path.exists() || journal_paths(&root)?.len() < RECEIPT_SETTLEMENT_OUTBOX_MAX_ENTRIES, "provider reservation recovery is full; refusing new compute until recovery progresses" @@ -70,21 +99,24 @@ pub(super) fn begin( fs2::FileExt::try_lock_exclusive(&held) .context("reservation is already executing or recovering")?; // No prompts, credential material, or card/account funding data. - write_private_json_once( - &path, - &json!({ - "schema_version": 1, "binding": { - "billing_epoch": active.billing_epoch, "reservation_id": active.reservation_id, - "reservation_expires_after_epoch": active.reservation_expires_after_epoch, - "reservation_receipt_grace_epochs": active.reservation_receipt_grace_epochs, - "billing_id": active.billing_id, "billing_attempt": active.billing_attempt, - "session_id": active.session_id, "user": active.user_pubkey, "rail": active.rail, - "provider": terms.provider, "payout_revision": active.payout_revision, - "model_id": terms.model_id, "enclave_id": terms.enclave_id, - }, - }), - )?; - Ok(Some(AttemptGuard { _lock: held })) + write_private_json_once(&path, &json!({"schema_version": 1, "binding": binding}))?; + Ok(AttemptGuard { _lock: held, path }) +} + +pub(super) fn discard(guard: AttemptGuard) -> Result<()> { + let path = guard.path.clone(); + if path.exists() { + fs::remove_file(&path)?; + sync_receipt_settlement_directory(path.parent().context("recovery parent missing")?)?; + } + let lock_path = path.with_extension("lock"); + drop(guard); + match fs::remove_file(lock_path) { + Ok(()) => {} + Err(error) if error.kind() == std::io::ErrorKind::NotFound => {} + Err(error) => return Err(error.into()), + } + Ok(()) } async fn confirmed(rpc: &PeerRpcClient, key: &str) -> Result { @@ -126,6 +158,29 @@ pub(super) async fn recover_one( "reservation journal filename mismatch" ); let reservation = confirmed(rpc, &format!("receipt/reservation/{id}")).await?; + if reservation.is_null() { + // The provider can observe the confirmed envelope before this key is + // visible in its local canonical view. That is pending propagation, + // not evidence that a different reservation owns this identity. + let state = confirmed(rpc, "epoch/apply/state").await?; + let updated_epoch = state["updated_epoch"] + .as_u64() + .context("reservation recovery epoch state is invalid")?; + let abandon_after = binding["reservation_expires_after_epoch"] + .as_u64() + .context("reservation recovery expiry is invalid")? + .saturating_add( + binding["reservation_receipt_grace_epochs"] + .as_u64() + .context("reservation recovery grace is invalid")?, + ); + if updated_epoch > abandon_after { + fs::remove_file(path)?; + sync_receipt_settlement_directory(path.parent().context("recovery parent missing")?)?; + return Ok(true); + } + return Ok(false); + } ensure!( reservation["type"] == "receipt_reservation_identity" && reservation_binding_matches(binding, &reservation), diff --git a/crates/mayhem-cli/src/provider_output_stream.rs b/crates/mayhem-cli/src/provider_output_stream.rs index 87263d2c..a879307e 100644 --- a/crates/mayhem-cli/src/provider_output_stream.rs +++ b/crates/mayhem-cli/src/provider_output_stream.rs @@ -1,7 +1,7 @@ //! Incremental presentation of engine output. Prompt construction, token IDs and //! the authoritative final tool parser retain the same tool identities. -use super::{ProviderEngineToolStrategy, ToolSpec, provider_qwen_xml_parameter_value}; -use serde_json::{Value, json}; +use super::{provider_qwen_xml_parameter_value, ProviderEngineToolStrategy, ToolSpec}; +use serde_json::{json, Value}; #[derive(Default, Debug)] pub(super) struct Delta { @@ -59,6 +59,7 @@ impl OutputStream { } if !self.in_tools { let marker = match self.strategy { + ProviderEngineToolStrategy::OpenAiToolCalls => "{\"tool_calls\"", ProviderEngineToolStrategy::QwenFunctionXml => "", ProviderEngineToolStrategy::GemmaFunctionCall => "<|tool_call>call:", _ => "", @@ -127,6 +128,10 @@ impl OutputStream { pub fn finish_text(&mut self, has_tools: bool) -> String { let tail = if has_tools { String::new() + } else if self.in_tools && self.strategy == ProviderEngineToolStrategy::OpenAiToolCalls { + // Once a canonical native tool envelope begins, never expose a + // malformed or truncated remainder as assistant text. + String::new() } else if self.in_tools && self.emitted.is_empty() { std::mem::take(&mut self.tool_text) } else { @@ -469,8 +474,8 @@ mod tests { #[test] fn constrained_json_streams_as_tools_or_answer_without_false_reasoning() { use crate::{ - ProviderReasoningOutputFilter, ProviderReasoningOutputMode, - provider_constrained_reasoning_output_mode, + provider_constrained_reasoning_output_mode, ProviderReasoningOutputFilter, + ProviderReasoningOutputMode, }; let mode = provider_constrained_reasoning_output_mode( ProviderReasoningOutputMode::StripPrefilled, @@ -605,6 +610,55 @@ mod tests { ); } + #[test] + fn openai_tool_envelope_after_commentary_streams_without_leaking_json() { + let raw = concat!( + "Plan saved. Now writing the files.\n\n", + r#"{"tool_calls":[{"id":"native","type":"function","function":{"name":"write","arguments":"{\"path\":\"src/app.js\",\"content\":\"hello\"}"}}]}"#, + ); + let expected = provider_engine_tool_call_outputs( + raw, + ProviderEngineToolStrategy::OpenAiToolCalls, + &tools(), + ) + .expect("tool call after commentary"); + let mut stream = OutputStream::new(ProviderEngineToolStrategy::OpenAiToolCalls, tools()); + let mut visible = String::new(); + let mut calls = Vec::new(); + let mut streamed_before_end = false; + for (index, ch) in raw.char_indices() { + let delta = stream.push(&ch.to_string()); + visible.push_str(&delta.text); + streamed_before_end |= !delta.tools.is_empty() && index < raw.len() - 5; + collect(delta, &mut calls); + } + visible.push_str(&stream.finish_text(true)); + assert_eq!(visible, "Plan saved. Now writing the files.\n\n"); + assert!(streamed_before_end); + compare(&calls, &expected); + } + + #[test] + fn malformed_openai_tool_envelope_after_commentary_fails_closed() { + let raw = concat!( + "I will write it now.\n\n", + r#"{"tool_calls":[{"function":{"name":"write","arguments":"{\"path\":""#, + ); + let mut stream = OutputStream::new(ProviderEngineToolStrategy::OpenAiToolCalls, tools()); + let mut visible = String::new(); + for ch in raw.chars() { + visible.push_str(&stream.push(&ch.to_string()).text); + } + visible.push_str(&stream.finish_text(false)); + assert_eq!(visible, "I will write it now.\n\n"); + assert!(provider_engine_tool_call_outputs( + raw, + ProviderEngineToolStrategy::OpenAiToolCalls, + &tools(), + ) + .is_none()); + } + #[test] fn advertising_tools_does_not_buffer_plain_text_or_ordinary_json() { for strategy in [ @@ -632,15 +686,20 @@ mod tests { #[test] fn schema_invalid_nonstrict_edit_keeps_reasoning_and_streamed_arguments_for_correction() { use crate::{ProviderReasoningOutputFilter, ProviderReasoningOutputMode}; - let tools = vec![ToolSpec::new("edit_file", json!({ - "type":"object", "additionalProperties":false, - "properties":{"path":{"type":"string"}, "old_text":{"type":"string", "minLength":1}, - "new_text":{"type":"string"}}, "required":["path","old_text","new_text"] - }))]; + let tools = vec![ToolSpec::new( + "edit_file", + json!({ + "type":"object", "additionalProperties":false, + "properties":{"path":{"type":"string"}, "old_text":{"type":"string", "minLength":1}, + "new_text":{"type":"string"}}, "required":["path","old_text","new_text"] + }), + )]; let raw = "app.jsprivate replacement"; let generated = format!("Reasoning before the edit. {raw}"); - let mut reasoning = ProviderReasoningOutputFilter::new(ProviderReasoningOutputMode::StripPrefilled); - let mut stream = OutputStream::new(ProviderEngineToolStrategy::QwenFunctionXml, tools.clone()); + let mut reasoning = + ProviderReasoningOutputFilter::new(ProviderReasoningOutputMode::StripPrefilled); + let mut stream = + OutputStream::new(ProviderEngineToolStrategy::QwenFunctionXml, tools.clone()); let mut calls = Vec::new(); let mut hidden = String::new(); let mut delivered_before_end = false; @@ -653,10 +712,16 @@ mod tests { } assert!(hidden.contains("Reasoning before the edit.")); assert!(delivered_before_end); - let expected = provider_engine_tool_call_outputs(raw, ProviderEngineToolStrategy::QwenFunctionXml, &tools).unwrap(); + let expected = provider_engine_tool_call_outputs( + raw, + ProviderEngineToolStrategy::QwenFunctionXml, + &tools, + ) + .unwrap(); compare(&calls, &expected); validate_provider_engine_tool_call_outputs(&expected, &tools).unwrap(); - let arguments: Value = serde_json::from_str(expected[0]["arguments"].as_str().unwrap()).unwrap(); + let arguments: Value = + serde_json::from_str(expected[0]["arguments"].as_str().unwrap()).unwrap(); assert_eq!(arguments["old_text"], ""); assert_eq!(arguments["new_text"], "private replacement"); assert!(mayhem_engine::validate_tool_call_arguments(&tools[0], &arguments).is_err()); diff --git a/crates/mayhem-cli/src/python_runtime.rs b/crates/mayhem-cli/src/python_runtime.rs index a65cb8e5..79ea31a1 100644 --- a/crates/mayhem-cli/src/python_runtime.rs +++ b/crates/mayhem-cli/src/python_runtime.rs @@ -26,6 +26,7 @@ const MLX_REQUIREMENTS: &[u8] = include_bytes!("../resources/python/mlx.txt"); const LLAMA_MEDIA_REQUIREMENTS: &[u8] = include_bytes!("../resources/python/llama-media.txt"); const TRANSFORMERS_ASR_REQUIREMENTS: &[u8] = include_bytes!("../resources/python/transformers-asr.txt"); +const LAYA_REQUIREMENTS: &[u8] = include_bytes!("../resources/python/laya.txt"); const CHATTERBOX_CPU_PROJECT: &[u8] = include_bytes!("../resources/python/chatterbox-runtime-cpu/pyproject.toml"); const CHATTERBOX_CPU_LOCK: &[u8] = @@ -165,16 +166,26 @@ pub(crate) fn ensure_vllm_python( }?; let backup = home.join("runtime-patches").join("vllm-prefix-v1"); fs::create_dir_all(&backup)?; - let lock = OpenOptions::new().create(true).truncate(false).write(true) + let lock = OpenOptions::new() + .create(true) + .truncate(false) + .write(true) .open(backup.join("install.lock"))?; lock.lock_exclusive()?; let output = Command::new(&runtime.python) - .arg("-B").arg("-c") + .arg("-B") + .arg("-c") .arg(include_str!("../../../scripts/vllm-prefix-runtime.py")) - .arg("--backup").arg(&backup).arg("--apply") - .output().context("applying mandatory vLLM prefix runtime correction")?; - ensure!(output.status.success(), "vLLM prefix runtime correction failed: {}", - String::from_utf8_lossy(&output.stderr)); + .arg("--backup") + .arg(&backup) + .arg("--apply") + .output() + .context("applying mandatory vLLM prefix runtime correction")?; + ensure!( + output.status.success(), + "vLLM prefix runtime correction failed: {}", + String::from_utf8_lossy(&output.stderr) + ); Ok(runtime) } @@ -378,7 +389,9 @@ pub(crate) fn ensure_backend_python(home: &Path, backend: &str) -> Result Option { min_free_bytes: 8 * GIB, embedded_module: None, }), + "laya" => Some(PythonRuntimeSpec { + backend: "laya", + override_env: "MAYHEM_LAYA_PYTHON", + distribution: "laya", + required_imports: &[ + "laya", + "transformers", + "torch", + "tokenizers", + "safetensors", + "huggingface_hub", + "numpy", + ], + version: "0.3.5", + requirements: LAYA_REQUIREMENTS, + requirements_sha256: "b146de268c7caf04ee58e7c70bde58e7876ee9b280a8de63cf79c68fd06bab14", + extra_index_urls: &["https://download.pytorch.org/whl/cu130"], + min_free_bytes: 8 * GIB, + embedded_module: None, + }), "sulphur" => Some(PythonRuntimeSpec { backend: "sulphur", override_env: "MAYHEM_SULPHUR_PYTHON", @@ -4789,8 +4822,20 @@ mod tests { #[test] fn version_bound_runtime_requires_managed_python_without_changing_baseline() { - assert_eq!(managed_venv_args(false), vec!["venv", "--python", MANAGED_PYTHON_VERSION, "--seed"]); - assert_eq!(managed_venv_args(true), vec!["venv", "--python", MANAGED_PYTHON_VERSION, "--seed", "--managed-python"]); + assert_eq!( + managed_venv_args(false), + vec!["venv", "--python", MANAGED_PYTHON_VERSION, "--seed"] + ); + assert_eq!( + managed_venv_args(true), + vec![ + "venv", + "--python", + MANAGED_PYTHON_VERSION, + "--seed", + "--managed-python" + ] + ); } #[test] @@ -4820,7 +4865,8 @@ mod tests { .output() .unwrap(); assert!(!result.status.success()); - assert!(String::from_utf8_lossy(&result.stderr).contains("requires CPython development headers")); + assert!(String::from_utf8_lossy(&result.stderr) + .contains("requires CPython development headers")); let base = python_runtime_spec("vllm").unwrap(); let script = python_validation_script(&base).unwrap(); diff --git a/crates/mayhem-engine/Cargo.toml b/crates/mayhem-engine/Cargo.toml index be4a4490..f8a10745 100644 --- a/crates/mayhem-engine/Cargo.toml +++ b/crates/mayhem-engine/Cargo.toml @@ -24,6 +24,7 @@ mlx = [] needle = ["dep:mayhem-enclave"] trt-llm = [] transformers-asr = [] +laya = [] vllm = [] [dependencies] @@ -32,15 +33,18 @@ blake3 = "1" encoding_rs = { version = "0.8", optional = true } flate2 = { version = "1", optional = true } jsonschema = { version = "0.53.0", default-features = false } +futures-util = "0.3" llama-cpp-2 = { version = "0.1.150", optional = true, default-features = false, features = ["common", "sampler"] } mayhem-enclave = { path = "../mayhem-enclave", optional = true } mayhem-proto = { path = "../mayhem-proto" } serde = { version = "1", features = ["derive"] } serde_json = "1" sha2 = "0.10" +reqwest = { version = "0.13", default-features = false, features = ["json", "stream"] } tar = { version = "0.4", optional = true } tempfile = "3.27" thiserror = "2" +tokio = { version = "1", features = ["macros", "net", "rt", "sync", "time"] } wait-timeout = { version = "0.2", optional = true } [target.'cfg(windows)'.dependencies] diff --git a/crates/mayhem-engine/src/comfyui_backend.rs b/crates/mayhem-engine/src/comfyui_backend.rs index af626063..a0f92d87 100644 --- a/crates/mayhem-engine/src/comfyui_backend.rs +++ b/crates/mayhem-engine/src/comfyui_backend.rs @@ -32,6 +32,7 @@ const WORKER_STDIN_BOOTSTRAP: &str = concat!( const WORKER_PROTOCOL_PREFIX: &str = "__mayhem_comfyui_worker_v1__"; const PYTHON_ENV: &str = "MAYHEM_COMFYUI_PYTHON"; const DEVICE_ENV: &str = "MAYHEM_COMFYUI_DEVICE"; +const VRAM_RESERVE_GB_ENV: &str = "MAYHEM_COMFYUI_RESERVE_VRAM_GB"; const ARTIFACT_CHUNK_BYTES: usize = 256 * 1024; const WORKER_STDERR_TAIL_BYTES: usize = 64 * 1024; const MAX_WORKER_REQUEST_LINE_BYTES: usize = 64 * 1024 * 1024; @@ -83,6 +84,12 @@ struct WorkerWorkflowResult { progress_events: Vec, } +#[derive(Debug, Deserialize)] +#[serde(deny_unknown_fields)] +struct WorkerMemoryReclaimResult { + requested: bool, +} + #[derive(Debug, Deserialize)] #[serde(deny_unknown_fields)] struct WorkerArtifact { @@ -144,6 +151,10 @@ impl EngineBackend for ComfyUiBackend { .map(PathBuf::from) .unwrap_or_else(|| PathBuf::from("python3")); let device = env::var(DEVICE_ENV).unwrap_or_else(|_| default_comfyui_device().to_owned()); + let vram_reserve_gb = env::var(VRAM_RESERVE_GB_ENV) + .ok() + .map(|value| parse_comfyui_vram_reserve_gb(&value)) + .transpose()?; let socket_dir = short_socket_dir(); let custom_node_whitelist = config .comfyui_custom_nodes @@ -168,6 +179,7 @@ impl EngineBackend for ComfyUiBackend { "base_dir": base_dir, "socket_path": socket_path, "device": device, + "vram_reserve_gb": vram_reserve_gb, "custom_node_whitelist": custom_node_whitelist, "model_path_aliases": model_path_aliases, }), @@ -188,6 +200,7 @@ impl EngineBackend for ComfyUiBackend { "object_info_classes": response.object_info_classes, "node_classes_hash": sha256_json(&response.node_classes)?, "device": device, + "vram_reserve_gb": vram_reserve_gb, }); self.loaded = Some(LoadedComfyUi { evidence }); self.worker = Some(worker); @@ -211,6 +224,15 @@ impl EngineBackend for ComfyUiBackend { .unwrap_or_default() } + fn reclaim_idle_memory(&mut self) -> Result { + let id = Self::next_request_id(); + self.worker()?.send(id, "reclaim_memory", Value::Null)?; + let response: WorkerMemoryReclaimResult = + self.worker()? + .wait_response(id, LOAD_TIMEOUT, &CancellationToken::new())?; + Ok(response.requested) + } + fn tokenize(&self, _text: &str) -> Result { Err(EngineError::InvalidConfig( "ComfyUI backend does not tokenize text".to_owned(), @@ -1207,6 +1229,20 @@ fn default_comfyui_device() -> &'static str { "auto" } +fn parse_comfyui_vram_reserve_gb(value: &str) -> Result { + let reserve = value.parse::().map_err(|_| { + EngineError::ComfyUi(format!( + "{VRAM_RESERVE_GB_ENV} must be a finite number between 0 and 1024" + )) + })?; + if !reserve.is_finite() || !(0.0..=1024.0).contains(&reserve) { + return Err(EngineError::ComfyUi(format!( + "{VRAM_RESERVE_GB_ENV} must be a finite number between 0 and 1024" + ))); + } + Ok(reserve) +} + #[cfg(test)] mod tests { use super::*; @@ -1272,6 +1308,14 @@ mod tests { assert_eq!(default_comfyui_device(), "auto"); } + #[test] + fn comfyui_vram_reserve_requires_a_bounded_finite_number() { + assert_eq!(parse_comfyui_vram_reserve_gb("12").unwrap(), 12.0); + for invalid in ["", "-1", "nan", "inf", "1025"] { + assert!(parse_comfyui_vram_reserve_gb(invalid).is_err(), "{invalid}"); + } + } + #[test] fn model_paths_use_native_separators_only_at_execution() { let files = vec![super::super::ComfyUiModelFile { diff --git a/crates/mayhem-engine/src/comfyui_worker.py b/crates/mayhem-engine/src/comfyui_worker.py index 63b2bccc..40d69dd1 100644 --- a/crates/mayhem-engine/src/comfyui_worker.py +++ b/crates/mayhem-engine/src/comfyui_worker.py @@ -3,9 +3,9 @@ import contextlib import errno import json +import math import os import sys -import tempfile import time import traceback import uuid @@ -228,7 +228,11 @@ def input_transfer_lock(): @contextlib.contextmanager def materialized_input_files(payload): with input_transfer_lock(): - with staged_input_files(payload): + if payload.get("input_files"): + with staged_input_files(payload): + yield + else: + recover_input_transfers() yield @@ -237,7 +241,17 @@ def staged_input_files(payload): # Cancellation can terminate this worker, bypassing finally. Recover its # journals before another graph can read any input left by that request. recover_input_transfers() - backup_root = Path(tempfile.mkdtemp(prefix="request-", dir=input_transfer_root())) + # tempfile.mkdtemp uses mode 0o700. Python 3.12 maps that mode to a + # restrictive Windows DACL, so an AppContainer worker can create the + # journal and then lose DELETE access to it. A normal mkdir inherits the + # writable-tree ACL installed by the sandbox. + while True: + backup_root = input_transfer_root() / f"request-{uuid.uuid4().hex}" + try: + backup_root.mkdir() + break + except FileExistsError: + continue written = [] seen = set() try: @@ -397,6 +411,19 @@ def load(payload): base_dir = Path(comfy_path(Path(payload["base_dir"]).resolve())) socket_path = Path(comfy_path(Path(payload["socket_path"]).resolve())) device = payload.get("device", "auto") + vram_reserve_gb = payload.get("vram_reserve_gb") + if vram_reserve_gb is not None: + if isinstance(vram_reserve_gb, bool) or not isinstance( + vram_reserve_gb, (int, float) + ): + raise ValueError( + "ComfyUI vram_reserve_gb must be a finite number between 0 and 1024" + ) + vram_reserve_gb = float(vram_reserve_gb) + if not math.isfinite(vram_reserve_gb) or not 0 <= vram_reserve_gb <= 1024: + raise ValueError( + "ComfyUI vram_reserve_gb must be a finite number between 0 and 1024" + ) custom_node_whitelist = payload.get("custom_node_whitelist", []) aliases = payload.get("model_path_aliases", {}) if not isinstance(aliases, dict) or any( @@ -426,6 +453,8 @@ def load(payload): argv.extend(str(name) for name in custom_node_whitelist) if device == "cpu": argv.append("--cpu") + if vram_reserve_gb is not None: + argv.extend(("--reserve-vram", format(vram_reserve_gb, "g"))) sys.argv = argv os.chdir(comfy_path(runtime_root)) @@ -590,6 +619,17 @@ async def shutdown(): await runner.cleanup() +async def reclaim_memory(): + if prompt_server is None: + return {"requested": False} + # This is the same supported path as ComfyUI's /free endpoint. The prompt + # worker owns its executor caches, so flags let it reset those caches and + # unload model weights on the correct thread after the queue is idle. + prompt_server.prompt_queue.set_flag("unload_models", True) + prompt_server.prompt_queue.set_flag("free_memory", True) + return {"requested": True} + + def dispatch(message): op = message.get("op") payload = message.get("payload") @@ -597,6 +637,8 @@ def dispatch(message): return load(payload) if op == "run_workflow": return loop.run_until_complete(run_workflow(payload)) + if op == "reclaim_memory": + return loop.run_until_complete(reclaim_memory()) if op == "shutdown": loop.run_until_complete(shutdown()) return {"shutdown": True} diff --git a/crates/mayhem-engine/src/laya_worker.py b/crates/mayhem-engine/src/laya_worker.py new file mode 100644 index 00000000..afb24bb3 --- /dev/null +++ b/crates/mayhem-engine/src/laya_worker.py @@ -0,0 +1,380 @@ +import contextlib +import json +import math +import os +import sys + +import numpy as np +import torch + + +router = None + + +def emit(request_id, *, ok=False, result=None, error=None): + message = {"id": request_id, "ok": ok} + if result is not None: + message["result"] = result + if error is not None: + message["error"] = error + print(json.dumps(message, ensure_ascii=False, allow_nan=False), flush=True) + + +def require_cuda(): + if not torch.cuda.is_available(): + raise RuntimeError("Laya production backend requires CUDA") + + +def validate_resident_cuda(): + for name, agent in router._agents.items(): + if getattr(agent, "device", None) is None or agent.device.type != "cuda": + raise RuntimeError("Laya checkpoint %s is not resident on CUDA" % name) + + +def load_model(payload): + global router + root = os.path.realpath(payload["path"]) + if not os.path.isdir(root): + raise RuntimeError("Laya artifact path must be a local snapshot directory") + required = [ + os.path.join(root, "model.safetensors"), + os.path.join(root, "multilingual", "model.safetensors"), + os.path.join(root, "typed-decisions", "model.safetensors"), + ] + missing = [path for path in required if not os.path.isfile(path)] + if missing: + raise RuntimeError("Laya snapshot is incomplete: " + ", ".join(missing)) + + require_cuda() + from laya import Router + + models = { + "english": (root, None), + "multilingual": (root, "multilingual"), + "typed-decisions": (root, "typed-decisions"), + } + with contextlib.redirect_stdout(sys.stderr): + router = Router( + models=models, + device="cuda", + max_loaded=3, + auto_task_detection=False, + preload=True, + ) + validate_resident_cuda() + vocab = max(int(getattr(agent.tok, "vocab_size", 0)) for agent in router._agents.values()) + return { + "device": "cuda", + "n_ctx_train": 1024, + "n_vocab": vocab, + "loaded": list(router.loaded), + } + + +def validate_finite(value): + if isinstance(value, float) and not math.isfinite(value): + raise RuntimeError("Laya returned a non-finite number") + if isinstance(value, dict): + for child in value.values(): + validate_finite(child) + elif isinstance(value, list): + for child in value: + validate_finite(child) + + +def preprocess_state(state, email): + if email is None: + return state, None + from laya import clean_email_body + + if not isinstance(state, dict) or not isinstance(state.get("body"), str): + raise RuntimeError("email preprocessing requires state.body to be a string") + clean = bool(email.get("clean", True)) + max_chars = int(email.get("max_chars", 3000)) + updated = dict(state) + original = updated["body"] + updated["body"] = clean_email_body(original, max_chars=max_chars) if clean else original[:max_chars] + return updated, { + "email": True, + "clean": clean, + "max_chars": max_chars, + "input_body_chars": len(original), + "processed_body_chars": len(updated["body"]), + } + + +def per_request_system_one(agent, state, questions, temperatures, limits): + if temperatures is None and limits is None: + return agent.system_one(state, questions) + + from laya.common import ( + QTYPES, + QTYPE_NAMES, + build_sequence, + collate_items, + confidence_from_probs, + render_options, + temp_bucket, + ) + + ids = list(questions.keys()) + items = [] + temperatures = temperatures or {} + limits = limits or {} + config = getattr(agent, "cfg", None) or {} + max_len = int(limits.get("max_len", config.get("max_len", 512))) + head_max_len = int( + limits.get("head_max_len", config.get("head_max_len", 192)) + ) + for qid in ids: + question = agent._to_internal(questions[qid]) + sequence, markers = build_sequence(agent.tok, state, question, max_len, head_max_len) + if len(markers) != len(render_options(question)): + raise RuntimeError("question %r options exceed head_max_len=%d" % (qid, head_max_len)) + items.append({"ids": sequence, "markers": markers, "qtype": QTYPES[question["t"]]}) + + batch = collate_items([items], agent.tok.pad_token_id) + use_amp = agent.device.type == "cuda" + with torch.no_grad(), torch.autocast( + device_type=agent.device.type, dtype=agent.dtype, enabled=use_amp + ): + logits, action = agent.model( + batch["input_ids"].to(agent.device), + batch["attention_mask"].to(agent.device), + batch["marker_pos"].to(agent.device), + batch["marker_mask"].to(agent.device), + batch["qtype"].to(agent.device), + ) + logits = logits.float().cpu().numpy() + action = torch.softmax(action.float(), -1).cpu().numpy() + answers = {} + for row, qid in enumerate(ids): + question = agent._to_internal(questions[qid]) + count = len(items[row]["markers"]) + kind = QTYPES[question["t"]] + bucket = temp_bucket(kind, count) + default = agent.temperature_by_options.get(bucket, agent.temperature[kind]) + scale = float(temperatures.get(bucket, temperatures.get(QTYPE_NAMES[kind], default))) + values = logits[row, :count] / scale + probabilities = np.exp(values - values.max()) + probabilities = probabilities / probabilities.sum() + confidence = round(confidence_from_probs(probabilities, count), 4) + action_result = {"act_probability": round(float(action[row, 0]), 4)} + if question["t"] == "choice": + keys = list(question["crit"].keys()) + answers[qid] = { + "type": "choice", + "choice": keys[int(probabilities.argmax())], + "probabilities": { + key: round(float(value), 4) for key, value in zip(keys, probabilities) + }, + "confidence": confidence, + "action": action_result, + } + elif question["t"] == "score": + answers[qid] = { + "type": "score", + "score": round(float((np.arange(count) * probabilities).sum()), 4), + "legend": {str(index): criterion for index, criterion in enumerate(question["crit"])}, + "probabilities": { + str(index): round(float(value), 4) + for index, value in enumerate(probabilities) + }, + "confidence": confidence, + "action": action_result, + } + else: + truth = float(probabilities[1]) + answers[qid] = { + "type": "noul", + "noul": round(truth, 4), + "confidence": round(max(truth, 1.0 - truth), 4), + "action": action_result, + } + return { + "model": "laya-rl-agent", + "answers": answers, + "usage": { + "input_tokens": int(batch["attention_mask"].sum()), + "output_tokens": 0, + }, + } + + +def counted_embed_fn(agent, max_length, batch_size): + counter = {"input_tokens": 0} + + def embed(texts): + rows = ["" if text is None else str(text) for text in texts] + parts = [] + for start in range(0, len(rows), batch_size): + encoded = agent.tok( + rows[start : start + batch_size], + padding=True, + truncation=True, + max_length=max_length, + return_tensors="pt", + ) + input_ids = encoded["input_ids"].to(agent.device) + attention_mask = encoded["attention_mask"].to(agent.device) + counter["input_tokens"] += int(attention_mask.sum()) + with torch.inference_mode(): + hidden = agent.model.encoder( + input_ids=input_ids, attention_mask=attention_mask + ).last_hidden_state + mask = attention_mask.unsqueeze(-1).to(dtype=hidden.dtype) + pooled = (hidden * mask).sum(dim=1) / mask.sum(dim=1).clamp(min=1.0) + parts.append(pooled.float().cpu().numpy()) + return np.concatenate(parts, axis=0) + + return embed, counter + + +def supplied_vector_embed_fn(questions, supplied, k): + matrices = [] + for qid, question in questions.items(): + if question.get("type") != "choice": + continue + criteria = question["criteria"] + labels = list(criteria.keys()) if isinstance(criteria, dict) else list(range(len(criteria))) + if len(labels) <= k: + continue + entry = supplied[qid] + options = entry["options"] + option_vectors = ( + [options[label] for label in labels] + if isinstance(criteria, dict) + else options + ) + matrices.append(np.asarray([entry["query"], *option_vectors], dtype=np.float32)) + cursor = {"index": 0} + + def embed(texts): + index = cursor["index"] + if index >= len(matrices): + raise RuntimeError("shortlist supplied-vector calls exceeded validated questions") + matrix = matrices[index] + cursor["index"] += 1 + if matrix.ndim != 2 or matrix.shape[0] != len(texts): + raise RuntimeError("shortlist supplied vectors do not match the requested rows") + return matrix + + return embed, cursor, len(matrices) + + +class PerRequestPredictor: + def __init__(self, agent, temperatures, limits): + self.agent = agent + self.temperatures = temperatures + self.limits = limits + + def predict(self, state, questions): + return per_request_system_one( + self.agent, state, questions, self.temperatures, self.limits + ) + + +def decide(payload): + if router is None: + raise RuntimeError("Laya model is not loaded") + validate_resident_cuda() + state, preprocessing = preprocess_state(payload["state"], payload.get("email")) + questions = payload["questions"] + checkpoint = payload.get("checkpoint") + task = payload.get("task") + workflow = None + if payload.get("auto_task_detection", False) and checkpoint is None and task is None: + from laya.router import match_typed_decisions_workflow + + workflow = match_typed_decisions_workflow(questions) + if workflow is not None: + task = "typed_decisions" + with contextlib.redirect_stdout(sys.stderr): + route = router.route( + state, + questions, + model=checkpoint, + task=task, + lang=payload.get("lang"), + ) + if workflow is not None: + route["reason"] = ( + "question ids match the %r typed-decisions workflow" % workflow + ) + route["workflow"] = workflow + route.pop("repo", None) + agent = router.load(route["model"]) + shortlist = payload.get("shortlist") + if shortlist is not None: + from laya import predict_shortlist + + supplied = shortlist.get("vectors") + k = int(shortlist.get("k", 20)) + if supplied is None: + embed, counter = counted_embed_fn( + agent, + int(shortlist.get("max_length", 512)), + int(shortlist.get("batch_size", 32)), + ) + expected_calls = None + else: + embed, cursor, expected_calls = supplied_vector_embed_fn( + questions, supplied, k + ) + counter = {"input_tokens": 0} + result = predict_shortlist( + PerRequestPredictor( + agent, payload.get("temperature"), payload.get("limits") + ), + state, + questions, + embed, + k=k, + ) + if expected_calls is not None and cursor["index"] != expected_calls: + raise RuntimeError("shortlist did not consume every supplied vector set") + result["usage"]["shortlist_input_tokens"] = counter["input_tokens"] + result["usage"]["input_tokens"] += counter["input_tokens"] + for metadata in result["shortlist"].values(): + metadata["embedding_source"] = ( + "supplied_vectors" if supplied is not None else "local_encoder" + ) if not metadata["passthrough"] else "passthrough" + else: + result = per_request_system_one( + agent, + state, + questions, + payload.get("temperature"), + payload.get("limits"), + ) + result["routing"] = dict(route) + if preprocessing is not None: + result["preprocessing"] = preprocessing + validate_resident_cuda() + validate_finite(result) + return result + + +def main(): + for line in sys.stdin: + request_id = 0 + try: + message = json.loads(line) + request_id = int(message.get("id", 0)) + operation = message.get("op") + payload = message.get("payload") or {} + if operation == "shutdown": + emit(request_id, ok=True, result={}) + return + if operation == "load": + emit(request_id, ok=True, result=load_model(payload)) + elif operation == "decide": + emit(request_id, ok=True, result=decide(payload)) + else: + raise RuntimeError("unsupported Laya worker operation: %r" % operation) + except Exception as error: + emit(request_id, error=str(error)) + + +if __name__ == "__main__": + main() diff --git a/crates/mayhem-engine/src/lib.rs b/crates/mayhem-engine/src/lib.rs index c01fda2e..90b171dd 100644 --- a/crates/mayhem-engine/src/lib.rs +++ b/crates/mayhem-engine/src/lib.rs @@ -15,6 +15,12 @@ use serde_json::{json, Value}; use sha2::{Digest, Sha256}; use thiserror::Error; +mod openai_compatible; +pub use openai_compatible::{ + OpenAiCompatibleBackend, OpenAiCompatibleBackendConfig, OpenAiCompatibleLifecycle, + OpenAiCompatiblePreflightProfile, OpenAiCompatibleRuntimeBinding, +}; + pub const CRATE_NAME: &str = "mayhem-engine"; pub const DEFAULT_CONTEXT_SIZE: u32 = 2048; pub const DEFAULT_BATCH_SIZE: u32 = 512; @@ -32,7 +38,8 @@ const VLLM_MAX_MTP_SPECULATIVE_TOKENS: u32 = 32; feature = "mlx", feature = "vllm", feature = "trt-llm", - feature = "transformers-asr" + feature = "transformers-asr", + feature = "laya" ))] const WORKER_STDOUT_QUEUE_CAPACITY: usize = 64; @@ -74,6 +81,8 @@ pub enum EngineError { Vllm(String), #[error("Transformers ASR backend error: {0}")] TransformersAsr(String), + #[error("Laya backend error: {0}")] + Laya(String), #[error("ACE-Step backend error: {0}")] AceStep(String), #[error("Chatterbox backend error: {0}")] @@ -90,6 +99,8 @@ pub enum EngineError { WhisperCpp(String), #[error("piper backend error: {0}")] Piper(String), + #[error("OpenAI-compatible backend error: {0}")] + OpenAiCompatible(String), #[error("I/O error: {0}")] Io(#[from] std::io::Error), #[error("JSON error: {0}")] @@ -222,7 +233,9 @@ pub enum ArtifactFormat { MlxSafetensors, TensorRtLlmCheckpoint, VllmSafetensors, + OpenAiCompatibleModel, TransformersSafetensors, + LayaSafetensors, AceStepSafetensors, ChatterboxSafetensors, ComfyUiRuntime, @@ -239,7 +252,9 @@ impl ArtifactFormat { Self::MlxSafetensors => b"", Self::TensorRtLlmCheckpoint => b"", Self::VllmSafetensors => b"", + Self::OpenAiCompatibleModel => b"", Self::TransformersSafetensors => b"", + Self::LayaSafetensors => b"", Self::AceStepSafetensors => b"", Self::ChatterboxSafetensors => b"", Self::ComfyUiRuntime => b"", @@ -256,7 +271,9 @@ impl ArtifactFormat { Self::MlxSafetensors => "MLX safetensors", Self::TensorRtLlmCheckpoint => "TensorRT-LLM checkpoint", Self::VllmSafetensors => "vLLM safetensors", + Self::OpenAiCompatibleModel => "OpenAI-compatible model artifact", Self::TransformersSafetensors => "Transformers safetensors", + Self::LayaSafetensors => "Laya safetensors bundle", Self::AceStepSafetensors => "ACE-Step safetensors", Self::ChatterboxSafetensors => "Chatterbox safetensors", Self::ComfyUiRuntime => "ComfyUI runtime", @@ -359,6 +376,15 @@ impl ModelArtifact { } } + pub fn openai_compatible_model(path: impl Into) -> Self { + Self { + path: path.into(), + format: ArtifactFormat::OpenAiCompatibleModel, + sha256: None, + sha256_path: None, + } + } + pub fn transformers_safetensors(path: impl Into) -> Self { Self { path: path.into(), @@ -368,6 +394,15 @@ impl ModelArtifact { } } + pub fn laya_safetensors(path: impl Into) -> Self { + Self { + path: path.into(), + format: ArtifactFormat::LayaSafetensors, + sha256: None, + sha256_path: None, + } + } + pub fn ace_step_safetensors(path: impl Into) -> Self { Self { path: path.into(), @@ -452,6 +487,14 @@ pub enum VllmGenerationTopology { IsolatedWorkers, } +#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "snake_case")] +pub enum VllmTask { + #[default] + Generate, + Embedding, +} + #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] pub struct LoadConfig { pub artifact: ModelArtifact, @@ -483,6 +526,8 @@ pub struct LoadConfig { pub ubatch_size: u32, #[serde(default, skip_serializing_if = "Option::is_none")] pub vllm_max_num_seqs: Option, + #[serde(default, skip_serializing_if = "is_default_vllm_task")] + pub vllm_task: VllmTask, #[serde(default, skip_serializing_if = "Option::is_none")] pub vllm_concurrent_generation_capacity: Option, #[serde(default, skip_serializing_if = "Option::is_none")] @@ -587,6 +632,13 @@ impl LoadConfig { } } + pub fn openai_compatible_model(path: impl Into) -> Self { + Self { + artifact: ModelArtifact::openai_compatible_model(path), + ..Self::default() + } + } + pub fn transformers_safetensors(path: impl Into) -> Self { Self { artifact: ModelArtifact::transformers_safetensors(path), @@ -594,6 +646,13 @@ impl LoadConfig { } } + pub fn laya_safetensors(path: impl Into) -> Self { + Self { + artifact: ModelArtifact::laya_safetensors(path), + ..Self::default() + } + } + pub fn ace_step_safetensors(path: impl Into) -> Self { Self { artifact: ModelArtifact::ace_step_safetensors(path), @@ -655,6 +714,7 @@ impl Default for LoadConfig { batch_size: DEFAULT_BATCH_SIZE, ubatch_size: DEFAULT_UBATCH_SIZE, vllm_max_num_seqs: None, + vllm_task: VllmTask::Generate, vllm_concurrent_generation_capacity: None, vllm_generation_topology: None, vllm_worker_address_space_limit_bytes: None, @@ -780,6 +840,28 @@ pub struct EmbeddingRequest { pub dimensions: Option, } +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct DecisionRequest { + pub state: Value, + pub questions: Value, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub checkpoint: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub task: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub lang: Option, + #[serde(default)] + pub auto_task_detection: bool, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub email: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub shortlist: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub temperature: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub limits: Option, +} + #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct ImageGenerationRequest { pub prompt: String, @@ -831,15 +913,25 @@ impl ImageGenerationRequest { pub fn validate(&self) -> Result<()> { if let Some(reference) = &self.input_reference { - mayhem_proto::image_reference_metadata(reference).map_err(EngineError::InvalidRequest)?; - let strength = self.strength.ok_or_else(|| EngineError::InvalidRequest( - "image reference requires an explicit strength".to_owned(), - ))?; + mayhem_proto::image_reference_metadata(reference) + .map_err(EngineError::InvalidRequest)?; + let strength = self.strength.ok_or_else(|| { + EngineError::InvalidRequest( + "image reference requires an explicit strength".to_owned(), + ) + })?; if !strength.is_finite() || !(0.0..=1.0).contains(&strength) { - return Err(EngineError::InvalidRequest("image strength must be between 0 and 1".to_owned())); + return Err(EngineError::InvalidRequest( + "image strength must be between 0 and 1".to_owned(), + )); } - } else if self.strength.is_some_and(|value| !value.is_finite() || !(0.0..=1.0).contains(&value)) { - return Err(EngineError::InvalidRequest("image strength must be between 0 and 1".to_owned())); + } else if self + .strength + .is_some_and(|value| !value.is_finite() || !(0.0..=1.0).contains(&value)) + { + return Err(EngineError::InvalidRequest( + "image strength must be between 0 and 1".to_owned(), + )); } if self.prompt.trim().is_empty() { return Err(EngineError::InvalidConfig( @@ -1121,6 +1213,12 @@ pub struct EmbeddingOutput { pub usage: UsageCounters, } +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct DecisionOutput { + pub result: Value, + pub usage: UsageCounters, +} + #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] pub struct ImageGenerationOutput { pub image_count: u32, @@ -1528,6 +1626,11 @@ impl ArtifactSink for NoopArtifactSink { pub trait ConcurrentGenerationBackend: Send + Sync { fn capacity(&self) -> usize; + fn tokenize(&self, _text: &str) -> Result { + Err(EngineError::InvalidConfig( + "concurrent backend does not expose exact tokenization".to_owned(), + )) + } fn generate( &self, request: GenerateRequest, @@ -1536,6 +1639,15 @@ pub trait ConcurrentGenerationBackend: Send + Sync { ) -> Result; } +pub trait ConcurrentEmbeddingBackend: Send + Sync { + fn capacity(&self) -> usize; + fn embed( + &self, + request: EmbeddingRequest, + cancellation: &CancellationToken, + ) -> Result; +} + #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum ComponentRecovery { Unsupported, @@ -1550,6 +1662,16 @@ pub trait EngineBackend { fn prefix_caching_enabled(&self) -> bool { false } + fn decide( + &mut self, + _request: DecisionRequest, + _cancellation: &CancellationToken, + ) -> Result { + Err(EngineError::InvalidConfig(format!( + "{} backend does not support typed decisions", + self.backend_id() + ))) + } fn loaded_backend_evidence(&self) -> Option { None } @@ -1561,12 +1683,26 @@ pub trait EngineBackend { fn recover_component(&mut self) -> Result { Ok(ComponentRecovery::Unsupported) } + /// Ask an idle engine to release cached model and allocator memory without + /// tearing down the provider process. Backends that do not retain large + /// caches can leave this unsupported. + fn reclaim_idle_memory(&mut self) -> Result { + Ok(false) + } fn process_ids(&self) -> Vec { Vec::new() } + /// True when backend recovery requires its owning provider process to exit + /// so an external supervisor can recreate all managed runtime state. + fn requires_owner_restart(&self) -> bool { + false + } fn concurrent_generation_backend(&self) -> Option> { None } + fn concurrent_embedding_backend(&self) -> Option> { + None + } fn tokenize(&self, text: &str) -> Result; fn generate( &mut self, @@ -2020,11 +2156,33 @@ pub fn verify_artifact(artifact: &ModelArtifact) -> Result<()> { verify_safetensors_header_as(&payload, artifact.format.label())?; payload } + ArtifactFormat::OpenAiCompatibleModel => { + let payload = vllm_safetensors_payload_path(&artifact.path)?; + verify_safetensors_header_as(&payload, artifact.format.label())?; + payload + } ArtifactFormat::TransformersSafetensors => { let payload = transformers_safetensors_payload_path(&artifact.path)?; verify_safetensors_header_as(&payload, artifact.format.label())?; payload } + ArtifactFormat::LayaSafetensors => { + let root_payload = laya_safetensors_payload_path(&artifact.path)?; + verify_safetensors_header_as(&root_payload, artifact.format.label())?; + let root = root_payload.parent().ok_or_else(|| { + EngineError::InvalidConfig(format!( + "Laya weights path {} has no parent", + root_payload.display() + )) + })?; + for relative in [ + "multilingual/model.safetensors", + "typed-decisions/model.safetensors", + ] { + verify_safetensors_header_as(&root.join(relative), artifact.format.label())?; + } + root_payload + } ArtifactFormat::AceStepSafetensors => { if !artifact.path.is_file() { return Err(EngineError::InvalidConfig(format!( @@ -2194,6 +2352,11 @@ fn validate_load_config(config: &LoadConfig) -> Result<()> { )); } if config.vllm_generation_topology == Some(VllmGenerationTopology::IsolatedWorkers) { + if config.vllm_task != VllmTask::Generate { + return Err(EngineError::InvalidConfig( + "isolated vLLM workers currently require the generation task".to_owned(), + )); + } if !config .vllm_worker_address_space_limit_bytes .is_some_and(|bytes| bytes >= 1024 && bytes <= i64::MAX as u64) @@ -2231,13 +2394,17 @@ fn validate_load_config(config: &LoadConfig) -> Result<()> { "vllm_concurrent_generation_capacity cannot exceed vllm_max_num_seqs".to_owned(), )); } - if config.vllm_worker_address_space_limit_bytes - .is_some_and(|bytes| bytes < 1024 || bytes > i64::MAX as u64) { + if config + .vllm_worker_address_space_limit_bytes + .is_some_and(|bytes| bytes < 1024 || bytes > i64::MAX as u64) + { return Err(EngineError::InvalidConfig( - "vllm_worker_address_space_limit_bytes must be a finite limit of at least 1024 bytes".to_owned(), + "vllm_worker_address_space_limit_bytes must be a finite limit of at least 1024 bytes" + .to_owned(), )); } - let has_vllm_execution_properties = config.vllm_generation_topology.is_some() + let has_vllm_execution_properties = config.vllm_task != VllmTask::Generate + || config.vllm_generation_topology.is_some() || config.vllm_worker_address_space_limit_bytes.is_some() || config.vllm_enforce_eager.is_some() || config.vllm_compilation_mode.is_some() @@ -2522,6 +2689,29 @@ fn transformers_safetensors_payload_path(path: &Path) -> Result { }) } +fn laya_safetensors_payload_path(path: &Path) -> Result { + let model_dir = if path.is_file() { + path.parent().ok_or_else(|| { + EngineError::InvalidConfig(format!( + "Laya weights path {} has no parent", + path.display() + )) + })? + } else if path.is_dir() { + path + } else { + return Err(EngineError::ModelPathMissing(path.to_path_buf())); + }; + let payload = model_dir.join("model.safetensors"); + if !payload.is_file() { + return Err(EngineError::InvalidConfig(format!( + "Laya artifact {} is missing model.safetensors", + model_dir.display() + ))); + } + Ok(payload) +} + fn chatterbox_safetensors_payload_path(path: &Path) -> Result { let model_root = if path.is_file() { path.parent().ok_or_else(|| { @@ -2757,6 +2947,10 @@ fn default_ubatch_size() -> u32 { DEFAULT_UBATCH_SIZE } +fn is_default_vllm_task(task: &VllmTask) -> bool { + *task == VllmTask::Generate +} + fn effective_vllm_max_num_seqs(config: &LoadConfig) -> u32 { let default = if config.vllm_generation_topology == Some(VllmGenerationTopology::IsolatedWorkers) { @@ -2821,6 +3015,9 @@ pub use vllm_backend::VllmBackend; #[cfg(feature = "transformers-asr")] pub use transformers_asr_backend::TransformersAsrBackend; +#[cfg(feature = "laya")] +pub use laya_backend::LayaBackend; + #[cfg(feature = "ace-step")] mod ace_step_backend; @@ -3113,7 +3310,7 @@ mod transformers_asr_backend { self.call_existing("load", json!({ "path": model_path }), None)?; let loaded = LoadedModelInfo { backend: self.backend_id().to_owned(), - artifact: config.artifact, + artifact: config.artifact.clone(), ctx_size: config.ctx_size, n_ctx_train: worker_info.n_ctx_train, n_vocab: worker_info.n_vocab, @@ -3415,7 +3612,1108 @@ mod transformers_asr_backend { } Err(error) => { let _ = sender.send(WorkerRead::Error(format!( - "reading ASR worker stdout failed: {error}" + "reading ASR worker stdout failed: {error}" + ))); + return; + } + } + } + } +} + +#[cfg(feature = "laya")] +mod laya_backend { + use super::{ + attach_worker_containment, engine_worker_command, laya_safetensors_payload_path, + validate_load_config, verify_artifact, ArtifactFormat, CancellationToken, DecisionOutput, + DecisionRequest, EngineBackend, EngineError, GenerateOutput, GenerateRequest, LoadConfig, + LoadedModelInfo, Result, TokenSink, Tokenization, UsageCounters, WorkerContainment, + }; + use serde::de::DeserializeOwned; + use serde::Deserialize; + use serde_json::{json, Value}; + use std::env; + use std::fs; + use std::io::{BufRead, BufReader, Write}; + use std::path::{Path, PathBuf}; + use std::process::{Child, ChildStdin, ChildStdout, Stdio}; + use std::sync::mpsc::{self, Receiver, RecvTimeoutError}; + use std::thread::{self, JoinHandle}; + use std::time::Duration; + + const WORKER: &str = include_str!("laya_worker.py"); + const PYTHON_ENV: &str = "MAYHEM_LAYA_PYTHON"; + const MAX_QUESTIONS: usize = 64; + const MAX_CHOICE_OPTIONS: usize = 20; + const MAX_SHORTLIST_CHOICE_OPTIONS: usize = 256; + const MAX_REQUEST_JSON_BYTES: usize = 256 * 1024; + const REQUIRED_CHECKPOINTS: [&str; 3] = ["english", "multilingual", "typed-decisions"]; + + pub struct LayaBackend { + python: PathBuf, + worker: Option, + loaded: Option, + config: Option, + next_id: u64, + evidence: Option, + } + + impl LayaBackend { + pub fn new() -> Result { + let python = env::var_os(PYTHON_ENV) + .map(PathBuf::from) + .unwrap_or_else(|| PathBuf::from("python3")); + Self::with_python(python) + } + + pub fn with_python(python: impl Into) -> Result { + Ok(Self { + python: python.into(), + worker: None, + loaded: None, + config: None, + next_id: 1, + evidence: None, + }) + } + + fn ensure_worker_loaded(&mut self) -> Result<()> { + if self.worker.is_some() { + return Ok(()); + } + let config = self.config.clone().ok_or(EngineError::NotLoaded)?; + self.worker = Some(LayaWorker::spawn( + &self.python, + config.memory_limit_bytes, + config.backend_cache_dir.as_deref(), + )?); + let root = laya_snapshot_dir(&config.artifact.path)?; + let worker_info: WorkerLoadInfo = + self.call_existing("load", json!({ "path": root }), None)?; + validate_worker_load_info(&worker_info)?; + self.evidence = Some(worker_info.evidence()); + Ok(()) + } + + fn call( + &mut self, + operation: &str, + payload: Value, + cancellation: Option<&CancellationToken>, + ) -> Result + where + T: DeserializeOwned, + { + self.ensure_worker_loaded()?; + self.call_existing(operation, payload, cancellation) + } + + fn call_existing( + &mut self, + operation: &str, + payload: Value, + cancellation: Option<&CancellationToken>, + ) -> Result + where + T: DeserializeOwned, + { + let id = self.next_id; + self.next_id = self.next_id.saturating_add(1); + self.worker_mut()?.send(id, operation, payload)?; + loop { + if cancellation.is_some_and(CancellationToken::is_cancelled) { + self.stop_worker(); + return Err(EngineError::Cancelled); + } + let message = match self.worker_mut()?.read_message(Duration::from_millis(25)) { + Ok(Some(message)) => message, + Ok(None) => continue, + Err(error) => { + self.stop_worker(); + return Err(error); + } + }; + if message.id != id { + self.stop_worker(); + return Err(EngineError::Laya(format!( + "worker response id {} did not match request id {id}", + message.id + ))); + } + if message.ok { + return Ok(serde_json::from_value( + message.result.unwrap_or(Value::Null), + )?); + } + return Err(EngineError::Laya( + message + .error + .unwrap_or_else(|| "worker returned an unknown error".to_owned()), + )); + } + } + + fn worker_mut(&mut self) -> Result<&mut LayaWorker> { + self.worker + .as_mut() + .ok_or_else(|| EngineError::Laya("Laya worker is not running".to_owned())) + } + + fn stop_worker(&mut self) { + if let Some(mut worker) = self.worker.take() { + worker.stop(); + } + } + } + + impl EngineBackend for LayaBackend { + fn backend_id(&self) -> &'static str { + "laya" + } + + fn load(&mut self, config: LoadConfig) -> Result { + validate_load_config(&config)?; + if config.artifact.format != ArtifactFormat::LayaSafetensors { + return Err(EngineError::InvalidConfig(format!( + "Laya requires a Transformers safetensors snapshot, got {:?}", + config.artifact.format + ))); + } + verify_artifact(&config.artifact)?; + self.stop_worker(); + self.config = Some(config.clone()); + self.worker = Some(LayaWorker::spawn( + &self.python, + config.memory_limit_bytes, + config.backend_cache_dir.as_deref(), + )?); + let root = laya_snapshot_dir(&config.artifact.path)?; + let worker_info: WorkerLoadInfo = + self.call_existing("load", json!({ "path": root }), None)?; + if let Err(error) = validate_worker_load_info(&worker_info) { + self.stop_worker(); + return Err(error); + } + self.evidence = Some(worker_info.evidence()); + let loaded = LoadedModelInfo { + backend: self.backend_id().to_owned(), + artifact: config.artifact.clone(), + ctx_size: config.ctx_size, + n_ctx_train: worker_info.n_ctx_train, + n_vocab: worker_info.n_vocab, + }; + self.loaded = Some(loaded.clone()); + Ok(loaded) + } + + fn loaded_backend_evidence(&self) -> Option { + self.evidence.clone() + } + + fn component_healthy(&mut self) -> bool { + match self.worker.as_mut() { + Some(worker) => matches!(worker.child.try_wait(), Ok(None)), + None => self.loaded.is_none(), + } + } + + fn process_ids(&self) -> Vec { + self.worker + .as_ref() + .map(|worker| vec![worker.child.id()]) + .unwrap_or_default() + } + + fn tokenize(&self, text: &str) -> Result { + self.loaded.as_ref().ok_or(EngineError::NotLoaded)?; + let _ = text; + Err(EngineError::InvalidConfig( + "exact standalone tokenization is unavailable for typed decisions".to_owned(), + )) + } + + fn generate( + &mut self, + _request: GenerateRequest, + _sink: &mut dyn TokenSink, + _cancellation: &CancellationToken, + ) -> Result { + Err(EngineError::InvalidConfig( + "Laya produces typed decisions; use decide".to_owned(), + )) + } + + fn decide( + &mut self, + request: DecisionRequest, + cancellation: &CancellationToken, + ) -> Result { + cancellation.check()?; + self.loaded.as_ref().ok_or(EngineError::NotLoaded)?; + validate_decision_request(&request)?; + let result: Value = self.call( + "decide", + serde_json::to_value(&request)?, + Some(cancellation), + )?; + let input_tokens = result + .get("usage") + .and_then(|usage| usage.get("input_tokens")) + .and_then(Value::as_u64) + .ok_or_else(|| { + EngineError::InvalidOutput( + "Laya result is missing integer usage.input_tokens".to_owned(), + ) + })?; + let input_tokens = u32::try_from(input_tokens).map_err(|_| { + EngineError::InvalidOutput( + "Laya usage.input_tokens exceeds the supported u32 range".to_owned(), + ) + })?; + validate_decision_result(&request.questions, &result)?; + Ok(DecisionOutput { + result, + usage: UsageCounters::new(input_tokens, 0), + }) + } + } + + impl Drop for LayaBackend { + fn drop(&mut self) { + self.stop_worker(); + } + } + + fn validate_decision_request(request: &DecisionRequest) -> Result<()> { + let bytes = serde_json::to_vec(request)?; + if bytes.len() > MAX_REQUEST_JSON_BYTES { + return Err(EngineError::InvalidRequest(format!( + "Laya request exceeds {MAX_REQUEST_JSON_BYTES} bytes" + ))); + } + if !(request.state.is_string() || request.state.is_object() || request.state.is_array()) { + return Err(EngineError::InvalidRequest( + "Laya state must be a string, object, or array".to_owned(), + )); + } + let questions = request.questions.as_object().ok_or_else(|| { + EngineError::InvalidRequest("Laya questions must be an object".to_owned()) + })?; + if questions.is_empty() || questions.len() > MAX_QUESTIONS { + return Err(EngineError::InvalidRequest(format!( + "Laya questions must contain 1 to {MAX_QUESTIONS} entries" + ))); + } + validate_decision_email(request)?; + let shortlist_k = validate_decision_shortlist(request)?; + validate_decision_temperatures(request)?; + validate_decision_limits(request)?; + for (id, raw) in questions { + if id.is_empty() || id.len() > 128 { + return Err(EngineError::InvalidRequest( + "Laya question ids must contain 1 to 128 bytes".to_owned(), + )); + } + let question = raw.as_object().ok_or_else(|| { + EngineError::InvalidRequest(format!("Laya question {id} must be an object")) + })?; + let kind = question + .get("type") + .and_then(Value::as_str) + .ok_or_else(|| { + EngineError::InvalidRequest(format!("Laya question {id} is missing type")) + })?; + if !matches!(kind, "choice" | "score" | "noul") { + return Err(EngineError::InvalidRequest(format!( + "Laya question {id} has unsupported type {kind}" + ))); + } + if !question.contains_key("instructions") { + return Err(EngineError::InvalidRequest(format!( + "Laya question {id} is missing instructions" + ))); + } + match kind { + "choice" => { + let count = match question.get("criteria") { + Some(Value::Object(values)) => values.len(), + Some(Value::Array(values)) => values.len(), + _ => 0, + }; + let maximum = if shortlist_k.is_some() { + MAX_SHORTLIST_CHOICE_OPTIONS + } else { + MAX_CHOICE_OPTIONS + }; + if !(2..=maximum).contains(&count) { + return Err(EngineError::InvalidRequest(format!( + "Laya choice question {id} must contain 2 to {maximum} criteria" + ))); + } + } + "score" => { + let count = question + .get("criteria") + .and_then(Value::as_array) + .map(Vec::len) + .unwrap_or(0); + if !(2..=MAX_CHOICE_OPTIONS).contains(&count) { + return Err(EngineError::InvalidRequest(format!( + "Laya score question {id} must contain 2 to {MAX_CHOICE_OPTIONS} criteria" + ))); + } + } + _ => {} + } + } + if request + .checkpoint + .as_deref() + .is_some_and(|value| !matches!(value, "english" | "multilingual" | "typed-decisions")) + { + return Err(EngineError::InvalidRequest( + "Laya checkpoint must be english, multilingual, or typed-decisions".to_owned(), + )); + } + if request + .task + .as_deref() + .is_some_and(|value| value != "typed_decisions") + { + return Err(EngineError::InvalidRequest( + "Laya task must be typed_decisions".to_owned(), + )); + } + if request + .lang + .as_deref() + .is_some_and(|value| value.is_empty() || value.len() > 128) + { + return Err(EngineError::InvalidRequest( + "Laya lang must contain 1 to 128 bytes".to_owned(), + )); + } + Ok(()) + } + + fn validate_decision_email(request: &DecisionRequest) -> Result<()> { + let Some(raw) = request.email.as_ref() else { + return Ok(()); + }; + let email = raw.as_object().ok_or_else(|| { + EngineError::InvalidRequest("Laya email must be an object".to_owned()) + })?; + for key in email.keys() { + if !matches!(key.as_str(), "clean" | "max_chars") { + return Err(EngineError::InvalidRequest(format!( + "Laya email contains unsupported field {key}" + ))); + } + } + if email.get("clean").is_some_and(|value| !value.is_boolean()) { + return Err(EngineError::InvalidRequest( + "Laya email.clean must be a boolean".to_owned(), + )); + } + optional_bounded_u64(email.get("max_chars"), 1, 10_000, "email.max_chars")?; + if !request + .state + .as_object() + .and_then(|state| state.get("body")) + .is_some_and(Value::is_string) + { + return Err(EngineError::InvalidRequest( + "Laya email preprocessing requires state.body to be a string".to_owned(), + )); + } + Ok(()) + } + + fn validate_decision_shortlist(request: &DecisionRequest) -> Result> { + let Some(raw) = request.shortlist.as_ref() else { + return Ok(None); + }; + let shortlist = raw.as_object().ok_or_else(|| { + EngineError::InvalidRequest("Laya shortlist must be an object".to_owned()) + })?; + for key in shortlist.keys() { + if !matches!(key.as_str(), "k" | "max_length" | "batch_size" | "vectors") { + return Err(EngineError::InvalidRequest(format!( + "Laya shortlist contains unsupported field {key}" + ))); + } + } + let k = optional_bounded_u64(shortlist.get("k"), 1, 20, "shortlist.k")?.unwrap_or(20); + optional_bounded_u64( + shortlist.get("max_length"), + 1, + 1_024, + "shortlist.max_length", + )?; + optional_bounded_u64(shortlist.get("batch_size"), 1, 64, "shortlist.batch_size")?; + if let Some(raw_vectors) = shortlist.get("vectors") { + if shortlist.contains_key("max_length") || shortlist.contains_key("batch_size") { + return Err(EngineError::InvalidRequest( + "Laya shortlist supplied vectors cannot be combined with encoder controls" + .to_owned(), + )); + } + validate_supplied_shortlist_vectors(request, raw_vectors, k as usize)?; + } + Ok(Some(k)) + } + + fn validate_supplied_shortlist_vectors( + request: &DecisionRequest, + raw_vectors: &Value, + k: usize, + ) -> Result<()> { + const MAX_VECTOR_DIMENSIONS: usize = 4_096; + let vectors = raw_vectors.as_object().ok_or_else(|| { + EngineError::InvalidRequest("Laya shortlist.vectors must be an object".to_owned()) + })?; + let questions = request.questions.as_object().ok_or_else(|| { + EngineError::InvalidRequest("Laya questions must be an object".to_owned()) + })?; + let expected = questions + .iter() + .filter_map(|(id, question)| { + let question = question.as_object()?; + if question.get("type").and_then(Value::as_str) != Some("choice") { + return None; + } + let count = match question.get("criteria") { + Some(Value::Object(values)) => values.len(), + Some(Value::Array(values)) => values.len(), + _ => 0, + }; + (count > k).then_some(id.as_str()) + }) + .collect::>(); + let supplied = vectors + .keys() + .map(String::as_str) + .collect::>(); + if supplied != expected { + return Err(EngineError::InvalidRequest( + "Laya shortlist.vectors must cover exactly the choice questions reduced by k" + .to_owned(), + )); + } + for id in expected { + let entry = vectors[id].as_object().ok_or_else(|| { + EngineError::InvalidRequest(format!( + "Laya shortlist.vectors.{id} must be an object" + )) + })?; + if entry + .keys() + .any(|key| !matches!(key.as_str(), "query" | "options")) + || !entry.contains_key("query") + || !entry.contains_key("options") + { + return Err(EngineError::InvalidRequest(format!( + "Laya shortlist.vectors.{id} must contain only query and options" + ))); + } + let dimensions = validate_decision_vector( + entry.get("query").expect("checked query"), + MAX_VECTOR_DIMENSIONS, + &format!("shortlist.vectors.{id}.query"), + )?; + let criteria = questions[id] + .get("criteria") + .expect("validated choice criteria"); + match (criteria, entry.get("options").expect("checked options")) { + (Value::Object(criteria), Value::Object(options)) => { + if criteria.len() != options.len() + || criteria.keys().any(|label| !options.contains_key(label)) + { + return Err(EngineError::InvalidRequest(format!( + "Laya shortlist.vectors.{id}.options must match every criteria label" + ))); + } + for (label, vector) in options { + if !criteria.contains_key(label) + || validate_decision_vector( + vector, + MAX_VECTOR_DIMENSIONS, + &format!("shortlist.vectors.{id}.options.{label}"), + )? != dimensions + { + return Err(EngineError::InvalidRequest(format!( + "Laya shortlist.vectors.{id}.options vectors must match query dimensions" + ))); + } + } + } + (Value::Array(criteria), Value::Array(options)) => { + if criteria.len() != options.len() { + return Err(EngineError::InvalidRequest(format!( + "Laya shortlist.vectors.{id}.options must match every criteria entry" + ))); + } + for vector in options { + if validate_decision_vector( + vector, + MAX_VECTOR_DIMENSIONS, + &format!("shortlist.vectors.{id}.options"), + )? != dimensions + { + return Err(EngineError::InvalidRequest(format!( + "Laya shortlist.vectors.{id}.options vectors must match query dimensions" + ))); + } + } + } + _ => { + return Err(EngineError::InvalidRequest(format!( + "Laya shortlist.vectors.{id}.options shape must match criteria" + ))) + } + } + } + Ok(()) + } + + fn validate_decision_vector(value: &Value, maximum: usize, field: &str) -> Result { + let values = value.as_array().ok_or_else(|| { + EngineError::InvalidRequest(format!("Laya {field} must be a numeric array")) + })?; + if values.is_empty() || values.len() > maximum { + return Err(EngineError::InvalidRequest(format!( + "Laya {field} must contain 1 to {maximum} dimensions" + ))); + } + if values + .iter() + .any(|value| !value.as_f64().is_some_and(f64::is_finite)) + { + return Err(EngineError::InvalidRequest(format!( + "Laya {field} must contain only finite numbers" + ))); + } + Ok(values.len()) + } + + fn validate_decision_temperatures(request: &DecisionRequest) -> Result<()> { + let Some(raw) = request.temperature.as_ref() else { + return Ok(()); + }; + let temperatures = raw.as_object().ok_or_else(|| { + EngineError::InvalidRequest("Laya temperature must be an object".to_owned()) + })?; + for (key, value) in temperatures { + if !matches!( + key.as_str(), + "choice" + | "score" + | "noul" + | "choice:2" + | "choice:3-5" + | "choice:6-10" + | "choice:11+" + | "score:2" + | "score:3-5" + | "score:6-10" + | "score:11+" + | "noul:2" + ) { + return Err(EngineError::InvalidRequest(format!( + "Laya temperature contains unsupported field {key}" + ))); + } + let number = value.as_f64().ok_or_else(|| { + EngineError::InvalidRequest(format!("Laya temperature.{key} must be a number")) + })?; + if !number.is_finite() || !(0.5..=5.0).contains(&number) { + return Err(EngineError::InvalidRequest(format!( + "Laya temperature.{key} must be between 0.5 and 5.0" + ))); + } + } + Ok(()) + } + + fn validate_decision_limits(request: &DecisionRequest) -> Result<()> { + let Some(raw) = request.limits.as_ref() else { + return Ok(()); + }; + let limits = raw.as_object().ok_or_else(|| { + EngineError::InvalidRequest("Laya limits must be an object".to_owned()) + })?; + for key in limits.keys() { + if !matches!(key.as_str(), "max_len" | "head_max_len") { + return Err(EngineError::InvalidRequest(format!( + "Laya limits contains unsupported field {key}" + ))); + } + } + let max_len = optional_bounded_u64(limits.get("max_len"), 128, 1_024, "limits.max_len")?; + let head_max_len = + optional_bounded_u64(limits.get("head_max_len"), 32, 512, "limits.head_max_len")?; + if matches!((max_len, head_max_len), (Some(max_len), Some(head_max_len)) if head_max_len >= max_len) + { + return Err(EngineError::InvalidRequest( + "Laya limits.head_max_len must be smaller than limits.max_len".to_owned(), + )); + } + Ok(()) + } + + fn optional_bounded_u64( + value: Option<&Value>, + minimum: u64, + maximum: u64, + field: &str, + ) -> Result> { + let Some(value) = value else { + return Ok(None); + }; + let number = value.as_u64().ok_or_else(|| { + EngineError::InvalidRequest(format!("Laya {field} must be an integer")) + })?; + if !(minimum..=maximum).contains(&number) { + return Err(EngineError::InvalidRequest(format!( + "Laya {field} must be between {minimum} and {maximum}" + ))); + } + Ok(Some(number)) + } + + fn validate_decision_result(questions: &Value, result: &Value) -> Result<()> { + let object = result.as_object().ok_or_else(|| { + EngineError::InvalidOutput("Laya result must be an object".to_owned()) + })?; + let expected_questions = questions.as_object().ok_or_else(|| { + EngineError::InvalidOutput("Laya request questions were not an object".to_owned()) + })?; + let answers = object + .get("answers") + .and_then(Value::as_object) + .ok_or_else(|| { + EngineError::InvalidOutput("Laya result is missing an answers object".to_owned()) + })?; + if answers.len() != expected_questions.len() + || expected_questions + .keys() + .any(|id| !answers.contains_key(id)) + { + return Err(EngineError::InvalidOutput( + "Laya result answer ids do not match the request".to_owned(), + )); + } + for (id, answer) in answers { + let answer = answer.as_object().ok_or_else(|| { + EngineError::InvalidOutput(format!("Laya answer {id} must be an object")) + })?; + let expected_type = expected_questions[id] + .get("type") + .and_then(Value::as_str) + .unwrap_or_default(); + if answer.get("type").and_then(Value::as_str) != Some(expected_type) { + return Err(EngineError::InvalidOutput(format!( + "Laya answer {id} type does not match the request" + ))); + } + } + let routing = object + .get("routing") + .and_then(Value::as_object) + .ok_or_else(|| { + EngineError::InvalidOutput("Laya result is missing a routing object".to_owned()) + })?; + for key in routing.keys() { + if !matches!(key.as_str(), "model" | "reason" | "detection" | "workflow") { + return Err(EngineError::InvalidOutput(format!( + "Laya routing contains unsupported field {key}" + ))); + } + } + if !routing + .get("model") + .and_then(Value::as_str) + .is_some_and(|value| matches!(value, "english" | "multilingual" | "typed-decisions")) + { + return Err(EngineError::InvalidOutput( + "Laya routing.model is invalid".to_owned(), + )); + } + if !routing + .get("reason") + .and_then(Value::as_str) + .is_some_and(|value| !value.is_empty() && value.len() <= 1_024) + { + return Err(EngineError::InvalidOutput( + "Laya routing.reason is invalid".to_owned(), + )); + } + let usage = object + .get("usage") + .and_then(Value::as_object) + .ok_or_else(|| { + EngineError::InvalidOutput("Laya result is missing a usage object".to_owned()) + })?; + if !usage + .get("input_tokens") + .and_then(Value::as_u64) + .is_some_and(|value| value > 0) + || usage.get("output_tokens").and_then(Value::as_u64) != Some(0) + { + return Err(EngineError::InvalidOutput( + "Laya result usage is invalid".to_owned(), + )); + } + let encoded = serde_json::to_vec(result)?; + if encoded.len() > MAX_REQUEST_JSON_BYTES { + return Err(EngineError::InvalidOutput( + "Laya result exceeds the bounded response size".to_owned(), + )); + } + Ok(()) + } + + #[derive(Debug, Deserialize)] + struct WorkerLoadInfo { + device: String, + n_ctx_train: u32, + n_vocab: i32, + loaded: Vec, + } + + fn validate_worker_load_info(info: &WorkerLoadInfo) -> Result<()> { + if info.device != "cuda" { + return Err(EngineError::Laya( + "Laya production worker did not load on CUDA".to_owned(), + )); + } + let mut loaded = info.loaded.iter().map(String::as_str).collect::>(); + loaded.sort_unstable(); + let mut required = REQUIRED_CHECKPOINTS.to_vec(); + required.sort_unstable(); + if loaded != required { + return Err(EngineError::Laya(format!( + "Laya production worker must preload exactly {}; loaded {}", + REQUIRED_CHECKPOINTS.join(", "), + info.loaded.join(", ") + ))); + } + if info.n_ctx_train != 1_024 || info.n_vocab <= 0 { + return Err(EngineError::Laya( + "Laya production worker reported invalid model dimensions".to_owned(), + )); + } + Ok(()) + } + + impl WorkerLoadInfo { + fn evidence(&self) -> Value { + json!({ + "device": self.device, + "preloaded_checkpoints": self.loaded, + "offline": true, + }) + } + } + + #[derive(Debug, Deserialize)] + struct WorkerMessage { + id: u64, + #[serde(default)] + ok: bool, + #[serde(default)] + result: Option, + #[serde(default)] + error: Option, + } + + struct LayaWorker { + child: Child, + _containment: WorkerContainment, + stdin: ChildStdin, + stdout_rx: Option>, + reader: Option>, + } + + impl LayaWorker { + fn spawn( + python: &Path, + memory_limit_bytes: Option, + cache_root: Option<&Path>, + ) -> Result { + let mut command = engine_worker_command(python, memory_limit_bytes); + configure_worker_environment(&mut command, python, cache_root)?; + command + .arg("-u") + .arg("-c") + .arg(WORKER) + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .stderr(Stdio::inherit()); + let mut child = command.spawn().map_err(|error| { + EngineError::Laya(format!( + "spawning Laya worker with {} failed: {error}", + python.display() + )) + })?; + let stdin = child + .stdin + .take() + .ok_or_else(|| EngineError::Laya("opening Laya worker stdin failed".to_owned()))?; + let stdout = child + .stdout + .take() + .ok_or_else(|| EngineError::Laya("opening Laya worker stdout failed".to_owned()))?; + let containment = + attach_worker_containment(&child, memory_limit_bytes).map_err(|error| { + EngineError::Laya(format!("applying Laya worker containment failed: {error}")) + })?; + let (stdout_tx, stdout_rx) = mpsc::sync_channel(super::WORKER_STDOUT_QUEUE_CAPACITY); + let reader = thread::spawn(move || read_worker_stdout(stdout, stdout_tx)); + Ok(Self { + child, + _containment: containment, + stdin, + stdout_rx: Some(stdout_rx), + reader: Some(reader), + }) + } + + fn send(&mut self, id: u64, operation: &str, payload: Value) -> Result<()> { + serde_json::to_writer( + &mut self.stdin, + &json!({ "id": id, "op": operation, "payload": payload }), + )?; + self.stdin.write_all(b"\n")?; + self.stdin.flush()?; + Ok(()) + } + + fn read_message(&mut self, wait: Duration) -> Result> { + let read = match self + .stdout_rx + .as_ref() + .ok_or_else(|| EngineError::Laya("Laya worker stdout is closed".to_owned()))? + .recv_timeout(wait) + { + Ok(read) => read, + Err(RecvTimeoutError::Timeout) => return Ok(None), + Err(RecvTimeoutError::Disconnected) => { + return Err(EngineError::Laya( + "Laya worker stdout reader stopped".to_owned(), + )) + } + }; + let line = match read { + WorkerRead::Line(line) => line, + WorkerRead::Eof => { + return Err(EngineError::Laya( + "Laya worker exited before replying".to_owned(), + )) + } + WorkerRead::Error(error) => return Err(EngineError::Laya(error)), + }; + serde_json::from_str(line.trim_end()) + .map(Some) + .map_err(Into::into) + } + + fn stop(&mut self) { + let _ = self.send(0, "shutdown", Value::Null); + self.stdout_rx.take(); + let _ = self.child.kill(); + let _ = self.child.wait(); + if let Some(reader) = self.reader.take() { + let _ = reader.join(); + } + } + } + + fn configure_worker_environment( + command: &mut std::process::Command, + python: &Path, + cache_root: Option<&Path>, + ) -> Result<()> { + let cache_root = cache_root + .map(Path::to_path_buf) + .or_else(|| { + env::var_os("MAYHEM_HOME") + .map(PathBuf::from) + .map(|home| home.join("cache/laya")) + }) + .or_else(|| { + env::var_os("HOME") + .map(PathBuf::from) + .map(|home| home.join(".mayhem/cache/laya")) + }) + .unwrap_or_else(|| env::temp_dir().join("mayhem-laya-cache")); + for (name, default_path) in [ + ("XDG_CACHE_HOME", cache_root.join("xdg")), + ("HF_HOME", cache_root.join("huggingface")), + ("HF_HUB_CACHE", cache_root.join("huggingface/hub")), + ("TRANSFORMERS_CACHE", cache_root.join("transformers")), + ] { + let path = env::var_os(name).map(PathBuf::from).unwrap_or(default_path); + fs::create_dir_all(&path).map_err(|error| { + EngineError::Laya(format!( + "creating Laya cache directory {} failed: {error}", + path.display() + )) + })?; + command.env(name, path); + } + command + .env("HF_HUB_OFFLINE", "1") + .env("HF_DATASETS_OFFLINE", "1") + .env("TRANSFORMERS_OFFLINE", "1") + .env("HF_HUB_DISABLE_TELEMETRY", "1") + .env("TOKENIZERS_PARALLELISM", "false"); + if let Some(python_bin) = python.parent() { + let mut paths = vec![python_bin.to_path_buf()]; + if let Some(current) = env::var_os("PATH") { + paths.extend(env::split_paths(¤t)); + } + if let Ok(path) = env::join_paths(paths) { + command.env("PATH", path); + } + } + Ok(()) + } + + fn laya_snapshot_dir(path: &Path) -> Result { + let payload = laya_safetensors_payload_path(path)?; + payload.parent().map(Path::to_path_buf).ok_or_else(|| { + EngineError::InvalidConfig(format!( + "Laya weights path {} has no parent", + payload.display() + )) + }) + } + + #[cfg(test)] + mod tests { + use super::*; + + fn choice_request(option_count: usize) -> DecisionRequest { + let criteria = (0..option_count) + .map(|index| (format!("option-{index}"), json!(format!("Option {index}")))) + .collect::>(); + DecisionRequest { + state: json!({"body": "Please reset my password"}), + questions: json!({ + "intent": { + "type": "choice", + "instructions": "Choose an intent", + "criteria": criteria, + } + }), + checkpoint: Some("english".to_owned()), + task: None, + lang: None, + auto_task_detection: false, + email: None, + shortlist: None, + temperature: None, + limits: None, + } + } + + #[test] + fn high_cardinality_choices_require_bounded_shortlisting() { + let mut request = choice_request(25); + assert!(validate_decision_request(&request).is_err()); + request.shortlist = Some(json!({"k": 20, "max_length": 512, "batch_size": 32})); + validate_decision_request(&request).expect("bounded shortlist request"); + request.shortlist = Some(json!({"k": 21})); + assert!(validate_decision_request(&request).is_err()); + } + + #[test] + fn request_local_decision_controls_are_strictly_validated() { + let mut request = choice_request(4); + request.email = Some(json!({"clean": true, "max_chars": 3000})); + request.temperature = Some(json!({"choice:3-5": 1.25})); + request.limits = Some(json!({"max_len": 512, "head_max_len": 192})); + validate_decision_request(&request).expect("valid local controls"); + + request.temperature = Some(json!({"choice:3-5": 0.1})); + assert!(validate_decision_request(&request).is_err()); + request.temperature = Some(json!({"choice:3-5": 1.25})); + request.limits = Some(json!({"max_len": 256, "head_max_len": 256})); + assert!(validate_decision_request(&request).is_err()); + request.limits = Some(json!({"max_len": 512, "head_max_len": 192})); + request.email = Some(json!({"clean": "yes"})); + assert!(validate_decision_request(&request).is_err()); + } + + #[test] + fn supplied_shortlist_vectors_are_data_only_and_shape_checked() { + let mut request = choice_request(25); + let options = (0..25) + .map(|index| (format!("option-{index}"), json!([index as f64, 1.0]))) + .collect::>(); + request.shortlist = Some(json!({ + "k": 20, + "vectors": { + "intent": { + "query": [0.0, 1.0], + "options": options, + } + } + })); + validate_decision_request(&request).expect("valid supplied shortlist vectors"); + + request.shortlist.as_mut().unwrap()["vectors"]["intent"]["options"]["option-0"] = + json!([1.0]); + assert!(validate_decision_request(&request).is_err()); + } + + #[test] + fn production_load_requires_the_complete_cuda_checkpoint_family() { + let complete = WorkerLoadInfo { + device: "cuda".to_owned(), + n_ctx_train: 1_024, + n_vocab: 32_000, + loaded: vec![ + "typed-decisions".to_owned(), + "english".to_owned(), + "multilingual".to_owned(), + ], + }; + validate_worker_load_info(&complete).expect("complete CUDA checkpoint family"); + + let mut missing = complete; + missing.loaded.pop(); + assert!(validate_worker_load_info(&missing).is_err()); + missing.loaded.push("multilingual".to_owned()); + missing.device = "cpu".to_owned(); + assert!(validate_worker_load_info(&missing).is_err()); + } + } + + enum WorkerRead { + Line(String), + Eof, + Error(String), + } + + fn read_worker_stdout(stdout: ChildStdout, sender: mpsc::SyncSender) { + let mut stdout = BufReader::new(stdout); + loop { + let mut line = String::new(); + match stdout.read_line(&mut line) { + Ok(0) => { + let _ = sender.send(WorkerRead::Eof); + return; + } + Ok(_) => { + if sender.send(WorkerRead::Line(line)).is_err() { + return; + } + } + Err(error) => { + let _ = sender.send(WorkerRead::Error(format!( + "reading Laya worker stdout failed: {error}" ))); return; } @@ -5157,17 +6455,20 @@ mod stable_diffusion_tests { let root = tempfile::tempdir().unwrap(); let model = root.path().join("model.safetensors"); fs::write(&model, stable_empty_safetensors()).unwrap(); - let reference = format!("data:image/png;base64,{}", general_purpose::STANDARD.encode( - include_bytes!("../tests/fixtures/reference.png"), - )); + let reference = format!( + "data:image/png;base64,{}", + general_purpose::STANDARD.encode(include_bytes!("../tests/fixtures/reference.png"),) + ); let expected = json!({ "prompt": "A blue sculpture", "width": 64, "height": 64, "steps": 2, "cfg_scale": 1.0, "seed": 7, "batch_size": 1, "init_images": [reference], "denoising_strength": 0.5 }); let image = include_bytes!("../tests/fixtures/reference.png").to_vec(); let (address, server) = serve_sdapi_once(expected, vec![image.clone()]); let mut backend = StableDiffusionCppBackend::with_ready_server( - LoadConfig::stable_diffusion_checkpoint(&model), address, - ).unwrap(); + LoadConfig::stable_diffusion_checkpoint(&model), + address, + ) + .unwrap(); let mut request = ImageGenerationRequest::new("A blue sculpture"); request.width = 64; request.height = 64; @@ -5175,10 +6476,22 @@ mod stable_diffusion_tests { request.guidance_scale = 1.0; request.seed = Some(7); request.input_reference = Some(reference); - assert!(request.validate().is_err(), "reference strength must be explicit"); + assert!( + request.validate().is_err(), + "reference strength must be explicit" + ); request.strength = Some(0.5); let mut output = Vec::new(); - backend.generate_image(request, &mut |chunk: ArtifactChunk| { output.extend(chunk.bytes); Ok(()) }, &CancellationToken::new()).unwrap(); + backend + .generate_image( + request, + &mut |chunk: ArtifactChunk| { + output.extend(chunk.bytes); + Ok(()) + }, + &CancellationToken::new(), + ) + .unwrap(); server.join().unwrap(); assert_eq!(output, image); } @@ -5814,7 +7127,6 @@ cp "{}" "$out" #[cfg(feature = "llama-cpp")] mod llama_cpp_backend { mod prefix_cache; - use prefix_cache::PrefixCache; use base64::{engine::general_purpose, Engine as _}; use encoding_rs::UTF_8; use llama_cpp_2::context::params::{KvCacheType, LlamaContextParams, LlamaPoolingType}; @@ -5828,6 +7140,7 @@ mod llama_cpp_backend { }; use llama_cpp_2::sampling::LlamaSampler; use llama_cpp_2::token::LlamaToken; + use prefix_cache::PrefixCache; use super::{ tool_call_json_schema, validate_load_config, verify_artifact, ArtifactFormat, @@ -5901,13 +7214,18 @@ mod llama_cpp_backend { /// Test/embedding cache budget; provider admission rejects disabled caching. pub fn set_prefix_cache_limit(&mut self, max_bytes: usize) { - *self.prefix_cache.get_mut().unwrap_or_else(|p| p.into_inner()) = - PrefixCache::new(max_bytes); + *self + .prefix_cache + .get_mut() + .unwrap_or_else(|p| p.into_inner()) = PrefixCache::new(max_bytes); } /// Last text request's total and reused prompt tokens, for runtime checks. pub fn prefix_cache_tokens(&self) -> (usize, usize) { - self.prefix_cache.lock().unwrap_or_else(|p| p.into_inner()).last_tokens() + self.prefix_cache + .lock() + .unwrap_or_else(|p| p.into_inner()) + .last_tokens() } } @@ -5975,7 +7293,10 @@ mod llama_cpp_backend { } fn load(&mut self, config: LoadConfig) -> Result { - self.prefix_cache.get_mut().unwrap_or_else(|p| p.into_inner()).clear(); + self.prefix_cache + .get_mut() + .unwrap_or_else(|p| p.into_inner()) + .clear(); validate_load_config(&config)?; if config.artifact.format != ArtifactFormat::Gguf { return Err(EngineError::InvalidConfig(format!( @@ -6047,7 +7368,11 @@ mod llama_cpp_backend { fn prefix_caching_enabled(&self) -> bool { self.loaded.is_some() - && self.prefix_cache.lock().unwrap_or_else(|p| p.into_inner()).enabled() + && self + .prefix_cache + .lock() + .unwrap_or_else(|p| p.into_inner()) + .enabled() } fn tokenize(&self, text: &str) -> Result { @@ -6101,13 +7426,21 @@ mod llama_cpp_backend { (cached, cache.capture_len().max(cached)) }; let last_prompt_index = prompt_tokens.len().checked_sub(1).ok_or_else(|| { - EngineError::InvalidConfig("llama.cpp prompt tokenization produced no tokens".into()) + EngineError::InvalidConfig( + "llama.cpp prompt tokenization produced no tokens".into(), + ) })?; let mut batch_ranges = Vec::new(); - for (start, end) in [(cached_tokens, capture_len), (capture_len, last_prompt_index)] { + for (start, end) in [ + (cached_tokens, capture_len), + (capture_len, last_prompt_index), + ] { if end > start { - batch_ranges.extend(llama_prompt_batch_ranges(end - start, ctx.n_batch())? - .into_iter().map(|range| start + range.start..start + range.end)); + batch_ranges.extend( + llama_prompt_batch_ranges(end - start, ctx.n_batch())? + .into_iter() + .map(|range| start + range.start..start + range.end), + ); } } let batch_capacity = batch_ranges @@ -6133,17 +7466,28 @@ mod llama_cpp_backend { ctx.decode(&mut batch)?; cancellation.check()?; if end == capture_len && capture_len > cached_tokens { - self.prefix_cache.lock().unwrap_or_else(|p| p.into_inner()) + self.prefix_cache + .lock() + .unwrap_or_else(|p| p.into_inner()) .save(&ctx, &prompt_tokens[..capture_len])?; } } - eprintln!("prefix_cache backend=llama.cpp prompt_tokens={} cached_tokens={}", - prompt_tokens.len(), cached_tokens); + eprintln!( + "prefix_cache backend=llama.cpp prompt_tokens={} cached_tokens={}", + prompt_tokens.len(), + cached_tokens + ); cancellation.check()?; batch.clear(); - batch.add(prompt_tokens[last_prompt_index], i32::try_from(last_prompt_index) - .map_err(|err| EngineError::InvalidConfig(format!("prompt position overflow: {err}")))?, &[0], true)?; + batch.add( + prompt_tokens[last_prompt_index], + i32::try_from(last_prompt_index).map_err(|err| { + EngineError::InvalidConfig(format!("prompt position overflow: {err}")) + })?, + &[0], + true, + )?; ctx.decode(&mut batch)?; cancellation.check()?; @@ -7644,7 +8988,7 @@ mod mlx_backend { )?; let loaded = LoadedModelInfo { backend: self.backend_id().to_owned(), - artifact: config.artifact, + artifact: config.artifact.clone(), ctx_size: config.ctx_size, n_ctx_train: info.n_ctx_train, n_vocab: info.n_vocab, @@ -8153,9 +9497,10 @@ mod vllm_backend { select_runtime_compatible_cuda_home, validate_load_config, validate_vllm_compilation_config, validate_vllm_kernel_backend, verify_artifact, vllm_safetensors_payload_path, ArtifactFormat, CancellationToken, ComponentRecovery, - ConcurrentGenerationBackend, EngineBackend, EngineError, FinishReason, GenerateOutput, - GenerateRequest, LoadConfig, LoadedModelInfo, Result, TokenChunk, TokenSink, Tokenization, - UsageCounters, VllmGenerationTopology, WorkerContainment, + ConcurrentEmbeddingBackend, ConcurrentGenerationBackend, EmbeddingOutput, EmbeddingRequest, + EngineBackend, EngineError, FinishReason, GenerateOutput, GenerateRequest, LoadConfig, + LoadedModelInfo, Result, TokenChunk, TokenSink, Tokenization, UsageCounters, + VllmGenerationTopology, VllmTask, WorkerContainment, }; use serde::de::DeserializeOwned; use serde::{Deserialize, Serialize}; @@ -8197,9 +9542,13 @@ mod vllm_backend { generation_gate: Arc>, generation_epoch: Arc, concurrent_generation: Option>, + concurrent_embedding: Option>, concurrent_generation_enabled: bool, + concurrent_embedding_enabled: bool, loaded_batch_invariant: Option, loaded_generation_capacity: Option, + loaded_embedding_capacity: Option, + loaded_task: Option, loaded_kv_cache_size_tokens: Option, loaded_kv_full_context_capacity: Option, loaded_execution: Option, @@ -8229,9 +9578,13 @@ mod vllm_backend { generation_gate: Arc::new(RwLock::new(())), generation_epoch: Arc::new(AtomicU64::new(0)), concurrent_generation: None, + concurrent_embedding: None, concurrent_generation_enabled: false, + concurrent_embedding_enabled: false, loaded_batch_invariant: None, loaded_generation_capacity: None, + loaded_embedding_capacity: None, + loaded_task: None, loaded_kv_cache_size_tokens: None, loaded_kv_full_context_capacity: None, loaded_execution: None, @@ -8262,17 +9615,24 @@ mod vllm_backend { } } self.reset_worker(); - let worker = Arc::new(if let Some(address_limit) = self.worker_address_space_limit_bytes { - VllmWorker::spawn_isolated( - &self.python, self.memory_limit_bytes, address_limit, - self.cache_root.as_deref(), execution_probe, - )? - } else { - VllmWorker::spawn( - &self.python, self.memory_limit_bytes, - self.cache_root.as_deref(), execution_probe, - )? - }); + let worker = Arc::new( + if let Some(address_limit) = self.worker_address_space_limit_bytes { + VllmWorker::spawn_isolated( + &self.python, + self.memory_limit_bytes, + address_limit, + self.cache_root.as_deref(), + execution_probe, + )? + } else { + VllmWorker::spawn( + &self.python, + self.memory_limit_bytes, + self.cache_root.as_deref(), + execution_probe, + )? + }, + ); self.worker = Some(Arc::clone(&worker)); Ok(worker) } @@ -8505,6 +9865,50 @@ mod vllm_backend { self.limiter.capacity() } + fn tokenize(&self, text: &str) -> Result { + let _generation = self + .generation_gate + .read() + .unwrap_or_else(|poisoned| poisoned.into_inner()); + if self.generation_epoch.load(Ordering::Acquire) != self.expected_epoch { + return Err(EngineError::NotLoaded); + } + let (worker, _isolated_guard) = match &self.dispatch { + VllmGenerationDispatch::Shared(worker) => (Arc::clone(worker), None), + VllmGenerationDispatch::Isolated(workers) => { + let workers = workers.read().unwrap_or_else(|p| p.into_inner()); + let worker = workers + .iter() + .find(|worker| worker.component_healthy()) + .cloned() + .ok_or_else(|| { + EngineError::Vllm( + "isolated vLLM worker pool has no healthy tokenizer".to_owned(), + ) + })?; + let guard = IsolatedGenerationGuard { + worker: Arc::clone(&worker), + }; + (worker, Some(guard)) + } + }; + let tokenization: Tokenization = worker.call_streaming( + self.next_request_id(), + "tokenize", + json!({ "text": text }), + &mut |_| Ok(()), + None, + false, + 1, + )?; + if !text.is_empty() && tokenization.is_empty() { + return Err(EngineError::InvalidConfig( + "vLLM tokenizer returned no tokens for non-empty input".to_owned(), + )); + } + Ok(tokenization) + } + fn generate( &self, request: GenerateRequest, @@ -8560,6 +9964,82 @@ mod vllm_backend { } } + struct VllmConcurrentEmbedding { + worker: Arc, + next_id: Arc, + generation_gate: Arc>, + generation_epoch: Arc, + expected_epoch: u64, + limiter: Arc, + } + + impl VllmConcurrentEmbedding { + fn next_request_id(&self) -> u64 { + loop { + let id = self.next_id.fetch_add(1, Ordering::Relaxed); + if id != 0 { + return id; + } + } + } + } + + impl ConcurrentEmbeddingBackend for VllmConcurrentEmbedding { + fn capacity(&self) -> usize { + self.limiter.capacity() + } + + fn embed( + &self, + request: EmbeddingRequest, + cancellation: &CancellationToken, + ) -> Result { + cancellation.check()?; + if request.inputs.is_empty() { + return Err(EngineError::InvalidConfig( + "embedding request must include at least one input".to_owned(), + )); + } + if request.dimensions == Some(0) { + return Err(EngineError::InvalidConfig( + "embedding dimensions must be greater than zero".to_owned(), + )); + } + let _generation = self + .generation_gate + .read() + .unwrap_or_else(|poisoned| poisoned.into_inner()); + if self.generation_epoch.load(Ordering::Acquire) != self.expected_epoch { + return Err(EngineError::NotLoaded); + } + let _permit = self.limiter.acquire(cancellation)?; + let expected_count = request.inputs.len(); + let expected_dimensions = request.dimensions; + let route_capacity = expected_count.saturating_add(1); + let output: EmbeddingOutput = self.worker.call_streaming( + self.next_request_id(), + "embed", + serde_json::to_value(request)?, + &mut |_| Ok(()), + Some(cancellation), + false, + route_capacity, + )?; + if output.embeddings.len() != expected_count + || output.embeddings.iter().any(|row| { + row.is_empty() + || expected_dimensions.is_some_and(|dimensions| row.len() != dimensions) + || row.iter().any(|value| !value.is_finite()) + }) + { + return Err(EngineError::Vllm( + "vLLM worker returned an invalid embedding vector".to_owned(), + )); + } + Ok(output) + } + } + struct IsolatedGenerationGuard { worker: Arc, } @@ -8686,9 +10166,13 @@ mod vllm_backend { .unwrap_or_else(|poisoned| poisoned.into_inner()); self.loaded = None; self.concurrent_generation = None; + self.concurrent_embedding = None; self.concurrent_generation_enabled = false; + self.concurrent_embedding_enabled = false; self.loaded_batch_invariant = None; self.loaded_generation_capacity = None; + self.loaded_embedding_capacity = None; + self.loaded_task = None; self.loaded_kv_cache_size_tokens = None; self.loaded_kv_full_context_capacity = None; self.loaded_execution = None; @@ -8699,7 +10183,9 @@ mod vllm_backend { .fetch_add(1, Ordering::AcqRel) .wrapping_add(1); if self.memory_limit_bytes != config.memory_limit_bytes - || self.worker_address_space_limit_bytes != config.vllm_worker_address_space_limit_bytes { + || self.worker_address_space_limit_bytes + != config.vllm_worker_address_space_limit_bytes + { self.reset_worker(); } self.memory_limit_bytes = config.memory_limit_bytes; @@ -8761,14 +10247,58 @@ mod vllm_backend { EngineError::Vllm("vLLM load exhausted memory-utilization attempts".to_owned()) })?; let has_explicit_execution_profile = has_explicit_vllm_execution_properties(&config); - if let Err(error) = validate_vllm_prefix_caching(&info) - .and_then(|()| validate_vllm_execution_report(&config, info.execution.as_ref())) { + let validation = if info.task != config.vllm_task { + Err(EngineError::Vllm(format!( + "vLLM worker loaded {:?}, expected {:?}", + info.task, config.vllm_task + ))) + } else { + validate_vllm_prefix_caching(&info) + .and_then(|()| validate_vllm_execution_report(&config, info.execution.as_ref())) + }; + if let Err(error) = validation { self.reset_worker(); return Err(error); } let scheduler_capacity = usize::try_from(effective_vllm_max_num_seqs(&config)) .unwrap_or(usize::MAX) .max(1); + let loaded = LoadedModelInfo { + backend: self.backend_id().to_owned(), + artifact: config.artifact.clone(), + ctx_size: config.ctx_size, + n_ctx_train: if info.n_ctx_train == 0 { + config.ctx_size + } else { + info.n_ctx_train + }, + n_vocab: info.n_vocab, + }; + let worker = self + .worker + .as_ref() + .ok_or_else(|| EngineError::Vllm("loaded vLLM worker is missing".to_owned()))?; + self.loaded_task = Some(config.vllm_task); + self.loaded_execution = has_explicit_execution_profile + .then_some(info.execution.clone()) + .flatten(); + self.loaded_batch_invariant = info.determinism.batch_invariant; + + if config.vllm_task == VllmTask::Embedding { + self.concurrent_embedding = Some(Arc::new(VllmConcurrentEmbedding { + worker: Arc::clone(worker), + next_id: Arc::clone(&self.next_id), + generation_gate: Arc::clone(&self.generation_gate), + generation_epoch: Arc::clone(&self.generation_epoch), + expected_epoch: generation_epoch, + limiter: Arc::new(GenerationLimiter::new(scheduler_capacity)), + })); + self.concurrent_embedding_enabled = scheduler_capacity > 1; + self.loaded_embedding_capacity = Some(scheduler_capacity); + self.loaded = Some(loaded.clone()); + return Ok(loaded); + } + let requested_execution_capacity = usize::try_from(config.vllm_concurrent_generation_capacity.unwrap_or(1)) .unwrap_or(usize::MAX) @@ -8814,21 +10344,6 @@ mod vllm_backend { })]; self.loaded_topology = config.vllm_generation_topology; } - let loaded = LoadedModelInfo { - backend: self.backend_id().to_owned(), - artifact: config.artifact, - ctx_size: config.ctx_size, - n_ctx_train: if info.n_ctx_train == 0 { - config.ctx_size - } else { - info.n_ctx_train - }, - n_vocab: info.n_vocab, - }; - let worker = self - .worker - .as_ref() - .ok_or_else(|| EngineError::Vllm("loaded vLLM worker is missing".to_owned()))?; self.concurrent_generation = Some(Arc::new(VllmConcurrentGeneration { dispatch: VllmGenerationDispatch::Shared(Arc::clone(worker)), next_id: Arc::clone(&self.next_id), @@ -8838,13 +10353,9 @@ mod vllm_backend { limiter: Arc::new(GenerationLimiter::new(execution_capacity)), })); self.concurrent_generation_enabled = execution_capacity > 1; - self.loaded_batch_invariant = info.determinism.batch_invariant; self.loaded_generation_capacity = Some(execution_capacity); self.loaded_kv_cache_size_tokens = info.kv_cache_size_tokens; self.loaded_kv_full_context_capacity = runtime_full_context_capacity; - self.loaded_execution = has_explicit_execution_profile - .then_some(info.execution) - .flatten(); debug_assert!(scheduler_capacity >= execution_capacity); self.loaded = Some(loaded.clone()); Ok(loaded) @@ -9006,6 +10517,7 @@ mod vllm_backend { fn loaded_backend_evidence(&self) -> Option { self.loaded.as_ref()?; let mut evidence = json!({ + "task": self.loaded_task, "determinism": { "batch_invariant": self.loaded_batch_invariant, }, @@ -9013,6 +10525,10 @@ mod vllm_backend { "capacity": self.loaded_generation_capacity.unwrap_or(1), "concurrent": self.concurrent_generation_enabled, }, + "embedding": { + "capacity": self.loaded_embedding_capacity, + "concurrent": self.concurrent_embedding_enabled, + }, }); if let Some(tokens) = self.loaded_kv_cache_size_tokens { evidence["generation"]["runtime_kv_token_capacity"] = json!(tokens); @@ -9055,6 +10571,14 @@ mod vllm_backend { })? } + fn concurrent_embedding_backend(&self) -> Option> { + self.concurrent_embedding_enabled.then(|| { + self.concurrent_embedding + .as_ref() + .map(|backend| Arc::clone(backend) as Arc) + })? + } + fn tokenize(&self, text: &str) -> Result { self.loaded.as_ref().ok_or(EngineError::NotLoaded)?; let _exclusive = self @@ -9076,10 +10600,29 @@ mod vllm_backend { .ok_or(EngineError::NotLoaded)?; ConcurrentGenerationBackend::generate(backend.as_ref(), request, sink, cancellation) } + + fn embed( + &mut self, + request: EmbeddingRequest, + cancellation: &CancellationToken, + ) -> Result { + let backend = self.concurrent_embedding.as_ref().ok_or_else(|| { + if self.loaded.is_some() { + EngineError::InvalidConfig( + "loaded vLLM model is not an embedding runner".to_owned(), + ) + } else { + EngineError::NotLoaded + } + })?; + ConcurrentEmbeddingBackend::embed(backend.as_ref(), request, cancellation) + } } #[derive(Debug, Deserialize)] struct WorkerLoadInfo { + #[serde(default)] + task: VllmTask, #[serde(default)] prefix_caching: bool, #[serde(default)] @@ -9141,7 +10684,9 @@ mod vllm_backend { fn validate_vllm_prefix_caching(info: &WorkerLoadInfo) -> Result<()> { if !info.prefix_caching { - return Err(EngineError::Vllm("vLLM worker did not confirm mandatory prefix caching".into())); + return Err(EngineError::Vllm( + "vLLM worker did not confirm mandatory prefix caching".into(), + )); } Ok(()) } @@ -9302,8 +10847,16 @@ mod vllm_backend { } fn worker_response_error(message: WorkerMessage) -> EngineError { + if message.error_code.as_deref() == Some("invalid_response_schema") { + return EngineError::InvalidRequest( + message + .error + .unwrap_or_else(|| "unsupported response JSON schema".to_owned()), + ); + } if message.error_code.as_deref() == Some("context_length_exceeded") { - if let (Some(prompt_tokens), Some(ctx_size)) = (message.prompt_tokens, message.ctx_size) { + if let (Some(prompt_tokens), Some(ctx_size)) = (message.prompt_tokens, message.ctx_size) + { if ctx_size > 0 && prompt_tokens >= ctx_size as usize { return EngineError::PromptTooLong { prompt_tokens, @@ -10327,6 +11880,7 @@ exec "$@""#) fn vllm_load_payload(config: &LoadConfig, model_path: &Path) -> Value { let mut payload = json!({ "path": model_path, + "task": config.vllm_task, "ctx_size": config.ctx_size, "max_batch_size": effective_vllm_max_num_seqs(config), "max_num_tokens": config.ubatch_size.max(1), @@ -10491,6 +12045,19 @@ exec "$@""#) } } + #[test] + fn vllm_grammar_error_is_a_request_error_not_an_engine_failure() { + let message = serde_json::from_value(json!({ + "id": 1, "ok": false, "error_code": "invalid_response_schema", + "error": "Grammar error: Unimplemented keys: [\"uniqueItems\"]", + })) + .unwrap(); + assert!(matches!( + worker_response_error(message), + EngineError::InvalidRequest(_) + )); + } + #[test] fn vllm_fatal_engine_event_fails_active_and_future_requests() { let router = Arc::new(WorkerRouter::default()); @@ -10873,6 +12440,7 @@ import ast import asyncio import copy import inspect +import math import sys from enum import Enum from types import SimpleNamespace @@ -10883,7 +12451,7 @@ nodes = [node for node in tree.body if isinstance(node, (ast.FunctionDef, ast.As and any(isinstance(target, ast.Name) and target.id.startswith("MAX_") for target in node.targets) )] -namespace = {"asyncio": asyncio, "copy": copy, "inspect": inspect} +namespace = {"asyncio": asyncio, "copy": copy, "inspect": inspect, "math": math} exec(compile(ast.Module(body=nodes, type_ignores=[]), "vllm_worker.py", "exec"), namespace) namespace["configure_deterministic_runtime"] = lambda path: None namespace["model_uses_nvfp4"] = lambda path: nvfp4 @@ -10936,11 +12504,15 @@ def initialize(args): }, "scheduler_config": {"async_scheduling": args.async_scheduling}, "cache_config": {"cache_dtype": getattr(args, "kv_cache_dtype", "auto"), - "enable_prefix_caching": args.enable_prefix_caching, + "enable_prefix_caching": getattr(args, "enable_prefix_caching", False), "mamba_cache_mode": getattr(args, "mamba_cache_mode", None)}, "speculative_config": getattr(args, "speculative_config", None), "compilation_config": getattr(args, "compilation_config", {}), } + config["model_config"].update( + runner_type=getattr(args, "runner", "generate"), + convert_type=getattr(args, "convert", "none"), + ) mutate(config, args) return Engine(object_config(config) if use_objects else config) @@ -10961,6 +12533,13 @@ profile = { } nvfp4 = False mutate = lambda config, args: None +factory = Factory +use_objects = True + +create_engine({"path": profile["path"], "task": "embedding"}) +assert received_kwargs[-1]["runner"] == "pooling" +assert received_kwargs[-1]["convert"] == "embed" +assert received_kwargs[-1]["enable_prefix_caching"] is True # Both construction APIs must read the post-init config, including enum values. for factory in (Factory, initialize): @@ -11222,6 +12801,50 @@ for batches, expected_ids, expected_chunks in [ streamed_text = "".join(chunk["text"] for chunk in chunks) assert streamed_text == result["text"], (streamed_text, result["text"]) assert [chunk["text"] for chunk in chunks] == expected_chunks + +class PoolingParams: + def __init__(self, task, dimensions): + self.task = task + self.dimensions = dimensions + def clone(self): + return PoolingParams(self.task, self.dimensions) + +class PoolingEngine: + def __init__(self): + self.started = 0 + self.gate = asyncio.Event() + async def encode(self, *, prompt, pooling_params, request_id): + assert pooling_params.task == "embed" + assert pooling_params.dimensions is None + index = int(request_id.rsplit("-", 1)[1]) + self.started += 1 + if self.started == 2: + self.gate.set() + await asyncio.wait_for(self.gate.wait(), 1) + yield SimpleNamespace( + outputs=SimpleNamespace(data=([3.0, 4.0, 12.0] if index == 0 else [0.0, 5.0, 12.0])), + prompt_token_ids=list(range(index + 2)), + finished=True, + ) + +pooling_engine = PoolingEngine() +namespace.update({ + "engine": pooling_engine, + "worker_task": "embedding", + "embedding_engine_request_ids": {}, + "engine_health_monitor": None, + "request_cancelled": lambda request_id: False, + "import_attr": lambda candidates: PoolingParams, +}) +embedding = asyncio.run(namespace["async_handle_embed"]( + 77, {"inputs": ["first", "second"], "dimensions": 2} +)) +assert pooling_engine.started == 2 +assert embedding["embeddings"] == [[0.6, 0.8], [0.0, 1.0]] +assert embedding["usage"] == { + "prompt_tokens": 5, "completion_tokens": 0, "total_tokens": 5 +} +assert namespace["embedding_engine_request_ids"] == {} print("ok") "#; let mut child = std::process::Command::new("python3") @@ -11780,6 +13403,54 @@ printf '%s\n' '{"id":1,"type":"response","ok":true,"result":{"prefix_caching":tr assert_eq!(payload["max_num_tokens"], json!(512)); } + #[test] + fn vllm_embedding_runner_loads_without_generation_kv_and_preserves_batch_order() { + let root = unique_test_root("vllm-embedding-runner"); + let python = root.join("bin/python"); + let model = root.join("checkpoint/model.safetensors"); + fs::create_dir_all(python.parent().expect("python parent")).unwrap(); + fs::create_dir_all(model.parent().expect("model parent")).unwrap(); + let script = r#"#!/bin/sh +IFS= read -r load_request +printf '%s\n' '{"id":1,"type":"response","ok":true,"result":{"task":"embedding","prefix_caching":true,"n_ctx_train":32768,"n_vocab":151936,"determinism":{"batch_invariant":true}}}' +IFS= read -r embed_request +printf '%s\n' '{"id":2,"type":"response","ok":true,"result":{"embeddings":[[0.1,0.2,0.3],[0.4,0.5,0.6]],"usage":{"prompt_tokens":7,"completion_tokens":0,"total_tokens":7}}}' +IFS= read -r shutdown_request +"#; + write_fake_vllm_worker(&python, &model, script); + + let mut backend = VllmBackend::with_python(&python).unwrap(); + let mut config = LoadConfig::vllm_safetensors(&model); + config.vllm_task = VllmTask::Embedding; + config.ctx_size = 32_768; + config.vllm_max_num_seqs = Some(4); + config.backend_cache_dir = Some(root.join("cache")); + backend.load(config).expect("load embedding runner"); + + assert!(backend.prefix_caching_enabled()); + assert_eq!( + backend + .concurrent_embedding_backend() + .expect("concurrent embedding handle") + .capacity(), + 4 + ); + let output = backend + .embed( + EmbeddingRequest::many(["first", "second"]).with_dimensions(3), + &CancellationToken::new(), + ) + .expect("embedding result"); + assert_eq!( + output.embeddings, + vec![vec![0.1, 0.2, 0.3], vec![0.4, 0.5, 0.6]] + ); + assert_eq!(output.usage, UsageCounters::new(7, 0)); + + drop(backend); + let _ = fs::remove_dir_all(root); + } + #[test] fn vllm_execution_payload_preserves_legacy_absence_and_carries_explicit_values() { let legacy = LoadConfig::vllm_safetensors("/tmp/checkpoint"); @@ -12180,7 +13851,8 @@ read shutdown "vllm_mtp_num_speculative_tokens": null, })), ] { - let mut result = json!({"prefix_caching": true, "n_ctx_train": 4096, "n_vocab": 32000}); + let mut result = + json!({"prefix_caching": true, "n_ctx_train": 4096, "n_vocab": 32000}); if let Some(execution) = execution { result["execution"] = execution; } @@ -14115,14 +15787,20 @@ mod tests { #[test] fn tool_strict_flag_round_trips_without_weakening_executor_validation() { - let mut tool = ToolSpec::new("edit_file", json!({ - "type":"object", "properties":{"old_text":{"type":"string", "minLength":1}}, - "required":["old_text"] - })); + let mut tool = ToolSpec::new( + "edit_file", + json!({ + "type":"object", "properties":{"old_text":{"type":"string", "minLength":1}}, + "required":["old_text"] + }), + ); for strict in [false, true] { tool.strict = strict; let encoded = serde_json::to_value(&tool).unwrap(); - assert_eq!(encoded.get("strict").and_then(Value::as_bool), strict.then_some(true)); + assert_eq!( + encoded.get("strict").and_then(Value::as_bool), + strict.then_some(true) + ); let decoded: ToolSpec = serde_json::from_value(encoded).unwrap(); assert_eq!(decoded, tool); assert!(validate_tool_call_arguments(&decoded, &json!({"old_text":""})).is_err()); diff --git a/crates/mayhem-engine/src/openai_compatible.rs b/crates/mayhem-engine/src/openai_compatible.rs new file mode 100644 index 00000000..682eb4f0 --- /dev/null +++ b/crates/mayhem-engine/src/openai_compatible.rs @@ -0,0 +1,2848 @@ +use std::collections::{BTreeMap, BTreeSet}; +use std::ffi::OsStr; +use std::net::IpAddr; +use std::path::Path; +use std::sync::atomic::{AtomicU64, Ordering}; +use std::sync::{mpsc, Arc, Condvar, Mutex}; +use std::thread; +use std::time::{Duration, Instant}; + +use futures_util::StreamExt; +use reqwest::{Client, Url}; +use serde::{Deserialize, Serialize}; +use serde_json::{json, Map, Value}; + +use crate::{ + verify_artifact, CancellationToken, ConcurrentGenerationBackend, EngineBackend, EngineError, + FinishReason, GenerateOutput, GenerateRequest, GenerateSpecialityTarget, GrammarSpec, + LoadConfig, LoadedModelInfo, Result, TokenChunk, TokenSink, Tokenization, ToolSpec, + UsageCounters, +}; + +const BACKEND_ID: &str = "openai-compatible"; +const REQUEST_POLL_INTERVAL: Duration = Duration::from_millis(10); +const IDENTITY_PROBE_INTERVAL: Duration = Duration::from_secs(10); +const IDENTITY_RETRY_INTERVAL: Duration = Duration::from_secs(2); +const IDENTITY_UNAVAILABLE_GRACE: Duration = Duration::from_secs(30); +const IDENTITY_REQUEST_TIMEOUT: Duration = Duration::from_secs(5); +const OUTPUT_SHAPE_DIAGNOSTIC_LIMIT: u64 = 64; +static OUTPUT_SHAPE_DIAGNOSTIC_EMITTED: AtomicU64 = AtomicU64::new(0); + +fn output_shape_diagnostics_enabled() -> bool { + std::env::var_os("MAYHEM_PROVIDER_OUTPUT_DIAGNOSTICS").as_deref() == Some(OsStr::new("1")) + || std::env::var_os("MAYHEM_PROVIDER_OUTPUT_DIAGNOSTICS_FILE") + .is_some_and(|path| Path::new(&path).is_file()) +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +struct NativeResponseShape { + finish_reason: &'static str, + content_bytes: usize, + reasoning_bytes: usize, + tool_calls: usize, + // SSE arguments are fragments. Validate only the complete string after + // joining the native fragments for each tool-call index. + fragment_bytes_match: bool, + native_arguments_valid_json: bool, + envelope_arguments_valid_json: bool, +} + +impl NativeResponseShape { + fn anomalous(self) -> bool { + !self.native_arguments_valid_json + || !self.envelope_arguments_valid_json + || (self.content_bytes == 0 && self.tool_calls == 0) + } +} + +fn log_native_response_shape(shape: NativeResponseShape) { + if !shape.anomalous() || !output_shape_diagnostics_enabled() { + return; + } + if OUTPUT_SHAPE_DIAGNOSTIC_EMITTED + .fetch_update(Ordering::Relaxed, Ordering::Relaxed, |count| { + (count < OUTPUT_SHAPE_DIAGNOSTIC_LIMIT).then_some(count + 1) + }) + .is_err() + { + return; + } + eprintln!( + "[provider-output-shape] finish_reason={} content_bytes={} reasoning_bytes={} tool_calls={} fragment_bytes_match={} native_arguments_valid_json={} envelope_arguments_valid_json={}", + shape.finish_reason, + shape.content_bytes, + shape.reasoning_bytes, + shape.tool_calls, + shape.fragment_bytes_match, + shape.native_arguments_valid_json, + shape.envelope_arguments_valid_json, + ); +} +const PREFLIGHT_TIMEOUT: Duration = Duration::from_secs(45); +const ALLOWED_CAPABILITIES: &[&str] = &[ + "cancellation", + "image", + "json", + "prefix_cache", + "reasoning", + "streaming", + "tools", + "video", +]; + +/// Runtime and serving identity committed by the signed catalog artifact. +/// The operator supplies the loopback origin separately; network topology is +/// deliberately not catalog data. +#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct OpenAiCompatibleRuntimeBinding { + pub schema_version: u32, + pub lifecycle: OpenAiCompatibleLifecycle, + pub runtime_id: String, + pub runtime_version: String, + pub runtime_revision: String, + pub implementation: String, + pub implementation_version: String, + pub container_image_digest: String, + pub served_model: String, + pub native_context: u32, + pub served_context: u32, + pub max_concurrent: u32, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub calibrated_peak_gpu_memory_bytes: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub calibrated_peak_host_memory_bytes: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub memory_measurement_source: Option, + pub model_snapshot_file_count: u32, + pub snapshot_manifest_sidecar: String, + pub snapshot_manifest_sha256: String, + pub runtime_recipe_sidecar: String, + pub runtime_recipe_sha256: String, + pub capabilities: BTreeSet, + pub server_info_checks: BTreeMap, + pub preflight: OpenAiCompatiblePreflightProfile, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub prefix_cache_metric: Option, +} + +/// Signed request controls and budgets used to prove live capabilities. These +/// are catalog data because a model's default reasoning mode can consume a +/// short probe before it emits text, JSON, or a tool call. +#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct OpenAiCompatiblePreflightProfile { + pub non_reasoning_chat_template_kwargs: BTreeMap, + pub reasoning_chat_template_kwargs: BTreeMap, + pub streaming_max_tokens: u32, + pub tools_max_tokens: u32, + pub json_max_tokens: u32, + pub reasoning_max_tokens: u32, + pub cache_max_tokens: u32, + pub cancellation_max_tokens: u32, + pub concurrency_max_tokens: u32, + pub cancellation_idle_metrics: BTreeSet, + pub concurrency_active_metric: String, +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "snake_case")] +pub enum OpenAiCompatibleLifecycle { + ManagedOrVerifiedAttach, +} + +impl OpenAiCompatibleRuntimeBinding { + pub fn validate(&self) -> Result<()> { + if self.schema_version != 1 { + return Err(invalid("runtime binding schema_version must be 1")); + } + for (name, value) in [ + ("runtime_id", self.runtime_id.as_str()), + ("runtime_version", self.runtime_version.as_str()), + ("implementation", self.implementation.as_str()), + ( + "implementation_version", + self.implementation_version.as_str(), + ), + ] { + if !safe_identity(value) { + return Err(invalid(format!( + "runtime binding {name} must be a safe non-empty identifier" + ))); + } + } + if !matches!(self.runtime_revision.len(), 40 | 64) + || !self + .runtime_revision + .bytes() + .all(|byte| byte.is_ascii_hexdigit()) + || self.runtime_revision != self.runtime_revision.to_ascii_lowercase() + { + return Err(invalid( + "runtime binding runtime_revision must be exact lowercase 20- or 32-byte hex", + )); + } + let Some(container_digest) = self.container_image_digest.strip_prefix("sha256:") else { + return Err(invalid( + "runtime binding container_image_digest must use sha256:", + )); + }; + if container_digest.len() != 64 + || !container_digest + .bytes() + .all(|byte| byte.is_ascii_hexdigit()) + || container_digest != container_digest.to_ascii_lowercase() + { + return Err(invalid( + "runtime binding container_image_digest must be exact lowercase SHA-256", + )); + } + if self.served_model.trim().is_empty() + || self.served_model.len() > 256 + || self.served_model.chars().any(char::is_control) + { + return Err(invalid( + "runtime binding served_model must be a non-empty printable value of at most 256 bytes", + )); + } + if self.native_context == 0 || self.served_context < self.native_context { + return Err(invalid( + "runtime binding contexts must be positive and served_context must cover native_context", + )); + } + if !(1..=64).contains(&self.max_concurrent) { + return Err(invalid( + "runtime binding max_concurrent must be between 1 and 64", + )); + } + match ( + self.calibrated_peak_gpu_memory_bytes, + self.calibrated_peak_host_memory_bytes, + self.memory_measurement_source.as_deref(), + ) { + (None, None, None) => {} + (Some(gpu), Some(host), Some(source)) + if gpu > 0 && host > 0 && !source.trim().is_empty() && source.len() <= 1024 => {} + _ => { + return Err(invalid( + "runtime binding calibrated GPU/host peak bytes and memory measurement source must be supplied together", + )); + } + } + if self.model_snapshot_file_count == 0 { + return Err(invalid( + "runtime binding model_snapshot_file_count must be positive", + )); + } + for (label, sidecar) in [ + ( + "snapshot_manifest_sidecar", + self.snapshot_manifest_sidecar.as_str(), + ), + ( + "runtime_recipe_sidecar", + self.runtime_recipe_sidecar.as_str(), + ), + ] { + if !safe_identity(sidecar) { + return Err(invalid(format!( + "runtime binding {label} must be a safe sidecar name" + ))); + } + } + if self.snapshot_manifest_sidecar == self.runtime_recipe_sidecar { + return Err(invalid( + "runtime binding snapshot and recipe sidecars must be distinct", + )); + } + for (label, digest) in [ + ( + "snapshot_manifest_sha256", + self.snapshot_manifest_sha256.as_str(), + ), + ("runtime_recipe_sha256", self.runtime_recipe_sha256.as_str()), + ] { + if digest.len() != 64 + || !digest.bytes().all(|byte| byte.is_ascii_hexdigit()) + || digest != digest.to_ascii_lowercase() + { + return Err(invalid(format!( + "runtime binding {label} must be exact lowercase SHA-256" + ))); + } + } + if !self.capabilities.contains("streaming") { + return Err(invalid( + "runtime binding capabilities must include streaming", + )); + } + for capability in &self.capabilities { + if !ALLOWED_CAPABILITIES.contains(&capability.as_str()) { + return Err(invalid(format!( + "runtime binding contains unsupported capability {capability}" + ))); + } + } + if self.server_info_checks.is_empty() || self.server_info_checks.len() > 32 { + return Err(invalid( + "runtime binding server_info_checks must contain 1 to 32 exact checks", + )); + } + for (pointer, expected) in &self.server_info_checks { + if !pointer.starts_with('/') + || pointer.len() > 256 + || matches!(expected, Value::Array(_) | Value::Object(_)) + { + return Err(invalid(format!( + "runtime binding server_info check {pointer:?} must be a JSON pointer to a scalar" + ))); + } + } + self.preflight + .validate(self.capabilities.contains("cancellation"))?; + if self.capabilities.contains("prefix_cache") { + let metric = self + .prefix_cache_metric + .as_deref() + .ok_or_else(|| invalid("prefix_cache capability requires prefix_cache_metric"))?; + if !safe_metric_name(metric) { + return Err(invalid( + "runtime binding prefix_cache_metric must be a safe Prometheus metric name", + )); + } + } else if self.prefix_cache_metric.is_some() { + return Err(invalid( + "runtime binding prefix_cache_metric requires prefix_cache capability", + )); + } + Ok(()) + } +} + +impl OpenAiCompatiblePreflightProfile { + fn validate(&self, cancellation_claimed: bool) -> Result<()> { + for (label, controls) in [ + ( + "non_reasoning_chat_template_kwargs", + &self.non_reasoning_chat_template_kwargs, + ), + ( + "reasoning_chat_template_kwargs", + &self.reasoning_chat_template_kwargs, + ), + ] { + if controls.len() > 16 { + return Err(invalid(format!("preflight {label} has too many controls"))); + } + for (key, value) in controls { + if !safe_identity(key) || matches!(value, Value::Array(_) | Value::Object(_)) { + return Err(invalid(format!( + "preflight {label}.{key} must be a scalar chat-template control" + ))); + } + } + } + for (label, budget) in [ + ("streaming_max_tokens", self.streaming_max_tokens), + ("tools_max_tokens", self.tools_max_tokens), + ("json_max_tokens", self.json_max_tokens), + ("reasoning_max_tokens", self.reasoning_max_tokens), + ("cache_max_tokens", self.cache_max_tokens), + ("cancellation_max_tokens", self.cancellation_max_tokens), + ("concurrency_max_tokens", self.concurrency_max_tokens), + ] { + if !(1..=16_384).contains(&budget) { + return Err(invalid(format!( + "preflight {label} must be between 1 and 16384" + ))); + } + } + if cancellation_claimed && self.cancellation_idle_metrics.is_empty() { + return Err(invalid( + "cancellation capability requires signed cancellation_idle_metrics", + )); + } + for metric in &self.cancellation_idle_metrics { + if !safe_metric_name(metric) { + return Err(invalid(format!( + "preflight cancellation idle metric {metric:?} is invalid" + ))); + } + } + if !safe_metric_name(&self.concurrency_active_metric) { + return Err(invalid( + "preflight concurrency_active_metric must be a safe Prometheus metric name", + )); + } + Ok(()) + } +} + +#[derive(Clone, Debug)] +pub struct OpenAiCompatibleBackendConfig { + pub base_url: String, + pub runtime: OpenAiCompatibleRuntimeBinding, + pub readiness_timeout: Duration, +} + +#[derive(Debug)] +struct GenerationGate { + capacity: usize, + active: Mutex, + changed: Condvar, +} + +impl GenerationGate { + fn acquire(self: &Arc, cancellation: &CancellationToken) -> Result { + let mut active = self + .active + .lock() + .map_err(|_| backend_error("generation gate lock poisoned"))?; + loop { + cancellation.check()?; + if *active < self.capacity { + *active += 1; + return Ok(GenerationPermit { + gate: Arc::clone(self), + }); + } + let (next, _) = self + .changed + .wait_timeout(active, Duration::from_millis(25)) + .map_err(|_| backend_error("generation gate lock poisoned"))?; + active = next; + } + } +} + +struct GenerationPermit { + gate: Arc, +} + +impl Drop for GenerationPermit { + fn drop(&mut self) { + if let Ok(mut active) = self.gate.active.lock() { + *active = active.saturating_sub(1); + self.gate.changed.notify_one(); + } + } +} + +#[derive(Clone)] +struct LoadedBackend { + client: Client, + base_url: Url, + runtime: OpenAiCompatibleRuntimeBinding, + artifact: crate::ModelArtifact, + ctx_size: u32, + proven_capabilities: BTreeSet, + gate: Arc, +} + +pub struct OpenAiCompatibleBackend { + config: OpenAiCompatibleBackendConfig, + loaded: Option>, + identity_health: IdentityHealth, +} + +#[derive(Default)] +struct IdentityHealth { + next_probe_at: Option, + first_unavailable_at: Option, + failed: bool, +} + +enum IdentityProbeError { + Unavailable { + endpoint: &'static str, + reason: String, + }, + Mismatch { + endpoint: &'static str, + reason: String, + }, +} + +impl IdentityProbeError { + fn into_engine_error(self) -> EngineError { + match self { + Self::Unavailable { endpoint, reason } | Self::Mismatch { endpoint, reason } => { + backend_error(format!("/{endpoint} identity check: {reason}")) + } + } + } +} + +impl OpenAiCompatibleBackend { + pub fn new(config: OpenAiCompatibleBackendConfig) -> Result { + config.runtime.validate()?; + validate_loopback_base_url(&config.base_url)?; + Ok(Self { + config, + loaded: None, + identity_health: IdentityHealth::default(), + }) + } + + fn loaded(&self) -> Result> { + self.loaded.clone().ok_or(EngineError::NotLoaded) + } +} + +impl EngineBackend for OpenAiCompatibleBackend { + fn backend_id(&self) -> &'static str { + BACKEND_ID + } + + fn load(&mut self, config: LoadConfig) -> Result { + self.config.runtime.validate()?; + if config.ctx_size == 0 || config.ctx_size > self.config.runtime.native_context { + return Err(invalid(format!( + "requested ctx_size {} exceeds signed native context {}", + config.ctx_size, self.config.runtime.native_context + ))); + } + verify_artifact(&config.artifact)?; + let base_url = validate_loopback_base_url(&self.config.base_url)?; + let client = Client::builder() + .redirect(reqwest::redirect::Policy::none()) + .connect_timeout(Duration::from_secs(5)) + .build() + .map_err(map_http_error)?; + let loaded = Arc::new(LoadedBackend { + client, + base_url, + runtime: self.config.runtime.clone(), + artifact: config.artifact.clone(), + ctx_size: config.ctx_size, + proven_capabilities: BTreeSet::new(), + gate: Arc::new(GenerationGate { + capacity: usize::try_from(self.config.runtime.max_concurrent) + .map_err(|_| invalid("max_concurrent does not fit usize"))?, + active: Mutex::new(0), + changed: Condvar::new(), + }), + }); + let deadline = std::time::Instant::now() + self.config.readiness_timeout; + loop { + match verify_live_identity(&loaded).map_err(IdentityProbeError::into_engine_error) { + Ok(()) => break, + Err(error) if std::time::Instant::now() < deadline => { + thread::sleep(Duration::from_secs(2)); + if std::time::Instant::now() >= deadline { + return Err(error); + } + } + Err(error) => return Err(error), + } + } + let proven_capabilities = preflight_capabilities(&loaded)?; + let mut admitted = Arc::unwrap_or_clone(loaded); + admitted.proven_capabilities = proven_capabilities; + let admitted = Arc::new(admitted); + self.loaded = Some(Arc::clone(&admitted)); + self.identity_health = IdentityHealth::default(); + Ok(LoadedModelInfo { + backend: BACKEND_ID.to_owned(), + artifact: config.artifact, + ctx_size: config.ctx_size, + n_ctx_train: self.config.runtime.native_context, + n_vocab: 0, + }) + } + + fn prefix_caching_enabled(&self) -> bool { + self.loaded + .as_ref() + .is_some_and(|loaded| loaded.proven_capabilities.contains("prefix_cache")) + } + + fn loaded_backend_evidence(&self) -> Option { + let loaded = self.loaded.as_ref()?; + Some(json!({ + "schema_version": 1, + "engine": BACKEND_ID, + "lifecycle": loaded.runtime.lifecycle, + "runtime_id": loaded.runtime.runtime_id, + "runtime_version": loaded.runtime.runtime_version, + "runtime_revision": loaded.runtime.runtime_revision, + "implementation": loaded.runtime.implementation, + "implementation_version": loaded.runtime.implementation_version, + "container_image_digest": loaded.runtime.container_image_digest, + "snapshot_manifest_sha256": loaded.runtime.snapshot_manifest_sha256, + "runtime_recipe_sha256": loaded.runtime.runtime_recipe_sha256, + "served_model": loaded.runtime.served_model, + "native_context": loaded.runtime.native_context, + "served_context": loaded.runtime.served_context, + "max_concurrent": loaded.runtime.max_concurrent, + "ctx_size": loaded.ctx_size, + "artifact_format": loaded.artifact.format, + "proven_capabilities": loaded.proven_capabilities, + })) + } + + fn component_healthy(&mut self) -> bool { + let Some(loaded) = self.loaded.as_ref() else { + return false; + }; + let now = Instant::now(); + if self + .identity_health + .next_probe_at + .is_some_and(|at| now < at) + { + return !self.identity_health.failed; + } + match verify_live_identity(loaded) { + Ok(()) => { + if self.identity_health.first_unavailable_at.take().is_some() { + eprintln!("[provider-identity] runtime identity probe recovered"); + } + self.identity_health.failed = false; + self.identity_health.next_probe_at = Some(Instant::now() + IDENTITY_PROBE_INTERVAL); + } + Err(IdentityProbeError::Unavailable { endpoint, reason }) => { + let now = Instant::now(); + let since = *self + .identity_health + .first_unavailable_at + .get_or_insert_with(|| { + eprintln!( + "[provider-identity] /{endpoint} temporarily unavailable: {reason}" + ); + now + }); + self.identity_health.failed = + now.duration_since(since) >= IDENTITY_UNAVAILABLE_GRACE; + if self.identity_health.failed { + eprintln!("[provider-identity] runtime identity unavailable beyond grace period; endpoint=/{endpoint} reason={reason}"); + } + self.identity_health.next_probe_at = Some(now + IDENTITY_RETRY_INTERVAL); + } + Err(IdentityProbeError::Mismatch { endpoint, reason }) => { + eprintln!("[provider-identity] signed runtime identity mismatch; endpoint=/{endpoint} reason={reason}"); + self.identity_health.failed = true; + self.identity_health.next_probe_at = Some(Instant::now() + IDENTITY_RETRY_INTERVAL); + } + } + !self.identity_health.failed + } + + fn concurrent_generation_backend(&self) -> Option> { + self.loaded.as_ref().map(|loaded| { + Arc::new(OpenAiCompatibleConcurrent { + loaded: Arc::clone(loaded), + }) as Arc + }) + } + + fn tokenize(&self, text: &str) -> Result { + tokenize(self.loaded()?, text) + } + + fn generate( + &mut self, + request: GenerateRequest, + sink: &mut dyn TokenSink, + cancellation: &CancellationToken, + ) -> Result { + generate(self.loaded()?, request, sink, cancellation) + } +} + +struct OpenAiCompatibleConcurrent { + loaded: Arc, +} + +impl ConcurrentGenerationBackend for OpenAiCompatibleConcurrent { + fn capacity(&self) -> usize { + self.loaded.gate.capacity + } + + fn tokenize(&self, text: &str) -> Result { + let tokenization = tokenize(Arc::clone(&self.loaded), text)?; + if !text.is_empty() && tokenization.is_empty() { + return Err(backend_error( + "/v1/tokenize returned no tokens for non-empty input", + )); + } + Ok(tokenization) + } + + fn generate( + &self, + request: GenerateRequest, + sink: &mut dyn TokenSink, + cancellation: &CancellationToken, + ) -> Result { + generate(Arc::clone(&self.loaded), request, sink, cancellation) + } +} + +fn verify_live_identity(loaded: &LoadedBackend) -> std::result::Result<(), IdentityProbeError> { + verify_models_identity(loaded)?; + let server_info = get_identity_json(loaded, "server_info")?; + for (pointer, expected) in &loaded.runtime.server_info_checks { + let actual = server_info + .pointer(pointer) + .ok_or_else(|| IdentityProbeError::Mismatch { + endpoint: "server_info", + reason: format!("omitted signed identity field {pointer}"), + })?; + if actual != expected { + return Err(IdentityProbeError::Mismatch { + endpoint: "server_info", + reason: format!("identity mismatch at {pointer}"), + }); + } + } + Ok(()) +} + +fn verify_models_identity(loaded: &LoadedBackend) -> std::result::Result<(), IdentityProbeError> { + let models = get_identity_json(loaded, "v1/models")?; + let entries = models + .get("data") + .and_then(Value::as_array) + .ok_or_else(|| IdentityProbeError::Mismatch { + endpoint: "v1/models", + reason: "response is missing data".to_owned(), + })?; + let matches = entries + .iter() + .filter(|entry| { + entry.get("id").and_then(Value::as_str) == Some(loaded.runtime.served_model.as_str()) + }) + .collect::>(); + if matches.len() != 1 { + return Err(IdentityProbeError::Mismatch { + endpoint: "v1/models", + reason: "expected signed model ID exactly once".to_owned(), + }); + } + let max_model_len = matches[0] + .get("max_model_len") + .and_then(Value::as_u64) + .ok_or_else(|| IdentityProbeError::Mismatch { + endpoint: "v1/models", + reason: "model is missing max_model_len".to_owned(), + })?; + if max_model_len != u64::from(loaded.runtime.served_context) { + return Err(IdentityProbeError::Mismatch { + endpoint: "v1/models", + reason: "max_model_len differs from signed served_context".to_owned(), + }); + } + Ok(()) +} + +fn preflight_capabilities(loaded: &Arc) -> Result> { + let mut proven = BTreeSet::new(); + let profile = &loaded.runtime.preflight; + let output = generate( + Arc::clone(loaded), + preflight_request( + "Reply with the single word OK.", + profile.streaming_max_tokens, + &profile.non_reasoning_chat_template_kwargs, + ), + &mut crate::NoopTokenSink, + &CancellationToken::new(), + )?; + if output.text.trim().is_empty() { + return Err(backend_error("streaming preflight returned no text")); + } + proven.insert("streaming".to_owned()); + + if loaded.runtime.capabilities.contains("tools") { + let mut request = preflight_request( + "Call the probe tool with ok=true.", + profile.tools_max_tokens, + &profile.non_reasoning_chat_template_kwargs, + ); + request.grammar = Some(GrammarSpec::ToolCall { + tools: vec![ToolSpec { + name: "mayhem_runtime_probe".to_owned(), + description: Some("Report runtime probe success".to_owned()), + parameters: json!({ + "type": "object", + "properties": {"ok": {"type": "boolean"}}, + "required": ["ok"], + "additionalProperties": false, + }), + strict: true, + }], + }); + let output = generate( + Arc::clone(loaded), + request, + &mut crate::NoopTokenSink, + &CancellationToken::new(), + )?; + let calls = serde_json::from_str::(&output.text) + .ok() + .and_then(|value| value.get("tool_calls").cloned()) + .and_then(|value| value.as_array().cloned()) + .unwrap_or_default(); + if calls.is_empty() { + return Err(backend_error( + "tools capability preflight returned no tool call", + )); + } + proven.insert("tools".to_owned()); + } + + if loaded.runtime.capabilities.contains("json") { + let mut request = preflight_request( + "Return a JSON object whose ok field is true.", + profile.json_max_tokens, + &profile.non_reasoning_chat_template_kwargs, + ); + request.grammar = Some(GrammarSpec::JsonSchema { + schema: json!({ + "type": "object", + "properties": {"ok": {"type": "boolean"}}, + "required": ["ok"], + "additionalProperties": false, + }), + }); + let output = generate( + Arc::clone(loaded), + request, + &mut crate::NoopTokenSink, + &CancellationToken::new(), + )?; + serde_json::from_str::(output.text.trim()) + .map_err(|error| backend_error(format!("JSON capability preflight failed: {error}")))?; + proven.insert("json".to_owned()); + } + + if loaded.runtime.capabilities.contains("reasoning") { + let request = preflight_request( + "Think carefully, then answer 1+1.", + profile.reasoning_max_tokens, + &profile.reasoning_chat_template_kwargs, + ); + let output = generate( + Arc::clone(loaded), + request, + &mut crate::NoopTokenSink, + &CancellationToken::new(), + )?; + if !output.text.contains("") || !output.text.contains("") { + return Err(backend_error( + "reasoning capability preflight returned no reasoning_content", + )); + } + proven.insert("reasoning".to_owned()); + } + + if loaded.runtime.capabilities.contains("prefix_cache") { + verify_prefix_cache(loaded)?; + proven.insert("prefix_cache".to_owned()); + } + + if loaded.runtime.capabilities.contains("cancellation") { + verify_cancellation(loaded)?; + proven.insert("cancellation".to_owned()); + } + + if loaded.runtime.max_concurrent > 1 { + verify_concurrency(loaded)?; + proven.insert("concurrency".to_owned()); + } + + // Media is proven later with the signed catalog canaries, before the first + // provider heartbeat. Preserve the claim only after those requests pass. + for capability in ["image", "video"] { + if loaded.runtime.capabilities.contains(capability) { + proven.insert(format!("{capability}_pending_catalog_canary")); + } + } + Ok(proven) +} + +fn preflight_request( + prompt: impl Into, + max_tokens: u32, + chat_template_kwargs: &BTreeMap, +) -> GenerateRequest { + let mut request = GenerateRequest::new(prompt).with_max_new_tokens(max_tokens); + request + .speciality_parameters + .extend(chat_template_kwargs.iter().map(|(native_path, value)| { + crate::GenerateSpecialityParameter { + name: native_path.clone(), + level: "signed".to_owned(), + target: GenerateSpecialityTarget::ChatTemplateKwarg, + native_path: native_path.clone(), + value: value.clone(), + max_reasoning_tokens: None, + } + })); + request +} + +fn verify_prefix_cache(loaded: &Arc) -> Result<()> { + let metric = loaded + .runtime + .prefix_cache_metric + .as_deref() + .ok_or_else(|| invalid("prefix cache preflight has no signed metric"))?; + let prefix = "Mayhem prefix cache live verification sequence. ".repeat(1_500); + let prompt = format!("{prefix}\nReply with CACHE_OK."); + let request = preflight_request( + prompt, + loaded.runtime.preflight.cache_max_tokens, + &loaded.runtime.preflight.non_reasoning_chat_template_kwargs, + ); + generate( + Arc::clone(loaded), + request.clone(), + &mut crate::NoopTokenSink, + &CancellationToken::new(), + )?; + let between = get_text(loaded, "metrics")?; + generate( + Arc::clone(loaded), + request, + &mut crate::NoopTokenSink, + &CancellationToken::new(), + )?; + let after = get_text(loaded, "metrics")?; + verify_prefix_cache_metric_increase(metric, &between, &after) +} + +fn verify_prefix_cache_metric_increase( + metric: &str, + between_metrics: &str, + after_metrics: &str, +) -> Result<()> { + // SGLang creates the labeled counter only after the first cache hit. The + // warm request can therefore leave the signed series absent; that is an + // exact zero baseline. The replay must materialize the same signed series + // and increase it, so absence after replay remains a hard failure. + let between = prometheus_metric_sum_optional(between_metrics, metric)?.unwrap_or(0.0); + let after = prometheus_metric_sum(after_metrics, metric)?; + if after <= between { + return Err(backend_error(format!( + "prefix cache metric {metric} did not increase during repeated-prefix preflight" + ))); + } + Ok(()) +} + +fn verify_cancellation(loaded: &Arc) -> Result<()> { + let cancellation = CancellationToken::new(); + let sink_cancellation = cancellation.clone(); + let mut saw_token = false; + let result = generate( + Arc::clone(loaded), + { + let mut request = preflight_request( + "Write a long technical explanation of distributed consensus.", + loaded.runtime.preflight.cancellation_max_tokens, + &loaded.runtime.preflight.non_reasoning_chat_template_kwargs, + ); + request.ignore_eos = true; + request + }, + &mut |chunk: TokenChunk| { + if !chunk.text.is_empty() { + saw_token = true; + sink_cancellation.cancel(); + } + Ok(()) + }, + &cancellation, + ); + if !saw_token || !matches!(result, Err(EngineError::Cancelled)) { + return Err(backend_error( + "cancellation capability preflight did not abort an active stream", + )); + } + wait_for_scheduler_idle(loaded)?; + let recovered = generate( + Arc::clone(loaded), + preflight_request( + "Reply with RECOVERED.", + loaded.runtime.preflight.streaming_max_tokens, + &loaded.runtime.preflight.non_reasoning_chat_template_kwargs, + ), + &mut crate::NoopTokenSink, + &CancellationToken::new(), + )?; + if recovered.text.trim().is_empty() { + return Err(backend_error( + "runtime did not recover after cancellation preflight", + )); + } + Ok(()) +} + +fn wait_for_scheduler_idle(loaded: &LoadedBackend) -> Result<()> { + let deadline = std::time::Instant::now() + PREFLIGHT_TIMEOUT; + loop { + let metrics = get_text(loaded, "metrics")?; + let all_idle = loaded + .runtime + .preflight + .cancellation_idle_metrics + .iter() + .map(|metric| prometheus_metric_sum(&metrics, metric)) + .collect::>>()? + .into_iter() + .all(|value| value == 0.0); + if all_idle { + return Ok(()); + } + if std::time::Instant::now() >= deadline { + return Err(backend_error( + "runtime scheduler did not become idle after upstream cancellation", + )); + } + thread::sleep(Duration::from_millis(100)); + } +} + +fn verify_concurrency(loaded: &Arc) -> Result<()> { + let count = usize::try_from(loaded.runtime.max_concurrent) + .map_err(|_| invalid("max_concurrent does not fit usize"))?; + let (started_tx, started_rx) = mpsc::channel(); + let mut releases = Vec::new(); + let mut handles = Vec::new(); + for index in 0..count { + let backend = Arc::clone(loaded); + let started = started_tx.clone(); + let (release_tx, release_rx) = mpsc::channel(); + releases.push(release_tx); + handles.push(thread::spawn(move || { + let cancellation = CancellationToken::new(); + let cancel_after_release = cancellation.clone(); + let mut first = true; + let mut request = preflight_request( + format!("Runtime concurrency probe {index}: explain consensus at length."), + backend.runtime.preflight.concurrency_max_tokens, + &backend.runtime.preflight.non_reasoning_chat_template_kwargs, + ); + request.ignore_eos = true; + generate( + backend, + request, + &mut |chunk: TokenChunk| { + if first && !chunk.text.is_empty() { + first = false; + started.send(index).map_err(|_| { + backend_error("concurrency preflight coordinator stopped") + })?; + release_rx.recv_timeout(PREFLIGHT_TIMEOUT).map_err(|_| { + backend_error("concurrency preflight release timed out") + })?; + cancel_after_release.cancel(); + } + Ok(()) + }, + &cancellation, + ) + })); + } + drop(started_tx); + let proof = wait_for_concurrency_proof( + count, + &started_rx, + PREFLIGHT_TIMEOUT, + Duration::from_millis(100), + || { + prometheus_metric_sum( + &get_text(loaded, "metrics")?, + &loaded.runtime.preflight.concurrency_active_metric, + ) + }, + ); + for release in releases { + let _ = release.send(()); + } + let mut worker_error = None; + for handle in handles { + match handle.join() { + Ok(Err(EngineError::Cancelled)) | Ok(Ok(_)) => {} + Ok(Err(error)) if worker_error.is_none() => worker_error = Some(error), + Err(_) if worker_error.is_none() => { + worker_error = Some(backend_error("concurrency preflight worker panicked")); + } + Ok(Err(_)) | Err(_) => {} + } + } + proof?; + if let Some(error) = worker_error { + return Err(error); + } + if loaded.runtime.capabilities.contains("cancellation") { + wait_for_scheduler_idle(loaded)?; + } + Ok(()) +} + +fn wait_for_concurrency_proof( + count: usize, + started_rx: &mpsc::Receiver, + timeout: Duration, + poll_interval: Duration, + mut active_sample: F, +) -> Result<()> +where + F: FnMut() -> Result, +{ + let deadline = std::time::Instant::now() + timeout; + let mut started = BTreeSet::new(); + let mut peak_active = 0.0_f64; + let mut observed_active = false; + loop { + loop { + match started_rx.try_recv() { + Ok(index) if index < count => { + started.insert(index); + } + Ok(_) => { + return Err(backend_error( + "concurrency preflight worker reported an invalid index", + )); + } + Err(mpsc::TryRecvError::Empty) => break, + Err(mpsc::TryRecvError::Disconnected) => break, + } + } + let active = active_sample()?; + peak_active = peak_active.max(active); + observed_active |= active >= count as f64; + if observed_active && started.len() == count { + return Ok(()); + } + if std::time::Instant::now() >= deadline { + return Err(backend_error(format!( + "runtime did not prove {count} concurrent requests: first content from {}/{count}, peak signed active metric {peak_active}", + started.len() + ))); + } + thread::sleep(poll_interval); + } +} + +fn generate( + loaded: Arc, + request: GenerateRequest, + sink: &mut dyn TokenSink, + cancellation: &CancellationToken, +) -> Result { + request.validate_sampling()?; + cancellation.check()?; + let _permit = loaded.gate.acquire(cancellation)?; + let body = chat_request_body(&loaded.runtime.served_model, request)?; + let non_reasoning_kwargs = loaded + .runtime + .preflight + .non_reasoning_chat_template_kwargs + .clone(); + let url = endpoint_url(&loaded.base_url, "v1/chat/completions")?; + // Unbounded delivery keeps cancellation from deadlocking behind a full + // producer queue while the caller is unwinding after a disconnect. + let (events_tx, events_rx) = mpsc::channel(); + let network_cancellation = cancellation.clone(); + let internal_cancellation = CancellationToken::new(); + let worker_cancellation = internal_cancellation.clone(); + let client = loaded.client.clone(); + let error_events = events_tx.clone(); + let worker = thread::spawn(move || { + let result = run_async(move || async move { + stream_completion( + client, + url, + body, + non_reasoning_kwargs, + network_cancellation, + worker_cancellation, + events_tx, + ) + .await + }); + if let Err(error) = result { + let _ = error_events.send(StreamEvent::Error(error)); + } + }); + let mut index = 0u32; + let result = loop { + if cancellation.is_cancelled() { + internal_cancellation.cancel(); + break Err(EngineError::Cancelled); + } + match events_rx.recv_timeout(REQUEST_POLL_INTERVAL) { + Ok(StreamEvent::Text { kind, text }) => { + let chunk = TokenChunk { + index, + token_id: openai_compatible_pseudo_token_id(kind, &text), + text, + }; + index = index.saturating_add(1); + if let Err(error) = sink.on_token(chunk) { + internal_cancellation.cancel(); + break Err(error); + } + } + Ok(StreamEvent::Complete(output)) => break Ok(output), + Ok(StreamEvent::Error(error)) => break Err(error), + Err(mpsc::RecvTimeoutError::Timeout) => continue, + Err(mpsc::RecvTimeoutError::Disconnected) => { + break Err(backend_error( + "stream worker stopped without a terminal event", + )); + } + } + }; + internal_cancellation.cancel(); + if worker.join().is_err() && result.is_ok() { + return Err(backend_error("stream worker panicked")); + } + result +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +enum StreamTextKind { + Content, + Reasoning, + Synthetic, +} + +impl StreamTextKind { + fn domain(self) -> &'static [u8] { + match self { + Self::Content => b"content", + Self::Reasoning => b"reasoning", + Self::Synthetic => b"synthetic", + } + } +} + +fn openai_compatible_pseudo_token_id(kind: StreamTextKind, text: &str) -> i32 { + let mut hasher = blake3::Hasher::new(); + hasher.update(b"mayhem-openai-compatible-sse-pseudo-token-v1\0"); + hasher.update(kind.domain()); + hasher.update(b"\0"); + hasher.update(&u64::try_from(text.len()).unwrap_or(u64::MAX).to_be_bytes()); + hasher.update(text.as_bytes()); + let bytes: [u8; 4] = hasher.finalize().as_bytes()[..4].try_into().unwrap(); + let token_id = i32::from_be_bytes(bytes); + if token_id == 0 { + 1 + } else { + token_id + } +} + +enum StreamEvent { + Text { kind: StreamTextKind, text: String }, + Complete(GenerateOutput), + Error(EngineError), +} + +async fn stream_completion( + client: Client, + url: Url, + body: Value, + non_reasoning_kwargs: BTreeMap, + cancellation: CancellationToken, + internal_cancellation: CancellationToken, + events: mpsc::Sender, +) -> Result<()> { + let first = stream_completion_attempt( + &client, + &url, + &body, + &cancellation, + &internal_cancellation, + &events, + ) + .await?; + let output = if first.unexecuted_reasoning_tool_call + && first.advertised_reasoning_tool_call + && first.output.finish_reason == FinishReason::Stop + && non_reasoning_kwargs.get("enable_thinking") == Some(&Value::Bool(false)) + && body + .get("chat_template_kwargs") + .and_then(|kwargs| kwargs.get("enable_thinking")) + != Some(&Value::Bool(false)) + && body.get("tool_choice") != Some(&Value::String("none".to_owned())) + && body.get("response_format").is_none() + { + // A complete tool instruction in unfinished reasoning is not an + // executable call. Retry this one turn with the runtime's already + // proven non-reasoning profile; never promote private thought to a tool. + let mut retry_body = body.clone(); + let retry = retry_body + .as_object_mut() + .ok_or_else(|| backend_error("chat request body is not an object"))?; + let original_limit = retry + .get("max_tokens") + .and_then(Value::as_u64) + .ok_or_else(|| backend_error("chat request has no output token limit"))?; + let used = u64::from(first.output.usage.completion_tokens); + if used == 0 || original_limit.saturating_sub(used) < 32 { + return Err(backend_error( + "unexecuted reasoning tool call left no verifiable recovery budget", + )); + } + retry.insert("max_tokens".to_owned(), json!(original_limit - used)); + let template = retry + .entry("chat_template_kwargs") + .or_insert_with(|| json!({})) + .as_object_mut() + .ok_or_else(|| backend_error("chat template kwargs are not an object"))?; + template.extend(non_reasoning_kwargs); + let second = stream_completion_attempt( + &client, + &url, + &retry_body, + &cancellation, + &internal_cancellation, + &events, + ) + .await?; + if second.unexecuted_reasoning_tool_call + || (!second.has_tool_calls && !second.has_visible_content) + { + return Err(backend_error( + "tool-call recovery returned neither a structured call nor an answer", + )); + } + if u64::from(second.output.usage.completion_tokens) > original_limit - used { + return Err(backend_error( + "tool-call recovery exceeded the original output token limit", + )); + } + combine_recovered_output(first.output, second.output) + } else if first.unexecuted_reasoning_tool_call { + return Err(backend_error( + "runtime returned an unexecuted tool call inside reasoning", + )); + } else { + first.output + }; + let _ = events.send(StreamEvent::Complete(output)); + Ok(()) +} + +struct StreamAttempt { + output: GenerateOutput, + has_tool_calls: bool, + has_visible_content: bool, + unexecuted_reasoning_tool_call: bool, + advertised_reasoning_tool_call: bool, +} + +fn combine_recovered_output(mut first: GenerateOutput, second: GenerateOutput) -> GenerateOutput { + first.text.push_str(&second.text); + first.usage.completion_tokens = first + .usage + .completion_tokens + .saturating_add(second.usage.completion_tokens); + first.usage.reasoning_tokens = first + .usage + .reasoning_tokens + .saturating_add(second.usage.reasoning_tokens); + first.usage.total_tokens = first + .usage + .prompt_tokens + .saturating_add(first.usage.completion_tokens); + first.finish_reason = second.finish_reason; + first +} + +async fn stream_completion_attempt( + client: &Client, + url: &Url, + body: &Value, + cancellation: &CancellationToken, + internal_cancellation: &CancellationToken, + events: &mpsc::Sender, +) -> Result { + let response = tokio::select! { + () = wait_cancelled(&cancellation, &internal_cancellation) => { + return Err(EngineError::Cancelled); + } + response = client.post(url.clone()).json(body).send() => response.map_err(map_http_error)?, + }; + if !response.status().is_success() { + let status = response.status(); + let body = response.text().await.unwrap_or_default(); + return Err(backend_error(format!( + "chat completion returned HTTP {status}: {}", + bounded_error_text(&body) + ))); + } + let mut bytes = response.bytes_stream(); + let mut pending = Vec::new(); + let mut collector = StreamCollector::default(); + loop { + let next = tokio::select! { + () = wait_cancelled(&cancellation, &internal_cancellation) => { + return Err(EngineError::Cancelled); + } + next = bytes.next() => next, + }; + let Some(next) = next else { break }; + let chunk = next.map_err(map_http_error)?; + pending.extend_from_slice(&chunk); + while let Some(newline) = pending.iter().position(|byte| *byte == b'\n') { + let mut line = pending.drain(..=newline).collect::>(); + line.pop(); + if line.last() == Some(&b'\r') { + line.pop(); + } + let line = std::str::from_utf8(&line) + .map_err(|_| backend_error("chat completion SSE line was not UTF-8"))?; + if let Some(data) = line.strip_prefix("data:") { + let data = data.trim(); + if data == "[DONE]" { + if output_shape_diagnostics_enabled() + && OUTPUT_SHAPE_DIAGNOSTIC_EMITTED.load(Ordering::Relaxed) + < OUTPUT_SHAPE_DIAGNOSTIC_LIMIT + { + log_native_response_shape(collector.response_shape()); + } + let unexecuted_reasoning_tool_call = + collector.unexecuted_reasoning_tool_call(body); + let advertised_reasoning_tool_call = + collector.advertised_reasoning_tool_call(body); + let has_tool_calls = !collector.tool_calls.is_empty(); + let has_visible_content = !collector.text.trim().is_empty(); + let output = collector.finish(&events)?; + return Ok(StreamAttempt { + output, + has_tool_calls, + has_visible_content, + unexecuted_reasoning_tool_call, + advertised_reasoning_tool_call, + }); + } + if !data.is_empty() { + let value: Value = serde_json::from_str(data).map_err(|error| { + backend_error(format!("invalid chat completion SSE JSON: {error}")) + })?; + collector.push(value, &events)?; + } + } + } + } + Err(backend_error("chat completion stream ended before [DONE]")) +} + +#[derive(Default)] +struct StreamCollector { + text: String, + reasoning: String, + reasoning_open: bool, + reasoning_closed: bool, + tool_calls: BTreeMap, + usage: UsageCounters, + finish_reason: Option, + native_finish_reason: Option<&'static str>, +} + +#[derive(Default)] +struct ToolCallAccumulator { + id: String, + name: String, + arguments: String, + argument_fragment_bytes: usize, +} + +impl StreamCollector { + fn response_shape(&self) -> NativeResponseShape { + let native_arguments_valid_json = self.tool_calls.values().all(|call| { + serde_json::from_str::(&call.arguments) + .is_ok_and(|arguments| arguments.is_object()) + }); + // Check the escaped envelope delivered to Core separately from the + // native SSE argument string. The fragment byte count proves that the + // stream join did not drop bytes; neither check retains argument text. + let envelope_arguments_valid_json = self.tool_calls.values().all(|call| { + serde_json::to_string(&json!({ + "tool_calls": [{"function": {"arguments": call.arguments}}] + })) + .ok() + .and_then(|envelope| serde_json::from_str::(&envelope).ok()) + .and_then(|parsed| { + parsed["tool_calls"][0]["function"]["arguments"] + .as_str() + .map(str::to_owned) + }) + .is_some_and(|arguments| { + serde_json::from_str::(&arguments).is_ok_and(|value| value.is_object()) + }) + }); + NativeResponseShape { + finish_reason: self.native_finish_reason.unwrap_or("missing"), + content_bytes: self.text.len(), + reasoning_bytes: self.reasoning.len(), + tool_calls: self.tool_calls.len(), + fragment_bytes_match: self + .tool_calls + .values() + .all(|call| call.argument_fragment_bytes == call.arguments.len()), + native_arguments_valid_json, + envelope_arguments_valid_json, + } + } + + fn unexecuted_reasoning_tool_call(&self, body: &Value) -> bool { + if !self.tool_calls.is_empty() + || !self.text.trim().is_empty() + || self.finish_reason.is_none() + || body.get("tools").and_then(Value::as_array).is_none() + { + return false; + } + let reasoning = self.reasoning.trim_end(); + (reasoning.contains("") && reasoning.ends_with("")) + || (reasoning.contains("") && reasoning.ends_with("")) + } + + fn advertised_reasoning_tool_call(&self, body: &Value) -> bool { + let names = self + .reasoning + .split("').map(|(name, _)| name.trim())) + .collect::>(); + if names.is_empty() { + return false; + } + body.get("tools") + .and_then(Value::as_array) + .is_some_and(|tools| { + names.iter().all(|name| { + tools.iter().any(|tool| { + tool.get("function") + .and_then(|function| function.get("name")) + .and_then(Value::as_str) + == Some(*name) + }) + }) + }) + } + + fn push(&mut self, value: Value, events: &mpsc::Sender) -> Result<()> { + if let Some(usage) = value.get("usage") { + self.usage = parse_usage(usage); + } + for choice in value + .get("choices") + .and_then(Value::as_array) + .into_iter() + .flatten() + { + if let Some(reason) = choice.get("finish_reason").and_then(Value::as_str) { + self.native_finish_reason = Some(match reason { + "stop" => "stop", + "length" => "length", + "tool_calls" => "tool_calls", + "content_filter" => "content_filter", + _ => "other", + }); + self.finish_reason = Some(if reason == "length" { + FinishReason::Length + } else { + FinishReason::Stop + }); + } + let Some(delta) = choice.get("delta").and_then(Value::as_object) else { + continue; + }; + if let Some(reasoning) = delta.get("reasoning_content").and_then(Value::as_str) { + if !reasoning.is_empty() { + if !self.reasoning_open { + self.reasoning_open = true; + events + .send(StreamEvent::Text { + kind: StreamTextKind::Synthetic, + text: "".to_owned(), + }) + .map_err(|_| EngineError::Cancelled)?; + } + self.reasoning.push_str(reasoning); + events + .send(StreamEvent::Text { + kind: StreamTextKind::Reasoning, + text: reasoning.to_owned(), + }) + .map_err(|_| EngineError::Cancelled)?; + } + } + if let Some(content) = delta.get("content").and_then(Value::as_str) { + if !content.is_empty() { + self.close_reasoning(events)?; + self.text.push_str(content); + events + .send(StreamEvent::Text { + kind: StreamTextKind::Content, + text: content.to_owned(), + }) + .map_err(|_| EngineError::Cancelled)?; + } + } + for call in delta + .get("tool_calls") + .and_then(Value::as_array) + .into_iter() + .flatten() + { + let index = call.get("index").and_then(Value::as_u64).unwrap_or(0); + let index = usize::try_from(index) + .map_err(|_| backend_error("tool call index does not fit usize"))?; + let target = self.tool_calls.entry(index).or_default(); + if let Some(id) = call.get("id").and_then(Value::as_str) { + target.id.push_str(id); + } + if let Some(function) = call.get("function") { + if let Some(name) = function.get("name").and_then(Value::as_str) { + target.name.push_str(name); + } + if let Some(arguments) = function.get("arguments").and_then(Value::as_str) { + target.argument_fragment_bytes = target + .argument_fragment_bytes + .saturating_add(arguments.len()); + target.arguments.push_str(arguments); + } + } + } + } + Ok(()) + } + + fn close_reasoning(&mut self, events: &mpsc::Sender) -> Result<()> { + if self.reasoning_open && !self.reasoning_closed { + self.reasoning_closed = true; + events + .send(StreamEvent::Text { + kind: StreamTextKind::Synthetic, + text: "".to_owned(), + }) + .map_err(|_| EngineError::Cancelled)?; + } + Ok(()) + } + + fn finish(mut self, events: &mpsc::Sender) -> Result { + self.close_reasoning(events)?; + let mut output = mayhem_proto::openai_compatible_canary_output(&self.reasoning, &self.text); + if !self.tool_calls.is_empty() { + let calls = self + .tool_calls + .into_values() + .map(|call| { + json!({ + "id": call.id, + "type": "function", + "function": { + "name": call.name, + "arguments": call.arguments, + } + }) + }) + .collect::>(); + let envelope = serde_json::to_string(&json!({"tool_calls": calls}))?; + events + .send(StreamEvent::Text { + kind: StreamTextKind::Synthetic, + text: envelope.clone(), + }) + .map_err(|_| EngineError::Cancelled)?; + output.push_str(&envelope); + } + Ok(GenerateOutput { + text: output, + usage: self.usage, + finish_reason: self.finish_reason.unwrap_or(FinishReason::Stop), + }) + } +} + +fn chat_request_body(model: &str, request: GenerateRequest) -> Result { + let messages = if request.messages.is_empty() { + vec![json!({"role": "user", "content": request.prompt})] + } else { + normalize_openai_chat_messages(request.messages)? + }; + let mut body = Map::from_iter([ + ("model".to_owned(), json!(model)), + ("messages".to_owned(), Value::Array(messages)), + ("max_tokens".to_owned(), json!(request.max_new_tokens)), + ("stream".to_owned(), json!(true)), + ("stream_options".to_owned(), json!({"include_usage": true})), + ]); + insert_optional(&mut body, "temperature", request.temperature); + insert_optional(&mut body, "top_p", request.top_p); + insert_optional(&mut body, "top_k", request.top_k); + insert_optional(&mut body, "min_p", request.min_p); + insert_optional(&mut body, "repetition_penalty", request.repeat_penalty); + insert_optional(&mut body, "frequency_penalty", request.frequency_penalty); + insert_optional(&mut body, "presence_penalty", request.presence_penalty); + insert_optional(&mut body, "seed", request.seed); + if !request.stop.is_empty() { + body.insert("stop".to_owned(), json!(request.stop)); + } + if request.ignore_eos { + body.insert("ignore_eos".to_owned(), json!(true)); + } + if !request.tools.is_empty() { + body.insert("tools".to_owned(), Value::Array(request.tools)); + } + if let Some(parallel) = request.parallel_tool_calls { + body.insert("parallel_tool_calls".to_owned(), json!(parallel)); + } + if let Some(grammar) = request.grammar { + match grammar { + GrammarSpec::JsonSchema { schema } => { + body.insert( + "response_format".to_owned(), + json!({ + "type": "json_schema", + "json_schema": {"name": "mayhem_response", "schema": schema, "strict": true} + }), + ); + } + GrammarSpec::ToolCall { tools } => { + if !body.contains_key("tools") { + body.insert( + "tools".to_owned(), + Value::Array(tools.into_iter().map(openai_tool).collect()), + ); + } + body.insert("tool_choice".to_owned(), json!("required")); + } + GrammarSpec::Gbnf { .. } => { + return Err(EngineError::InvalidRequest( + "OpenAI-compatible runtimes do not have a portable GBNF request field" + .to_owned(), + )); + } + } + } + let mut template_kwargs = Map::new(); + for parameter in request.speciality_parameters { + match parameter.target { + GenerateSpecialityTarget::ChatTemplateKwarg => { + template_kwargs.insert(parameter.native_path, parameter.value); + } + GenerateSpecialityTarget::SamplingParameter + | GenerateSpecialityTarget::BackendParameter => { + body.insert(parameter.native_path, parameter.value); + } + GenerateSpecialityTarget::PromptSuffix => { + return Err(EngineError::InvalidRequest( + "OpenAI-compatible message requests cannot append a prompt suffix".to_owned(), + )); + } + } + } + if !template_kwargs.is_empty() { + body.insert( + "chat_template_kwargs".to_owned(), + Value::Object(template_kwargs), + ); + } + Ok(Value::Object(body)) +} + +fn normalize_openai_chat_messages(mut messages: Vec) -> Result> { + for (message_index, message) in messages.iter_mut().enumerate() { + let Some(parts) = message.get_mut("content").and_then(Value::as_array_mut) else { + continue; + }; + for (part_index, part) in parts.iter_mut().enumerate() { + if part.get("type").and_then(Value::as_str) != Some("video") { + continue; + } + let video = part + .get("video") + .and_then(Value::as_object) + .ok_or_else(|| { + EngineError::InvalidRequest(format!( + "message {message_index} content part {part_index} has no video descriptor" + )) + })?; + let url = match ( + video.get("url").and_then(Value::as_str), + video.get("data").and_then(Value::as_str), + ) { + (Some(url), None) if !url.trim().is_empty() => url.to_owned(), + (None, Some(data)) if !data.is_empty() => { + let content_type = video + .get("content_type") + .and_then(Value::as_str) + .filter(|content_type| valid_video_content_type(content_type)) + .ok_or_else(|| { + EngineError::InvalidRequest(format!( + "message {message_index} content part {part_index} has no valid video content_type" + )) + })?; + format!("data:{content_type};base64,{data}") + } + (Some(_), Some(_)) => { + return Err(EngineError::InvalidRequest(format!( + "message {message_index} content part {part_index} has ambiguous video data and url" + ))); + } + _ => { + return Err(EngineError::InvalidRequest(format!( + "message {message_index} content part {part_index} has no usable video data or url" + ))); + } + }; + *part = json!({"type": "video_url", "video_url": {"url": url}}); + } + } + Ok(messages) +} + +fn valid_video_content_type(content_type: &str) -> bool { + content_type.strip_prefix("video/").is_some_and(|subtype| { + !subtype.is_empty() + && subtype.bytes().all(|byte| { + byte.is_ascii_alphanumeric() + || matches!( + byte, + b'!' | b'#' | b'$' | b'&' | b'^' | b'_' | b'.' | b'+' | b'-' + ) + }) + }) +} + +fn openai_tool(tool: ToolSpec) -> Value { + json!({ + "type": "function", + "function": { + "name": tool.name, + "description": tool.description, + "parameters": tool.parameters, + "strict": tool.strict, + } + }) +} + +fn insert_optional(body: &mut Map, key: &str, value: Option) { + if let Some(value) = value { + body.insert(key.to_owned(), json!(value)); + } +} + +fn tokenize(loaded: Arc, text: &str) -> Result { + let client = loaded.client.clone(); + let url = endpoint_url(&loaded.base_url, "v1/tokenize")?; + let model = loaded.runtime.served_model.clone(); + let prompt = text.to_owned(); + let response = run_async(move || async move { + let response = client + .post(url) + .json(&json!({"model": model, "prompt": prompt})) + .send() + .await + .map_err(map_http_error)?; + json_response(response, "/v1/tokenize").await + })?; + let tokens = response + .get("tokens") + .and_then(Value::as_array) + .ok_or_else(|| backend_error("/v1/tokenize response is missing tokens"))?; + let token_ids = tokens + .iter() + .map(|token| { + token + .as_i64() + .and_then(|token| i32::try_from(token).ok()) + .ok_or_else(|| backend_error("/v1/tokenize returned a token outside i32")) + }) + .collect::>>()?; + Ok(Tokenization { token_ids }) +} + +fn get_identity_json( + loaded: &LoadedBackend, + path: &'static str, +) -> std::result::Result { + let client = loaded.client.clone(); + let url = + endpoint_url(&loaded.base_url, path).map_err(|error| IdentityProbeError::Unavailable { + endpoint: path, + reason: error.to_string(), + })?; + run_async(move || async move { + let response = client + .get(url) + .timeout(IDENTITY_REQUEST_TIMEOUT) + .send() + .await + .map_err(|error| { + if error.is_timeout() { + backend_error("request timed out") + } else if error.is_connect() { + backend_error("connection failed") + } else { + backend_error("request failed") + } + })?; + if !response.status().is_success() { + return Err(backend_error(format!( + "HTTP {}", + response.status().as_u16() + ))); + } + response + .json() + .await + .map_err(|_| backend_error("invalid JSON response")) + }) + .map_err(|error| IdentityProbeError::Unavailable { + endpoint: path, + reason: error.to_string(), + }) +} + +fn get_text(loaded: &LoadedBackend, path: &str) -> Result { + let client = loaded.client.clone(); + let url = endpoint_url(&loaded.base_url, path)?; + let label = path.to_owned(); + run_async(move || async move { + let response = client.get(url).send().await.map_err(map_http_error)?; + if !response.status().is_success() { + return Err(backend_error(format!( + "/{label} returned HTTP {}", + response.status() + ))); + } + response.text().await.map_err(map_http_error) + }) +} + +async fn json_response(response: reqwest::Response, label: &str) -> Result { + if !response.status().is_success() { + return Err(backend_error(format!( + "{label} returned HTTP {}", + response.status() + ))); + } + response.json().await.map_err(map_http_error) +} + +fn endpoint_url(base: &Url, path: &str) -> Result { + base.join(path) + .map_err(|error| invalid(format!("invalid endpoint path {path}: {error}"))) +} + +fn validate_loopback_base_url(value: &str) -> Result { + let mut url = Url::parse(value) + .map_err(|error| invalid(format!("invalid OpenAI-compatible base URL: {error}")))?; + if url.scheme() != "http" { + return Err(invalid("OpenAI-compatible base URL must use loopback HTTP")); + } + if !url.username().is_empty() + || url.password().is_some() + || url.query().is_some() + || url.fragment().is_some() + || url.path() != "/" + { + return Err(invalid( + "OpenAI-compatible base URL must be a bare origin without credentials, path, query, or fragment", + )); + } + let host = url + .host_str() + .ok_or_else(|| invalid("OpenAI-compatible base URL is missing an IP host"))?; + let ip = host + .trim_start_matches('[') + .trim_end_matches(']') + .parse::() + .map_err(|_| invalid("OpenAI-compatible base URL host must be a loopback IP literal"))?; + if !ip.is_loopback() { + return Err(invalid( + "OpenAI-compatible base URL host must be a loopback IP literal", + )); + } + if url.port().is_none() { + return Err(invalid( + "OpenAI-compatible base URL must include an explicit port", + )); + } + url.set_path("/"); + Ok(url) +} + +fn prometheus_metric_sum(metrics: &str, metric: &str) -> Result { + prometheus_metric_sum_optional(metrics, metric)?.ok_or_else(|| { + backend_error(format!( + "/metrics omitted signed prefix cache metric {metric}" + )) + }) +} + +fn prometheus_metric_sum_optional(metrics: &str, metric: &str) -> Result> { + let mut found = false; + let mut total = 0.0; + for line in metrics.lines() { + let line = line.trim(); + if line.starts_with('#') { + continue; + } + let Some(rest) = line.strip_prefix(metric) else { + continue; + }; + if !rest.starts_with('{') && !rest.starts_with(char::is_whitespace) { + continue; + } + let value = line + .split_whitespace() + .last() + .and_then(|value| value.parse::().ok()) + .ok_or_else(|| backend_error(format!("metric {metric} has an invalid sample")))?; + found = true; + total += value; + } + Ok(found.then_some(total)) +} + +fn parse_usage(value: &Value) -> UsageCounters { + let u32_field = |name: &str| { + value + .get(name) + .and_then(Value::as_u64) + .and_then(|value| u32::try_from(value).ok()) + .unwrap_or(0) + }; + let reasoning_tokens = value + .pointer("/completion_tokens_details/reasoning_tokens") + .and_then(Value::as_u64) + .and_then(|value| u32::try_from(value).ok()) + .unwrap_or(0); + let vision_tokens = value + .pointer("/prompt_tokens_details/image_tokens") + .and_then(Value::as_u64) + .and_then(|value| u32::try_from(value).ok()) + .unwrap_or(0) + .saturating_add( + value + .pointer("/prompt_tokens_details/video_tokens") + .and_then(Value::as_u64) + .and_then(|value| u32::try_from(value).ok()) + .unwrap_or(0), + ); + UsageCounters { + prompt_tokens: u32_field("prompt_tokens"), + completion_tokens: u32_field("completion_tokens"), + total_tokens: u32_field("total_tokens"), + reasoning_tokens, + vision_tokens, + audio_tokens: value + .pointer("/prompt_tokens_details/audio_tokens") + .and_then(Value::as_u64) + .and_then(|value| u32::try_from(value).ok()) + .unwrap_or(0), + } +} + +async fn wait_cancelled(external: &CancellationToken, internal: &CancellationToken) { + loop { + if external.is_cancelled() || internal.is_cancelled() { + return; + } + tokio::time::sleep(REQUEST_POLL_INTERVAL).await; + } +} + +fn run_async(operation: F) -> Result +where + T: Send + 'static, + F: FnOnce() -> Fut + Send + 'static, + Fut: std::future::Future> + 'static, +{ + thread::spawn(move || { + tokio::runtime::Builder::new_current_thread() + .enable_all() + .build() + .map_err(|error| backend_error(format!("building HTTP runtime failed: {error}")))? + .block_on(operation()) + }) + .join() + .map_err(|_| backend_error("HTTP runtime worker panicked"))? +} + +fn safe_identity(value: &str) -> bool { + !value.is_empty() + && value.len() <= 128 + && value + .bytes() + .all(|byte| byte.is_ascii_alphanumeric() || matches!(byte, b'.' | b'_' | b'+' | b'-')) +} + +fn safe_metric_name(value: &str) -> bool { + !value.is_empty() + && value.len() <= 128 + && value + .bytes() + .all(|byte| byte.is_ascii_alphanumeric() || matches!(byte, b'_' | b':' | b'.')) +} + +fn bounded_error_text(value: &str) -> String { + value.chars().take(512).collect() +} + +fn map_http_error(error: reqwest::Error) -> EngineError { + backend_error(error.to_string()) +} + +fn backend_error(message: impl Into) -> EngineError { + EngineError::OpenAiCompatible(message.into()) +} + +fn invalid(message: impl Into) -> EngineError { + EngineError::InvalidConfig(message.into()) +} + +#[cfg(test)] +mod tests { + use super::*; + use std::io::{Read, Write}; + use std::net::TcpListener; + use std::time::Instant; + + fn runtime() -> OpenAiCompatibleRuntimeBinding { + OpenAiCompatibleRuntimeBinding { + schema_version: 1, + lifecycle: OpenAiCompatibleLifecycle::ManagedOrVerifiedAttach, + runtime_id: "runtime".to_owned(), + runtime_version: "1.0.0".to_owned(), + runtime_revision: "ab".repeat(20), + implementation: "engine".to_owned(), + implementation_version: "1.0.0+rev".to_owned(), + container_image_digest: format!("sha256:{}", "cd".repeat(32)), + served_model: "org/model".to_owned(), + native_context: 262_144, + served_context: 524_288, + max_concurrent: 2, + calibrated_peak_gpu_memory_bytes: None, + calibrated_peak_host_memory_bytes: None, + memory_measurement_source: None, + model_snapshot_file_count: 419, + snapshot_manifest_sidecar: "snapshot_manifest".to_owned(), + snapshot_manifest_sha256: "de".repeat(32), + runtime_recipe_sidecar: "runtime_recipe".to_owned(), + runtime_recipe_sha256: "ef".repeat(32), + capabilities: BTreeSet::from(["streaming".to_owned()]), + server_info_checks: BTreeMap::from([("/version".to_owned(), json!("1.0"))]), + preflight: OpenAiCompatiblePreflightProfile { + non_reasoning_chat_template_kwargs: BTreeMap::from([( + "enable_thinking".to_owned(), + json!(false), + )]), + reasoning_chat_template_kwargs: BTreeMap::from([( + "enable_thinking".to_owned(), + json!(true), + )]), + streaming_max_tokens: 32, + tools_max_tokens: 384, + json_max_tokens: 256, + reasoning_max_tokens: 1024, + cache_max_tokens: 32, + cancellation_max_tokens: 4096, + concurrency_max_tokens: 4096, + cancellation_idle_metrics: BTreeSet::from([ + "runtime_active_requests".to_owned(), + "runtime_queued_requests".to_owned(), + ]), + concurrency_active_metric: "runtime_active_requests".to_owned(), + }, + prefix_cache_metric: None, + } + } + + #[test] + fn endpoint_accepts_only_bare_loopback_http_origins() { + assert!(validate_loopback_base_url("http://127.0.0.1:8000/").is_ok()); + assert!(validate_loopback_base_url("http://[::1]:8000/").is_ok()); + for rejected in [ + "https://127.0.0.1:8000/", + "http://localhost:8000/", + "http://192.0.2.1:8000/", + "http://127.0.0.1:8000/v1", + "http://user@127.0.0.1:8000/", + "http://127.0.0.1/", + ] { + assert!( + validate_loopback_base_url(rejected).is_err(), + "accepted {rejected}" + ); + } + } + + #[test] + fn signed_binding_validates_identity_and_capability_invariants() { + runtime().validate().unwrap(); + let mut binding = runtime(); + binding.max_concurrent = 0; + assert!(binding.validate().is_err()); + let mut binding = runtime(); + binding.capabilities.insert("prefix_cache".to_owned()); + assert!(binding.validate().is_err()); + let mut binding = runtime(); + binding.container_image_digest = "latest".to_owned(); + assert!(binding.validate().is_err()); + let mut binding = runtime(); + binding.preflight.concurrency_active_metric.clear(); + assert!(binding.validate().is_err()); + } + + #[test] + fn request_maps_native_tools_json_and_reasoning_controls() { + let mut request = GenerateRequest::new("hello"); + request.grammar = Some(GrammarSpec::ToolCall { + tools: vec![ToolSpec::new("lookup", json!({"type": "object"}))], + }); + request + .speciality_parameters + .push(crate::GenerateSpecialityParameter { + name: "reasoning".to_owned(), + level: "high".to_owned(), + target: GenerateSpecialityTarget::ChatTemplateKwarg, + native_path: "enable_thinking".to_owned(), + value: json!(true), + max_reasoning_tokens: None, + }); + let body = chat_request_body("org/model", request).unwrap(); + assert_eq!(body["model"], "org/model"); + assert_eq!(body["tool_choice"], "required"); + assert_eq!(body["tools"][0]["function"]["name"], "lookup"); + assert_eq!(body["chat_template_kwargs"]["enable_thinking"], true); + } + + #[test] + fn request_normalizes_hf_video_inside_mixed_chat_content() { + let mut request = GenerateRequest::new("unused"); + request.messages = vec![json!({ + "role": "user", + "content": [ + {"type": "text", "text": "Compare the inputs."}, + {"type": "image_url", "image_url": {"url": "data:image/png;base64,aW1hZ2U="}}, + {"type": "video", "video": { + "data": "dmlkZW8=", + "content_type": "video/mp4", + "num_frames": 8, + "fps": 2 + }}, + {"type": "input_audio", "input_audio": {"data": "UklGRg==", "format": "wav"}} + ] + })]; + + let body = chat_request_body("org/model", request).unwrap(); + let content = body["messages"][0]["content"].as_array().unwrap(); + assert_eq!( + content[0], + json!({"type": "text", "text": "Compare the inputs."}) + ); + assert_eq!( + content[1], + json!({"type": "image_url", "image_url": {"url": "data:image/png;base64,aW1hZ2U="}}) + ); + assert_eq!( + content[2], + json!({"type": "video_url", "video_url": {"url": "data:video/mp4;base64,dmlkZW8="}}) + ); + assert_eq!( + content[3], + json!({"type": "input_audio", "input_audio": {"data": "UklGRg==", "format": "wav"}}) + ); + } + + #[test] + fn request_rejects_ambiguous_or_unsafe_hf_video_descriptors() { + for video in [ + json!({"data": "dmlkZW8=", "url": "https://example.test/video.mp4", "content_type": "video/mp4"}), + json!({"data": "dmlkZW8=", "content_type": "text/plain"}), + json!({"frames": ["ZnJhbWU="]}), + ] { + let mut request = GenerateRequest::new("unused"); + request.messages = vec![json!({ + "role": "user", + "content": [{"type": "video", "video": video}] + })]; + assert!(chat_request_body("org/model", request).is_err(), "{video}"); + } + } + + #[test] + fn canonical_canary_units_ignore_sse_segmentation_and_bind_content() { + let fingerprint = |tokens: &[i32]| { + let mut hasher = blake3::Hasher::new(); + for token in tokens { + hasher.update(&token.to_be_bytes()); + } + hasher.finalize().to_hex().to_string() + }; + let collect = |deltas: &[Value]| { + let (events, _receiver) = mpsc::channel(); + let mut collector = StreamCollector::default(); + for delta in deltas { + collector.push(delta.clone(), &events).unwrap(); + } + collector.finish(&events).unwrap().text + }; + let split = collect(&[ + json!({"choices":[{"delta":{"reasoning_content":"inspect "}}]}), + json!({"choices":[{"delta":{"reasoning_content":"alpha"}}]}), + json!({"choices":[{"delta":{"content":"answer "}}]}), + json!({"choices":[{"delta":{"content":"α"}}]}), + ]); + let joined = collect(&[ + json!({"choices":[{"delta":{"reasoning_content":"inspect alpha"}}]}), + json!({"choices":[{"delta":{"content":"answer α"}}]}), + ]); + let different = collect(&[ + json!({"choices":[{"delta":{"reasoning_content":"inspect bravo"}}]}), + json!({"choices":[{"delta":{"content":"answer β"}}]}), + ]); + let split_units = mayhem_proto::openai_compatible_canary_units(&split); + let joined_units = mayhem_proto::openai_compatible_canary_units(&joined); + let different_units = mayhem_proto::openai_compatible_canary_units(&different); + + assert_eq!(split, "inspect alphaanswer α"); + assert_eq!(split, joined); + assert_eq!(split_units, joined_units); + assert_eq!(fingerprint(&split_units), fingerprint(&joined_units)); + assert_eq!(split_units.len(), different_units.len()); + assert_ne!(split_units, different_units); + assert_ne!(fingerprint(&split_units), fingerprint(&different_units)); + } + + #[test] + fn preflight_reasoning_controls_have_unique_safe_names() { + let controls = BTreeMap::from([ + ("enable_thinking".to_owned(), json!(true)), + ("preserve_thinking".to_owned(), json!(true)), + ("reasoning_effort".to_owned(), json!("xhigh")), + ]); + let request = preflight_request("reason", 1024, &controls); + + request.validate_sampling().unwrap(); + assert_eq!(request.speciality_parameters.len(), 3); + assert_eq!( + request + .speciality_parameters + .iter() + .map(|parameter| (parameter.name.as_str(), parameter.native_path.as_str())) + .collect::>(), + vec![ + ("enable_thinking", "enable_thinking"), + ("preserve_thinking", "preserve_thinking"), + ("reasoning_effort", "reasoning_effort"), + ] + ); + } + + #[test] + fn concurrency_proof_polls_past_a_delayed_active_metric_sample() { + let (started_tx, started_rx) = mpsc::channel(); + started_tx.send(0).unwrap(); + started_tx.send(1).unwrap(); + let mut samples = [0.0, 0.0, 2.0].into_iter(); + let mut polls = 0; + + wait_for_concurrency_proof( + 2, + &started_rx, + Duration::from_secs(1), + Duration::ZERO, + || { + polls += 1; + Ok(samples.next().unwrap_or(2.0)) + }, + ) + .unwrap(); + + assert_eq!(polls, 3); + } + + #[test] + fn prometheus_parser_sums_labeled_samples_exactly() { + let metrics = "# HELP x cache\nsglang:cached_tokens_total{rank=\"0\"} 12\nsglang:cached_tokens_total{rank=\"1\"} 7\nsglang:cached_tokens_total_extra 90\n"; + assert_eq!( + prometheus_metric_sum(metrics, "sglang:cached_tokens_total").unwrap(), + 19.0 + ); + } + + #[test] + fn prefix_cache_proof_accepts_lazy_zero_baseline_but_requires_replay_increase() { + let metric = "sglang:cached_tokens_total"; + let absent = "# HELP sglang:num_running_reqs running\nsglang:num_running_reqs 0\n"; + let first_hit = "sglang:cached_tokens_total{rank=\"0\",source=\"gpu\"} 25472\n"; + + verify_prefix_cache_metric_increase(metric, absent, first_hit).unwrap(); + assert!(verify_prefix_cache_metric_increase(metric, absent, absent).is_err()); + assert!(verify_prefix_cache_metric_increase(metric, first_hit, first_hit).is_err()); + } + + fn spawn_sse_server(chunks: Vec>, hold_open: bool) -> String { + let listener = TcpListener::bind("127.0.0.1:0").unwrap(); + let address = listener.local_addr().unwrap(); + thread::spawn(move || { + let (mut socket, _) = listener.accept().unwrap(); + let mut request = Vec::new(); + let mut buffer = [0u8; 4096]; + while !request.windows(4).any(|window| window == b"\r\n\r\n") { + let read = socket.read(&mut buffer).unwrap(); + if read == 0 { + return; + } + request.extend_from_slice(&buffer[..read]); + } + socket + .write_all( + b"HTTP/1.1 200 OK\r\ncontent-type: text/event-stream\r\ntransfer-encoding: chunked\r\n\r\n", + ) + .unwrap(); + for chunk in chunks { + write!(socket, "{:x}\r\n", chunk.len()).unwrap(); + socket.write_all(&chunk).unwrap(); + socket.write_all(b"\r\n").unwrap(); + socket.flush().unwrap(); + thread::sleep(Duration::from_millis(2)); + } + if hold_open { + thread::sleep(Duration::from_secs(3)); + } else { + socket.write_all(b"0\r\n\r\n").unwrap(); + } + }); + format!("http://{address}/") + } + + fn spawn_tool_recovery_server() -> (String, mpsc::Receiver) { + let listener = TcpListener::bind("127.0.0.1:0").unwrap(); + let address = listener.local_addr().unwrap(); + let (requests, observed) = mpsc::channel(); + thread::spawn(move || { + let responses = [ + vec![ + json!({"choices":[{"delta":{"reasoning_content":"I will call the tool.\ndate"}}]}), + json!({"choices":[{"delta":{},"finish_reason":"stop"}]}), + json!({"usage":{"prompt_tokens":100,"completion_tokens":24,"total_tokens":124,"reasoning_tokens":24}}), + ], + vec![ + json!({"choices":[{"delta":{"tool_calls":[{"index":0,"id":"call_recovered","function":{"name":"command","arguments":"{\"command\":\"date\"}"}}]}}]}), + json!({"choices":[{"delta":{},"finish_reason":"tool_calls"}]}), + json!({"usage":{"prompt_tokens":100,"completion_tokens":8,"total_tokens":108,"reasoning_tokens":0}}), + ], + ]; + for response in responses { + let (mut socket, _) = listener.accept().unwrap(); + let mut request = Vec::new(); + let mut buffer = [0u8; 4096]; + let header_end = loop { + let read = socket.read(&mut buffer).unwrap(); + assert!(read > 0); + request.extend_from_slice(&buffer[..read]); + if let Some(end) = request.windows(4).position(|part| part == b"\r\n\r\n") { + break end + 4; + } + }; + let headers = std::str::from_utf8(&request[..header_end]).unwrap(); + let length = headers + .lines() + .find_map(|line| { + line.to_ascii_lowercase() + .strip_prefix("content-length: ") + .and_then(|value| value.trim().parse::().ok()) + }) + .unwrap(); + while request.len() < header_end + length { + let read = socket.read(&mut buffer).unwrap(); + assert!(read > 0); + request.extend_from_slice(&buffer[..read]); + } + requests + .send( + serde_json::from_slice(&request[header_end..header_end + length]).unwrap(), + ) + .unwrap(); + let mut payload = response + .into_iter() + .map(|chunk| format!("data: {chunk}\n\n")) + .collect::(); + payload.push_str("data: [DONE]\n\n"); + write!( + socket, + "HTTP/1.1 200 OK\r\ncontent-type: text/event-stream\r\ncontent-length: {}\r\nconnection: close\r\n\r\n", + payload.len() + ) + .unwrap(); + socket.write_all(payload.as_bytes()).unwrap(); + } + }); + (format!("http://{address}/"), observed) + } + + fn spawn_identity_server(responses: Vec<(&'static str, Value)>) -> String { + spawn_identity_server_with_status( + responses + .into_iter() + .map(|(path, body)| (path, 200, body)) + .collect(), + ) + } + + fn spawn_identity_server_with_status(responses: Vec<(&'static str, u16, Value)>) -> String { + let listener = TcpListener::bind("127.0.0.1:0").unwrap(); + let address = listener.local_addr().unwrap(); + thread::spawn(move || { + for (expected_path, status, body) in responses { + let (mut socket, _) = listener.accept().unwrap(); + let mut request = Vec::new(); + let mut buffer = [0u8; 4096]; + while !request.windows(4).any(|window| window == b"\r\n\r\n") { + let read = socket.read(&mut buffer).unwrap(); + if read == 0 { + return; + } + request.extend_from_slice(&buffer[..read]); + } + let request = String::from_utf8(request).unwrap(); + assert!( + request.starts_with(&format!("GET {expected_path} HTTP/1.1\r\n")), + "unexpected request: {request}" + ); + let body = serde_json::to_vec(&body).unwrap(); + write!( + socket, + "HTTP/1.1 {status} Test\r\ncontent-type: application/json\r\ncontent-length: {}\r\nconnection: close\r\n\r\n", + body.len() + ) + .unwrap(); + socket.write_all(&body).unwrap(); + } + }); + format!("http://{address}/") + } + + #[test] + fn component_health_rechecks_models_and_signed_server_identity() { + let binding = runtime(); + let base_url = spawn_identity_server(vec![ + ( + "/v1/models", + json!({"data":[{"id":"org/model","max_model_len":524288}]}), + ), + ("/server_info", json!({"version":"1.0"})), + ( + "/v1/models", + json!({"data":[{"id":"org/model","max_model_len":524288}]}), + ), + ("/server_info", json!({"version":"drifted"})), + ]); + let mut backend = OpenAiCompatibleBackend::new(OpenAiCompatibleBackendConfig { + base_url: base_url.clone(), + runtime: binding.clone(), + readiness_timeout: Duration::ZERO, + }) + .unwrap(); + backend.loaded = Some(Arc::new(LoadedBackend { + client: Client::builder().build().unwrap(), + base_url: validate_loopback_base_url(&base_url).unwrap(), + runtime: binding, + artifact: crate::ModelArtifact::openai_compatible_model("unused"), + ctx_size: 1024, + proven_capabilities: BTreeSet::from(["streaming".to_owned()]), + gate: Arc::new(GenerationGate { + capacity: 2, + active: Mutex::new(0), + changed: Condvar::new(), + }), + })); + assert!(backend.component_healthy()); + backend.identity_health.next_probe_at = None; + assert!(!backend.component_healthy()); + } + + #[test] + fn transient_identity_failure_does_not_retire_live_runtime() { + let binding = runtime(); + let base_url = spawn_identity_server_with_status(vec![ + ("/v1/models", 503, json!({})), + ( + "/v1/models", + 200, + json!({"data":[{"id":"org/model","max_model_len":524288}]}), + ), + ("/server_info", 200, json!({"version":"1.0"})), + ]); + let mut backend = OpenAiCompatibleBackend::new(OpenAiCompatibleBackendConfig { + base_url: base_url.clone(), + runtime: binding.clone(), + readiness_timeout: Duration::ZERO, + }) + .unwrap(); + backend.loaded = Some(Arc::new(LoadedBackend { + client: Client::builder().build().unwrap(), + base_url: validate_loopback_base_url(&base_url).unwrap(), + runtime: binding, + artifact: crate::ModelArtifact::openai_compatible_model("unused"), + ctx_size: 1024, + proven_capabilities: BTreeSet::new(), + gate: Arc::new(GenerationGate { + capacity: 2, + active: Mutex::new(0), + changed: Condvar::new(), + }), + })); + assert!(backend.component_healthy()); + assert!(backend.component_healthy()); // cached during retry interval + assert!(backend.identity_health.first_unavailable_at.is_some()); + backend.identity_health.next_probe_at = None; + assert!(backend.component_healthy()); + assert!(backend.identity_health.first_unavailable_at.is_none()); + } + + #[test] + fn sustained_identity_failure_retires_runtime_after_grace() { + let binding = runtime(); + let base_url = spawn_identity_server_with_status(vec![ + ("/v1/models", 503, json!({})), + ("/v1/models", 503, json!({})), + ]); + let mut backend = OpenAiCompatibleBackend::new(OpenAiCompatibleBackendConfig { + base_url: base_url.clone(), + runtime: binding.clone(), + readiness_timeout: Duration::ZERO, + }) + .unwrap(); + backend.loaded = Some(Arc::new(LoadedBackend { + client: Client::builder().build().unwrap(), + base_url: validate_loopback_base_url(&base_url).unwrap(), + runtime: binding, + artifact: crate::ModelArtifact::openai_compatible_model("unused"), + ctx_size: 1024, + proven_capabilities: BTreeSet::new(), + gate: Arc::new(GenerationGate { + capacity: 2, + active: Mutex::new(0), + changed: Condvar::new(), + }), + })); + assert!(backend.component_healthy()); + backend.identity_health.first_unavailable_at = + Some(Instant::now() - IDENTITY_UNAVAILABLE_GRACE - Duration::from_secs(1)); + backend.identity_health.next_probe_at = None; + assert!(!backend.component_healthy()); + assert!(!backend.component_healthy()); // remains failed until recovery or restart + } + + #[test] + fn actual_http_stream_retains_split_utf8_and_sse_boundaries() { + let line = "data: {\"choices\":[{\"delta\":{\"content\":\"Grüße\"}}]}\n\n"; + let bytes = line.as_bytes(); + let split = bytes + .windows(2) + .position(|window| window == "ü".as_bytes()) + .unwrap() + + 1; + let base = spawn_sse_server( + vec![ + bytes[..split].to_vec(), + bytes[split..].to_vec(), + b"data: [DONE]\n\n".to_vec(), + ], + false, + ); + let client = Client::builder().build().unwrap(); + let url = Url::parse(&format!("{base}v1/chat/completions")).unwrap(); + let (tx, rx) = mpsc::channel(); + run_async(move || async move { + stream_completion( + client, + url, + json!({}), + BTreeMap::new(), + CancellationToken::new(), + CancellationToken::new(), + tx, + ) + .await + }) + .unwrap(); + let mut text = String::new(); + let mut complete = None; + for event in rx { + match event { + StreamEvent::Text { text: part, .. } => text.push_str(&part), + StreamEvent::Complete(output) => complete = Some(output), + StreamEvent::Error(error) => panic!("unexpected stream error: {error}"), + } + } + assert_eq!(text, "Grüße"); + assert_eq!(complete.unwrap().text, "Grüße"); + } + + #[test] + fn complete_tool_markup_in_reasoning_retries_as_structured_call() { + let (base, requests) = spawn_tool_recovery_server(); + let client = Client::builder().build().unwrap(); + let url = Url::parse(&format!("{base}v1/chat/completions")).unwrap(); + let (tx, rx) = mpsc::channel(); + run_async(move || async move { + stream_completion( + client, + url, + json!({ + "model":"org/model", + "max_tokens":256, + "tools":[{"type":"function","function":{"name":"command","parameters":{"type":"object"}}}], + "tool_choice":"auto", + "chat_template_kwargs":{"enable_thinking":true} + }), + BTreeMap::from([("enable_thinking".to_owned(), json!(false))]), + CancellationToken::new(), + CancellationToken::new(), + tx, + ) + .await + }) + .unwrap(); + let original = requests.recv_timeout(Duration::from_secs(1)).unwrap(); + let retry = requests.recv_timeout(Duration::from_secs(1)).unwrap(); + assert_eq!(original["chat_template_kwargs"]["enable_thinking"], true); + assert_eq!(original["tool_choice"], "auto"); + assert_eq!(retry["chat_template_kwargs"]["enable_thinking"], false); + assert_eq!(retry["tool_choice"], "auto"); + assert_eq!(retry["max_tokens"], 232); + assert_eq!(retry["tools"], original["tools"]); + let mut streamed = String::new(); + let mut complete = None; + for event in rx { + match event { + StreamEvent::Text { text, .. } => streamed.push_str(&text), + StreamEvent::Complete(output) => complete = Some(output), + StreamEvent::Error(error) => panic!("unexpected stream error: {error}"), + } + } + let complete = complete.unwrap(); + assert_eq!(streamed, complete.text); + assert!(complete.text.contains("\"name\":\"command\"")); + assert_eq!(complete.usage.prompt_tokens, 100); + assert_eq!(complete.usage.completion_tokens, 32); + } + + #[test] + fn reasoning_examples_and_real_tool_calls_do_not_trigger_recovery() { + let body = json!({ + "tools":[{"type":"function","function":{"name":"command"}}], + "tool_choice":"auto" + }); + let mut collector = StreamCollector { + reasoning: "An example is ." + .to_owned(), + text: "Here is the answer.".to_owned(), + finish_reason: Some(FinishReason::Stop), + ..Default::default() + }; + assert!(!collector.unexecuted_reasoning_tool_call(&body)); + collector.text.clear(); + collector + .tool_calls + .insert(0, ToolCallAccumulator::default()); + assert!(!collector.unexecuted_reasoning_tool_call(&body)); + collector.tool_calls.clear(); + collector.finish_reason = Some(FinishReason::Length); + assert!(!collector.unexecuted_reasoning_tool_call(&body)); + collector.finish_reason = Some(FinishReason::Stop); + collector.reasoning = "".to_owned(); + assert!(collector.unexecuted_reasoning_tool_call(&body)); + assert!(!collector.advertised_reasoning_tool_call(&body)); + collector.finish_reason = Some(FinishReason::Length); + assert!(collector.unexecuted_reasoning_tool_call(&body)); + } + + #[test] + fn native_response_shape_checks_completed_sse_arguments_without_content() { + let (events, _receiver) = mpsc::channel(); + let mut collector = StreamCollector::default(); + for delta in [ + json!({"choices":[{"delta":{"reasoning_content":"thinking"}}]}), + json!({"choices":[{"delta":{"tool_calls":[{"index":0,"function":{"name":"probe","arguments":"{\"value\":"}}]}}]}), + json!({"choices":[{"delta":{"tool_calls":[{"index":0,"function":{"arguments":"1}"}}]}}]}), + json!({"choices":[{"delta":{},"finish_reason":"tool_calls"}]}), + ] { + collector.push(delta, &events).unwrap(); + } + assert_eq!( + collector.response_shape(), + NativeResponseShape { + finish_reason: "tool_calls", + content_bytes: 0, + reasoning_bytes: 8, + tool_calls: 1, + fragment_bytes_match: true, + native_arguments_valid_json: true, + envelope_arguments_valid_json: true, + } + ); + collector.tool_calls.get_mut(&0).unwrap().arguments.pop(); + let invalid = collector.response_shape(); + assert!(!invalid.fragment_bytes_match); + assert!(!invalid.native_arguments_valid_json); + assert!(!invalid.envelope_arguments_valid_json); + assert!(invalid.anomalous()); + + let mut native_incomplete = StreamCollector::default(); + native_incomplete + .push( + json!({"choices":[{"delta":{"tool_calls":[{"index":0,"function":{"name":"probe","arguments":"{\"value\":1"}}]}}]}), + &events, + ) + .unwrap(); + let malformed_from_sse = native_incomplete.response_shape(); + assert!(malformed_from_sse.fragment_bytes_match); + assert!(!malformed_from_sse.native_arguments_valid_json); + } + + #[test] + fn cancellation_does_not_deadlock_behind_many_fast_deltas() { + let mut chunks = Vec::new(); + for _ in 0..256 { + chunks.push(b"data: {\"choices\":[{\"delta\":{\"content\":\"x\"}}]}\n\n".to_vec()); + } + let base_url = validate_loopback_base_url(&spawn_sse_server(chunks, true)).unwrap(); + let loaded = Arc::new(LoadedBackend { + client: Client::builder().build().unwrap(), + base_url, + runtime: runtime(), + artifact: crate::ModelArtifact::openai_compatible_model("unused"), + ctx_size: 1024, + proven_capabilities: BTreeSet::new(), + gate: Arc::new(GenerationGate { + capacity: 2, + active: Mutex::new(0), + changed: Condvar::new(), + }), + }); + let cancellation = CancellationToken::new(); + let cancel_from_sink = cancellation.clone(); + let started = Instant::now(); + let result = generate( + loaded, + GenerateRequest::new("cancel"), + &mut move |_chunk: TokenChunk| { + cancel_from_sink.cancel(); + Ok(()) + }, + &cancellation, + ); + assert!(matches!(result, Err(EngineError::Cancelled))); + assert!(started.elapsed() < Duration::from_secs(2)); + } +} diff --git a/crates/mayhem-engine/src/vllm_backend/isolated_tests.rs b/crates/mayhem-engine/src/vllm_backend/isolated_tests.rs index 6e1a2012..daf6e16c 100644 --- a/crates/mayhem-engine/src/vllm_backend/isolated_tests.rs +++ b/crates/mayhem-engine/src/vllm_backend/isolated_tests.rs @@ -351,13 +351,31 @@ fn shared_worker_preserves_separate_memory_limits_on_reload() { let mut backend = fixture.backend(); backend.load(config.clone()).unwrap(); #[cfg(target_os = "linux")] - assert_eq!(backend.worker.as_ref().unwrap().containment_report.as_ref().unwrap() - .address_space_limit_bytes, config.vllm_worker_address_space_limit_bytes); + assert_eq!( + backend + .worker + .as_ref() + .unwrap() + .containment_report + .as_ref() + .unwrap() + .address_space_limit_bytes, + config.vllm_worker_address_space_limit_bytes + ); config.vllm_worker_address_space_limit_bytes = Some(96 * 1024 * 1024 * 1024); backend.load(config.clone()).unwrap(); #[cfg(target_os = "linux")] - assert_eq!(backend.worker.as_ref().unwrap().containment_report.as_ref().unwrap() - .address_space_limit_bytes, config.vllm_worker_address_space_limit_bytes); + assert_eq!( + backend + .worker + .as_ref() + .unwrap() + .containment_report + .as_ref() + .unwrap() + .address_space_limit_bytes, + config.vllm_worker_address_space_limit_bytes + ); drop(backend); fixture.assert_exited(2); } @@ -572,14 +590,25 @@ fn prefix_caching_missing_or_false_rejects_every_pool_worker_and_recovery() { for value in [Value::Null, json!(false)] { let mut bad = plan(8192); if value.is_null() { - bad["load"]["result"].as_object_mut().unwrap().remove("prefix_caching"); + bad["load"]["result"] + .as_object_mut() + .unwrap() + .remove("prefix_caching"); } else { bad["load"]["result"]["prefix_caching"] = value; } for index in [0, 1] { - let fixture = Fixture::new(if index == 0 { json!([bad.clone()]) } else { json!([plan(8192), bad.clone()]) }); + let fixture = Fixture::new(if index == 0 { + json!([bad.clone()]) + } else { + json!([plan(8192), bad.clone()]) + }); let mut backend = fixture.backend(); - assert!(backend.load(fixture.config(2)).unwrap_err().to_string().contains("prefix caching")); + assert!(backend + .load(fixture.config(2)) + .unwrap_err() + .to_string() + .contains("prefix caching")); assert!(!backend.prefix_caching_enabled()); assert!(backend.process_ids().is_empty()); fixture.assert_exited(index + 1); @@ -593,9 +622,15 @@ fn prefix_caching_missing_or_false_rejects_every_pool_worker_and_recovery() { assert!(Instant::now() < deadline); thread::sleep(Duration::from_millis(10)); } - assert!(finish_recovery(&mut backend).unwrap_err().to_string().contains("prefix caching")); + assert!(finish_recovery(&mut backend) + .unwrap_err() + .to_string() + .contains("prefix caching")); assert!(!backend.component_healthy()); - assert_eq!(finish_recovery(&mut backend).unwrap(), ComponentRecovery::Recovered); + assert_eq!( + finish_recovery(&mut backend).unwrap(), + ComponentRecovery::Recovered + ); drop(backend); fixture.assert_exited(4); } diff --git a/crates/mayhem-engine/src/vllm_worker.py b/crates/mayhem-engine/src/vllm_worker.py index 0fd70e75..c704726b 100644 --- a/crates/mayhem-engine/src/vllm_worker.py +++ b/crates/mayhem-engine/src/vllm_worker.py @@ -35,6 +35,8 @@ completed_request_id = 0 generation_multiplexer = None engine_health_monitor = None +worker_task = "generate" +embedding_engine_request_ids = {} MAX_KERNEL_BACKEND_LENGTH = 64 @@ -56,6 +58,10 @@ def __init__(self, prompt_tokens, context_size): def request_error_fields(exc): + # Grammar compilation errors are deterministic request-schema failures, + # not a reason to cool an otherwise healthy serving route. + if str(exc).startswith("Grammar error:"): + return {"error_code": "invalid_response_schema"} # vLLM v0.24 validates again after multimodal expansion. Match only its # explicit decoder-context ValueError, never arbitrary engine/OOM failures. # https://github.com/vllm-project/vllm/blob/v0.24.0/vllm/v1/engine/input_processor.py @@ -179,9 +185,13 @@ async def abort_engine_request(request_id): abort = getattr(engine, "abort", None) if abort is None: return - result = abort(f"mayhem-{int(request_id)}") - if inspect.isawaitable(result): - await result + engine_ids = embedding_engine_request_ids.get( + int(request_id), (f"mayhem-{int(request_id)}",) + ) + for engine_id in tuple(engine_ids): + result = abort(engine_id) + if inspect.isawaitable(result): + await result class GenerationMultiplexer: @@ -645,6 +655,8 @@ def effective_execution_properties(initialized_engine, required_kwargs): ), "enable_prefix_caching": config_value(config_value(config, "cache_config"), "enable_prefix_caching"), "mamba_cache_mode": config_value(config_value(config, "cache_config"), "mamba_cache_mode"), + "runner": enum_value(config_value(model_config, "runner_type")), + "convert": enum_value(config_value(model_config, "convert_type")), } if "compilation_config" in required_kwargs: compilation = config_value(config, "compilation_config") @@ -1020,6 +1032,9 @@ def create_engine(payload): requested_moe_backend = optional_kernel_backend(payload, "vllm_moe_backend") requested_mtp_tokens = optional_mtp_num_speculative_tokens(payload) requested_compilation_config = optional_compilation_config(payload) + task = str(payload.get("task") or "generate") + if task not in ("generate", "embedding"): + raise ValueError(f"unsupported vLLM task {task!r}") kwargs = { "model": path, "tokenizer": path, @@ -1045,7 +1060,11 @@ def create_engine(payload): "use_fp64_gumbel", "async_scheduling", } - # Required for every provider, including models whose vLLM default is off. + if task == "embedding": + kwargs["runner"] = "pooling" + kwargs["convert"] = "embed" + required_options.update(("runner", "convert")) + # Required for every provider; vLLM pooling supports prefix reuse as well. kwargs["enable_prefix_caching"] = True required_options.add("enable_prefix_caching") if model_uses_hybrid_attention(path): @@ -1543,11 +1562,113 @@ async def async_handle_generate(request_id, payload): } +def embedding_pooling_params(payload): + PoolingParams = import_attr( + (("vllm.pooling_params", "PoolingParams"), ("vllm", "PoolingParams")) + ) + dimensions = payload.get("dimensions") + if dimensions is not None: + if type(dimensions) is not int or dimensions < 1: + raise ValueError("embedding dimensions must be a positive integer") + # Request the native vector and perform MRL slicing locally. Some models + # document MRL dimensions without populating vLLM's optional metadata. + return PoolingParams(task="embed", dimensions=None) + + +def embedding_vector(output, dimensions=None): + pooling_output = getattr(output, "outputs", None) + values = getattr(pooling_output, "embedding", None) + if values is None: + values = getattr(pooling_output, "data", None) + if values is None: + raise RuntimeError("vLLM embedding output is missing its vector") + if hasattr(values, "detach"): + values = values.detach().float().cpu().tolist() + vector = [float(value) for value in values] + if not vector or any(not math.isfinite(value) for value in vector): + raise RuntimeError("vLLM embedding output contains no finite vector") + if dimensions is not None: + if dimensions > len(vector): + raise ValueError( + f"embedding dimensions {dimensions} exceed native dimension {len(vector)}" + ) + vector = vector[:dimensions] + norm = math.sqrt(sum(value * value for value in vector)) + if not math.isfinite(norm) or norm <= 0: + raise RuntimeError("truncated embedding vector has no finite norm") + vector = [value / norm for value in vector] + return vector + + +async def async_handle_embed(request_id, payload): + check_cancelled(request_id) + if engine is None: + raise RuntimeError("model has not been loaded") + if worker_task != "embedding": + raise RuntimeError("loaded vLLM model is not an embedding runner") + inputs = payload.get("inputs") + if not isinstance(inputs, list) or not inputs: + raise ValueError("embedding request must include at least one input") + if any(not isinstance(item, str) for item in inputs): + raise ValueError("embedding inputs must be strings") + embedding_pooling_params(payload) + engine_ids = tuple(f"mayhem-{int(request_id)}-{index}" for index in range(len(inputs))) + embedding_engine_request_ids[int(request_id)] = engine_ids + + async def encode_one(index, text): + final = None + async for output in engine.encode( + prompt=text, + pooling_params=embedding_pooling_params(payload), + request_id=engine_ids[index], + ): + if request_cancelled(request_id): + raise RequestCancelled("engine request cancelled") + final = output + if final is None or not getattr(final, "finished", False): + raise RuntimeError("vLLM embedding request ended without a final output") + prompt_token_ids = getattr(final, "prompt_token_ids", None) + if prompt_token_ids is None: + raise RuntimeError("vLLM embedding output is missing prompt token usage") + return index, embedding_vector(final, payload.get("dimensions")), len(prompt_token_ids) + + tasks = [ + asyncio.create_task(encode_one(index, text)) + for index, text in enumerate(inputs) + ] + try: + if engine_health_monitor is not None: + engine_health_monitor.raise_if_dead() + results = await asyncio.gather(*tasks) + check_cancelled(request_id) + except BaseException: + for task in tasks: + task.cancel() + await asyncio.gather(*tasks, return_exceptions=True) + await abort_engine_request(request_id) + raise + finally: + embedding_engine_request_ids.pop(int(request_id), None) + results.sort(key=lambda item: item[0]) + prompt_tokens = sum(item[2] for item in results) + return { + "embeddings": [item[1] for item in results], + "usage": { + "prompt_tokens": prompt_tokens, + "completion_tokens": 0, + "total_tokens": prompt_tokens, + }, + } + + async def handle_load(payload): global engine, tokenizer, processor, ctx_size, model_path, generation_multiplexer - global execution_properties, engine_health_monitor + global execution_properties, engine_health_monitor, worker_task model_path = str(payload["path"]) ctx_size = positive_int(payload.get("ctx_size"), 2048) + worker_task = str(payload.get("task") or "generate") + if worker_task not in ("generate", "embedding"): + raise ValueError(f"unsupported vLLM task {worker_task!r}") initialized_engine = create_engine(payload) await stop_engine_health_monitor() engine = initialized_engine @@ -1586,7 +1707,7 @@ async def handle_load(payload): kv_cache = runtime_kv_cache_info() generation_multiplexer = GenerationMultiplexer( load_generation_capacity(payload), - async_handle_generate, + async_handle_embed if worker_task == "embedding" else async_handle_generate, abort_engine_request, send, finish_request, @@ -1597,7 +1718,11 @@ async def handle_load(payload): "n_vocab": int(vocab_size()), "kv_cache_size_tokens": kv_cache["size_tokens"], "kv_cache_max_concurrency": kv_cache["max_concurrency"], - "prefix_caching": config_value(config_value(engine.vllm_config, "cache_config"), "enable_prefix_caching") is True, + "task": worker_task, + "prefix_caching": config_value( + config_value(config_value(engine, "vllm_config"), "cache_config"), + "enable_prefix_caching", + ) is True, "execution": execution_properties, "determinism": { "async_scheduling": False, @@ -1686,7 +1811,16 @@ async def run_worker(): break request_id = int(request.get("id", 0)) op = str(request.get("op", "")) - if op == "generate": + request_op = "embed" if worker_task == "embedding" else "generate" + # Tokenization uses the already-loaded frontend tokenizer. It does + # not touch the generation engine or consume a generation slot, so + # it must remain available while generations are active. Draining + # here can hold a count request behind a minutes-long inference and + # makes the gateway's bounded control request time out. + if op == "tokenize": + await emit_control_response(request) + continue + if op == request_op: if generation_multiplexer is None: await emit_control_response(request) continue diff --git a/crates/mayhem-engine/tests/comfyui_reference_files_test.py b/crates/mayhem-engine/tests/comfyui_reference_files_test.py index b50cc7d7..5a289a82 100644 --- a/crates/mayhem-engine/tests/comfyui_reference_files_test.py +++ b/crates/mayhem-engine/tests/comfyui_reference_files_test.py @@ -10,6 +10,7 @@ import time import types import unittest +import uuid from unittest import mock from pathlib import Path @@ -23,7 +24,7 @@ def load_file_scope(base_dir): functions = [node for node in tree.body if isinstance(node, ast.FunctionDef) and node.name in names] namespace = {"base_dir": base_dir, "base64": base64, "contextlib": contextlib, "tempfile": tempfile, "Path": Path, "json": json, - "os": os, "time": time, "errno": errno} + "os": os, "time": time, "errno": errno, "uuid": uuid} exec(compile(ast.Module(body=functions, type_ignores=[]), source.name, "exec"), namespace) return namespace diff --git a/crates/mayhem-engine/tests/vllm_worker_health_test.py b/crates/mayhem-engine/tests/vllm_worker_health_test.py index 230cce3d..19b80c52 100644 --- a/crates/mayhem-engine/tests/vllm_worker_health_test.py +++ b/crates/mayhem-engine/tests/vllm_worker_health_test.py @@ -22,6 +22,7 @@ def load_health_scope(): "EngineHealthMonitor", "stop_engine_health_monitor", "async_handle_generate", + "config_value", "handle_load", "handle", "emit_control_response", @@ -39,6 +40,7 @@ def load_health_scope(): execution_properties=None, batch_invariant=False, kernel_policy="auto", + worker_task="generate", request_queue=queue.Queue(), ) exec(compile(ast.Module(body=retained, type_ignores=[]), WORKER_PATH.name, "exec"), namespace) @@ -327,6 +329,35 @@ async def test_worker_shutdown_and_eof_stop_monitor(self): self.assertIsNone(self.worker["engine_health_monitor"]) self.assertEqual(self.messages, []) + async def test_tokenize_does_not_wait_for_active_generation(self): + started = asyncio.Event() + release = asyncio.Event() + + async def generate(request_id, payload): + started.set() + await release.wait() + return {"text": "done"} + + self.multiplexer(generate) + self.worker["handle"] = lambda request_id, op, payload: ( + {"token_ids": [7, 8, 9]} + if op == "tokenize" + else (_ for _ in ()).throw(ValueError(f"unexpected op {op}")) + ) + self.worker["request_queue"].put({"id": 1, "op": "generate", "payload": {}}) + self.worker["request_queue"].put({"id": 2, "op": "tokenize", "payload": {"text": "hi"}}) + self.worker["request_queue"].put(None) + + worker = asyncio.create_task(self.worker["run_worker"]()) + await asyncio.wait_for(started.wait(), timeout=1.0) + await self.wait_until(lambda: any(message.get("id") == 2 for message in self.messages)) + token_response = next(message for message in self.messages if message.get("id") == 2) + self.assertEqual(token_response["result"], {"token_ids": [7, 8, 9]}) + self.assertFalse(worker.done()) + + release.set() + await asyncio.wait_for(worker, timeout=1.0) + if __name__ == "__main__": unittest.main() diff --git a/crates/mayhem-gateway/Cargo.toml b/crates/mayhem-gateway/Cargo.toml index 7081090c..33b26f48 100644 --- a/crates/mayhem-gateway/Cargo.toml +++ b/crates/mayhem-gateway/Cargo.toml @@ -32,6 +32,7 @@ getrandom = "0.3" hex = "0.4" image = { version = "0.25", default-features = false, features = ["jpeg", "png"] } jsonwebtoken = { version = "10.3", features = ["rust_crypto"] } +jsonschema = { version = "0.53.0", default-features = false } mayhem-bridge = { path = "../mayhem-bridge" } mayhem-attestation = { path = "../mayhem-attestation" } mayhem-proto = { path = "../mayhem-proto" } diff --git a/crates/mayhem-gateway/src/audit.rs b/crates/mayhem-gateway/src/audit.rs index efe946a0..d18e00b0 100644 --- a/crates/mayhem-gateway/src/audit.rs +++ b/crates/mayhem-gateway/src/audit.rs @@ -23,6 +23,7 @@ pub const CANARY_VERIFICATION_EMBEDDING_COSINE: &str = "embedding_cosine"; pub const CANARY_VERIFICATION_TRANSCRIPT_MATCH: &str = "transcript_match"; pub const CANARY_VERIFICATION_AUDIO_FINGERPRINT: &str = "audio_fingerprint"; pub const CANARY_VERIFICATION_VIDEO_AV_FINGERPRINT: &str = "video_av_fingerprint"; +pub const CANARY_VERIFICATION_DECISION_FINGERPRINT: &str = "decision_fingerprint"; pub const CANARY_VERIFICATION_ATTESTATION_OF_COMPUTE: &str = "attestation_of_compute"; #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] @@ -151,6 +152,7 @@ pub fn supported_canary_verification_method(method: &str) -> bool { | CANARY_VERIFICATION_TRANSCRIPT_MATCH | CANARY_VERIFICATION_AUDIO_FINGERPRINT | CANARY_VERIFICATION_VIDEO_AV_FINGERPRINT + | CANARY_VERIFICATION_DECISION_FINGERPRINT | CANARY_VERIFICATION_ATTESTATION_OF_COMPUTE ) } @@ -1400,6 +1402,53 @@ pub fn evaluate_catalog_canary_video_av_fingerprint_probe( } } +pub fn evaluate_catalog_canary_decision_fingerprint_probe( + spec: &CanaryProbeSpec, + expected_fingerprints_by_prompt: &BTreeMap, + observed_fingerprints_by_prompt: &BTreeMap, +) -> CanaryProbeEvaluation { + let total_positions = expected_fingerprints_by_prompt.len() as u32; + let matched_positions = expected_fingerprints_by_prompt + .iter() + .filter(|(prompt_id, expected)| { + observed_fingerprints_by_prompt + .get(*prompt_id) + .is_some_and(|observed| observed == *expected) + }) + .count() as u32; + let expected_fingerprint = aggregate_canary_fingerprints( + expected_fingerprints_by_prompt + .iter() + .map(|(prompt_id, fingerprint)| (prompt_id.as_str(), fingerprint.as_str())), + ); + let observed_fingerprint = + aggregate_canary_fingerprints(expected_fingerprints_by_prompt.keys().map(|prompt_id| { + ( + prompt_id.as_str(), + observed_fingerprints_by_prompt + .get(prompt_id) + .map(String::as_str) + .unwrap_or_default(), + ) + })); + let match_bps = if total_positions == 0 { + 0 + } else { + ((u64::from(matched_positions) * 10_000) / u64::from(total_positions)) as u32 + }; + CanaryProbeEvaluation { + verification_method: CANARY_VERIFICATION_DECISION_FINGERPRINT.to_owned(), + canary_set: spec.canary_set.clone(), + prompt_id: spec.prompt_id.clone(), + expected_fingerprint, + observed_fingerprint, + matched_positions, + total_positions, + match_bps, + pass: total_positions > 0 && matched_positions == total_positions, + } +} + pub fn perceptual_hash_match_stats( expected_hash: &str, observed_hash: &str, @@ -1548,6 +1597,33 @@ mod tests { ); } + #[test] + fn decision_fingerprint_probe_requires_every_exact_prompt_result() { + let expected = BTreeMap::from([ + ("choice".to_owned(), "11".repeat(32)), + ("score".to_owned(), "22".repeat(32)), + ]); + let passing = + evaluate_catalog_canary_decision_fingerprint_probe(&spec(), &expected, &expected); + assert!(passing.pass); + assert_eq!(passing.matched_positions, 2); + assert_eq!(passing.total_positions, 2); + assert_eq!(passing.match_bps, 10_000); + assert_eq!( + passing.verification_method, + CANARY_VERIFICATION_DECISION_FINGERPRINT + ); + + let missing = BTreeMap::from([("choice".to_owned(), "11".repeat(32))]); + let failing = + evaluate_catalog_canary_decision_fingerprint_probe(&spec(), &expected, &missing); + assert!(!failing.pass); + assert_eq!(failing.matched_positions, 1); + assert_eq!(failing.total_positions, 2); + assert_eq!(failing.match_bps, 5_000); + assert_ne!(failing.expected_fingerprint, failing.observed_fingerprint); + } + #[test] fn catalog_fingerprint_evaluation_emits_probe_fail_on_mismatch() { let evaluation = evaluate_catalog_canary_probe(&spec(), "aa", "bb", 9_000); diff --git a/crates/mayhem-gateway/src/job_store.rs b/crates/mayhem-gateway/src/job_store.rs index b317308b..a4de7f32 100644 --- a/crates/mayhem-gateway/src/job_store.rs +++ b/crates/mayhem-gateway/src/job_store.rs @@ -63,6 +63,8 @@ pub(crate) struct GatewayJobErrorInfo { pub(crate) code: String, pub(crate) category: String, pub(crate) retryable: bool, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub(crate) phase: Option, } #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] @@ -298,6 +300,7 @@ impl GatewayJobStore { code: "gateway_execution_interrupted".to_owned(), category: "execution_unknown".to_owned(), retryable: false, + phase: None, }), }; self.persist_active_recovery(recovery) @@ -722,9 +725,10 @@ impl GatewayJobStore { // their reservation was closed. Restore those jobs to recovery; // never rewrite the signed receipt's finality bit. if job.status != GatewayJobStatus::ReconciliationPending - && job.receipt.as_ref().is_some_and(|receipt| + && job.receipt.as_ref().is_some_and(|receipt| { receipt.pointer("/body/final") == Some(&Value::Bool(false)) - && receipt.get("canonical_settlement").is_none()) + && receipt.get("canonical_settlement").is_none() + }) { job.status = GatewayJobStatus::ReconciliationPending; let repaired = seal_job(&self.key, &job)?; @@ -1334,6 +1338,7 @@ mod tests { code: "provider_model_output_invalid".to_owned(), category: "provider_response".to_owned(), retryable: false, + phase: Some("admin_ack".to_owned()), }; for id in ["legacy", "typed"] { store diff --git a/crates/mayhem-gateway/src/lib.rs b/crates/mayhem-gateway/src/lib.rs index c899693c..d280606c 100644 --- a/crates/mayhem-gateway/src/lib.rs +++ b/crates/mayhem-gateway/src/lib.rs @@ -8,6 +8,7 @@ pub mod openai; pub mod pricing; pub mod provider_table; pub mod reputation; +pub mod structured_schema; pub use attestation_policy::*; pub use audit::*; pub use failover::*; @@ -3487,17 +3488,38 @@ mod tests { let provider = hex::encode(key.verifying_key().to_bytes()); let now = 1_800_000_000_000; let mut heartbeat = signed_heartbeat(&key, &provider, now, "ab"); - assert_eq!(serde_json::from_value::(heartbeat.clone()).unwrap().prefix_caching, None); + assert_eq!( + serde_json::from_value::(heartbeat.clone()) + .unwrap() + .prefix_caching, + None + ); heartbeat["prefix_caching"] = json!(true); - assert!(matches!(verify_heartbeat_signature(&heartbeat, &provider, heartbeat["sig"].as_str().unwrap()), - Err(GatewayError::BadHeartbeatSignature { .. }))); + assert!(matches!( + verify_heartbeat_signature(&heartbeat, &provider, heartbeat["sig"].as_str().unwrap()), + Err(GatewayError::BadHeartbeatSignature { .. }) + )); let mut body = heartbeat.clone(); body.as_object_mut().unwrap().remove("sig"); - heartbeat["sig"] = json!(hex::encode(key.sign(&heartbeat_signing_payload(&body).unwrap()).to_bytes())); - verify_heartbeat_signature(&heartbeat, &provider, heartbeat["sig"].as_str().unwrap()).unwrap(); - assert_eq!(serde_json::from_value::(heartbeat.clone()).unwrap().prefix_caching, Some(true)); + heartbeat["sig"] = json!(hex::encode( + key.sign(&heartbeat_signing_payload(&body).unwrap()) + .to_bytes() + )); + verify_heartbeat_signature(&heartbeat, &provider, heartbeat["sig"].as_str().unwrap()) + .unwrap(); + assert_eq!( + serde_json::from_value::(heartbeat.clone()) + .unwrap() + .prefix_caching, + Some(true) + ); heartbeat["prefix_caching"] = json!(false); - assert!(verify_heartbeat_signature(&heartbeat, &provider, heartbeat["sig"].as_str().unwrap()).is_err()); + assert!(verify_heartbeat_signature( + &heartbeat, + &provider, + heartbeat["sig"].as_str().unwrap() + ) + .is_err()); } #[test] diff --git a/crates/mayhem-gateway/src/openai.rs b/crates/mayhem-gateway/src/openai.rs index 34d05c63..c37c86f1 100644 --- a/crates/mayhem-gateway/src/openai.rs +++ b/crates/mayhem-gateway/src/openai.rs @@ -20,16 +20,18 @@ use crate::{ audit::{ aggregate_canary_fingerprints, audio_fingerprint, embedding_vector_fingerprint, evaluate_catalog_canary_audio_fingerprint_probe, + evaluate_catalog_canary_decision_fingerprint_probe, evaluate_catalog_canary_embedding_cosine_probe, evaluate_catalog_canary_perceptual_hash_probe, evaluate_catalog_canary_token_prefix_probe, evaluate_catalog_canary_transcript_match_probe, evaluate_catalog_canary_video_av_fingerprint_probe, image_average_hash_hex, supported_canary_verification_method, token_fingerprint, video_av_fingerprint, CanaryProbeEvaluation, CanaryProbeSpec, CANARY_VERIFICATION_AUDIO_FINGERPRINT, - CANARY_VERIFICATION_CONTEXT_NEEDLE, CANARY_VERIFICATION_EMBEDDING_COSINE, - CANARY_VERIFICATION_SEED_PERCEPTUAL_HASH, CANARY_VERIFICATION_TOKEN_FINGERPRINT, - CANARY_VERIFICATION_TRANSCRIPT_MATCH, CANARY_VERIFICATION_VIDEO_AV_FINGERPRINT, - DEFAULT_CANARY_MATCH_MIN_BPS, MIN_LAUNCH_CANARY_STABLE_PREFIX_TOKENS, + CANARY_VERIFICATION_CONTEXT_NEEDLE, CANARY_VERIFICATION_DECISION_FINGERPRINT, + CANARY_VERIFICATION_EMBEDDING_COSINE, CANARY_VERIFICATION_SEED_PERCEPTUAL_HASH, + CANARY_VERIFICATION_TOKEN_FINGERPRINT, CANARY_VERIFICATION_TRANSCRIPT_MATCH, + CANARY_VERIFICATION_VIDEO_AV_FINGERPRINT, DEFAULT_CANARY_MATCH_MIN_BPS, + MIN_LAUNCH_CANARY_STABLE_PREFIX_TOKENS, }, failover::{ effective_context_floor, midstream_stalled_after, x_mayhem_hedge_requested, FailoverPolicy, @@ -56,11 +58,11 @@ use crate::{ DEFAULT_IMAGE_FLOOR_IMAGES_PER_S, DEFAULT_LLM_GENERATION_FLOOR_TOK_S, DEFAULT_PROVIDER_HEARTBEAT_TTL_MILLIS, }, - verify_execution_mode_binding, verify_tier1_attestation, AttestationPolicyVerificationContext, - AttestationVerificationRequest, EnclaveContractRecord, HardwareQuoteVerifierCommand, - HeartbeatAttestation, HeartbeatCaps, HeartbeatPerf, HeartbeatQueue, HeartbeatSlots, - ProviderHeartbeat, ProviderKey, ProviderProbation, ReputationEventKind, VerifiedAttestation, - GATEWAY_ATTESTATION_VERIFIER_VERSION, + structured_schema, verify_execution_mode_binding, verify_tier1_attestation, + AttestationPolicyVerificationContext, AttestationVerificationRequest, EnclaveContractRecord, + HardwareQuoteVerifierCommand, HeartbeatAttestation, HeartbeatCaps, HeartbeatPerf, + HeartbeatQueue, HeartbeatSlots, ProviderHeartbeat, ProviderKey, ProviderProbation, + ReputationEventKind, VerifiedAttestation, GATEWAY_ATTESTATION_VERIFIER_VERSION, }; use axum::{ body::{Body, Bytes}, @@ -91,23 +93,22 @@ use mayhem_attestation::{ use mayhem_bridge::{ sc_bridge_session_transport, BridgeError, PeerRpcClient, ScBridgeClient, ScBridgeConfig, }; -#[cfg(test)] -use mayhem_proto::{record_usage_receipt_envelope, record_usage_receipt_feature_key}; use mayhem_proto::{ artifact_generation_inline_audio_load, ctx_bracket_for_tokens_in_schedule, default_ctx_bracket_schedule, default_model_class, metered_output_units, parse_record_usage_receipt_envelope, payload_chunk_at, payload_chunk_manifest, - receipt_signing_bytes, record_usage_receipt_feature_key_for_contract, - record_usage_receipt_signing_bytes, RECOVERABLE_RECEIPT_CONTRACT_VERSION, receipt_contract_version_is_supported, - session_accept_signing_bytes, session_frame_head, spend_voucher_signing_bytes, - stable_json_bytes, tools_only_model_input_prompt_units, validate_transcription_result, - validated_audio_metadata, validated_wav_audio_metadata, vllm_execution_mode_binding, - AdminAttestationPolicy, AdminEnclaveAttestationBinding, AttestationReport, - AttestationTrustDataRef, AttestationVerifierProfile, CheckpointPolicy, CtxBracketSchedule, - EndpointFamilyContract, EndpointValueType, ExecutionModeBinding, ExecutionModeRequestPolicy, - HardwareQuoteKind, HardwareQuoteRouteAdvertisement, HardwareQuoteRoutePolicyBinding, - ModelSpecialityDescriptor, MoneyAu, PayloadChunk, PayloadChunkCollector, PayloadChunkManifest, - ReceiptAck, ReceiptBody, ReceiptUsage, SessionReceipt, SpendVoucher, SpendVoucherBody, + receipt_contract_version_is_supported, receipt_schema_version_is_supported_for_contract, + receipt_signing_bytes, record_usage_receipt_feature_key_from_envelope_for_contract, + record_usage_receipt_signing_bytes, session_accept_signing_bytes, session_frame_head, + spend_voucher_signing_bytes, stable_json_bytes, tools_only_model_input_prompt_units, + validate_transcription_result, validated_audio_metadata, validated_wav_audio_metadata, + vllm_execution_mode_binding, AdminAttestationPolicy, AdminEnclaveAttestationBinding, + AttestationReport, AttestationTrustDataRef, AttestationVerifierProfile, CheckpointPolicy, + CtxBracketSchedule, EndpointFamilyContract, EndpointValueType, ExecutionModeBinding, + ExecutionModeRequestPolicy, HardwareQuoteKind, HardwareQuoteRouteAdvertisement, + HardwareQuoteRoutePolicyBinding, ModelSpecialityDescriptor, MoneyAu, PayloadChunk, + PayloadChunkCollector, PayloadChunkManifest, ReceiptAck, ReceiptBody, ReceiptUsage, + SessionReceipt, SpendVoucher, SpendVoucherBody, TokenizeRequestFrame, TokenizeResponseFrame, TpmActivateCredentialChallengeFrame, TpmActivateCredentialHello, TpmActivateCredentialResponseFrame, TranscriptionResult, TranscriptionResultLimits, ValidatedAudioFormat, VisibleToolCall, WorkflowBinding, WorkflowOutputBinding, ATTESTATION_ALG, @@ -115,14 +116,23 @@ use mayhem_proto::{ DEFAULT_SESSION_MAX_FRAME_BYTES, DEFAULT_SESSION_MAX_PAYLOAD_CHUNKS, DEFAULT_SESSION_MAX_REASSEMBLED_PAYLOAD_BYTES, DEFAULT_VIDEO_GENERATION_FPS, MAX_VISIBLE_OUTPUT_BYTES_PER_REQUEST_TOKEN, MAX_VISIBLE_OUTPUT_UNITS_PER_REQUEST_TOKEN, - SESSION_RECEIPT_SCHEMA_VERSION, TPM_ACTIVATE_CREDENTIAL_CHALLENGE_FRAME_TYPE, - TPM_ACTIVATE_CREDENTIAL_FRAME_VERSION, TPM_ACTIVATE_CREDENTIAL_RESPONSE_FRAME_TYPE, - TRANSPORT_MAX_OUTPUT_DURATION_SECONDS, USAGE_AUDIO_SECOND, USAGE_CACHED_INPUT_TOKEN, - USAGE_FRAME, USAGE_IMAGE, USAGE_INPUT_CHARACTER, USAGE_INPUT_TOKEN, USAGE_OUTPUT_TOKEN, - USAGE_STEP, USAGE_VIDEO_SECOND, + SESSION_RECEIPT_SCHEMA_VERSION, SPEND_VOUCHER_SCHEMA_VERSION, TOKENIZE_FRAME_VERSION, + TOKENIZE_REQUEST_CHUNK_FRAME_TYPE, TOKENIZE_REQUEST_FRAME_TYPE, + TOKENIZE_RESPONSE_CHUNK_FRAME_TYPE, TOKENIZE_RESPONSE_FRAME_TYPE, + TPM_ACTIVATE_CREDENTIAL_CHALLENGE_FRAME_TYPE, TPM_ACTIVATE_CREDENTIAL_FRAME_VERSION, + TPM_ACTIVATE_CREDENTIAL_RESPONSE_FRAME_TYPE, TRANSPORT_MAX_OUTPUT_DURATION_SECONDS, + USAGE_AUDIO_SECOND, USAGE_CACHED_INPUT_TOKEN, USAGE_FRAME, USAGE_IMAGE, USAGE_INPUT_CHARACTER, + USAGE_INPUT_TOKEN, USAGE_OUTPUT_TOKEN, USAGE_STEP, USAGE_VIDEO_SECOND, }; #[cfg(test)] -use mayhem_proto::{chunk_json_payload, visible_output_units}; +use mayhem_proto::{ + chunk_json_payload, visible_output_units, RECOVERABLE_RECEIPT_CONTRACT_VERSION, +}; +#[cfg(test)] +use mayhem_proto::{ + record_usage_receipt_envelope, record_usage_receipt_feature_key, + record_usage_receipt_feature_key_for_contract, +}; use serde::{de::DeserializeOwned, Deserialize, Serialize}; use serde_json::{json, Map, Value}; use sha2::{Digest as _, Sha256}; @@ -133,9 +143,9 @@ type SharedState = Arc; #[cfg(test)] mod durable_streaming_tests; -mod response_stream; -mod incremental_output; mod failure_recovery; +mod incremental_output; +mod response_stream; mod github_update; use github_update::{ @@ -215,6 +225,13 @@ const MAX_ASYNC_ARTIFACT_ROUTE_WAIT_MS: u64 = 60 * 60 * 1000; const ROUTE_WAIT_POLL_MS: u64 = 1_000; const SESSION_OPEN_REPLAY_INTERVAL_MS: u64 = 5_000; const DEFAULT_CHAT_OUTPUT_HEADROOM_TOKENS: u64 = 1_024; +const EMBEDDING_SPECIAL_TOKEN_ALLOWANCE_PER_INPUT: u64 = 16; +const DECISION_MAX_QUESTIONS: u64 = 64; +const DECISION_MAX_SEQUENCE_TOKENS: u64 = 1_024; +const DECISION_MAX_SHORTLIST_OPTIONS: u64 = 256; +const DECISION_DEFAULT_SHORTLIST_K: u64 = 20; +const DECISION_OUTPUT_FIXED_ALLOWANCE_BYTES: usize = 256 * 1024; +const DECISION_OUTPUT_REQUEST_SIZE_MULTIPLIER: usize = 16; const DEFAULT_SESSION_REQUEST_BYTES_PER_CONTEXT_TOKEN: usize = 256; const DEFAULT_SESSION_OUTPUT_BYTES_PER_REQUEST_TOKEN: usize = MAX_VISIBLE_OUTPUT_BYTES_PER_REQUEST_TOKEN as usize; @@ -249,6 +266,8 @@ const CONTEXT_NEEDLE_MIN_CTX: u32 = 32_768; const CONTEXT_NEEDLE_MAX_TOKENS: u32 = 16; const CONTEXT_NEEDLE_FILLER_WORDS_PER_LINE: usize = 32; const DEFAULT_THROUGHPUT_FLOOR_SAMPLE_MILLIS: u64 = 1_000; +const DEFAULT_THROUGHPUT_FLOOR_MIN_OUTPUT_TOKENS: u64 = 6; +const DEFAULT_THROUGHPUT_FLOOR_FAST_SAMPLE_MIN_OUTPUT_TOKENS: u64 = 32; const DEFAULT_EPOCH_SECONDS: u64 = 3_600; const DEFAULT_RESERVATION_MAX_LIFETIME_EPOCHS: u64 = 24; const DEFAULT_RESERVATION_RECEIPT_GRACE_EPOCHS: u64 = 6; @@ -269,6 +288,9 @@ const DASHBOARD_CSP: &str = "default-src 'self'; connect-src 'self' http://127.0 #[derive(Clone, Debug)] pub struct GatewayState { catalog_runtime: Arc>, + catalog_refresh: Arc>, + catalog_refresh_request: Arc, + catalog_refresh_complete: Arc, receipts: Arc>>, dashboard_history_path: Arc>, dashboard_history_write: Arc>, @@ -322,6 +344,12 @@ pub struct GatewayState { media_limits: Arc, } +#[derive(Debug, Default)] +struct GatewayCatalogRefresh { + generation: u64, + requested: bool, +} + #[derive(Clone, Debug)] struct GatewayCatalogRuntime { models: Arc>, @@ -994,7 +1022,11 @@ pub struct GatewayRouteCandidate { pub price_ref_au: Option, #[serde(default, with = "mayhem_proto::decimal_u128")] pub min_ask_au: MoneyAu, + /// Effective routing/accountability tier. Verified provider identity raises + /// this to T4 without changing the enclave's execution attestation tier. pub att_tier: u8, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub enclave_att_tier: Option, #[serde(default = "default_quant_bucket")] pub quant: String, #[serde(default, skip_serializing_if = "Option::is_none")] @@ -1025,6 +1057,10 @@ pub struct GatewayRouteCandidate { pub local_run: Option, } +fn route_enclave_attestation_tier(candidate: &GatewayRouteCandidate) -> u8 { + candidate.enclave_att_tier.unwrap_or(candidate.att_tier) +} + #[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)] pub struct GatewayLocalRunBadge { pub marker: String, @@ -1215,6 +1251,26 @@ impl GatewayFailoverInvocation { fn stall_timeout(self) -> Option { self.stall_timeout_ms.map(Duration::from_millis) } + + fn with_admission_attempt_budget(mut self, budget: Option) -> Self { + let Some(budget) = budget else { + return self; + }; + // The route wait budget protects the two transport phases: peer + // connect and session open. A signed provider accept also waits for a + // canonical spend reservation, so it must retain the configured + // admission window instead of inheriting a small fraction of route + // discovery time. + let accept_timeout_ms = self + .session_accept_timeout_ms + .unwrap_or(self.open_timeout_ms); + let phase_millis = u64::try_from(budget.as_millis() / 2) + .unwrap_or(u64::MAX) + .max(1); + self.open_timeout_ms = self.open_timeout_ms.min(phase_millis); + self.session_accept_timeout_ms = Some(accept_timeout_ms); + self + } } #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] @@ -1902,11 +1958,13 @@ pub struct GatewayCanaryModelConfig { pub prompts: Vec, pub fingerprints_by_artifact_root: BTreeMap, pub token_prefixes_by_artifact_root: BTreeMap>>, + pub openai_compatible_artifact_roots: BTreeSet, pub perceptual_hashes_by_artifact_root: BTreeMap>, pub embedding_vectors_by_artifact_root: BTreeMap>>, pub transcripts_by_artifact_root: BTreeMap>, pub audio_fingerprints_by_artifact_root: BTreeMap>, pub video_fingerprints_by_artifact_root: BTreeMap>, + pub decision_fingerprints_by_artifact_root: BTreeMap>, pub speciality_calibrations_by_artifact_root: BTreeMap>>, pub default_fingerprint: Option, @@ -1916,6 +1974,7 @@ pub struct GatewayCanaryModelConfig { pub default_transcripts: Option>, pub default_audio_fingerprints: Option>, pub default_video_fingerprints: Option>, + pub default_decision_fingerprints: Option>, } #[derive(Clone, Debug)] @@ -2382,6 +2441,34 @@ pub struct EmbeddingRequest { pub endpoint_request: Option, } +#[derive(Clone, Debug, Deserialize, Serialize)] +#[serde(deny_unknown_fields)] +pub struct DecisionRequest { + pub model: String, + pub state: Value, + pub questions: Value, + #[serde(default)] + pub checkpoint: Option, + #[serde(default)] + pub task: Option, + #[serde(default)] + pub lang: Option, + #[serde(default)] + pub auto_task_detection: bool, + #[serde(default)] + pub email: Option, + #[serde(default)] + pub shortlist: Option, + #[serde(default)] + pub temperature: Option, + #[serde(default)] + pub limits: Option, + #[serde(default)] + pub user: Option, + #[serde(skip)] + pub endpoint_request: Option, +} + #[derive(Clone, Debug, Deserialize, Serialize)] #[serde(deny_unknown_fields)] pub struct ImageGenerationRequest { @@ -2687,7 +2774,8 @@ pub fn normalize_endpoint_request_for_provider( | mayhem_proto::ENDPOINT_MAYHEM_AUDIO_GENERATIONS | mayhem_proto::ENDPOINT_MAYHEM_MUSIC_GENERATIONS | mayhem_proto::ENDPOINT_HF_TEXT_TO_AUDIO - | mayhem_proto::ENDPOINT_MAYHEM_COMFY_WORKFLOWS => {} + | mayhem_proto::ENDPOINT_MAYHEM_COMFY_WORKFLOWS + | mayhem_proto::ENDPOINT_MAYHEM_DECISIONS => {} other => { return Err(format!( "endpoint family {other} has no gateway normalization path" @@ -2735,12 +2823,15 @@ fn normalize_chat_client_metadata<'a>( request: &'a Value, ) -> Result, String> { if contract.family != mayhem_proto::ENDPOINT_OPENAI_CHAT_COMPLETIONS - || (request.get("store").is_none() && request.get("prompt_cache_key").is_none()) { + || (request.get("store").is_none() && request.get("prompt_cache_key").is_none()) + { return Ok(std::borrow::Cow::Borrowed(request)); } if let Some(value) = request.get("store") { if !value.is_null() && value != &Value::Bool(false) { - return Err("store: stored completions are not supported; use false or omit it".to_owned()); + return Err( + "store: stored completions are not supported; use false or omit it".to_owned(), + ); } } if let Some(value) = request.get("prompt_cache_key") { @@ -2841,6 +2932,9 @@ pub type GatewayHedgeProbeFuture<'a> = pub type GatewayTpmActivationFuture<'a> = Pin> + Send + 'a>>; +pub type GatewayTokenizeFuture<'a> = + Pin> + Send + 'a>>; + pub trait GatewaySessionBackend: Send + Sync + std::fmt::Debug { fn name(&self) -> &str; fn bridge_stream_config(&self) -> Option { @@ -2872,6 +2966,18 @@ pub trait GatewaySessionBackend: Send + Sync + std::fmt::Debug { }) } + fn run_tokenize<'a>( + &'a self, + _invocation: &'a GatewayTokenizeInvocation, + ) -> GatewayTokenizeFuture<'a> { + Box::pin(async move { + Err(GatewaySessionError::new(format!( + "{} backend does not support exact tokenization", + self.name() + ))) + }) + } + fn run_chat<'a>( &'a self, model: &'a GatewayModel, @@ -2961,6 +3067,13 @@ pub struct GatewaySessionResult { pub quality: Option, } +#[derive(Clone, Debug)] +pub struct GatewayTokenizeResult { + pub count: u64, + pub tokens: Option>, + pub provider: String, +} + #[derive(Clone, Debug)] pub struct GatewayEmbeddingResult { pub output: EmbeddingOutput, @@ -3103,6 +3216,17 @@ impl GatewayJobHandle { .is_active(&self.id) } + fn persisted_status(&self) -> Option { + match self + .store + .lock_recover("gateway job vault") + .lookup_read_only(&self.id, now_secs()) + { + Some(GatewayJobLookup::Terminal(job)) => Some(job.status), + Some(GatewayJobLookup::InProgress { .. }) | None => None, + } + } + async fn persist_reconciliation_pending( &self, result: Option, @@ -3216,6 +3340,12 @@ impl GatewayJobHandle { code: error.public_code.to_owned(), category: error.category.to_owned(), retryable: error.retryable, + phase: error + .safe_detail + .as_ref() + .and_then(|detail| detail.get("reservation_relay_phase")) + .and_then(Value::as_str) + .map(str::to_owned), }; let message = error.message.clone(); let persisted = tokio::task::spawn_blocking(move || { @@ -3420,6 +3550,45 @@ async fn finish_completed_invocation_job( Ok(()) } +async fn await_completed_invocation_reconciliation( + invocation: &GatewaySessionInvocation, + timeout: Duration, +) -> bool { + let Some(job) = invocation.job.as_ref() else { + return false; + }; + let deadline = Instant::now() + timeout; + loop { + match job.persisted_status() { + Some(GatewayJobStatus::Completed) => return true, + Some(GatewayJobStatus::Failed | GatewayJobStatus::Cancelled) => return false, + Some(GatewayJobStatus::ReconciliationPending) | None => {} + } + if Instant::now() >= deadline { + return false; + } + tokio::time::sleep(Duration::from_millis(100)).await; + } +} + +async fn finish_completed_invocation_after_handoff( + invocation: &GatewaySessionInvocation, + handoff: Result<(), GatewaySessionError>, + recovery_wait: Duration, +) -> Result<(), GatewaySessionError> { + if let Err(error) = handoff { + // The durable job is the terminal source of truth. A settlement + // publisher can report a non-retryable local enqueue error while its + // durable recovery worker completes the exact staged receipt. Do not + // turn that already-completed inference into a provider failure merely + // because the original handoff classified its local error differently. + if !await_completed_invocation_reconciliation(invocation, recovery_wait).await { + return Err(error); + } + } + finish_completed_invocation_job(invocation).await +} + async fn stage_cancelled_invocation_job( invocation: &GatewaySessionInvocation, provider_receipt: &ProviderSignedReceipt, @@ -3539,7 +3708,7 @@ async fn reconcile_and_persist_completed_invocation_job( stage_completed_invocation_job(invocation, result, artifacts, provider_receipt, receipt_ack) .await?; record_direct_session_receipt(invocation, provider_receipt, receipt_ack)?; - send_receipt_ack_and_queue_settlement( + let handoff = send_receipt_ack_and_queue_settlement( bridge, direct_peer, invocation, @@ -3548,8 +3717,13 @@ async fn reconcile_and_persist_completed_invocation_job( None, ack_context, ) - .await?; - finish_completed_invocation_job(invocation).await + .await; + let recovery_wait = invocation + .failover + .open_timeout() + .saturating_mul(2) + .clamp(Duration::from_secs(6), Duration::from_secs(30)); + finish_completed_invocation_after_handoff(invocation, handoff, recovery_wait).await } type GatewayReceiptAckRecoveryFuture<'a> = @@ -3796,15 +3970,23 @@ async fn reconcile_pending_gateway_job_once( if job.status != GatewayJobStatus::ReconciliationPending { return Ok(()); } - if job.receipt.as_ref().is_some_and(|raw| (raw.get("body").is_none() && raw.get("reservation").is_some()) || raw.get("canonical_settlement").is_some()) { + if job.receipt.as_ref().is_some_and(|raw| { + (raw.get("body").is_none() && raw.get("reservation").is_some()) + || raw.get("canonical_settlement").is_some() + }) { return failure_recovery::reconcile(state, &job).await; } let mut recovery = parse_gateway_job_receipt_recovery(&job)?; if !recovery.body.final_receipt { - if let (Some(feature), Some(publisher)) = ( - recovery.reconciliation.settlement_feature.as_ref(), state.receipt_settlement_publisher.as_ref().as_ref(), - ) { - publisher.queue(feature).map_err(GatewaySessionError::new)?; + if let Some(feature) = recovery + .reconciliation + .settlement_feature + .as_ref() + .filter(|feature| receipt_settlement_contract_version_is_supported(feature)) + { + if let Some(publisher) = state.receipt_settlement_publisher.as_ref().as_ref() { + publisher.queue(feature).map_err(GatewaySessionError::new)?; + } } return failure_recovery::reconcile(state, &job).await; } @@ -3813,7 +3995,9 @@ async fn reconcile_pending_gateway_job_once( None => match transport.deliver(&recovery).await { Ok(feature) => feature, Err(error) => { - if failure_recovery::reconcile(state, &job).await.is_ok() { return Ok(()); } + if failure_recovery::reconcile(state, &job).await.is_ok() { + return Ok(()); + } return Err(error); } }, @@ -3824,9 +4008,7 @@ async fn reconcile_pending_gateway_job_once( &feature, )?; recovery = persist_gateway_job_recovery_feature(state, id, recovery, feature.clone()).await?; - if feature.pointer("/value/contract_version").and_then(Value::as_u64) - .is_some_and(receipt_contract_version_is_supported) - { + if receipt_settlement_contract_version_is_supported(&feature) { let publisher = state .receipt_settlement_publisher .as_ref() @@ -3840,10 +4022,19 @@ async fn reconcile_pending_gateway_job_once( } else { // Historical signed bytes must never be rewritten or resubmitted under // the new revision. Retire local recovery only with exact ledger proof. - let rpc = state.canary_probe_contract_rpc.as_ref().as_ref().ok_or_else(|| { - GatewaySessionError::retryable("historical receipt recovery requires canonical ledger access") - })?; - let key = format!("receipt/head/{}/{}", recovery.body.billing_id, recovery.body.billing_attempt); + let rpc = state + .canary_probe_contract_rpc + .as_ref() + .as_ref() + .ok_or_else(|| { + GatewaySessionError::retryable( + "historical receipt recovery requires canonical ledger access", + ) + })?; + let key = format!( + "receipt/head/{}/{}", + recovery.body.billing_id, recovery.body.billing_attempt + ); let record = rpc.state(Some(&key), Some(true)).await.map_err(|err| { GatewaySessionError::retryable(format!("historical receipt confirmation failed: {err}")) })?; @@ -3875,7 +4066,10 @@ fn confirmed_receipt_recovery_matches(record: &Value, key: &str, feature: &Value record.get("confirmed").and_then(Value::as_bool) == Some(true) && record.get("key").and_then(Value::as_str) == Some(key) && record.pointer("/value/type").and_then(Value::as_str) == Some("canonical_receipt_head") - && record.pointer("/value/settlement_ready").and_then(Value::as_bool) == Some(true) + && record + .pointer("/value/settlement_ready") + .and_then(Value::as_bool) + == Some(true) && record.pointer("/value/feature_key") == feature.get("key") && record.pointer("/value/receipt") == feature.pointer("/value/receipt") } @@ -3920,8 +4114,12 @@ fn spawn_pending_gateway_job_reconciliation(state: &GatewayState) -> Result<(), .jobs .lock_recover("gateway job vault") .pending_reconciliations(now_secs())?; - if pending.is_empty() && (state.session_backend.bridge_stream_config().is_none() - || state.receipt_settlement_publisher.as_ref().is_none()) { return Ok(()); } + if pending.is_empty() + && (state.session_backend.bridge_stream_config().is_none() + || state.receipt_settlement_publisher.as_ref().is_none()) + { + return Ok(()); + } let config = state .session_backend .bridge_stream_config() @@ -3936,7 +4134,10 @@ fn spawn_pending_gateway_job_reconciliation(state: &GatewayState) -> Result<(), } for job in &pending { if let Err(err) = parse_gateway_job_receipt_recovery(job) { - eprintln!("Gateway receipt recovery for {} remains pending: {}", job.id, err.message); + eprintln!( + "Gateway receipt recovery for {} remains pending: {}", + job.id, err.message + ); } } let transport: Arc = @@ -4003,6 +4204,22 @@ fn chat_job_result(output: &ChatOutput) -> Value { }) } +fn chat_job_result_for_request(request: &ChatCompletionRequest, output: &ChatOutput) -> Value { + if direct_chat_endpoint_family(request) != mayhem_proto::ENDPOINT_MAYHEM_DECISIONS { + return chat_job_result(output); + } + let result = output + .content + .as_deref() + .and_then(|content| serde_json::from_str::(content).ok()) + .unwrap_or(Value::Null); + json!({ + "kind": "decision", + "result": result, + "usage": output.usage, + }) +} + fn embedding_job_result(output: &EmbeddingOutput) -> Value { json!({ "kind": "embedding", @@ -4127,6 +4344,19 @@ pub struct GatewayTpmActivationInvocation { pub hello: TpmActivateCredentialHello, } +#[derive(Clone, Debug)] +pub struct GatewayTokenizeInvocation { + pub session_id: String, + pub provider_pubkey: String, + pub transport_peer: String, + pub enclave_id: String, + pub room_id: String, + pub model: String, + pub served_ctx: u32, + pub request: Value, + pub return_tokens: bool, +} + impl GatewayHedgeProbeInvocation { fn provider_pubkey_required(&self) -> Result<&str, GatewaySessionError> { self.provider_pubkey @@ -4230,6 +4460,9 @@ pub struct GatewaySessionError { pub message: String, pub failure_class: GatewaySessionFailureClass, pub retryable: bool, + /// The provider has not accepted any metered request frames, so retrying + /// the same route cannot duplicate inference or spend. + pub safe_same_route_retry: bool, pub before_first_output: bool, pub transport_closed: bool, pub wait_elapsed: bool, @@ -4588,6 +4821,9 @@ impl GatewayState { execution_modes: Arc::new(GatewayExecutionModeRegistry::default()), attestation_authority: None, })), + catalog_refresh: Arc::new(Mutex::new(GatewayCatalogRefresh::default())), + catalog_refresh_request: Arc::new(Notify::new()), + catalog_refresh_complete: Arc::new(Notify::new()), receipts: Arc::new(Mutex::new(Vec::new())), dashboard_history_path: Arc::new(None), dashboard_history_write: Arc::new(Mutex::new(())), @@ -4856,6 +5092,36 @@ impl GatewayState { .clone() } + /// Wake the existing authenticated catalog watcher; concurrent refusals share + /// one refresh. Provider-supplied prices are never accepted as authority. + fn request_catalog_refresh(&self) -> u64 { + let mut refresh = self.catalog_refresh.lock_recover("catalog refresh"); + if !refresh.requested { + refresh.requested = true; + self.catalog_refresh_request.notify_one(); + } + refresh.generation + } + + pub async fn wait_for_catalog_refresh_request(&self) { + self.catalog_refresh_request.notified().await; + } + + /// Called only after a successful read/validation, even if the catalog did + /// not change (the rejecting provider may be behind this gateway). + pub fn complete_catalog_refresh(&self) { + let mut refresh = self.catalog_refresh.lock_recover("catalog refresh"); + refresh.generation = refresh.generation.wrapping_add(1); + refresh.requested = false; + self.catalog_refresh_complete.notify_waiters(); + } + + pub fn failed_catalog_refresh(&self) { + self.catalog_refresh + .lock_recover("catalog refresh") + .requested = false; + } + /// Swap the model catalog at runtime (contract catalog refresh). Keeps /// live heartbeat and observation state; only the contract-derived rows /// of the provider table are rebuilt, so enclave/binary approvals and @@ -5443,6 +5709,11 @@ impl GatewayState { Ok(()) } + #[cfg(any(test, feature = "dashboard-workbench"))] + fn record_workbench_probe(&self, probe: StoredProbeEvent) { + self.probes.lock_recover("probe store").push(probe); + } + fn paused_session_count(&self) -> usize { self.paused_sessions .lock_recover("paused session store") @@ -5710,9 +5981,12 @@ pub fn openai_router(state: GatewayState) -> Router { let state = Arc::new(state); let body_routes = Router::new() .route("/v1/chat/completions", post(create_chat_completion)) + .route("/v1/tokenize", post(create_tokenize)) + .route("/v1/count_tokens", post(create_tokenize)) .route("/v1/completions", post(create_completion)) .route("/v1/responses", post(create_response)) .route("/v1/embeddings", post(create_embedding)) + .route("/v1/decisions", post(create_decision)) .route("/v1/images/generations", post(create_image_generation)) .route("/v1/videos", post(create_video_generation)) .route("/v1/audio/speech", post(create_audio_speech)) @@ -5850,6 +6124,7 @@ pub async fn serve(bind: SocketAddr, mut state: GatewayState) -> std::io::Result let listener = TcpListener::bind(bind).await?; spawn_pending_gateway_job_reconciliation(&state) .map_err(|err| io::Error::new(io::ErrorKind::InvalidData, err))?; + failure_recovery::spawn_ledger_reservation_sweep(&state); axum::serve(listener, openai_router(state)).await } @@ -6254,15 +6529,32 @@ fn gateway_reporting_requirements_for_route( candidate: &GatewayRouteCandidate, now_millis: u64, ) -> Vec { + let has_generation_endpoint = model + .mayhem + .adapter + .endpoint_families + .iter() + .any(|contract| endpoint_family_requires_prefix_caching(&contract.family)); + let has_decision_endpoint = model + .mayhem + .adapter + .endpoint_families + .iter() + .any(|contract| contract.family == mayhem_proto::ENDPOINT_MAYHEM_DECISIONS); gateway_reporting_modality_shapes(model, candidate) .into_iter() .map(|modalities| { - let is_text_generation = modalities.as_slice() == ["text"]; + let text_only = modalities.as_slice() == ["text"]; + let is_text_generation = text_only && has_generation_endpoint; + let is_decision = text_only && has_decision_endpoint && !is_text_generation; + let is_token_metered_text = is_text_generation || is_decision; let input_tokens = u64::from( - is_text_generation || modalities.iter().any(|modality| modality == "embedding"), + is_token_metered_text || modalities.iter().any(|modality| modality == "embedding"), ); let output_tokens = if is_text_generation { gateway_reporting_text_output_tokens(model) + } else if is_decision { + 1 } else { 0 }; @@ -6280,7 +6572,7 @@ fn gateway_reporting_requirements_for_route( ) }) .collect::>(); - let usage = if is_text_generation { + let usage = if is_token_metered_text { ReceiptUsage::text(input_tokens, output_tokens) } else if modalities.iter().any(|modality| modality == "embedding") { ReceiptUsage::text(1, 0) @@ -6310,10 +6602,11 @@ fn gateway_reporting_requirements_for_route( RequestRequirements { current_rules_ver: state.receipt_config.rules_ver, requires_transport_peer: !state.dev_session_shim, + requires_prefix_caching: is_text_generation && !is_needle_cache_exception(model), workflow: gateway_reporting_workflow_requirements(model, &modalities), required_modalities: modalities, modality_load, - min_ctx: if is_text_generation { + min_ctx: if is_token_metered_text { u32::try_from(input_tokens.saturating_add(output_tokens)).unwrap_or(u32::MAX) } else { 1 @@ -6329,6 +6622,20 @@ fn gateway_reporting_requirements_for_route( .collect() } +fn endpoint_family_requires_prefix_caching(family: &str) -> bool { + matches!( + family, + mayhem_proto::ENDPOINT_OPENAI_CHAT_COMPLETIONS + | mayhem_proto::ENDPOINT_OPENAI_COMPLETIONS + | mayhem_proto::ENDPOINT_OPENAI_RESPONSES + | mayhem_proto::ENDPOINT_HF_MULTIMODAL_CHAT + ) +} + +fn is_needle_cache_exception(model: &GatewayModel) -> bool { + model.id == "Cactus-Compute/needle" && model.mayhem.model_class == "text-generation" +} + fn gateway_reporting_text_output_tokens(model: &GatewayModel) -> u64 { model .mayhem @@ -6547,16 +6854,17 @@ fn gateway_route_attestation_readiness( candidate: &GatewayRouteCandidate, entries: &[ProviderTableEntry], ) -> RouteAttestationPolicyReadiness { + let enclave_att_tier = route_enclave_attestation_tier(candidate); dashboard_entry_for_route(entries, candidate) .map(|entry| entry.contract.attestation_policy.clone()) .unwrap_or_else(|| { - if matches!(candidate.att_tier, 2 | 3) { + if matches!(enclave_att_tier, 2 | 3) { RouteAttestationPolicyReadiness::unavailable( - candidate.att_tier, + enclave_att_tier, "route has no local provider-table policy snapshot", ) } else { - RouteAttestationPolicyReadiness::not_required(candidate.att_tier) + RouteAttestationPolicyReadiness::not_required(enclave_att_tier) } }) } @@ -6576,8 +6884,8 @@ fn gateway_registered_route_value( let readiness = gateway_route_attestation_readiness(candidate, entries); object.insert("dispatch_eligible".to_owned(), json!(dispatch_eligible)); let entry = dashboard_entry_for_route(entries, candidate); - let fresh = entry - .is_some_and(|entry| entry.has_fresh_heartbeat(state.provider_heartbeat_ttl_millis)); + let fresh = + entry.is_some_and(|entry| entry.has_fresh_heartbeat(state.provider_heartbeat_ttl_millis)); let presence = if fresh { "online" } else if entry.is_some() { @@ -6608,8 +6916,8 @@ fn gateway_registered_route_value( "attestation_verification".to_owned(), serde_json::to_value(readiness).unwrap_or_else(|_| { json!({ - "attestation_tier": candidate.att_tier, - "policy_required": matches!(candidate.att_tier, 2 | 3), + "attestation_tier": route_enclave_attestation_tier(candidate), + "policy_required": matches!(route_enclave_attestation_tier(candidate), 2 | 3), "locally_ready": false, "runtime_binary_hash_evidence_only": true, "reason": "local attestation readiness could not be encoded", @@ -6618,10 +6926,7 @@ fn gateway_registered_route_value( ); if fresh { if let Some(entry) = entry { - gateway_apply_heartbeat_route_caps( - &mut value, - GatewayLiveRoute { candidate, entry }, - ); + gateway_apply_heartbeat_route_caps(&mut value, GatewayLiveRoute { candidate, entry }); } } value @@ -6970,7 +7275,7 @@ fn gateway_route_belongs_to_market( ) -> bool { if candidate.enclave_id != market.enclave_id || !market.room_ids.contains(&candidate.room_id) - || candidate.att_tier != market.att_tier + || route_enclave_attestation_tier(candidate) != market.att_tier || !candidate.quant.eq_ignore_ascii_case(&market.quant) || !candidate .accepted_rails @@ -7061,14 +7366,7 @@ fn gateway_model_info_value( let live_route_values = live_routes .iter() .map(|route| { - gateway_registered_route_value( - state, - model, - route.candidate, - entries, - true, - now_millis, - ) + gateway_registered_route_value(state, model, route.candidate, entries, true, now_millis) }) .collect::>(); let registered_route_values = model @@ -9340,6 +9638,14 @@ fn durable_streaming_endpoint_family(family: &str) -> bool { ) } +// The job vault also persists non-streaming requests. Recovery by idempotency +// key must be available for every signed endpoint family, even when the +// original response never reached the buyer. Keep the streaming predicate +// above separate: it governs partial-receipt checkpoint validation. +fn lookupable_gateway_job_family(family: &str) -> bool { + mayhem_proto::endpoint_family_contract_template(family).is_some() +} + #[derive(Deserialize)] #[serde(deny_unknown_fields)] struct GatewayJobKeyLookupQuery { @@ -9355,9 +9661,9 @@ async fn lookup_gateway_job_by_key( Ok(owner) => owner, Err(error) => return error.into_response(), }; - if !durable_streaming_endpoint_family(&query.endpoint_family) { + if !lookupable_gateway_job_family(&query.endpoint_family) { return ApiError::bad_request( - "unsupported streaming endpoint family", + "unsupported endpoint family", Some("endpoint_family"), ) .into_response(); @@ -9637,6 +9943,9 @@ async fn create_chat_completion( }; request.endpoint_family = Some(endpoint_family); request.endpoint_request = Some(normalized_request); + if let Err(err) = validate_requested_response_schema(request.response_format.as_ref()) { + return err.into_response(); + } let mut options = match state.request_options_from_headers(&headers) { Ok(options) => options, Err(err) => return err.into_response(), @@ -9702,6 +10011,169 @@ async fn create_chat_completion( } } +async fn create_tokenize( + State(state): State, + headers: HeaderMap, + Json(raw_request): Json, +) -> Response { + match build_tokenize_response(&state, &headers, raw_request).await { + Ok(response) => Json(response).into_response(), + Err(error) => error.into_response(), + } +} + +async fn build_tokenize_response( + state: &GatewayState, + headers: &HeaderMap, + mut raw_request: Value, +) -> Result { + let object = raw_request.as_object_mut().ok_or_else(|| { + ApiError::bad_request( + "tokenization request must be a JSON object", + Some("request"), + ) + })?; + let return_tokens = object + .remove("return_tokens") + .map(|value| { + value.as_bool().ok_or_else(|| { + ApiError::bad_request("return_tokens must be a boolean", Some("return_tokens")) + }) + }) + .transpose()? + .unwrap_or(false); + if let Some(prompt) = object.remove("prompt") { + if object.contains_key("messages") { + return Err(ApiError::bad_request( + "provide either prompt or messages, not both", + Some("prompt"), + )); + } + let prompt = prompt + .as_str() + .filter(|value| !value.is_empty()) + .ok_or_else(|| { + ApiError::bad_request("prompt must be a non-empty string", Some("prompt")) + })?; + object.insert( + "messages".to_owned(), + json!([{ "role": "user", "content": prompt }]), + ); + } + let model_id = endpoint_request_model(&raw_request)?; + let access_token = state.authorize_gateway_request(headers, Some(model_id))?; + let endpoint_family = chat_endpoint_family(state, model_id, &raw_request)?; + let (mut request, normalized_request) = parse_catalog_endpoint_request::( + state, + &raw_request, + &endpoint_family, + )?; + request.endpoint_family = Some(endpoint_family); + request.endpoint_request = Some(normalized_request); + let model = require_model(state, model_id)?; + apply_model_sampling_defaults(&model, &mut request)?; + apply_model_speciality_defaults(&model, &mut request)?; + synchronize_effective_chat_contract_request(&model, &mut request)?; + if request.messages.is_empty() { + return Err(ApiError::bad_request( + "messages must contain at least one item", + Some("messages"), + )); + } + validate_chat_modalities(&model, &request, &state.media_limits)?; + let mut options = state.request_options_from_headers(headers)?; + options.access_token = access_token; + state.refresh_provider_table_routes(&model); + let routes = order_strict_preferred_routes( + state, + &model, + &options, + eligible_route_candidates( + &model, + options.min_att_tier, + options.quant.as_deref(), + &state.receipt_config.rail, + ), + ); + let route = routes + .into_iter() + .find(|route| route_has_live_control_transport(state, route)); + let route = route.ok_or_else(|| { + ApiError::service_unavailable( + "no live provider can perform exact tokenization for this model", + Some("model"), + ) + .with_public_error("token_count_unavailable", "provider_admission", true) + })?; + let transport_peer = state.transport_peer_for_route(Some(route)).ok_or_else(|| { + ApiError::service_unavailable( + "the selected tokenizer provider has no live transport", + Some("model"), + ) + .with_public_error("token_count_unavailable", "provider_admission", true) + })?; + let transport_body = direct_session_request_body(&request); + let sealed_request = seal_direct_session_request_body( + &model, + direct_chat_endpoint_family(&request), + transport_body.clone(), + direct_chat_contract_request(&request, &transport_body), + ) + .map_err(|error| ApiError::bad_request(error.message, Some("request")))?; + let mut entropy = [0_u8; 32]; + getrandom::fill(&mut entropy).map_err(|error| { + ApiError::internal_message(format!( + "generating tokenization session identity failed: {error}" + )) + })?; + let invocation = GatewayTokenizeInvocation { + session_id: hex::encode(entropy), + provider_pubkey: route.provider.clone(), + transport_peer, + enclave_id: route.enclave_id.clone(), + room_id: route.room_id.clone(), + model: model.id.clone(), + served_ctx: state.served_ctx_for_route(&model, Some(route)), + request: sealed_request, + return_tokens, + }; + let result = state + .session_backend + .run_tokenize(&invocation) + .await + .map_err(|error| { + if error.message.contains("Exact tokenization is unavailable") + || error + .message + .contains("does not support exact tokenization") + { + ApiError::bad_request( + "exact tokenization is unavailable for this model runtime", + Some("model"), + ) + .with_public_error("token_count_unsupported", "capability", false) + } else { + ApiError::service_unavailable( + "the tokenizer provider could not complete this request", + Some("model"), + ) + .with_public_error( + "token_count_failed", + "provider_execution", + true, + ) + } + })?; + Ok(json!({ + "object": "tokenization", + "model": model.id, + "count": result.count, + "tokens": result.tokens, + "max_model_len": state.served_ctx_for_route(&model, Some(route)), + "provider": result.provider, + })) +} + fn chat_endpoint_family( state: &GatewayState, model_id: &str, @@ -9848,6 +10320,11 @@ async fn create_response( Ok(request) => request, Err(err) => return err.into_response(), }; + if let Err(err) = validate_requested_response_schema( + request.text.as_ref().and_then(|text| text.get("format")), + ) { + return err.into_response(); + } let mut options = match state.request_options_from_headers(&headers) { Ok(options) => options, Err(err) => return err.into_response(), @@ -9906,6 +10383,83 @@ async fn create_response( } } +async fn create_decision( + State(state): State, + headers: HeaderMap, + Json(raw_request): Json, +) -> Response { + let model_id = match endpoint_request_model(&raw_request) { + Ok(model) => model, + Err(err) => return err.into_response(), + }; + let access_token = match state.authorize_gateway_request(&headers, Some(model_id)) { + Ok(access_token) => access_token, + Err(err) => return err.into_response(), + }; + let (mut request, normalized_request) = match parse_catalog_endpoint_request::( + &state, + &raw_request, + mayhem_proto::ENDPOINT_MAYHEM_DECISIONS, + ) { + Ok(request) => request, + Err(err) => return err.into_response(), + }; + request.endpoint_request = Some(normalized_request); + let mut options = match state.request_options_from_headers(&headers) { + Ok(options) => options, + Err(err) => return err.into_response(), + }; + options.access_token = access_token; + let job = match prepare_gateway_job( + &state, + &headers, + mayhem_proto::ENDPOINT_MAYHEM_DECISIONS, + &request.model, + request + .endpoint_request + .as_ref() + .expect("normalized decision request is present"), + &options.access_token, + ) + .await + { + Ok(PreparedGatewayJob::Started(job)) => job, + Ok(PreparedGatewayJob::InProgress(id)) => return gateway_job_pending_response(&id), + Ok(PreparedGatewayJob::Existing(job)) => return gateway_existing_job_response(job), + Err(err) => return err.into_response(), + }; + options.job = Some(job.clone()); + let cancellation = job.cancellation(); + options.client_cancellation = Some(cancellation.clone()); + if gateway_prefers_async_response(&headers) { + let job_id = job.id.clone(); + let request_state = state.clone(); + spawn_gateway_job_request(job, async move { + build_decision(&request_state, request, options).await + }); + return gateway_job_pending_response(&job_id); + } + let request_state = state.clone(); + let result = run_detached_gateway_job_request(cancellation, job.clone(), async move { + build_decision(&request_state, request, options).await + }) + .await; + match result { + Ok(value) => { + if let Err(err) = job + .persist_completed_if_active(value.clone(), Vec::new(), None) + .await + { + return err.into_response(); + } + let mut response = Json(value).into_response(); + attach_gateway_job_headers(&mut response, &job.id); + response + } + Err(err) => err.into_response(), + } +} + async fn create_embedding( State(state): State, headers: HeaderMap, @@ -13483,6 +14037,7 @@ struct DirectArtifactGenerationSessionCollected { } struct GatewaySessionRun { + model: GatewayModel, result: GatewaySessionResult, invocation: GatewaySessionInvocation, metering_request: ChatCompletionRequest, @@ -13490,6 +14045,7 @@ struct GatewaySessionRun { } struct GatewayEmbeddingRun { + model: GatewayModel, result: GatewayEmbeddingResult, invocation: GatewaySessionInvocation, metering_inputs: Vec, @@ -13497,6 +14053,7 @@ struct GatewayEmbeddingRun { } struct GatewayImageGenerationRun { + model: GatewayModel, result: GatewayImageGenerationResult, invocation: GatewaySessionInvocation, metering_request: ImageGenerationRequest, @@ -13504,6 +14061,7 @@ struct GatewayImageGenerationRun { } struct GatewayAudioSpeechRun { + model: GatewayModel, result: GatewayAudioSpeechResult, invocation: GatewaySessionInvocation, metering_request: AudioSpeechRequest, @@ -13511,6 +14069,7 @@ struct GatewayAudioSpeechRun { } struct GatewayAudioTranscriptionRun { + model: GatewayModel, result: GatewayAudioTranscriptionResult, invocation: GatewaySessionInvocation, metering_request: AudioTranscriptionRequest, @@ -13518,6 +14077,7 @@ struct GatewayAudioTranscriptionRun { } struct GatewayArtifactGenerationRun { + model: GatewayModel, result: GatewayArtifactGenerationResult, invocation: GatewaySessionInvocation, metering_request: ArtifactGenerationRequest, @@ -13727,18 +14287,29 @@ fn canary_registry_from_catalog_root( { continue; } + let decision_fingerprints = canary_decision_fingerprints_by_artifact(canary); + if verification_method == CANARY_VERIFICATION_DECISION_FINGERPRINT + && decision_fingerprints.is_empty() + { + continue; + } let mut fingerprints_by_artifact_root = BTreeMap::new(); let mut token_prefixes_by_artifact_root = BTreeMap::new(); + let mut openai_compatible_artifact_roots = BTreeSet::new(); let mut perceptual_hashes_by_artifact_root = BTreeMap::new(); let mut embedding_vectors_by_artifact_root = BTreeMap::new(); let mut transcripts_by_artifact_root = BTreeMap::new(); let mut audio_fingerprints_by_artifact_root = BTreeMap::new(); let mut video_fingerprints_by_artifact_root = BTreeMap::new(); + let mut decision_fingerprints_by_artifact_root = BTreeMap::new(); let speciality_calibrations = speciality_calibrations_from_catalog_value(model); let mut speciality_calibrations_by_artifact_root = BTreeMap::new(); if let Some(artifacts) = model.get("artifacts").and_then(Value::as_object) { for (artifact_name, artifact) in artifacts { if let Some(artifact_root) = artifact.get("artifact_root").and_then(Value::as_str) { + if artifact.get("engine").and_then(Value::as_str) == Some("openai-compatible") { + openai_compatible_artifact_roots.insert(artifact_root.to_owned()); + } if let Some(calibrations) = speciality_calibrations.get(artifact_name) { speciality_calibrations_by_artifact_root .insert(artifact_root.to_owned(), calibrations.clone()); @@ -13774,6 +14345,10 @@ fn canary_registry_from_catalog_root( } else if let Some(expected) = video_fingerprints.get(artifact_name.as_str()) { video_fingerprints_by_artifact_root .insert(artifact_root.to_owned(), expected.clone()); + } else if let Some(expected) = decision_fingerprints.get(artifact_name.as_str()) + { + decision_fingerprints_by_artifact_root + .insert(artifact_root.to_owned(), expected.clone()); } } } @@ -13785,6 +14360,7 @@ fn canary_registry_from_catalog_root( let default_transcripts = transcripts.values().next().cloned(); let default_audio_fingerprints = audio_fingerprints.values().next().cloned(); let default_video_fingerprints = video_fingerprints.values().next().cloned(); + let default_decision_fingerprints = decision_fingerprints.values().next().cloned(); models.insert( model_id.to_owned(), GatewayCanaryModelConfig { @@ -13796,11 +14372,13 @@ fn canary_registry_from_catalog_root( prompts, fingerprints_by_artifact_root, token_prefixes_by_artifact_root, + openai_compatible_artifact_roots, perceptual_hashes_by_artifact_root, embedding_vectors_by_artifact_root, transcripts_by_artifact_root, audio_fingerprints_by_artifact_root, video_fingerprints_by_artifact_root, + decision_fingerprints_by_artifact_root, speciality_calibrations_by_artifact_root, default_fingerprint, default_token_prefixes, @@ -13809,6 +14387,7 @@ fn canary_registry_from_catalog_root( default_transcripts, default_audio_fingerprints, default_video_fingerprints, + default_decision_fingerprints, }, ); } @@ -13968,6 +14547,9 @@ fn mode_canary_has_exact_evidence(canary: &GatewayCanaryModelConfig, artifact_ro CANARY_VERIFICATION_VIDEO_AV_FINGERPRINT => canary .video_fingerprints_by_artifact_root .contains_key(artifact_root), + CANARY_VERIFICATION_DECISION_FINGERPRINT => canary + .decision_fingerprints_by_artifact_root + .contains_key(artifact_root), _ => false, } } @@ -14124,6 +14706,35 @@ fn canary_video_fingerprints_by_artifact( .collect() } +fn canary_decision_fingerprints_by_artifact( + canary: &Value, +) -> BTreeMap> { + canary + .get("decision_fingerprints") + .and_then(Value::as_object) + .into_iter() + .flat_map(|object| object.iter()) + .filter_map(|(artifact_name, value)| { + let prompts = value.as_object()?; + let prompts = prompts + .iter() + .filter_map(|(prompt_id, fingerprint)| { + fingerprint + .as_str() + .filter(|fingerprint| { + fingerprint.len() == 64 + && fingerprint.bytes().all(|byte| { + byte.is_ascii_hexdigit() && !byte.is_ascii_uppercase() + }) + }) + .map(|fingerprint| (prompt_id.clone(), fingerprint.to_owned())) + }) + .collect::>(); + (!prompts.is_empty()).then(|| (artifact_name.clone(), prompts)) + }) + .collect() +} + fn aggregate_token_prefixes_for_prompts( prompts: &[GatewayCanaryPrompt], prefixes: &BTreeMap>, @@ -14266,7 +14877,9 @@ fn contract_snapshot_for_route( resolved .map(|resolved| resolved.readiness) .unwrap_or_else(|| { - RouteAttestationPolicyReadiness::not_required(candidate.att_tier) + RouteAttestationPolicyReadiness::not_required(route_enclave_attestation_tier( + candidate, + )) }) }, ); @@ -14403,12 +15016,13 @@ fn route_attestation_policy_resolution( verifier_command: Option<&HardwareQuoteVerifierCommand>, now_millis: u64, ) -> Result, RouteAttestationPolicyReadiness> { - if !matches!(candidate.att_tier, 2 | 3) { + let enclave_att_tier = route_enclave_attestation_tier(candidate); + if !matches!(enclave_att_tier, 2 | 3) { return Ok(None); } let mut status = RouteAttestationPolicyReadiness::unavailable( - candidate.att_tier, + enclave_att_tier, "authenticated attestation authority is not configured", ); let Some(authority) = authority.cloned() else { @@ -14423,12 +15037,12 @@ fn route_attestation_policy_resolution( }; let advertisement = &live_advertisement.advertisement; status.quote_kind = Some(advertisement.kind); - if advertisement.kind.attestation_tier() != candidate.att_tier { + if advertisement.kind.attestation_tier() != enclave_att_tier { status.reason = Some(format!( "quote kind {} proves Tier {}, not route Tier {}", advertisement.kind.as_str(), advertisement.kind.attestation_tier(), - candidate.att_tier + enclave_att_tier )); return Err(status); } @@ -14889,6 +15503,10 @@ fn canonical_route_candidate(candidate: &GatewayRouteCandidate) -> bool { .all(|root| is_hex_len(root, 64)) && is_hex_len(&candidate.manifest_hash, 64) && is_hex_len(&candidate.binary_hash, 64) + && candidate + .enclave_att_tier + .is_none_or(|tier| (1..=3).contains(&tier)) + && (candidate.att_tier != 4 || candidate.enclave_att_tier.is_some()) } fn canonical_market_info(market: &GatewayMarketInfo) -> bool { @@ -14916,6 +15534,7 @@ impl GatewaySessionError { message: message.into(), failure_class: GatewaySessionFailureClass::ProviderFault, retryable: false, + safe_same_route_retry: false, before_first_output: false, transport_closed: false, wait_elapsed: false, @@ -14931,6 +15550,7 @@ impl GatewaySessionError { message: message.into(), failure_class: GatewaySessionFailureClass::ProviderFault, retryable: true, + safe_same_route_retry: false, before_first_output: false, transport_closed: false, wait_elapsed: false, @@ -14946,6 +15566,7 @@ impl GatewaySessionError { message: message.into(), failure_class: GatewaySessionFailureClass::ProviderFault, retryable: true, + safe_same_route_retry: false, before_first_output: true, transport_closed: false, wait_elapsed: false, @@ -14961,6 +15582,7 @@ impl GatewaySessionError { message: message.into(), failure_class: GatewaySessionFailureClass::ProviderFault, retryable: true, + safe_same_route_retry: false, before_first_output: false, transport_closed: true, wait_elapsed: false, @@ -14976,6 +15598,7 @@ impl GatewaySessionError { message: message.into(), failure_class: GatewaySessionFailureClass::ProviderFault, retryable: true, + safe_same_route_retry: false, before_first_output: false, transport_closed: false, wait_elapsed: true, @@ -14995,6 +15618,7 @@ impl GatewaySessionError { message: message.into(), failure_class: GatewaySessionFailureClass::ProviderFault, retryable: true, + safe_same_route_retry: false, before_first_output: false, transport_closed: false, wait_elapsed: false, @@ -15010,6 +15634,7 @@ impl GatewaySessionError { message: message.into(), failure_class: GatewaySessionFailureClass::ProviderFault, retryable: true, + safe_same_route_retry: false, before_first_output: false, transport_closed: false, wait_elapsed: false, @@ -15028,6 +15653,7 @@ impl GatewaySessionError { message: message.into(), failure_class: GatewaySessionFailureClass::ProviderFault, retryable: true, + safe_same_route_retry: false, before_first_output: false, transport_closed: false, wait_elapsed: false, @@ -15061,6 +15687,17 @@ impl GatewaySessionError { self } + fn into_safe_same_route_retry(mut self) -> Self { + self.safe_same_route_retry = self.retryable; + self + } + + fn into_non_retryable_admission_outcome(mut self) -> Self { + self.retryable = false; + self.safe_same_route_retry = false; + self + } + fn into_retryable_before_output(mut self) -> Self { self.retryable = true; self.before_first_output = true; @@ -15078,6 +15715,7 @@ fn provider_reported_session_error( frame: &Value, session_context: &str, retryable: bool, + execution_evidence_observed: bool, ) -> GatewaySessionError { let code = frame .get("code") @@ -15088,12 +15726,28 @@ fn provider_reported_session_error( .and_then(Value::as_str) .unwrap_or("provider returned s.error"); let message = format!("provider returned {code} on {session_context}: {message}"); + let failure_receipt_observed = frame.get("receipt").is_some(); + let execution_evidence_observed = execution_evidence_observed || failure_receipt_observed; match code { - "context_length_exceeded" | "request_invalid" | "request_chunk_failed" | "request_reassembly_failed" => { - GatewaySessionError::buyer_local(message) - } + // Some provider workers report an atomic admission race as a lowercase + // s.error instead of the uppercase s.reject code. It is a clean + // pre-spend refusal only while the collector has seen no evidence that + // execution began. Once output or a receipt exists, fail closed so a + // retry cannot duplicate inference or spend. + "capacity" if !execution_evidence_observed => { + GatewaySessionError::clean_refusal_with_code(message, Some("CAPACITY")) + } + "capacity" => GatewaySessionError::new(message), + "context_length_exceeded" + | "request_invalid" + | "request_chunk_failed" + | "request_reassembly_failed" => GatewaySessionError::buyer_local(message), "model_output_invalid" => GatewaySessionError::request_scoped(message), - _ if retryable => GatewaySessionError::retryable(message), + // A signed failure receipt settles the attempt before this error is + // returned. Never redispatch a generic provider fault after that paid + // terminal outcome. Request-scoped codes above retain their precise + // public classification even when the frame includes a receipt. + _ if retryable && !failure_receipt_observed => GatewaySessionError::retryable(message), _ => GatewaySessionError::new(message), } } @@ -15581,6 +16235,29 @@ fn sc_bridge_session_transport_valid(opened: &Value) -> bool { sc_bridge_session_transport(opened).is_ok() } +async fn open_direct_session_with_timeout( + bridge: &mut ScBridgeClient, + provider: &str, + direct_peer: &str, + session_id: &str, + timeout: Duration, +) -> Result { + tokio::time::timeout(timeout, bridge.session_open(direct_peer, session_id)) + .await + .map_err(|_| { + GatewaySessionError::retryable(format!( + "opening direct session {session_id} to provider {provider} transport peer {direct_peer} timed out" + )) + .into_safe_same_route_retry() + })? + .map_err(|err| { + GatewaySessionError::retryable(format!( + "opening direct session {session_id} to provider {provider} transport peer {direct_peer} failed: {err}" + )) + .into_safe_same_route_retry() + }) +} + impl GatewaySessionBackend for ScBridgeGatewaySessionBackend { fn name(&self) -> &str { "sc-bridge-direct-session" @@ -15606,6 +16283,13 @@ impl GatewaySessionBackend for ScBridgeGatewaySessionBackend { Box::pin(async move { self.activate_tpm_over_bridge(invocation).await }) } + fn run_tokenize<'a>( + &'a self, + invocation: &'a GatewayTokenizeInvocation, + ) -> GatewayTokenizeFuture<'a> { + Box::pin(async move { self.run_tokenize_over_bridge(invocation).await }) + } + fn run_chat<'a>( &'a self, model: &'a GatewayModel, @@ -15677,6 +16361,173 @@ impl GatewaySessionBackend for ScBridgeGatewaySessionBackend { } impl ScBridgeGatewaySessionBackend { + async fn run_tokenize_over_bridge( + &self, + invocation: &GatewayTokenizeInvocation, + ) -> Result { + let mut bridge = ScBridgeClient::connect(self.config.bridge_config()?).await?; + bridge + .session_subscribe([invocation.session_id.as_str()]) + .await?; + bridge + .peer_connect(&invocation.transport_peer, self.config.open_timeout) + .await + .map_err(|err| { + GatewaySessionError::retryable(format!( + "connecting provider {} for tokenization failed: {err}", + invocation.provider_pubkey + )) + })?; + let opened = bridge + .session_open(&invocation.transport_peer, &invocation.session_id) + .await + .map_err(|err| { + GatewaySessionError::retryable(format!( + "opening tokenization session {} to provider {} failed: {err}", + invocation.session_id, invocation.provider_pubkey + )) + })?; + if !sc_bridge_session_transport_valid(&opened) { + return Err(GatewaySessionError::retryable( + "tokenization session did not open an authenticated direct-or-relayed channel", + )); + } + send_tokenize_request_frames(&mut bridge, invocation).await?; + let response_deadline = Instant::now() + Duration::from_secs(30); + let max_token_payload_bytes = usize::try_from(invocation.served_ctx) + .unwrap_or(usize::MAX / 16) + .saturating_mul(16) + .max(DEFAULT_SESSION_MAX_REASSEMBLED_PAYLOAD_BYTES); + let max_token_payload_chunks = max_token_payload_bytes + .div_ceil(1024) + .max(DEFAULT_SESSION_MAX_PAYLOAD_CHUNKS); + let mut token_chunks = + PayloadChunkCollector::new(max_token_payload_bytes, max_token_payload_chunks); + let mut saw_token_chunk = false; + let mut response = loop { + let remaining = response_deadline.saturating_duration_since(Instant::now()); + if remaining.is_zero() { + return Err(GatewaySessionError::retryable(format!( + "waiting for tokenization response from provider {} timed out", + invocation.provider_pubkey + ))); + } + let event = bridge + .next_session_frame_for(&invocation.session_id, Some(remaining)) + .await + .map_err(|err| { + GatewaySessionError::retryable(format!( + "waiting for tokenization response from provider {} failed: {err}", + invocation.provider_pubkey + )) + })?; + let remote = event + .get("remote") + .and_then(Value::as_str) + .unwrap_or_default(); + let frame = event + .get("frame") + .cloned() + .ok_or_else(|| GatewaySessionError::new("tokenization event has no frame"))?; + let frame_type = frame.get("t").and_then(Value::as_str).unwrap_or_default(); + let binding_matches = remote == invocation.transport_peer + && frame.get("v").and_then(Value::as_u64) + == Some(u64::from(TOKENIZE_FRAME_VERSION)) + && frame.get("session_id").and_then(Value::as_str) + == Some(invocation.session_id.as_str()) + && frame.get("provider").and_then(Value::as_str) + == Some(invocation.provider_pubkey.as_str()) + && frame.get("enclave_id").and_then(Value::as_str) + == Some(invocation.enclave_id.as_str()) + && frame.get("room_id").and_then(Value::as_str) + == Some(invocation.room_id.as_str()) + && frame.get("model").and_then(Value::as_str) == Some(invocation.model.as_str()); + if !binding_matches { + return Err(GatewaySessionError::new( + "tokenization response does not match the authenticated route", + )); + } + if frame_type == TOKENIZE_RESPONSE_CHUNK_FRAME_TYPE { + let chunk: PayloadChunk = + serde_json::from_value(frame.get("chunk").cloned().ok_or_else(|| { + GatewaySessionError::new("tokenization response chunk has no payload") + })?) + .map_err(|err| { + GatewaySessionError::new(format!( + "invalid tokenization response chunk: {err}" + )) + })?; + token_chunks.push(chunk).map_err(|err| { + GatewaySessionError::new(format!( + "collecting tokenization response chunk failed: {err}" + )) + })?; + saw_token_chunk = true; + continue; + } + if frame_type != TOKENIZE_RESPONSE_FRAME_TYPE { + return Err(GatewaySessionError::new( + "provider returned an unsupported tokenization response frame", + )); + } + break serde_json::from_value::(frame).map_err(|err| { + GatewaySessionError::new(format!("invalid tokenization response: {err}")) + })?; + }; + let _ = bridge + .session_close(&invocation.transport_peer, &invocation.session_id) + .await; + if let Some(manifest) = response.tokens_ref.take() { + if response.tokens.is_some() { + return Err(GatewaySessionError::new( + "tokenization response contains both tokens and tokens_ref", + )); + } + let tokens_value = token_chunks.finish_json(&manifest).map_err(|err| { + GatewaySessionError::new(format!( + "reassembling tokenization response tokens failed: {err}" + )) + })?; + response.tokens = Some(serde_json::from_value(tokens_value).map_err(|err| { + GatewaySessionError::new(format!( + "tokenization response token payload is invalid: {err}" + )) + })?); + } else if saw_token_chunk { + return Err(GatewaySessionError::new( + "tokenization response chunks were not bound by a final manifest", + )); + } + if !response.ok { + let code = response + .error_code + .as_deref() + .unwrap_or("token_count_failed"); + let message = response.error.unwrap_or_else(|| { + "The provider tokenizer could not complete this request.".to_owned() + }); + return Err(GatewaySessionError::new(format!("{code}: {message}"))); + } + let count = response + .count + .ok_or_else(|| GatewaySessionError::new("tokenization response is missing count"))?; + if invocation.return_tokens + && response + .tokens + .as_ref() + .is_none_or(|tokens| u64::try_from(tokens.len()).ok() != Some(count)) + { + return Err(GatewaySessionError::new( + "tokenization response token list does not match count", + )); + } + Ok(GatewayTokenizeResult { + count, + tokens: response.tokens, + provider: response.provider, + }) + } + async fn activate_tpm_over_bridge( &self, invocation: &GatewayTpmActivationInvocation, @@ -15813,21 +16664,22 @@ impl ScBridgeGatewaySessionBackend { "hedge peer connect to provider {} via transport peer {} for session {} failed: {err}", provider, direct_peer, invocation.session_id )) + .into_safe_same_route_retry() })?; - let opened = bridge - .session_open(direct_peer, &invocation.session_id) - .await - .map_err(|err| { - GatewaySessionError::retryable(format!( - "hedge session open {} to provider {} via transport peer {} failed: {err}", - invocation.session_id, provider, direct_peer - )) - })?; + let opened = open_direct_session_with_timeout( + &mut bridge, + provider, + direct_peer, + &invocation.session_id, + invocation.failover.open_timeout(), + ) + .await?; if !sc_bridge_session_transport_valid(&opened) { return Err(GatewaySessionError::retryable(format!( "hedge session {} did not open an authenticated direct-or-relayed transport", invocation.session_id - ))); + )) + .into_safe_same_route_retry()); } let _ = close_direct_session_channel( &mut bridge, @@ -15862,21 +16714,22 @@ impl ScBridgeGatewaySessionBackend { "connecting provider {} transport peer {} for session {} failed: {err}", provider, direct_peer, invocation.session_id )) + .into_safe_same_route_retry() })?; - let opened = bridge - .session_open(direct_peer, &invocation.session_id) - .await - .map_err(|err| { - GatewaySessionError::retryable(format!( - "opening direct session {} to provider {} transport peer {} failed: {err}", - invocation.session_id, provider, direct_peer - )) - })?; + let opened = open_direct_session_with_timeout( + &mut bridge, + provider, + direct_peer, + &invocation.session_id, + invocation.failover.open_timeout(), + ) + .await?; if !sc_bridge_session_transport_valid(&opened) { return Err(GatewaySessionError::retryable(format!( "session {} did not open an authenticated direct-or-relayed channel", invocation.session_id - ))); + )) + .into_safe_same_route_retry()); } let now = now_millis_u64(); @@ -15978,7 +16831,7 @@ impl ScBridgeGatewaySessionBackend { provider, model, &accept_info.enclave_pubkey, - blake3_hex(chat_prompt_text(request).as_bytes()), + direct_chat_prompt_hash(request), expected_usage, expected_seq, ) @@ -15988,7 +16841,9 @@ impl ScBridgeGatewaySessionBackend { Err(err) => { if err.failure_class.is_request_scoped() || invocation.job.as_ref().is_some_and(|job| !job.is_active()) - { return Err(err); } + { + return Err(err); + } if let Some(partial) = err.partial.as_ref() { let receipt_ack = direct_session_partial_receipt_ack( request, invocation, partial, provider, model, @@ -16031,7 +16886,7 @@ impl ScBridgeGatewaySessionBackend { &mut bridge, direct_peer, invocation, - chat_job_result(&collected.output), + chat_job_result_for_request(request, &collected.output), &collected.output.artifacts, &collected.provider_receipt, &receipt_ack, @@ -16074,21 +16929,22 @@ impl ScBridgeGatewaySessionBackend { "connecting provider {} transport peer {} for embedding session {} failed: {err}", provider, direct_peer, invocation.session_id )) + .into_safe_same_route_retry() })?; - let opened = bridge - .session_open(direct_peer, &invocation.session_id) - .await - .map_err(|err| { - GatewaySessionError::retryable(format!( - "opening direct embedding session {} to provider {} transport peer {} failed: {err}", - invocation.session_id, provider, direct_peer - )) - })?; + let opened = open_direct_session_with_timeout( + &mut bridge, + provider, + direct_peer, + &invocation.session_id, + invocation.failover.open_timeout(), + ) + .await?; if !sc_bridge_session_transport_valid(&opened) { return Err(GatewaySessionError::retryable(format!( "embedding session {} did not open an authenticated direct-or-relayed channel", invocation.session_id - ))); + )) + .into_safe_same_route_retry()); } let now = now_millis_u64(); @@ -16165,7 +17021,8 @@ impl ScBridgeGatewaySessionBackend { invocation.failover, &inputs, &accept_info.enclave_pubkey, - invocation, model, + invocation, + model, invocation.client_cancellation.as_ref(), ) .await @@ -16240,21 +17097,22 @@ impl ScBridgeGatewaySessionBackend { "connecting provider {} transport peer {} for image session {} failed: {err}", provider, direct_peer, invocation.session_id )) + .into_safe_same_route_retry() })?; - let opened = bridge - .session_open(direct_peer, &invocation.session_id) - .await - .map_err(|err| { - GatewaySessionError::retryable(format!( - "opening direct image session {} to provider {} transport peer {} failed: {err}", - invocation.session_id, provider, direct_peer - )) - })?; + let opened = open_direct_session_with_timeout( + &mut bridge, + provider, + direct_peer, + &invocation.session_id, + invocation.failover.open_timeout(), + ) + .await?; if !sc_bridge_session_transport_valid(&opened) { return Err(GatewaySessionError::retryable(format!( "image session {} did not open an authenticated direct-or-relayed channel", invocation.session_id - ))); + )) + .into_safe_same_route_retry()); } let now = now_millis_u64(); @@ -16331,7 +17189,8 @@ impl ScBridgeGatewaySessionBackend { invocation.failover, request, &accept_info.enclave_pubkey, - invocation, model, + invocation, + model, invocation.client_cancellation.as_ref(), ) .await @@ -16406,21 +17265,22 @@ impl ScBridgeGatewaySessionBackend { "connecting provider {} transport peer {} for audio speech session {} failed: {err}", provider, direct_peer, invocation.session_id )) + .into_safe_same_route_retry() })?; - let opened = bridge - .session_open(direct_peer, &invocation.session_id) - .await - .map_err(|err| { - GatewaySessionError::retryable(format!( - "opening direct audio speech session {} to provider {} transport peer {} failed: {err}", - invocation.session_id, provider, direct_peer - )) - })?; + let opened = open_direct_session_with_timeout( + &mut bridge, + provider, + direct_peer, + &invocation.session_id, + invocation.failover.open_timeout(), + ) + .await?; if !sc_bridge_session_transport_valid(&opened) { return Err(GatewaySessionError::retryable(format!( "audio speech session {} did not open an authenticated direct-or-relayed channel", invocation.session_id - ))); + )) + .into_safe_same_route_retry()); } let now = now_millis_u64(); @@ -16487,7 +17347,8 @@ impl ScBridgeGatewaySessionBackend { invocation.failover, request, &accept_info.enclave_pubkey, - invocation, model, + invocation, + model, invocation.client_cancellation.as_ref(), ) .await @@ -16562,21 +17423,22 @@ impl ScBridgeGatewaySessionBackend { "connecting provider {} transport peer {} for audio transcription session {} failed: {err}", provider, direct_peer, invocation.session_id )) + .into_safe_same_route_retry() })?; - let opened = bridge - .session_open(direct_peer, &invocation.session_id) - .await - .map_err(|err| { - GatewaySessionError::retryable(format!( - "opening direct audio transcription session {} to provider {} transport peer {} failed: {err}", - invocation.session_id, provider, direct_peer - )) - })?; + let opened = open_direct_session_with_timeout( + &mut bridge, + provider, + direct_peer, + &invocation.session_id, + invocation.failover.open_timeout(), + ) + .await?; if !sc_bridge_session_transport_valid(&opened) { return Err(GatewaySessionError::retryable(format!( "audio transcription session {} did not open an authenticated direct-or-relayed channel", invocation.session_id - ))); + )) + .into_safe_same_route_retry()); } let now = now_millis_u64(); @@ -16643,7 +17505,8 @@ impl ScBridgeGatewaySessionBackend { invocation.failover, request, &accept_info.enclave_pubkey, - invocation, model, + invocation, + model, invocation.client_cancellation.as_ref(), ) .await @@ -16718,21 +17581,22 @@ impl ScBridgeGatewaySessionBackend { "connecting provider {} transport peer {} for {} generation session {} failed: {err}", provider, direct_peer, request.output_modality, invocation.session_id )) + .into_safe_same_route_retry() })?; - let opened = bridge - .session_open(direct_peer, &invocation.session_id) - .await - .map_err(|err| { - GatewaySessionError::retryable(format!( - "opening direct {} generation session {} to provider {} transport peer {} failed: {err}", - request.output_modality, invocation.session_id, provider, direct_peer - )) - })?; + let opened = open_direct_session_with_timeout( + &mut bridge, + provider, + direct_peer, + &invocation.session_id, + invocation.failover.open_timeout(), + ) + .await?; if !sc_bridge_session_transport_valid(&opened) { return Err(GatewaySessionError::retryable(format!( "{} generation session {} did not open an authenticated direct-or-relayed channel", request.output_modality, invocation.session_id - ))); + )) + .into_safe_same_route_retry()); } let now = now_millis_u64(); @@ -16799,7 +17663,8 @@ impl ScBridgeGatewaySessionBackend { invocation.failover, request, &accept_info.enclave_pubkey, - invocation, model, + invocation, + model, invocation.client_cancellation.as_ref(), ) .await @@ -16855,6 +17720,125 @@ impl ScBridgeGatewaySessionBackend { } } +async fn send_tokenize_request_frames( + bridge: &mut ScBridgeClient, + invocation: &GatewayTokenizeInvocation, +) -> Result<(), GatewaySessionError> { + let max_frame_bytes = direct_session_max_frame_bytes(); + let request_bytes = stable_json_bytes(&invocation.request).map_err(|err| { + GatewaySessionError::new(format!("serializing tokenization payload failed: {err}")) + })?; + let max_request_bytes = direct_session_request_byte_limit_for_len( + invocation.served_ctx, + request_bytes.len(), + configured_optional_positive_usize("MAYHEM_GATEWAY_SESSION_MAX_REQUEST_BYTES"), + ); + for frame in tokenize_request_frames(invocation, max_frame_bytes, max_request_bytes)? { + bridge + .session_send(&invocation.transport_peer, &invocation.session_id, frame) + .await + .map_err(|err| { + GatewaySessionError::retryable(format!( + "sending tokenization request to provider {} failed: {err}", + invocation.provider_pubkey + )) + })?; + } + Ok(()) +} + +fn tokenize_request_frames( + invocation: &GatewayTokenizeInvocation, + max_frame_bytes: usize, + max_request_bytes: usize, +) -> Result, GatewaySessionError> { + let inline = TokenizeRequestFrame { + frame_type: TOKENIZE_REQUEST_FRAME_TYPE.to_owned(), + version: TOKENIZE_FRAME_VERSION, + session_id: invocation.session_id.clone(), + provider: invocation.provider_pubkey.clone(), + enclave_id: invocation.enclave_id.clone(), + room_id: invocation.room_id.clone(), + model: invocation.model.clone(), + request_id: String::new(), + request: Some(invocation.request.clone()), + request_ref: None, + return_tokens: invocation.return_tokens, + }; + let inline_value = serde_json::to_value(&inline).map_err(|err| { + GatewaySessionError::new(format!("serializing tokenization request failed: {err}")) + })?; + if session_frame_json_len(&inline_value)? <= max_frame_bytes { + return Ok(vec![inline_value]); + } + + let bytes = stable_json_bytes(&invocation.request).map_err(|err| { + GatewaySessionError::new(format!("serializing tokenization payload failed: {err}")) + })?; + if bytes.len() > max_request_bytes { + return Err(GatewaySessionError::new(format!( + "tokenization request body {} bytes exceeds the {max_request_bytes}-byte selected-session budget", + bytes.len() + ))); + } + let chunk_size = direct_session_payload_chunk_bytes(max_frame_bytes); + let manifest = payload_chunk_manifest(&bytes, chunk_size).map_err(|err| { + GatewaySessionError::new(format!( + "planning tokenization request chunks failed: {err}" + )) + })?; + let request_id = request_id_for_body(&invocation.session_id, &invocation.request); + let mut frames = Vec::new(); + for index in 0..manifest.chunk_count { + let chunk = payload_chunk_at(&bytes, chunk_size, index) + .map_err(|err| { + GatewaySessionError::new(format!( + "building tokenization request chunk failed: {err}" + )) + })? + .ok_or_else(|| { + GatewaySessionError::new("planned tokenization request chunk was missing") + })?; + let frame = json!({ + "t": TOKENIZE_REQUEST_CHUNK_FRAME_TYPE, + "v": TOKENIZE_FRAME_VERSION, + "session_id": invocation.session_id, + "provider": invocation.provider_pubkey, + "enclave_id": invocation.enclave_id, + "room_id": invocation.room_id, + "model": invocation.model, + "rid": request_id, + "payload_id": manifest.blake3, + "chunk": chunk, + }); + let len = session_frame_json_len(&frame)?; + if len > max_frame_bytes { + return Err(GatewaySessionError::new(format!( + "chunked tokenization request frame {len} bytes exceeds session max {max_frame_bytes} bytes" + ))); + } + frames.push(frame); + } + let final_frame = TokenizeRequestFrame { + request_id, + request: None, + request_ref: Some(manifest), + ..inline + }; + let final_frame = serde_json::to_value(final_frame).map_err(|err| { + GatewaySessionError::new(format!( + "serializing tokenization request manifest failed: {err}" + )) + })?; + if session_frame_json_len(&final_frame)? > max_frame_bytes { + return Err(GatewaySessionError::new( + "tokenization request manifest exceeds the session frame limit", + )); + } + frames.push(final_frame); + Ok(frames) +} + fn validate_direct_session_accept( frame: &Value, invocation: &GatewaySessionInvocation, @@ -16998,6 +17982,11 @@ fn provider_reject_session_error(frame: &Value, session_id: &str) -> GatewaySess .and_then(Value::as_str) .unwrap_or("no reason provided"); let message = format!("provider rejected session {session_id} with {code}: {reason}"); + if code == "RESERVATION_PENDING" + || (code == "BALANCE" && reservation_admission_outcome_ambiguous(reason)) + { + return GatewaySessionError::new(message); + } if clean_provider_reject_code(code) { GatewaySessionError::clean_refusal_with_code(message, Some(code)) } else { @@ -17005,6 +17994,15 @@ fn provider_reject_session_error(frame: &Value, session_id: &str) -> GatewaySess } } +fn reservation_admission_outcome_ambiguous(reason: &str) -> bool { + let reason = reason.to_ascii_lowercase(); + reason.contains("reservation_pending") + || reason.contains("reservation_relay_phase=admin_ack") + || (reason.contains("accepted the append") && reason.contains("canonical result")) + || (reason.contains("spend reservation did not complete within") + && reason.contains("provider admission budget")) +} + fn clean_provider_reject_code(code: &str) -> bool { matches!( code, @@ -17013,6 +18011,7 @@ fn clean_provider_reject_code(code: &str) -> bool { | "RATE" | "QUOTA" | "PRICE_FLOOR" + | "PRICE_VER" | "DRAINING" | "BALANCE" | "EXECUTION_MODE" @@ -17042,6 +18041,9 @@ fn provider_session_api_error(err: &GatewaySessionError) -> ApiError { if let Some(error) = request_scoped_api_error(err) { return error; } + if is_price_version_refusal(err) { + return catalog_price_refresh_error(); + } let lower = err.message.to_ascii_lowercase(); if err.transport_closed { return ApiError::bad_gateway( @@ -17120,6 +18122,9 @@ fn route_attempt_error_code(last_error: Option<&str>) -> (&'static str, &'static { return ("payment_reservation_failed", "payment", true); } + if lower.contains("price_ver") { + return ("catalog_price_refresh_pending", "route_selection", true); + } if lower.contains("price_floor") || lower.contains("price floor") { return ("provider_price_floor", "route_selection", false); } @@ -17138,12 +18143,57 @@ fn route_attempt_error_code(last_error: Option<&str>) -> (&'static str, &'static ("provider_attempts_failed", "provider_response", true) } +fn route_attempt_error_priority(error: &str) -> u8 { + match route_attempt_error_code(Some(error)).0 { + "payment_reservation_failed" + | "provider_verification_failed" + | "request_exceeds_provider_capacity" + | "provider_price_floor" + | "execution_mode_unavailable" => 3, + "provider_admission_no_capacity" + | "provider_transport_closed" + | "provider_response_timeout" => 2, + _ => 1, + } +} + +fn retain_most_specific_route_attempt_error(current: &mut Option, candidate: String) { + let replace = match current.as_deref() { + None => true, + Some(existing) => { + route_attempt_error_priority(&candidate) >= route_attempt_error_priority(existing) + } + }; + if replace { + *current = Some(candidate); + } +} + +fn reservation_relay_phase(last_error: Option<&str>) -> Option<&'static str> { + let message = last_error?; + let marker = "[reservation_relay_phase="; + let start = message.find(marker)? + marker.len(); + let phase = message.get(start..)?.split_once(']')?.0; + match phase { + "transport_unavailable" => Some("transport_unavailable"), + "health_proof_unavailable" => Some("health_proof_unavailable"), + "protocol_incompatible" => Some("protocol_incompatible"), + "transport_changed" => Some("transport_changed"), + "transport_rejoin_failed" => Some("transport_rejoin_failed"), + "transport_recovering" => Some("transport_recovering"), + "request_send" => Some("request_send"), + "admin_ack" => Some("admin_ack"), + _ => None, + } +} + fn route_attempts_failed_error( attempts_made: usize, last_error: Option, phase: &'static str, ) -> ApiError { let (code, category, retryable) = route_attempt_error_code(last_error.as_deref()); + let reservation_relay_phase = reservation_relay_phase(last_error.as_deref()); let message = match code { "request_exceeds_provider_capacity" => { "The request exceeds the signed capacity envelope of every otherwise eligible provider." @@ -17157,6 +18207,9 @@ fn route_attempts_failed_error( "provider_price_floor" => { "Every otherwise eligible provider refused the request at the offered price." } + "catalog_price_refresh_pending" => { + "The current model price is still being synchronized. Please retry shortly." + } "execution_mode_unavailable" => { "No currently available execution mode supports this request." } @@ -17173,21 +18226,24 @@ fn route_attempts_failed_error( }; let status = match code { "request_exceeds_provider_capacity" => StatusCode::BAD_REQUEST, - "provider_admission_no_capacity" | "provider_response_timeout" => { - StatusCode::SERVICE_UNAVAILABLE - } + "provider_admission_no_capacity" + | "provider_response_timeout" + | "catalog_price_refresh_pending" => StatusCode::SERVICE_UNAVAILABLE, "execution_mode_unavailable" => StatusCode::SERVICE_UNAVAILABLE, "payment_reservation_failed" => StatusCode::PAYMENT_REQUIRED, "provider_price_floor" => StatusCode::BAD_REQUEST, "provider_verification_failed" => StatusCode::BAD_GATEWAY, _ => StatusCode::BAD_GATEWAY, }; - ApiError::new(status, message, Some("model"), code, category, retryable).with_safe_detail( - json!({ - "attempts": attempts_made, - "phase": phase, - }), - ) + let mut safe_detail = json!({ + "attempts": attempts_made, + "phase": phase, + }); + if let Some(reservation_relay_phase) = reservation_relay_phase { + safe_detail["reservation_relay_phase"] = json!(reservation_relay_phase); + } + ApiError::new(status, message, Some("model"), code, category, retryable) + .with_safe_detail(safe_detail) } fn verify_provider_receipt_signature( @@ -17343,12 +18399,18 @@ async fn next_session_frame_with_optional_wait( } fn direct_session_request_body(request: &ChatCompletionRequest) -> Value { + let endpoint_family = direct_chat_endpoint_family(request); + let transport_kind = if endpoint_family == mayhem_proto::ENDPOINT_MAYHEM_DECISIONS { + "decision" + } else { + "chat" + }; let mut body = json!({ - "kind": "chat", + "kind": transport_kind, "model": &request.model, "messages": &request.messages, "stream": request.stream, - "endpoint_family": mayhem_proto::ENDPOINT_OPENAI_CHAT_COMPLETIONS, + "endpoint_family": endpoint_family, }); set_optional_json( &mut body, @@ -17435,6 +18497,15 @@ fn direct_chat_endpoint_family(request: &ChatCompletionRequest) -> &str { .unwrap_or(mayhem_proto::ENDPOINT_OPENAI_CHAT_COMPLETIONS) } +fn direct_chat_prompt_hash(request: &ChatCompletionRequest) -> String { + if direct_chat_endpoint_family(request) == mayhem_proto::ENDPOINT_MAYHEM_DECISIONS { + return mayhem_proto::endpoint_request_fingerprint( + request.endpoint_request.as_ref().unwrap_or(&Value::Null), + ); + } + blake3_hex(chat_prompt_text(request).as_bytes()) +} + fn direct_chat_contract_request(request: &ChatCompletionRequest, transport_body: &Value) -> Value { request .endpoint_request @@ -17600,12 +18671,17 @@ fn seal_direct_session_request_body_with_workflow_output( "schema_version": 1, "endpoint_family": endpoint_family, "endpoint_contract_fingerprint": mayhem_proto::endpoint_contract_fingerprint(contract), + "endpoint_contract_canonical_fingerprint": mayhem_proto::endpoint_contract_canonical_fingerprint(contract), "normalized_request_fingerprint": mayhem_proto::endpoint_request_fingerprint(&contract_request), "transport_request_fingerprint": transport_request_fingerprint, }); - if matches!(endpoint_family, - mayhem_proto::ENDPOINT_OPENAI_CHAT_COMPLETIONS | mayhem_proto::ENDPOINT_OPENAI_COMPLETIONS - | mayhem_proto::ENDPOINT_OPENAI_RESPONSES | mayhem_proto::ENDPOINT_HF_MULTIMODAL_CHAT) { + if matches!( + endpoint_family, + mayhem_proto::ENDPOINT_OPENAI_CHAT_COMPLETIONS + | mayhem_proto::ENDPOINT_OPENAI_COMPLETIONS + | mayhem_proto::ENDPOINT_OPENAI_RESPONSES + | mayhem_proto::ENDPOINT_HF_MULTIMODAL_CHAT + ) { // Opt in without changing model-visible input or signed billing units. // Older providers ignore this; new providers emit only to opted-in gateways. sealed["mayhem_contract"]["context_usage"] = json!(1); @@ -17668,8 +18744,16 @@ fn direct_session_image_generation_request_body(request: &ImageGenerationRequest .expect("validated image request has an admin-signed response format"), "endpoint_family": image_generation_endpoint_family(request), }); - set_optional_json(&mut body, "input_reference", request.input_reference.as_ref().map(|value| json!(value))); - set_optional_json(&mut body, "strength", request.strength.map(|value| json!(value))); + set_optional_json( + &mut body, + "input_reference", + request.input_reference.as_ref().map(|value| json!(value)), + ); + set_optional_json( + &mut body, + "strength", + request.strength.map(|value| json!(value)), + ); set_optional_json( &mut body, "background", @@ -18286,13 +19370,18 @@ fn generated_tokens_per_second( first_delta_at_millis: u64, completed_at_millis: u64, ) -> Option { - let token_intervals = output_tokens.checked_sub(1)?; - if token_intervals == 0 { + if output_tokens < DEFAULT_THROUGHPUT_FLOOR_MIN_OUTPUT_TOKENS { return None; } + let token_intervals = output_tokens.checked_sub(1)?; let elapsed_millis = completed_at_millis .saturating_sub(first_delta_at_millis) .max(1); + if elapsed_millis < DEFAULT_THROUGHPUT_FLOOR_SAMPLE_MILLIS + && output_tokens < DEFAULT_THROUGHPUT_FLOOR_FAST_SAMPLE_MIN_OUTPUT_TOKENS + { + return None; + } let tok_s = token_intervals as f64 * 1000.0 / elapsed_millis as f64; tok_s.is_finite().then_some(tok_s) } @@ -18359,10 +19448,134 @@ fn configured_optional_positive_usize(name: &str) -> Option { .filter(|value| *value > 0) } +fn is_decision_chat_request(request: &ChatCompletionRequest) -> bool { + direct_chat_endpoint_family(request) == mayhem_proto::ENDPOINT_MAYHEM_DECISIONS +} + +fn decision_contract_request(request: &ChatCompletionRequest) -> Option<&Value> { + is_decision_chat_request(request) + .then_some(request.endpoint_request.as_ref()) + .flatten() +} + +fn decision_question_count(request: &ChatCompletionRequest) -> u64 { + decision_contract_request(request) + .and_then(|body| body.get("questions")) + .and_then(Value::as_object) + .map(|questions| u64::try_from(questions.len()).unwrap_or(u64::MAX)) + .filter(|count| *count > 0) + .unwrap_or(DECISION_MAX_QUESTIONS) + .min(DECISION_MAX_QUESTIONS) +} + +fn decision_sequence_token_limit(request: &ChatCompletionRequest) -> u64 { + decision_contract_request(request) + .and_then(|body| body.get("limits")) + .and_then(Value::as_object) + .and_then(|limits| limits.get("max_len")) + .and_then(Value::as_u64) + .filter(|value| (128..=DECISION_MAX_SEQUENCE_TOKENS).contains(value)) + .unwrap_or(DECISION_MAX_SEQUENCE_TOKENS) +} + +fn decision_input_token_bounds(request: &ChatCompletionRequest) -> (u64, u64) { + let question_count = decision_question_count(request); + let mut upper = question_count.saturating_mul(decision_sequence_token_limit(request)); + let Some(body) = decision_contract_request(request) else { + let shortlist = DECISION_MAX_QUESTIONS + .saturating_mul(DECISION_MAX_SHORTLIST_OPTIONS.saturating_add(1)) + .saturating_mul(DECISION_MAX_SEQUENCE_TOKENS); + return (1, upper.saturating_add(shortlist)); + }; + let Some(shortlist) = body.get("shortlist").and_then(Value::as_object) else { + return (question_count.max(1), upper.max(question_count.max(1))); + }; + if shortlist.get("vectors").is_some() { + return (question_count.max(1), upper.max(question_count.max(1))); + } + let k = shortlist + .get("k") + .and_then(Value::as_u64) + .filter(|value| (1..=DECISION_DEFAULT_SHORTLIST_K).contains(value)) + .unwrap_or(DECISION_DEFAULT_SHORTLIST_K); + let max_length = shortlist + .get("max_length") + .and_then(Value::as_u64) + .filter(|value| (1..=DECISION_MAX_SEQUENCE_TOKENS).contains(value)) + .unwrap_or(DECISION_MAX_SEQUENCE_TOKENS); + if let Some(questions) = body.get("questions").and_then(Value::as_object) { + for question in questions.values().filter_map(Value::as_object) { + if question.get("type").and_then(Value::as_str) != Some("choice") { + continue; + } + let criteria_count = match question.get("criteria") { + Some(Value::Object(criteria)) => u64::try_from(criteria.len()).unwrap_or(u64::MAX), + Some(Value::Array(criteria)) => u64::try_from(criteria.len()).unwrap_or(u64::MAX), + _ => DECISION_MAX_SHORTLIST_OPTIONS, + } + .min(DECISION_MAX_SHORTLIST_OPTIONS); + if criteria_count > k { + upper = upper + .saturating_add(criteria_count.saturating_add(1).saturating_mul(max_length)); + } + } + } else { + upper = upper.saturating_add( + DECISION_MAX_QUESTIONS + .saturating_mul(DECISION_MAX_SHORTLIST_OPTIONS.saturating_add(1)) + .saturating_mul(max_length), + ); + } + (question_count.max(1), upper.max(question_count.max(1))) +} + +fn decision_output_byte_upper_bound(request: &ChatCompletionRequest) -> usize { + let request_bytes = decision_contract_request(request) + .and_then(|body| serde_json::to_vec(body).ok()) + .map(|bytes| bytes.len()) + .unwrap_or(256 * 1024); + request_bytes + .saturating_mul(DECISION_OUTPUT_REQUEST_SIZE_MULTIPLIER) + .saturating_add(DECISION_OUTPUT_FIXED_ALLOWANCE_BYTES) + .min(DEFAULT_SESSION_MAX_REASSEMBLED_PAYLOAD_BYTES) + .max(DECISION_OUTPUT_FIXED_ALLOWANCE_BYTES) +} + +fn decision_output_unit_upper_bound(request: &ChatCompletionRequest) -> u64 { + u64::try_from(decision_output_byte_upper_bound(request)) + .unwrap_or(u64::MAX) + .div_ceil(mayhem_proto::VISIBLE_OUTPUT_BYTES_PER_UNIT) +} + +fn validate_decision_input_usage( + request: &ChatCompletionRequest, + usage: &ReceiptUsage, +) -> Result<(), GatewaySessionError> { + let (lower_bound, upper_bound) = decision_input_token_bounds(request); + let input_tokens = usage.input_tokens(); + let has_unsupported_units = usage.units().iter().any(|(unit, count)| { + *count > 0 && !matches!(unit.as_str(), USAGE_INPUT_TOKEN | USAGE_OUTPUT_TOKEN) + }); + if input_tokens < lower_bound + || input_tokens > upper_bound + || usage.cached_input_tokens() != 0 + || has_unsupported_units + { + return Err(GatewaySessionError::new(format!( + "decision session reported invalid input usage: input={input_tokens}, expected {lower_bound}..={upper_bound} with only input_token and output_token units", + ))); + } + Ok(()) +} + fn direct_session_chat_output_byte_limit( request: &ChatCompletionRequest, invocation: &GatewaySessionInvocation, ) -> usize { + if is_decision_chat_request(request) { + return configured_optional_positive_usize("MAYHEM_SESSION_MAX_TEXT_OUTPUT_BYTES") + .unwrap_or_else(|| decision_output_byte_upper_bound(request)); + } let prompt_tokens = rough_tokens(&chat_prompt_text(request)); let available_tokens = u64::from(invocation.served_ctx) .saturating_sub(prompt_tokens) @@ -18952,13 +20165,24 @@ async fn collect_direct_session_output( &frame, &format!("session {session_id}"), false, + delta_sequence.next_index > 0 + || latest_checkpoint_receipt.is_some() + || pending_checkpoint_receipt.is_some() + || final_provider_receipt.is_some(), ); settle_failed_direct_session_frame( - bridge, invocation, model, enclave_pubkey, &frame, + bridge, + invocation, + model, + enclave_pubkey, + &frame, latest_checkpoint_receipt.as_ref(), - blake3_hex(chat_prompt_text(request).as_bytes()), - ).await?; - if frame.get("receipt").is_some() { return Err(error); } + direct_chat_prompt_hash(request), + ) + .await?; + if frame.get("receipt").is_some() { + return Err(error); + } if error.failure_class.is_request_scoped() { return Err(error); } @@ -19135,7 +20359,10 @@ fn reconcile_final_chat_prompt_usage( vision_tokens: u64, audio_tokens: u64, ) -> Result<(), GatewaySessionError> { - if let Some(expected_prompt_tokens) = + if is_decision_chat_request(request) { + validate_decision_input_usage(request, provider_usage)?; + usage.prompt_tokens = provider_usage.input_tokens(); + } else if let Some(expected_prompt_tokens) = tools_only_prompt_token_units(model, request, invocation.served_ctx)? { usage.prompt_tokens = @@ -19236,13 +20463,20 @@ async fn collect_direct_session_embedding_output( } Some("s.error") => { settle_failed_direct_session_frame( - bridge, invocation, model, enclave_pubkey, &frame, None, + bridge, + invocation, + model, + enclave_pubkey, + &frame, + None, blake3_hex(embedding_prompt_text(inputs).as_bytes()), - ).await?; + ) + .await?; return Err(provider_reported_session_error( &frame, &format!("embedding session {session_id}"), true, + delta_sequence.next_index > 0 || provider_receipt.is_some(), )); } Some("s.close") => { @@ -19271,13 +20505,16 @@ async fn collect_direct_session_embedding_output( inputs.len() ))); } - let observed_usage = embedding_usage_for_inputs(inputs); - if let Some(reported_usage) = usage.as_ref() { - ensure_reported_token_usage_matches(reported_usage, &observed_usage, "embedding session")?; - } + let provider_receipt = provider_receipt.ok_or_else(|| { + GatewaySessionError::new(format!( + "provider embedding session {session_id} ended without a final receipt" + )) + })?; + let signed_usage = + verified_embedding_session_usage(inputs, usage.as_ref(), &provider_receipt.body.usage)?; let output = EmbeddingOutput { embeddings, - usage: observed_usage, + usage: signed_usage, }; let quality = provider_quality.or_else(|| { watchdog @@ -19291,11 +20528,6 @@ async fn collect_direct_session_embedding_output( ), }) }); - let provider_receipt = provider_receipt.ok_or_else(|| { - GatewaySessionError::new(format!( - "provider embedding session {session_id} ended without a final receipt" - )) - })?; Ok(DirectEmbeddingSessionCollected { output, provider_receipt, @@ -19375,13 +20607,20 @@ async fn collect_direct_session_image_generation_output( } Some("s.error") => { settle_failed_direct_session_frame( - bridge, invocation, model, enclave_pubkey, &frame, None, + bridge, + invocation, + model, + enclave_pubkey, + &frame, + None, image_generation_prompt_hash(request), - ).await?; + ) + .await?; return Err(provider_reported_session_error( &frame, &format!("image session {session_id}"), true, + delta_sequence.next_index > 0 || provider_receipt.is_some(), )); } Some("s.close") => { @@ -19516,13 +20755,20 @@ async fn collect_direct_session_audio_speech_output( } Some("s.error") => { settle_failed_direct_session_frame( - bridge, invocation, model, enclave_pubkey, &frame, None, + bridge, + invocation, + model, + enclave_pubkey, + &frame, + None, audio_speech_prompt_hash(request), - ).await?; + ) + .await?; return Err(provider_reported_session_error( &frame, &format!("audio speech session {session_id}"), true, + delta_sequence.next_index > 0 || provider_receipt.is_some(), )); } Some("s.close") => { @@ -19648,9 +20894,15 @@ async fn collect_direct_session_artifact_generation_output( } Some("s.error") => { settle_failed_direct_session_frame( - bridge, invocation, model, enclave_pubkey, &frame, None, + bridge, + invocation, + model, + enclave_pubkey, + &frame, + None, artifact_generation_prompt_hash(request), - ).await?; + ) + .await?; return Err(provider_reported_session_error( &frame, &format!( @@ -19658,6 +20910,7 @@ async fn collect_direct_session_artifact_generation_output( request.output_modality ), true, + delta_sequence.next_index > 0 || provider_receipt.is_some(), )); } Some("s.close") => { @@ -19837,13 +21090,20 @@ async fn collect_direct_session_audio_transcription_output( } Some("s.error") => { settle_failed_direct_session_frame( - bridge, invocation, model, enclave_pubkey, &frame, None, + bridge, + invocation, + model, + enclave_pubkey, + &frame, + None, audio_transcription_prompt_hash(request), - ).await?; + ) + .await?; return Err(provider_reported_session_error( &frame, &format!("audio transcription session {session_id}"), true, + delta_sequence.next_index > 0 || provider_receipt.is_some(), )); } Some("s.close") => { @@ -20605,7 +21865,7 @@ fn direct_session_receipt_ack( seq: provider_receipt.body.seq, final_receipt: true, au_owed_cum: calculate_locked_au_owed(invocation, &usage), - prompt_hash: blake3_hex(chat_prompt_text(request).as_bytes()), + prompt_hash: direct_chat_prompt_hash(request), usage, }; if locked_increment_exceeds_voucher(invocation, expected.au_owed_cum) { @@ -20832,16 +22092,32 @@ fn validate_receipt_settlement_feature_for_receipt( receipt_ack: &ReceiptAck, feature: &Value, ) -> Result<(), GatewaySessionError> { - if feature.pointer("/value/contract_version").and_then(Value::as_u64) - != Some(u64::from(CONTRACT_VERSION)) - { + if !receipt_settlement_contract_version_is_supported(feature) { return Err(GatewaySessionError::new( "receipt settlement feature has the wrong operation or contract version", )); } + let contract_version = feature["value"]["contract_version"] + .as_u64() + .expect("validated receipt settlement contract version"); + if !receipt_schema_version_is_supported_for_contract( + u64::from(provider_receipt.body.schema_version), + contract_version, + ) { + return Err(GatewaySessionError::new( + "receipt settlement feature has an unsupported receipt schema", + )); + } validate_stored_receipt_settlement_feature(provider_receipt, receipt_ack, feature) } +fn receipt_settlement_contract_version_is_supported(feature: &Value) -> bool { + feature + .pointer("/value/contract_version") + .and_then(Value::as_u64) + .is_some_and(receipt_contract_version_is_supported) +} + fn validate_stored_receipt_settlement_feature( provider_receipt: &ProviderSignedReceipt, receipt_ack: &ReceiptAck, @@ -20861,7 +22137,9 @@ fn validate_stored_receipt_settlement_feature( .filter(|value| value.is_object()) .ok_or_else(|| GatewaySessionError::new("receipt settlement feature is missing value"))?; if value.get("op").and_then(Value::as_str) != Some("record_usage_receipt") - || !value.get("contract_version").and_then(Value::as_u64) + || !value + .get("contract_version") + .and_then(Value::as_u64) .is_some_and(|version| version > 0 && version <= u64::from(CONTRACT_VERSION)) { return Err(GatewaySessionError::new( @@ -20874,11 +22152,11 @@ fn validate_stored_receipt_settlement_feature( enclave_pubkey: provider_receipt.enclave_pubkey.clone(), user_sig: receipt_ack.user_sig.clone(), }; + let receipt_envelope = value + .get("receipt") + .ok_or_else(|| GatewaySessionError::new("receipt settlement feature is missing receipt"))?; let actual_receipt = - parse_record_usage_receipt_envelope(value.get("receipt").ok_or_else(|| { - GatewaySessionError::new("receipt settlement feature is missing receipt") - })?) - .map_err(GatewaySessionError::new)?; + parse_record_usage_receipt_envelope(receipt_envelope).map_err(GatewaySessionError::new)?; if actual_receipt != expected_receipt { return Err(GatewaySessionError::new( "receipt settlement feature does not contain the exact co-signed receipt", @@ -20894,8 +22172,16 @@ fn validate_stored_receipt_settlement_feature( } // A durable receipt's original version participates in its signed feature // key. Historical evidence is never silently re-keyed to this release. - let contract_version = value["contract_version"].as_u64().expect("validated contract version") as u32; - if key != record_usage_receipt_feature_key_for_contract(&expected_receipt, contract_version) { + let contract_version = value["contract_version"] + .as_u64() + .expect("validated contract version") as u32; + if key + != record_usage_receipt_feature_key_from_envelope_for_contract( + receipt_envelope, + contract_version, + ) + .map_err(GatewaySessionError::new)? + { return Err(GatewaySessionError::new( "receipt settlement feature key is not canonical", )); @@ -21003,14 +22289,25 @@ fn failed_direct_session_receipt_ack( prompt_hash: String, ) -> Result { if !invocation.receipt_cosign_enabled { - return Err(GatewaySessionError::new("failed session receipt co-signing is disabled")); + return Err(GatewaySessionError::new( + "failed session receipt co-signing is disabled", + )); } - let (usage, attribution, seq) = checkpoint.map(|checkpoint| ( - checkpoint.body.usage.clone(), checkpoint.body.usage_attribution.clone(), - checkpoint.body.seq.saturating_add(1), - )).unwrap_or_else(|| ( - invocation.spend_voucher.body.billing_prior_usage.clone(), BTreeMap::new(), 1, - )); + let (usage, attribution, seq) = checkpoint + .map(|checkpoint| { + ( + checkpoint.body.usage.clone(), + checkpoint.body.usage_attribution.clone(), + checkpoint.body.seq.saturating_add(1), + ) + }) + .unwrap_or_else(|| { + ( + invocation.spend_voucher.body.billing_prior_usage.clone(), + BTreeMap::new(), + 1, + ) + }); let amount = calculate_locked_au_owed(invocation, &usage); if amount <= invocation.spend_voucher.body.billing_prior_au_owed_cum || receipt.body.usage_attribution != attribution @@ -21020,12 +22317,22 @@ fn failed_direct_session_receipt_ack( )); } ensure_final_receipt_within_voucher(invocation, amount)?; - validate_provider_receipt(model, invocation, receipt, ExpectedProviderReceipt { - provider: invocation.provider_pubkey_required()?, seq, final_receipt: true, - au_owed_cum: amount, usage, prompt_hash, - })?; - receipt_ack_for_body(&invocation.receipt_user_seed, &receipt.body) - .map_err(|error| GatewaySessionError::new(format!("signing failed session receipt: {error}"))) + validate_provider_receipt( + model, + invocation, + receipt, + ExpectedProviderReceipt { + provider: invocation.provider_pubkey_required()?, + seq, + final_receipt: true, + au_owed_cum: amount, + usage, + prompt_hash, + }, + )?; + receipt_ack_for_body(&invocation.receipt_user_seed, &receipt.body).map_err(|error| { + GatewaySessionError::new(format!("signing failed session receipt: {error}")) + }) } async fn settle_failed_direct_session_frame( @@ -21037,44 +22344,75 @@ async fn settle_failed_direct_session_frame( checkpoint: Option<&ProviderSignedReceipt>, prompt_hash: String, ) -> Result<(), GatewaySessionError> { - if frame.get("receipt").is_none() { return Ok(()); } - let receipt = provider_signed_receipt_from_frame( - frame, &invocation.session_id, enclave_pubkey, - )?; - let ack = failed_direct_session_receipt_ack(model, invocation, &receipt, checkpoint, prompt_hash)?; - let failure = provider_reported_session_error(frame, "failed generation", false); + if frame.get("receipt").is_none() { + return Ok(()); + } + let receipt = + provider_signed_receipt_from_frame(frame, &invocation.session_id, enclave_pubkey)?; + let ack = + failed_direct_session_receipt_ack(model, invocation, &receipt, checkpoint, prompt_hash)?; + let failure = provider_reported_session_error(frame, "failed generation", false, true); let public_error = provider_session_api_error(&failure); if let Some(job) = invocation.job.as_ref() { job.mark_settlement_reconciliation_started(); let recovery = gateway_job_settled_receipt( - invocation, &receipt, &ack, GatewayJobStatus::Failed, - Some(public_error.message.clone()), Some("provider_failure".to_owned()), + invocation, + &receipt, + &ack, + GatewayJobStatus::Failed, + Some(public_error.message.clone()), + Some("provider_failure".to_owned()), )?; let store = job.store.clone(); let id = job.id.clone(); let message = public_error.message.clone(); let info = GatewayJobErrorInfo { code: public_error.public_code.to_owned(), - category: public_error.category.to_owned(), retryable: false, + category: public_error.category.to_owned(), + retryable: false, + phase: None, }; - tokio::task::spawn_blocking(move || store.lock_recover("gateway job vault") - .complete_with_error_info(&id, GatewayJobStatus::ReconciliationPending, - None, Vec::new(), Some(recovery), Some(message), Some(info), now_secs())) - .await.map_err(|error| GatewaySessionError::new(error.to_string()))? - .map_err(GatewaySessionError::new)?; + tokio::task::spawn_blocking(move || { + store + .lock_recover("gateway job vault") + .complete_with_error_info( + &id, + GatewayJobStatus::ReconciliationPending, + None, + Vec::new(), + Some(recovery), + Some(message), + Some(info), + now_secs(), + ) + }) + .await + .map_err(|error| GatewaySessionError::new(error.to_string()))? + .map_err(GatewaySessionError::new)?; } record_direct_session_receipt(invocation, &receipt, &ack)?; send_receipt_ack_and_queue_settlement( - bridge, invocation.direct_peer()?, invocation, &receipt, &ack, - Some("provider_failure"), "acknowledging failed generation accounting", - ).await.map_err(|error| { + bridge, + invocation.direct_peer()?, + invocation, + &receipt, + &ack, + Some("provider_failure"), + "acknowledging failed generation accounting", + ) + .await + .map_err(|error| { // Accounting recovery already owns the signed evidence. Keep the // generation failure visible instead of encouraging a paid retry. - eprintln!("Failed generation accounting handoff remains pending: {}", error.message); + eprintln!( + "Failed generation accounting handoff remains pending: {}", + error.message + ); failure.clone() })?; if let Some(job) = invocation.job.as_ref() { - job.finish_reconciliation(GatewayJobStatus::Failed, Some(public_error.message)).await?; + job.finish_reconciliation(GatewayJobStatus::Failed, Some(public_error.message)) + .await?; } Ok(()) } @@ -21187,7 +22525,9 @@ async fn cancel_and_settle_direct_session( // was not acknowledged by the interrupted collector, so do not // advance settlement to it. The provider cancels at its previous // signed high water after its bounded ACK drain. - if !receipt.body.final_receipt { continue; } + if !receipt.body.final_receipt { + continue; + } let receipt_ack = record_cancelled_direct_session_receipt( model, invocation, @@ -21273,7 +22613,7 @@ fn direct_session_partial_receipt_ack( final_receipt: false, au_owed_cum, usage, - prompt_hash: blake3_hex(chat_prompt_text(request).as_bytes()), + prompt_hash: direct_chat_prompt_hash(request), }, )?; receipt_ack_for_body(&invocation.receipt_user_seed, body).map_err(|err| { @@ -21294,9 +22634,43 @@ fn authoritative_embedding_usage( inputs.len() ))); } - let observed = embedding_usage_for_inputs(inputs); - ensure_reported_token_usage_matches(&output.usage, &observed, "embedding session")?; - Ok(ReceiptUsage::text(observed.prompt_tokens, 0)) + validate_embedding_usage_bounds(inputs, &output.usage)?; + Ok(ReceiptUsage::text(output.usage.prompt_tokens, 0)) +} + +fn validate_embedding_usage_bounds( + inputs: &[String], + reported: &Usage, +) -> Result<(), GatewaySessionError> { + // The gateway cannot reproduce every provider tokenizer. Accept its exact + // count within the same conservative envelope used for the signed voucher. + let lower_bound = embedding_input_token_count(inputs); + let upper_bound = embedding_input_token_upper_bound(inputs); + if reported.prompt_tokens == 0 + || reported.completion_tokens != 0 + || reported.total_tokens != reported.prompt_tokens + || reported.prompt_tokens < lower_bound + || reported.prompt_tokens > upper_bound + { + return Err(GatewaySessionError::new(format!( + "embedding session reported invalid token usage: prompt={}, completion={}, total={}, expected positive prompt count in {lower_bound}..={upper_bound} and no completion tokens", + reported.prompt_tokens, reported.completion_tokens, reported.total_tokens, + ))); + } + Ok(()) +} + +fn verified_embedding_session_usage( + inputs: &[String], + delta_usage: Option<&Usage>, + signed_usage: &ReceiptUsage, +) -> Result { + let signed_usage = usage_from_receipt_usage(signed_usage); + validate_embedding_usage_bounds(inputs, &signed_usage)?; + if let Some(delta_usage) = delta_usage { + ensure_reported_token_usage_matches(delta_usage, &signed_usage, "embedding session")?; + } + Ok(signed_usage) } fn expected_embedding_provider_receipt<'a>( @@ -21540,6 +22914,24 @@ fn expected_chat_usage_for_provider( locked_rate_map: &[RateMapEntry], protocol_prompt_tokens: Option, ) -> Result { + if is_decision_chat_request(request) { + let usage = provider_usage.cloned().unwrap_or_else(|| { + ReceiptUsage::text(observed_prompt_tokens, observed_completion_tokens) + }); + if provider_usage.is_some() { + validate_decision_input_usage(request, &usage)?; + if usage.output_tokens() != observed_completion_tokens { + return Err(GatewaySessionError::new(format!( + "decision session reported {} output units, expected {observed_completion_tokens}", + usage.output_tokens() + ))); + } + } + return Ok(ReceiptUsage::text( + usage.input_tokens(), + observed_completion_tokens, + )); + } let text = expected_text_usage_for_provider( provider_usage, observed_prompt_tokens, @@ -21618,9 +23010,9 @@ fn validate_provider_receipt( provider_receipt: &ProviderSignedReceipt, expected: ExpectedProviderReceipt<'_>, ) -> Result<(), GatewaySessionError> { - if invocation.spend_voucher.body.schema_version != SESSION_RECEIPT_SCHEMA_VERSION { + if invocation.spend_voucher.body.schema_version != SPEND_VOUCHER_SCHEMA_VERSION { return Err(GatewaySessionError::new(format!( - "spend voucher schema_version must be {SESSION_RECEIPT_SCHEMA_VERSION}" + "spend voucher schema_version must be {SPEND_VOUCHER_SCHEMA_VERSION}" ))); } if provider_receipt.body.schema_version != SESSION_RECEIPT_SCHEMA_VERSION { @@ -21632,7 +23024,9 @@ fn validate_provider_receipt( validate_usage_attribution(&body.usage, &body.usage_attribution)?; if let Some(tokens) = body.usage_attribution.get("context_input_tokens") { if *tokens == 0 || *tokens > u64::from(invocation.served_ctx) { - return Err(GatewaySessionError::new("provider context input tokens exceed served context")); + return Err(GatewaySessionError::new( + "provider context input tokens exceed served context", + )); } } let checks = [ @@ -21882,7 +23276,10 @@ fn validate_usage_attribution( for axis in attribution.keys() { if !matches!( axis.as_str(), - "reasoning_output_tokens" | "vision_input_tokens" | "audio_input_tokens" | "context_input_tokens" + "reasoning_output_tokens" + | "vision_input_tokens" + | "audio_input_tokens" + | "context_input_tokens" ) { return Err(GatewaySessionError::new(format!( "unsupported provider usage attribution {axis}" @@ -22022,7 +23419,8 @@ async fn run_embedding_with_route_retry( deadline, route_retry_total_attempt_limit(state, model, &options), ); - let mut pending_routes = eligible_routes; + let mut current_model = model.clone(); + let mut pending_routes = eligible_routes.into_iter().cloned().collect::>(); let mut attempt_options = options.clone(); let mut billing = options.billing.clone().unwrap_or_else(|| { GatewayBillingContext::initial(logical_billing_id_for( @@ -22033,14 +23431,15 @@ async fn run_embedding_with_route_retry( attempt_options.billing = Some(billing.clone()); let mut last_retryable_error = None; loop { + let model = ¤t_model; if !recovery.can_attempt() || (recovery.has_capacity_waiters() && recovery.deadline.remaining().is_zero()) { break; } - pending_routes = recovery.filter_routes(state, pending_routes); + pending_routes = recovery.filter_owned_routes(state, pending_routes); if pending_routes.is_empty() && recovery.has_capacity_waiters() { - pending_routes = wait_for_capacity_recovery( + pending_routes = wait_for_owned_capacity_recovery( state, &recovery, || { @@ -22051,12 +23450,16 @@ async fn run_embedding_with_route_retry( inputs, &attempt_options, ) + .into_iter() + .cloned() + .collect() }, Duration::from_millis(ROUTE_WAIT_POLL_MS), ) .await; } - let route = pending_routes.first().copied(); + let owned_route = pending_routes.first().cloned(); + let route = owned_route.as_ref(); if route.is_none() && ((!model.mayhem.route_candidates.is_empty() || !state.dev_session_shim) || recovery.dev_route_attempted) @@ -22074,18 +23477,25 @@ async fn run_embedding_with_route_retry( Some(request), inputs, &attempt_options, - ); + ) + .into_iter() + .cloned() + .collect(); continue; } }; + let admission_attempt_budget = recovery.admission_attempt_budget(pending_routes.len()); recovery.begin_attempt(route); - let invocation = state.prepare_embedding_invocation_for_route( + let mut invocation = state.prepare_embedding_invocation_for_route( model, Some(request), inputs, route, &attempt_options, )?; + invocation.failover = invocation + .failover + .with_admission_attempt_budget(admission_attempt_budget); let attempt_started = Instant::now(); match state .session_backend @@ -22100,6 +23510,7 @@ async fn run_embedding_with_route_retry( ); let metering_output = result.output.clone(); return Ok(GatewayEmbeddingRun { + model: model.clone(), result, invocation, metering_inputs: inputs.to_vec(), @@ -22112,27 +23523,64 @@ async fn run_embedding_with_route_retry( Err(err) if err.failure_class.is_request_scoped() => { return Err(request_scoped_api_error(&err).expect("request-scoped error")); } + Err(err) if is_price_version_refusal(&err) => { + // PRICE_VER is rejected before reservation/compute. Do not + // penalize or exhaust the provider, or discard prior partials. + drop(_modality_admission); + current_model = + refresh_model_after_price_refusal(state, model, &invocation, deadline).await?; + let model = ¤t_model; + billing = billing.after_attempt(None); + attempt_options.billing = Some(billing.clone()); + retain_most_specific_route_attempt_error(&mut last_retryable_error, err.message); + pending_routes = ordered_route_candidates_for_embedding_with_options( + state, + model, + Some(request), + inputs, + &attempt_options, + ) + .into_iter() + .cloned() + .collect(); + continue; + } Err(err) if err.retryable => { record_route_attempt_error(state, route, attempt_started.elapsed(), &err); - if let Some(refusal) = terminal_balance_refusal(&err) { + let balance_refusal = terminal_balance_refusal(&err); + let payment_pending = + route_attempt_error_code(Some(&err.message)).0 == "payment_reservation_failed"; + if (balance_refusal.is_some() || payment_pending) + && wait_for_pending_receipt_settlement( + state.receipt_settlement_publisher.as_ref().as_ref(), + &invocation.user_pubkey, + &invocation.rail, + deadline, + ) + .await + { + recovery.total_attempt_limit = recovery.total_attempt_limit.saturating_add(1); + billing = billing.after_attempt(None); + attempt_options.billing = Some(billing.clone()); + continue; + } + if let Some(refusal) = balance_refusal { return Err(refusal); } billing = billing.after_attempt(None); attempt_options.billing = Some(billing.clone()); - let is_capacity = capacity_refusal(&err); - last_retryable_error = Some(err.message); - if is_capacity { - recovery.record_capacity_refusal(state, route); - } else { - recovery.exhaust(route); - } + recovery.record_retryable_attempt(state, route, &err); + retain_most_specific_route_attempt_error(&mut last_retryable_error, err.message); pending_routes = ordered_route_candidates_for_embedding_with_options( state, model, Some(request), inputs, &attempt_options, - ); + ) + .into_iter() + .cloned() + .collect(); } Err(err) => { record_route_attempt_error(state, route, attempt_started.elapsed(), &err); @@ -22213,7 +23661,8 @@ async fn run_image_generation_with_route_retry( deadline, route_retry_total_attempt_limit(state, model, &options), ); - let mut pending_routes = eligible_routes; + let mut current_model = model.clone(); + let mut pending_routes = eligible_routes.into_iter().cloned().collect::>(); let mut attempt_options = options.clone(); let mut billing = options.billing.clone().unwrap_or_else(|| { GatewayBillingContext::initial(logical_billing_id_for( @@ -22224,14 +23673,15 @@ async fn run_image_generation_with_route_retry( attempt_options.billing = Some(billing.clone()); let mut last_retryable_error = None; loop { + let model = ¤t_model; if !recovery.can_attempt() || (recovery.has_capacity_waiters() && recovery.deadline.remaining().is_zero()) { break; } - pending_routes = recovery.filter_routes(state, pending_routes); + pending_routes = recovery.filter_owned_routes(state, pending_routes); if pending_routes.is_empty() && recovery.has_capacity_waiters() { - pending_routes = wait_for_capacity_recovery( + pending_routes = wait_for_owned_capacity_recovery( state, &recovery, || { @@ -22241,12 +23691,16 @@ async fn run_image_generation_with_route_retry( request, &attempt_options, ) + .into_iter() + .cloned() + .collect() }, Duration::from_millis(ROUTE_WAIT_POLL_MS), ) .await; } - let route = pending_routes.first().copied(); + let owned_route = pending_routes.first().cloned(); + let route = owned_route.as_ref(); if route.is_none() && ((!model.mayhem.route_candidates.is_empty() || !state.dev_session_shim) || recovery.dev_route_attempted) @@ -22263,17 +23717,24 @@ async fn run_image_generation_with_route_retry( model, request, &attempt_options, - ); + ) + .into_iter() + .cloned() + .collect(); continue; } }; + let admission_attempt_budget = recovery.admission_attempt_budget(pending_routes.len()); recovery.begin_attempt(route); - let invocation = state.prepare_image_generation_invocation_for_route( + let mut invocation = state.prepare_image_generation_invocation_for_route( model, request, route, &attempt_options, )?; + invocation.failover = invocation + .failover + .with_admission_attempt_budget(admission_attempt_budget); let attempt_started = Instant::now(); match state .session_backend @@ -22292,6 +23753,7 @@ async fn run_image_generation_with_route_retry( let metering_request = request.clone(); let metering_output = result.output.clone(); return Ok(GatewayImageGenerationRun { + model: model.clone(), result, invocation, metering_request, @@ -22304,26 +23766,62 @@ async fn run_image_generation_with_route_retry( Err(err) if err.failure_class.is_request_scoped() => { return Err(request_scoped_api_error(&err).expect("request-scoped error")); } + Err(err) if is_price_version_refusal(&err) => { + // PRICE_VER is rejected before reservation/compute. Do not + // penalize or exhaust the provider, or discard prior partials. + drop(_modality_admission); + current_model = + refresh_model_after_price_refusal(state, model, &invocation, deadline).await?; + let model = ¤t_model; + billing = billing.after_attempt(None); + attempt_options.billing = Some(billing.clone()); + retain_most_specific_route_attempt_error(&mut last_retryable_error, err.message); + pending_routes = ordered_route_candidates_for_image_generation_with_options( + state, + model, + request, + &attempt_options, + ) + .into_iter() + .cloned() + .collect(); + continue; + } Err(err) if err.retryable => { record_route_attempt_error(state, route, attempt_started.elapsed(), &err); - if let Some(refusal) = terminal_balance_refusal(&err) { + let balance_refusal = terminal_balance_refusal(&err); + let payment_pending = + route_attempt_error_code(Some(&err.message)).0 == "payment_reservation_failed"; + if (balance_refusal.is_some() || payment_pending) + && wait_for_pending_receipt_settlement( + state.receipt_settlement_publisher.as_ref().as_ref(), + &invocation.user_pubkey, + &invocation.rail, + deadline, + ) + .await + { + recovery.total_attempt_limit = recovery.total_attempt_limit.saturating_add(1); + billing = billing.after_attempt(None); + attempt_options.billing = Some(billing.clone()); + continue; + } + if let Some(refusal) = balance_refusal { return Err(refusal); } billing = billing.after_attempt(None); attempt_options.billing = Some(billing.clone()); - let is_capacity = capacity_refusal(&err); - last_retryable_error = Some(err.message); - if is_capacity { - recovery.record_capacity_refusal(state, route); - } else { - recovery.exhaust(route); - } + recovery.record_retryable_attempt(state, route, &err); + retain_most_specific_route_attempt_error(&mut last_retryable_error, err.message); pending_routes = ordered_route_candidates_for_image_generation_with_options( state, model, request, &attempt_options, - ); + ) + .into_iter() + .cloned() + .collect(); } Err(err) => { record_route_attempt_error(state, route, attempt_started.elapsed(), &err); @@ -22400,7 +23898,8 @@ async fn run_audio_speech_with_route_retry( deadline, route_retry_total_attempt_limit(state, model, &options), ); - let mut pending_routes = eligible_routes; + let mut current_model = model.clone(); + let mut pending_routes = eligible_routes.into_iter().cloned().collect::>(); let mut attempt_options = options.clone(); let mut billing = options.billing.clone().unwrap_or_else(|| { GatewayBillingContext::initial(logical_billing_id_for( @@ -22411,14 +23910,15 @@ async fn run_audio_speech_with_route_retry( attempt_options.billing = Some(billing.clone()); let mut last_retryable_error = None; loop { + let model = ¤t_model; if !recovery.can_attempt() || (recovery.has_capacity_waiters() && recovery.deadline.remaining().is_zero()) { break; } - pending_routes = recovery.filter_routes(state, pending_routes); + pending_routes = recovery.filter_owned_routes(state, pending_routes); if pending_routes.is_empty() && recovery.has_capacity_waiters() { - pending_routes = wait_for_capacity_recovery( + pending_routes = wait_for_owned_capacity_recovery( state, &recovery, || { @@ -22428,12 +23928,16 @@ async fn run_audio_speech_with_route_retry( request, &attempt_options, ) + .into_iter() + .cloned() + .collect() }, Duration::from_millis(ROUTE_WAIT_POLL_MS), ) .await; } - let route = pending_routes.first().copied(); + let owned_route = pending_routes.first().cloned(); + let route = owned_route.as_ref(); if route.is_none() && ((!model.mayhem.route_candidates.is_empty() || !state.dev_session_shim) || recovery.dev_route_attempted) @@ -22450,17 +23954,24 @@ async fn run_audio_speech_with_route_retry( model, request, &attempt_options, - ); + ) + .into_iter() + .cloned() + .collect(); continue; } }; + let admission_attempt_budget = recovery.admission_attempt_budget(pending_routes.len()); recovery.begin_attempt(route); - let invocation = state.prepare_audio_speech_invocation_for_route( + let mut invocation = state.prepare_audio_speech_invocation_for_route( model, request, route, &attempt_options, )?; + invocation.failover = invocation + .failover + .with_admission_attempt_budget(admission_attempt_budget); let attempt_started = Instant::now(); match state .session_backend @@ -22479,6 +23990,7 @@ async fn run_audio_speech_with_route_retry( let metering_request = request.clone(); let metering_output = result.output.clone(); return Ok(GatewayAudioSpeechRun { + model: model.clone(), result, invocation, metering_request, @@ -22491,26 +24003,62 @@ async fn run_audio_speech_with_route_retry( Err(err) if err.failure_class.is_request_scoped() => { return Err(request_scoped_api_error(&err).expect("request-scoped error")); } + Err(err) if is_price_version_refusal(&err) => { + // PRICE_VER is rejected before reservation/compute. Do not + // penalize or exhaust the provider, or discard prior partials. + drop(_modality_admission); + current_model = + refresh_model_after_price_refusal(state, model, &invocation, deadline).await?; + let model = ¤t_model; + billing = billing.after_attempt(None); + attempt_options.billing = Some(billing.clone()); + retain_most_specific_route_attempt_error(&mut last_retryable_error, err.message); + pending_routes = ordered_route_candidates_for_audio_speech_with_options( + state, + model, + request, + &attempt_options, + ) + .into_iter() + .cloned() + .collect(); + continue; + } Err(err) if err.retryable => { record_route_attempt_error(state, route, attempt_started.elapsed(), &err); - if let Some(refusal) = terminal_balance_refusal(&err) { + let balance_refusal = terminal_balance_refusal(&err); + let payment_pending = + route_attempt_error_code(Some(&err.message)).0 == "payment_reservation_failed"; + if (balance_refusal.is_some() || payment_pending) + && wait_for_pending_receipt_settlement( + state.receipt_settlement_publisher.as_ref().as_ref(), + &invocation.user_pubkey, + &invocation.rail, + deadline, + ) + .await + { + recovery.total_attempt_limit = recovery.total_attempt_limit.saturating_add(1); + billing = billing.after_attempt(None); + attempt_options.billing = Some(billing.clone()); + continue; + } + if let Some(refusal) = balance_refusal { return Err(refusal); } billing = billing.after_attempt(None); attempt_options.billing = Some(billing.clone()); - let is_capacity = capacity_refusal(&err); - last_retryable_error = Some(err.message); - if is_capacity { - recovery.record_capacity_refusal(state, route); - } else { - recovery.exhaust(route); - } + recovery.record_retryable_attempt(state, route, &err); + retain_most_specific_route_attempt_error(&mut last_retryable_error, err.message); pending_routes = ordered_route_candidates_for_audio_speech_with_options( state, model, request, &attempt_options, - ); + ) + .into_iter() + .cloned() + .collect(); } Err(err) => { record_route_attempt_error(state, route, attempt_started.elapsed(), &err); @@ -22591,7 +24139,8 @@ async fn run_audio_transcription_with_route_retry( deadline, route_retry_total_attempt_limit(state, model, &options), ); - let mut pending_routes = eligible_routes; + let mut current_model = model.clone(); + let mut pending_routes = eligible_routes.into_iter().cloned().collect::>(); let mut attempt_options = options.clone(); let mut billing = options.billing.clone().unwrap_or_else(|| { GatewayBillingContext::initial(logical_billing_id_for( @@ -22602,14 +24151,15 @@ async fn run_audio_transcription_with_route_retry( attempt_options.billing = Some(billing.clone()); let mut last_retryable_error = None; loop { + let model = ¤t_model; if !recovery.can_attempt() || (recovery.has_capacity_waiters() && recovery.deadline.remaining().is_zero()) { break; } - pending_routes = recovery.filter_routes(state, pending_routes); + pending_routes = recovery.filter_owned_routes(state, pending_routes); if pending_routes.is_empty() && recovery.has_capacity_waiters() { - pending_routes = wait_for_capacity_recovery( + pending_routes = wait_for_owned_capacity_recovery( state, &recovery, || { @@ -22619,12 +24169,16 @@ async fn run_audio_transcription_with_route_retry( request, &attempt_options, ) + .into_iter() + .cloned() + .collect() }, Duration::from_millis(ROUTE_WAIT_POLL_MS), ) .await; } - let route = pending_routes.first().copied(); + let owned_route = pending_routes.first().cloned(); + let route = owned_route.as_ref(); if route.is_none() && ((!model.mayhem.route_candidates.is_empty() || !state.dev_session_shim) || recovery.dev_route_attempted) @@ -22641,17 +24195,24 @@ async fn run_audio_transcription_with_route_retry( model, request, &attempt_options, - ); + ) + .into_iter() + .cloned() + .collect(); continue; } }; + let admission_attempt_budget = recovery.admission_attempt_budget(pending_routes.len()); recovery.begin_attempt(route); - let invocation = state.prepare_audio_transcription_invocation_for_route( + let mut invocation = state.prepare_audio_transcription_invocation_for_route( model, request, route, &attempt_options, )?; + invocation.failover = invocation + .failover + .with_admission_attempt_budget(admission_attempt_budget); let attempt_started = Instant::now(); match state .session_backend @@ -22670,6 +24231,7 @@ async fn run_audio_transcription_with_route_retry( let metering_request = request.clone(); let metering_output = result.output.clone(); return Ok(GatewayAudioTranscriptionRun { + model: model.clone(), result, invocation, metering_request, @@ -22682,26 +24244,62 @@ async fn run_audio_transcription_with_route_retry( Err(err) if err.failure_class.is_request_scoped() => { return Err(request_scoped_api_error(&err).expect("request-scoped error")); } + Err(err) if is_price_version_refusal(&err) => { + // PRICE_VER is rejected before reservation/compute. Do not + // penalize or exhaust the provider, or discard prior partials. + drop(_modality_admission); + current_model = + refresh_model_after_price_refusal(state, model, &invocation, deadline).await?; + let model = ¤t_model; + billing = billing.after_attempt(None); + attempt_options.billing = Some(billing.clone()); + retain_most_specific_route_attempt_error(&mut last_retryable_error, err.message); + pending_routes = ordered_route_candidates_for_audio_transcription_with_options( + state, + model, + request, + &attempt_options, + ) + .into_iter() + .cloned() + .collect(); + continue; + } Err(err) if err.retryable => { record_route_attempt_error(state, route, attempt_started.elapsed(), &err); - if let Some(refusal) = terminal_balance_refusal(&err) { + let balance_refusal = terminal_balance_refusal(&err); + let payment_pending = + route_attempt_error_code(Some(&err.message)).0 == "payment_reservation_failed"; + if (balance_refusal.is_some() || payment_pending) + && wait_for_pending_receipt_settlement( + state.receipt_settlement_publisher.as_ref().as_ref(), + &invocation.user_pubkey, + &invocation.rail, + deadline, + ) + .await + { + recovery.total_attempt_limit = recovery.total_attempt_limit.saturating_add(1); + billing = billing.after_attempt(None); + attempt_options.billing = Some(billing.clone()); + continue; + } + if let Some(refusal) = balance_refusal { return Err(refusal); } billing = billing.after_attempt(None); attempt_options.billing = Some(billing.clone()); - let is_capacity = capacity_refusal(&err); - last_retryable_error = Some(err.message); - if is_capacity { - recovery.record_capacity_refusal(state, route); - } else { - recovery.exhaust(route); - } + recovery.record_retryable_attempt(state, route, &err); + retain_most_specific_route_attempt_error(&mut last_retryable_error, err.message); pending_routes = ordered_route_candidates_for_audio_transcription_with_options( state, model, request, &attempt_options, - ); + ) + .into_iter() + .cloned() + .collect(); } Err(err) => { record_route_attempt_error(state, route, attempt_started.elapsed(), &err); @@ -22791,7 +24389,8 @@ async fn run_artifact_generation_with_route_retry( deadline, route_retry_total_attempt_limit(state, model, &options), ); - let mut pending_routes = eligible_routes; + let mut current_model = model.clone(); + let mut pending_routes = eligible_routes.into_iter().cloned().collect::>(); let mut attempt_options = options.clone(); let mut billing = options.billing.clone().unwrap_or_else(|| { GatewayBillingContext::initial(logical_billing_id_for( @@ -22802,14 +24401,15 @@ async fn run_artifact_generation_with_route_retry( attempt_options.billing = Some(billing.clone()); let mut last_retryable_error = None; loop { + let model = ¤t_model; if !recovery.can_attempt() || (recovery.has_capacity_waiters() && recovery.deadline.remaining().is_zero()) { break; } - pending_routes = recovery.filter_routes(state, pending_routes); + pending_routes = recovery.filter_owned_routes(state, pending_routes); if pending_routes.is_empty() && recovery.has_capacity_waiters() { - pending_routes = wait_for_capacity_recovery( + pending_routes = wait_for_owned_capacity_recovery( state, &recovery, || { @@ -22819,12 +24419,16 @@ async fn run_artifact_generation_with_route_retry( request, &attempt_options, ) + .into_iter() + .cloned() + .collect() }, Duration::from_millis(ROUTE_WAIT_POLL_MS), ) .await; } - let route = pending_routes.first().copied(); + let owned_route = pending_routes.first().cloned(); + let route = owned_route.as_ref(); if route.is_none() && ((!model.mayhem.route_candidates.is_empty() || !state.dev_session_shim) || recovery.dev_route_attempted) @@ -22842,7 +24446,10 @@ async fn run_artifact_generation_with_route_retry( model, request, &attempt_options, - ); + ) + .into_iter() + .cloned() + .collect(); continue; } }; @@ -22873,17 +24480,24 @@ async fn run_artifact_generation_with_route_retry( model, request, &attempt_options, - ); + ) + .into_iter() + .cloned() + .collect(); continue; } }; + let admission_attempt_budget = recovery.admission_attempt_budget(pending_routes.len()); recovery.begin_attempt(route); - let invocation = state.prepare_artifact_generation_invocation_for_route( + let mut invocation = state.prepare_artifact_generation_invocation_for_route( model, &attempt_request, route, &attempt_options, )?; + invocation.failover = invocation + .failover + .with_admission_attempt_budget(admission_attempt_budget); let attempt_started = Instant::now(); match state .session_backend @@ -22901,6 +24515,7 @@ async fn run_artifact_generation_with_route_retry( ), ); return Ok(GatewayArtifactGenerationRun { + model: model.clone(), metering_request: attempt_request, metering_output: result.output.clone(), result, @@ -22913,26 +24528,62 @@ async fn run_artifact_generation_with_route_retry( Err(err) if err.failure_class.is_request_scoped() => { return Err(request_scoped_api_error(&err).expect("request-scoped error")); } + Err(err) if is_price_version_refusal(&err) => { + // PRICE_VER is rejected before reservation/compute. Do not + // penalize or exhaust the provider, or discard prior partials. + drop(_modality_admission); + current_model = + refresh_model_after_price_refusal(state, model, &invocation, deadline).await?; + let model = ¤t_model; + billing = billing.after_attempt(None); + attempt_options.billing = Some(billing.clone()); + retain_most_specific_route_attempt_error(&mut last_retryable_error, err.message); + pending_routes = ordered_route_candidates_for_artifact_generation_with_options( + state, + model, + request, + &attempt_options, + ) + .into_iter() + .cloned() + .collect(); + continue; + } Err(err) if err.retryable => { record_route_attempt_error(state, route, attempt_started.elapsed(), &err); - if let Some(refusal) = terminal_balance_refusal(&err) { + let balance_refusal = terminal_balance_refusal(&err); + let payment_pending = + route_attempt_error_code(Some(&err.message)).0 == "payment_reservation_failed"; + if (balance_refusal.is_some() || payment_pending) + && wait_for_pending_receipt_settlement( + state.receipt_settlement_publisher.as_ref().as_ref(), + &invocation.user_pubkey, + &invocation.rail, + deadline, + ) + .await + { + recovery.total_attempt_limit = recovery.total_attempt_limit.saturating_add(1); + billing = billing.after_attempt(None); + attempt_options.billing = Some(billing.clone()); + continue; + } + if let Some(refusal) = balance_refusal { return Err(refusal); } billing = billing.after_attempt(None); attempt_options.billing = Some(billing.clone()); - let is_capacity = capacity_refusal(&err); - last_retryable_error = Some(err.message); - if is_capacity { - recovery.record_capacity_refusal(state, route); - } else { - recovery.exhaust(route); - } + recovery.record_retryable_attempt(state, route, &err); + retain_most_specific_route_attempt_error(&mut last_retryable_error, err.message); pending_routes = ordered_route_candidates_for_artifact_generation_with_options( state, model, request, &attempt_options, - ); + ) + .into_iter() + .cloned() + .collect(); } Err(err) => { record_route_attempt_error(state, route, attempt_started.elapsed(), &err); @@ -23218,25 +24869,23 @@ fn chat_context_capacity_error( request: &ChatCompletionRequest, options: &GatewayRequestOptions, ) -> Option { - if let Some(error) = preferred_provider_refusal_error(state, model, options) { - return Some(error); - } let required_ctx = effective_context_floor( options.min_ctx, chat_context_input_tokens(request), chat_output_headroom_tokens(request), ); - let now_millis = now_millis_u64(); state.refresh_provider_table_routes(model); - let candidates = eligible_route_candidates( + let candidates = order_strict_preferred_routes( + state, model, - options.min_att_tier, - options.quant.as_deref(), - &state.receipt_config.rail, - ) - .into_iter() - .filter(|candidate| !state.route_provider_in_cooloff(candidate, now_millis)) - .collect::>(); + options, + eligible_route_candidates( + model, + options.min_att_tier, + options.quant.as_deref(), + &state.receipt_config.rail, + ), + ); (!candidates.is_empty() && candidates .iter() @@ -23297,6 +24946,97 @@ impl RouteWaitDeadline { } } +fn is_price_version_refusal(err: &GatewaySessionError) -> bool { + err.clean_refusal + && err.clean_refusal_code.as_deref() == Some("PRICE_VER") + // Set by the s.open admission path, not by an in-flight s.error. + && err.safe_same_route_retry + && err.partial.is_none() + && err.interrupted.is_none() +} + +fn catalog_price_refresh_error() -> ApiError { + ApiError::service_unavailable( + "The current model price is still being synchronized. Please retry shortly.", + Some("model"), + ) + .with_public_error("catalog_price_refresh_pending", "route_selection", true) +} + +async fn refresh_model_after_price_refusal( + state: &GatewayState, + model: &GatewayModel, + invocation: &GatewaySessionInvocation, + deadline: RouteWaitDeadline, +) -> Result { + if invocation + .client_cancellation + .as_ref() + .is_some_and(|c| c.is_cancelled()) + { + return Err(ApiError::client_closed_request("request cancelled")); + } + // A concurrent request or the periodic watcher may already have obtained + // newer authenticated terms while this request was awaiting s.reject. + if let Some(current) = state + .models_snapshot() + .iter() + .find(|current| current.id == model.id) + { + let route = current + .mayhem + .route_candidates + .iter() + .find(|route| invocation.selected_route_key.as_ref() == Some(&route_key(route))); + if route_price_ref_au(current, route).ver != invocation.price_ver { + return Ok(current.clone()); + } + } + let generation = state.request_catalog_refresh(); + loop { + // Register before checking generation, so completion cannot be lost + // between the check and awaiting notification. + let completed = state.catalog_refresh_complete.notified(); + tokio::pin!(completed); + completed.as_mut().enable(); + if invocation + .client_cancellation + .as_ref() + .is_some_and(|c| c.is_cancelled()) + { + return Err(ApiError::client_closed_request("request cancelled")); + } + if state + .catalog_refresh + .lock_recover("catalog refresh") + .generation + != generation + { + return state + .models_snapshot() + .iter() + .find(|current| current.id == model.id) + .cloned() + .ok_or_else(catalog_price_refresh_error); + } + if deadline.remaining().is_zero() { + return Err(catalog_price_refresh_error()); + } + tokio::select! { + _ = &mut completed => {}, + _ = tokio::time::sleep(deadline.remaining()) => { + return Err(catalog_price_refresh_error()); + }, + _ = async { + match invocation.client_cancellation.as_ref() { + Some(cancellation) => cancellation.cancelled().await, + None => std::future::pending::<()>().await, + } + } => return Err(ApiError::client_closed_request("request cancelled")), + } + } +} + async fn wait_for_pending_receipt_settlement( publisher: Option<&Arc>, user: &str, @@ -23304,9 +25044,8 @@ async fn wait_for_pending_receipt_settlement( deadline: RouteWaitDeadline, ) -> bool { if deadline.remaining().is_zero() - || !publisher.is_some_and(|publisher| { - publisher.has_pending_final_receipts(user, rail) == Ok(true) - }) + || !publisher + .is_some_and(|publisher| publisher.has_pending_final_receipts(user, rail) == Ok(true)) { return false; } @@ -23314,6 +25053,10 @@ async fn wait_for_pending_receipt_settlement( !deadline.remaining().is_zero() } +// One replay covers a cold or flapping admission path while keeping a silent +// sole route from multiplying the configured transport timeout indefinitely. +const MAX_PRE_SPEND_SAME_ROUTE_RETRIES: u8 = 1; + #[derive(Debug)] struct RouteAdmissionRecovery { deadline: RouteWaitDeadline, @@ -23322,6 +25065,7 @@ struct RouteAdmissionRecovery { attempted_providers: BTreeSet, exhausted_route_keys: BTreeSet, capacity_refusal_generations: BTreeMap, + safe_same_route_retry_counts: BTreeMap, dev_route_attempted: bool, attempts_made: usize, } @@ -23341,11 +25085,13 @@ impl RouteAdmissionRecovery { attempted_providers: BTreeSet::new(), exhausted_route_keys: BTreeSet::new(), capacity_refusal_generations: BTreeMap::new(), + safe_same_route_retry_counts: BTreeMap::new(), dev_route_attempted: false, attempts_made: 0, } } + #[cfg(test)] fn filter_routes<'a>( &self, state: &GatewayState, @@ -23456,6 +25202,22 @@ impl RouteAdmissionRecovery { self.attempts_made < self.total_attempt_limit } + fn admission_attempt_budget(&self, candidate_count: usize) -> Option { + // With no alternate route to protect, keep the configured transport + // timeout. A route-discovery wait budget is not a safe substitute for + // the time needed to establish a cold direct session. + if candidate_count <= 1 { + return None; + } + let total = match self.deadline.remaining() { + remaining if remaining.is_zero() => Duration::from_millis(DEFAULT_ROUTE_MAX_WAIT_MS), + remaining => remaining, + }; + let remaining_attempts = self.total_attempt_limit.saturating_sub(self.attempts_made); + let shares = candidate_count.max(1).min(remaining_attempts.max(1)); + Some(total / u32::try_from(shares).unwrap_or(u32::MAX).max(1)) + } + fn begin_attempt(&mut self, route: Option<&GatewayRouteCandidate>) { self.attempts_made = self.attempts_made.saturating_add(1); let Some(route) = route else { @@ -23504,6 +25266,31 @@ impl RouteAdmissionRecovery { } } + fn record_retryable_attempt( + &mut self, + state: &GatewayState, + route: Option<&GatewayRouteCandidate>, + error: &GatewaySessionError, + ) { + if capacity_refusal(error) { + self.record_capacity_refusal(state, route); + return; + } + if error.safe_same_route_retry { + if let Some(route) = route { + let retries = self + .safe_same_route_retry_counts + .entry(route_key(route)) + .or_default(); + if *retries < MAX_PRE_SPEND_SAME_ROUTE_RETRIES { + *retries += 1; + return; + } + } + } + self.exhaust(route); + } + fn record_modality_admission_error( &mut self, state: &GatewayState, @@ -23641,6 +25428,7 @@ where RouteWaitOutcome { routes, waited } } +#[cfg(test)] async fn wait_for_capacity_recovery<'a, F>( state: &GatewayState, recovery: &RouteAdmissionRecovery, @@ -24034,6 +25822,10 @@ fn synchronize_effective_chat_contract_request( "max_completion_tokens", request.max_completion_tokens.map(|value| json!(value)), ), + ( + "max_output_tokens", + request.max_tokens.map(|value| json!(value)), + ), ]; for (name, value) in values { if contract.request_attribute_specs.contains_key(name) @@ -24233,8 +26025,10 @@ async fn build_chat_completion( ) -> Result { let model = require_model(&state, &request.model)?; let mut request = request; + validate_requested_response_schema(request.response_format.as_ref())?; apply_model_sampling_defaults(&model, &mut request)?; apply_model_speciality_defaults(&model, &mut request)?; + fit_chat_output_budget_to_context(&state, &model, &mut request, &options); synchronize_effective_chat_contract_request(&model, &mut request)?; if request.messages.is_empty() { return Err(ApiError::bad_request( @@ -24278,6 +26072,7 @@ async fn build_chat_completion( } } let GatewaySessionRun { + model, result: GatewaySessionResult { output, @@ -24315,7 +26110,7 @@ async fn build_chat_completion( }; if let Some(job) = invocation.job.as_ref() { job.persist_completed_if_active( - chat_job_result(&output), + chat_job_result_for_request(&request, &output), gateway_job_artifacts(&output.artifacts), receipt.as_ref().map(|receipt| { if request.stream { @@ -24392,7 +26187,7 @@ async fn build_live_chat_completion( async fn prepare_live_direct_chat_session( state: SharedState, - model: GatewayModel, + mut model: GatewayModel, request: ChatCompletionRequest, options: GatewayRequestOptions, id: String, @@ -24411,6 +26206,11 @@ async fn prepare_live_direct_chat_session( ); let eligible_route_refs = ordered_route_candidates_for_request_with_options(&state, &model, &request, &options); + if eligible_route_refs.is_empty() { + if let Some(error) = chat_context_capacity_error(&state, &model, &request, &options) { + return Err(error); + } + } let RouteWaitOutcome { routes: mut eligible_route_refs, waited, @@ -24424,15 +26224,13 @@ async fn prepare_live_direct_chat_session( || ordered_route_candidates_for_request_with_options(&state, &model, &request, &options), ) .await; - if eligible_route_refs.is_empty() { - if let Some(error) = preferred_provider_refusal_error(&state, &model, &options) { - return Err(error); - } - } if !model.mayhem.route_candidates.is_empty() && eligible_route_refs.is_empty() { if let Some(error) = chat_context_capacity_error(&state, &model, &request, &options) { return Err(error); } + if let Some(error) = preferred_provider_refusal_error(&state, &model, &options) { + return Err(error); + } if waited { return Err(route_wait_expired_error(&options)); } @@ -24520,13 +26318,17 @@ async fn prepare_live_direct_chat_session( continue; } }; + let admission_attempt_budget = recovery.admission_attempt_budget(pending_routes.len()); recovery.begin_attempt(route.as_ref()); - let invocation = state.prepare_chat_invocation_for_route( + let mut invocation = state.prepare_chat_invocation_for_route( &model, &request, route.as_ref(), &attempt_options, )?; + invocation.failover = invocation + .failover + .with_admission_attempt_budget(admission_attempt_budget); let invocation = invocation.with_hedge_probe_outcome(&hedge_probe); let attempt_started = Instant::now(); match open_live_direct_chat_session(&config, &model, &request, &invocation).await { @@ -24563,37 +26365,53 @@ async fn prepare_live_direct_chat_session( _modality_admission: modality_admission, }); } + Err(err) if is_price_version_refusal(&err) => { + drop(modality_admission); + model = refresh_model_after_price_refusal(&state, &model, &invocation, deadline) + .await?; + billing = billing.after_attempt(None); + attempt_options.billing = Some(billing.clone()); + retain_most_specific_route_attempt_error(&mut last_retryable_error, err.message); + pending_routes = ordered_route_candidates_for_request_with_options( + &state, + &model, + &request, + &attempt_options, + ) + .into_iter() + .cloned() + .collect(); + continue; + } Err(err) if err.retryable => { record_route_attempt_error(&state, route.as_ref(), attempt_started.elapsed(), &err); - if let Some(refusal) = terminal_balance_refusal(&err) { - if wait_for_pending_receipt_settlement( + let balance_refusal = terminal_balance_refusal(&err); + let payment_pending = + route_attempt_error_code(Some(&err.message)).0 == "payment_reservation_failed"; + if (balance_refusal.is_some() || payment_pending) + && wait_for_pending_receipt_settlement( state.receipt_settlement_publisher.as_ref().as_ref(), &invocation.user_pubkey, &invocation.rail, deadline, ) .await - { - // A clean refusal performed no inference and reserved no - // canonical funds. Pending final receipts can release - // the previous request's hold within the route deadline. - recovery.total_attempt_limit = - recovery.total_attempt_limit.saturating_add(1); - billing = billing.after_attempt(None); - attempt_options.billing = Some(billing.clone()); - continue; - } + { + // No inference or canonical spend occurred. A pending final + // receipt can release the previous request's hold within + // this request's route deadline. + recovery.total_attempt_limit = recovery.total_attempt_limit.saturating_add(1); + billing = billing.after_attempt(None); + attempt_options.billing = Some(billing.clone()); + continue; + } + if let Some(refusal) = balance_refusal { return Err(refusal); } billing = billing.after_attempt(None); attempt_options.billing = Some(billing.clone()); - let is_capacity = capacity_refusal(&err); - last_retryable_error = Some(err.message); - if is_capacity { - recovery.record_capacity_refusal(&state, route.as_ref()); - } else { - recovery.exhaust(route.as_ref()); - } + recovery.record_retryable_attempt(&state, route.as_ref(), &err); + retain_most_specific_route_attempt_error(&mut last_retryable_error, err.message); pending_routes = ordered_route_candidates_for_request_with_options( &state, &model, @@ -24651,16 +26469,16 @@ async fn open_live_direct_chat_session( "connecting provider {} transport peer {} for session {} failed: {err}", provider, direct_peer, invocation.session_id )) + .into_safe_same_route_retry() })?; - let opened = bridge - .session_open(direct_peer, &invocation.session_id) - .await - .map_err(|err| { - GatewaySessionError::retryable(format!( - "opening direct session {} to provider {} transport peer {} failed: {err}", - invocation.session_id, provider, direct_peer - )) - })?; + let opened = open_direct_session_with_timeout( + &mut bridge, + provider, + direct_peer, + &invocation.session_id, + invocation.failover.open_timeout(), + ) + .await?; if !sc_bridge_session_transport_valid(&opened) { let _ = bridge .session_close(direct_peer, &invocation.session_id) @@ -24668,7 +26486,8 @@ async fn open_live_direct_chat_session( return Err(GatewaySessionError::retryable(format!( "session {} did not open an authenticated direct-or-relayed channel", invocation.session_id - ))); + )) + .into_safe_same_route_retry()); } let open_result = async { @@ -24776,15 +26595,15 @@ async fn send_open_and_validate_session_accept( open_head: &str, att_nonce: &str, ) -> Result { - if invocation.spend_voucher.body.schema_version != SESSION_RECEIPT_SCHEMA_VERSION + if invocation.spend_voucher.body.schema_version != SPEND_VOUCHER_SCHEMA_VERSION || open_frame .get("voucher") .and_then(|voucher| voucher.get("schema_version")) .and_then(Value::as_u64) - != Some(u64::from(SESSION_RECEIPT_SCHEMA_VERSION)) + != Some(u64::from(SPEND_VOUCHER_SCHEMA_VERSION)) { return Err(GatewaySessionError::new(format!( - "spend voucher schema_version must be {SESSION_RECEIPT_SCHEMA_VERSION}" + "spend voucher schema_version must be {SPEND_VOUCHER_SCHEMA_VERSION}" ))); } let result = async { @@ -24820,7 +26639,18 @@ async fn send_open_and_validate_session_accept( ) .await; } - result + result.map_err(|error| { + if error.clean_refusal { + // Explicit pre-spend refusals (capacity, policy, or a canonical + // balance rejection) did not start provider work and are safe to + // route elsewhere. Transport loss, timeout, and malformed accepts + // after s.open are outcome-ambiguous because the reservation may + // already exist even if its acknowledgement did not arrive. + error.into_safe_same_route_retry() + } else { + error.into_non_retryable_admission_outcome() + } + }) } async fn send_open_and_await_session_accept( @@ -24840,20 +26670,22 @@ async fn send_open_and_await_session_accept( )) })?; let explicit_accept_timeout = invocation.failover.session_accept_timeout(); - let accept_wait = explicit_accept_timeout - .unwrap_or_else(|| Duration::from_millis(SESSION_OPEN_REPLAY_INTERVAL_MS)); + let accept_budget = + explicit_accept_timeout.unwrap_or_else(|| invocation.failover.open_timeout()); + let replay_interval = Duration::from_millis(SESSION_OPEN_REPLAY_INTERVAL_MS); // Total budget for the provider to answer s.open. Without it a silent // provider (or a flapping transport) keeps this loop replaying forever, // even after the end user is long gone. - let accept_deadline = Instant::now() - + explicit_accept_timeout.unwrap_or_else(|| invocation.failover.open_timeout()); + let accept_deadline = Instant::now() + accept_budget; loop { - if Instant::now() >= accept_deadline { + let remaining = accept_deadline.saturating_duration_since(Instant::now()); + if remaining.is_zero() { return Err(GatewaySessionError::retryable(format!( "provider {} did not answer s.open for session {} within the accept budget", provider, invocation.session_id ))); } + let accept_wait = replay_interval.min(remaining); match next_session_frame_with_optional_wait( bridge, &invocation.session_id, @@ -24875,7 +26707,13 @@ async fn send_open_and_await_session_accept( ) .await?; } - Err(err) if err.wait_elapsed && explicit_accept_timeout.is_none() => { + Err(err) if err.wait_elapsed => { + if Instant::now() >= accept_deadline { + return Err(GatewaySessionError::retryable(format!( + "provider {} did not answer s.open for session {} within the accept budget", + provider, invocation.session_id + ))); + } if bridge .session_send(direct_peer, &invocation.session_id, open_frame) .await @@ -25133,14 +26971,28 @@ async fn finish_live_direct_chat_after_client_disconnect( ) -> Result<(), GatewaySessionError> { // Stop generation but keep the direct channel alive until a final signed // receipt closes the voucher. Only acknowledged checkpoints are chargeable. - let (usage, seq) = err.partial.as_ref().map(|partial| - (partial.provider_receipt.body.usage.clone(), partial.provider_receipt.body.seq.saturating_add(1))) + let (usage, seq) = err + .partial + .as_ref() + .map(|partial| { + ( + partial.provider_receipt.body.usage.clone(), + partial.provider_receipt.body.seq.saturating_add(1), + ) + }) .unwrap_or_else(|| (ReceiptUsage::default(), 1)); cancel_and_settle_direct_session( - &mut session.bridge, &session.invocation, &session.transport_peer, - &session.provider, &session.model, &session.enclave_pubkey, - blake3_hex(chat_prompt_text(&session.request).as_bytes()), usage, seq, - ).await + &mut session.bridge, + &session.invocation, + &session.transport_peer, + &session.provider, + &session.model, + &session.enclave_pubkey, + direct_chat_prompt_hash(&session.request), + usage, + seq, + ) + .await } async fn recover_live_direct_chat_after_retryable( @@ -25204,6 +27056,12 @@ async fn recover_live_direct_chat_after_retryable( } else { redispatch_request_with_partials(&session.request, &partials) }; + fit_chat_output_budget_to_context( + &session.state, + &session.model, + &mut retry_request, + &session.options, + ); synchronize_effective_chat_contract_request(&session.model, &mut retry_request).map_err( |_| GatewaySessionError::new("effective redispatch request failed contract validation"), )?; @@ -25217,6 +27075,7 @@ async fn recover_live_direct_chat_after_retryable( )); } let GatewaySessionRun { + model, result, invocation, metering_request, @@ -25229,6 +27088,7 @@ async fn recover_live_direct_chat_after_retryable( ) .await .map_err(|err| GatewaySessionError::new(err.message))?; + session.model = model; let receipt = session .state .meter_chat_session( @@ -25551,16 +27411,43 @@ async fn run_live_direct_chat_sse_inner( )); } } - let reasoning_delta = reasoning_stream.push(session_delta_reasoning_evidence(&frame)?); + let reasoning_delta = + reasoning_stream.push(session_delta_reasoning_evidence(&frame)?); let tool_deltas = tool_stream.push(&frame, &session.request, max_text_bytes)?; let mut public_delta = json!({}); - if !reasoning_delta.is_empty() { public_delta["reasoning_content"] = json!(reasoning_delta); } - if !tool_deltas.is_empty() { public_delta["tool_calls"] = json!(tool_deltas); } - if public_delta.as_object().is_some_and(|delta| !delta.is_empty()) && !send_live_sse_value(session.options.continue_after_stream_disconnect, tx, - chat_chunk(&session.id, session.created, &session.model.id, public_delta, None, None)).await { - return Err(client_disconnect_direct_session_error(&session.request, &content, - &reasoning_evidence, tool_calls.clone(), latest_checkpoint_receipt.as_ref(), - &token_ids, &watchdog, now)); + if !reasoning_delta.is_empty() { + public_delta["reasoning_content"] = json!(reasoning_delta); + } + if !tool_deltas.is_empty() { + public_delta["tool_calls"] = json!(tool_deltas); + } + if public_delta + .as_object() + .is_some_and(|delta| !delta.is_empty()) + && !send_live_sse_value( + session.options.continue_after_stream_disconnect, + tx, + chat_chunk( + &session.id, + session.created, + &session.model.id, + public_delta, + None, + None, + ), + ) + .await + { + return Err(client_disconnect_direct_session_error( + &session.request, + &content, + &reasoning_evidence, + tool_calls.clone(), + latest_checkpoint_receipt.as_ref(), + &token_ids, + &watchdog, + now, + )); } if let Some(receipt) = pending_checkpoint_receipt.take() { if let Some(ack_frame) = maybe_ack_direct_session_checkpoint_receipt( @@ -25620,13 +27507,35 @@ async fn run_live_direct_chat_sse_inner( } collect_artifact_from_session_delta(&frame, &mut artifact_builders)?; if let Some(fin) = frame.get("fin").and_then(Value::as_str) { - if tool_calls.is_empty() { tool_stream.finish(&[])?; } + if tool_calls.is_empty() { + tool_stream.finish(&[])?; + } let tail = reasoning_stream.finish(); - if !tail.is_empty() && !send_live_sse_value(session.options.continue_after_stream_disconnect, tx, chat_chunk(&session.id, session.created, - &session.model.id, json!({"reasoning_content":tail}), None, None)).await { - return Err(client_disconnect_direct_session_error(&session.request, &content, - &reasoning_evidence, tool_calls.clone(), latest_checkpoint_receipt.as_ref(), - &token_ids, &watchdog, now)); + if !tail.is_empty() + && !send_live_sse_value( + session.options.continue_after_stream_disconnect, + tx, + chat_chunk( + &session.id, + session.created, + &session.model.id, + json!({"reasoning_content":tail}), + None, + None, + ), + ) + .await + { + return Err(client_disconnect_direct_session_error( + &session.request, + &content, + &reasoning_evidence, + tool_calls.clone(), + latest_checkpoint_receipt.as_ref(), + &token_ids, + &watchdog, + now, + )); } finish_reason = Some(fin.to_owned()); claimed_usage = usage_from_session_delta(&frame); @@ -25710,13 +27619,24 @@ async fn run_live_direct_chat_sse_inner( &frame, &format!("session {}", session.invocation.session_id), false, + delta_sequence.next_index > 0 + || latest_checkpoint_receipt.is_some() + || pending_checkpoint_receipt.is_some() + || final_provider_receipt.is_some(), ); settle_failed_direct_session_frame( - &mut session.bridge, &session.invocation, &session.model, - &session.enclave_pubkey, &frame, latest_checkpoint_receipt.as_ref(), - blake3_hex(chat_prompt_text(&session.request).as_bytes()), - ).await?; - if frame.get("receipt").is_some() { return Err(error); } + &mut session.bridge, + &session.invocation, + &session.model, + &session.enclave_pubkey, + &frame, + latest_checkpoint_receipt.as_ref(), + direct_chat_prompt_hash(&session.request), + ) + .await?; + if frame.get("receipt").is_some() { + return Err(error); + } if error.failure_class.is_request_scoped() { return Err(error); } @@ -25860,6 +27780,7 @@ async fn run_live_direct_chat_sse_inner( finish_reason, usage, }; + validate_structured_chat_output(&session.request, &output)?; let provider_receipt = final_provider_receipt.ok_or_else(|| { GatewaySessionError::new(format!( "provider session {} ended without a final receipt", @@ -25878,7 +27799,7 @@ async fn run_live_direct_chat_sse_inner( &mut session.bridge, &session.transport_peer, &session.invocation, - chat_job_result(&output), + chat_job_result_for_request(&session.request, &output), &output.artifacts, &provider_receipt, &receipt_ack, @@ -26185,6 +28106,11 @@ async fn run_chat_with_route_retry( ); let eligible_routes = ordered_route_candidates_for_request_with_options(state, model, request, &options); + if eligible_routes.is_empty() { + if let Some(error) = chat_context_capacity_error(state, model, request, &options) { + return Err(error); + } + } let RouteWaitOutcome { routes: mut eligible_routes, waited, @@ -26198,15 +28124,13 @@ async fn run_chat_with_route_retry( || ordered_route_candidates_for_request_with_options(state, model, request, &options), ) .await; - if eligible_routes.is_empty() { - if let Some(error) = preferred_provider_refusal_error(state, model, &options) { - return Err(error); - } - } if !model.mayhem.route_candidates.is_empty() && eligible_routes.is_empty() { if let Some(error) = chat_context_capacity_error(state, model, request, &options) { return Err(error); } + if let Some(error) = preferred_provider_refusal_error(state, model, &options) { + return Err(error); + } if waited { return Err(route_wait_expired_error(&options)); } @@ -26245,19 +28169,21 @@ async fn run_chat_with_route_retry( deadline, route_retry_total_attempt_limit(state, model, &options), ); - let mut pending_routes = eligible_routes; + let mut current_model = model.clone(); + let mut pending_routes = eligible_routes.into_iter().cloned().collect::>(); let mut last_retryable_error = None; let mut partials = Vec::new(); loop { + let model = ¤t_model; if !recovery.can_attempt() || (recovery.has_capacity_waiters() && recovery.deadline.remaining().is_zero()) { break; } - pending_routes = recovery.filter_routes(state, pending_routes); + pending_routes = recovery.filter_owned_routes(state, pending_routes); if pending_routes.is_empty() && recovery.has_capacity_waiters() { - pending_routes = wait_for_capacity_recovery( + pending_routes = wait_for_owned_capacity_recovery( state, &recovery, || { @@ -26267,12 +28193,16 @@ async fn run_chat_with_route_retry( &attempt_request, &attempt_options, ) + .into_iter() + .cloned() + .collect() }, Duration::from_millis(ROUTE_WAIT_POLL_MS), ) .await; } - let route = pending_routes.first().copied(); + let owned_route = pending_routes.first().cloned(); + let route = owned_route.as_ref(); if route.is_none() { if recovery.has_capacity_waiters() && recovery.deadline.remaining().is_zero() { break; @@ -26308,17 +28238,24 @@ async fn run_chat_with_route_retry( model, &attempt_request, &attempt_options, - ); + ) + .into_iter() + .cloned() + .collect(); continue; } }; + let admission_attempt_budget = recovery.admission_attempt_budget(pending_routes.len()); recovery.begin_attempt(route); - let invocation = state.prepare_chat_invocation_for_route( + let mut invocation = state.prepare_chat_invocation_for_route( model, &attempt_request, route, &attempt_options, )?; + invocation.failover = invocation + .failover + .with_admission_attempt_budget(admission_attempt_budget); let invocation = invocation.with_hedge_probe_outcome(&hedge_probe); let attempt_started = Instant::now(); match state @@ -26350,10 +28287,22 @@ async fn run_chat_with_route_retry( model, &attempt_request, &attempt_options, - ); + ) + .into_iter() + .cloned() + .collect(); continue; } } + let metering_request = attempt_request.clone(); + let metering_output = result.output.clone(); + if !partials.is_empty() { + stitch_partials_into_result(&mut result, &partials); + } + validate_structured_chat_output(request, &result.output).map_err(|error| { + request_scoped_api_error(&error) + .expect("structured output failure is request scoped") + })?; record_route_observation( state, route, @@ -26362,12 +28311,8 @@ async fn run_chat_with_route_retry( if let Some(route) = route { state.record_chat_affinity(model, request, route); } - let metering_request = attempt_request.clone(); - let metering_output = result.output.clone(); - if !partials.is_empty() { - stitch_partials_into_result(&mut result, &partials); - } return Ok(GatewaySessionRun { + model: model.clone(), result, invocation, metering_request, @@ -26380,16 +28325,53 @@ async fn run_chat_with_route_retry( Err(err) if err.failure_class.is_request_scoped() => { return Err(request_scoped_api_error(&err).expect("request-scoped error")); } + Err(err) if is_price_version_refusal(&err) => { + // PRICE_VER is rejected before reservation/compute. Do not + // penalize or exhaust the provider, or discard prior partials. + drop(_modality_admission); + current_model = + refresh_model_after_price_refusal(state, model, &invocation, deadline).await?; + let model = ¤t_model; + billing = billing.after_attempt(None); + attempt_options.billing = Some(billing.clone()); + retain_most_specific_route_attempt_error(&mut last_retryable_error, err.message); + pending_routes = ordered_route_candidates_for_request_with_options( + state, + model, + &attempt_request, + &attempt_options, + ) + .into_iter() + .cloned() + .collect(); + continue; + } Err(err) if err.retryable => { record_route_attempt_error(state, route, attempt_started.elapsed(), &err); - if let Some(refusal) = terminal_balance_refusal(&err) { + let balance_refusal = terminal_balance_refusal(&err); + let payment_pending = + route_attempt_error_code(Some(&err.message)).0 == "payment_reservation_failed"; + if (balance_refusal.is_some() || payment_pending) + && wait_for_pending_receipt_settlement( + state.receipt_settlement_publisher.as_ref().as_ref(), + &invocation.user_pubkey, + &invocation.rail, + deadline, + ) + .await + { + recovery.total_attempt_limit = recovery.total_attempt_limit.saturating_add(1); + billing = billing.after_attempt(None); + attempt_options.billing = Some(billing.clone()); + continue; + } + if let Some(refusal) = balance_refusal { return Err(refusal); } let billed_receipt = err .partial .as_ref() .map(|partial| partial.provider_receipt.body.clone()); - let is_capacity = capacity_refusal(&err); if let Some(partial) = err.partial.as_ref() { state.record_partial_provider_receipt( model, @@ -26398,9 +28380,16 @@ async fn run_chat_with_route_retry( partial, )?; } + recovery.record_retryable_attempt(state, route, &err); if let Some(partial) = err.partial { partials.push(*partial); attempt_request = redispatch_request_with_partials(request, &partials); + fit_chat_output_budget_to_context( + state, + model, + &mut attempt_request, + &attempt_options, + ); synchronize_effective_chat_contract_request(model, &mut attempt_request)?; attempt_options.min_ctx = Some(exact_conversation_floor_after_partials( &attempt_request, @@ -26410,18 +28399,16 @@ async fn run_chat_with_route_retry( } billing = billing.after_attempt(billed_receipt.as_ref()); attempt_options.billing = Some(billing.clone()); - last_retryable_error = Some(err.message); - if is_capacity { - recovery.record_capacity_refusal(state, route); - } else { - recovery.exhaust(route); - } + retain_most_specific_route_attempt_error(&mut last_retryable_error, err.message); pending_routes = ordered_route_candidates_for_request_with_options( state, model, &attempt_request, &attempt_options, - ); + ) + .into_iter() + .cloned() + .collect(); } Err(err) => { record_route_attempt_error(state, route, attempt_started.elapsed(), &err); @@ -27521,9 +29508,14 @@ impl GatewayState { .get(&(provider.clone(), modality.clone())) .copied() .unwrap_or_default(); + // `active_items` is an advisory heartbeat snapshot and may already + // include this gateway's live reservations. Adding both counters + // double-counts overlapping work, so reserve against the more + // conservative observation. The provider remains the atomic + // admission authority and can still reject a race with CAPACITY. if capacity .active_items - .saturating_add(locally_active) + .max(locally_active) .saturating_add(load.item_count) > capacity.max_inflight_items { @@ -27773,13 +29765,39 @@ fn chat_affinity_key( fn request_requirements_for_chat( state: &GatewayState, - _model: &GatewayModel, + model: &GatewayModel, request: &ChatCompletionRequest, now_millis: u64, max_price_au: Option, explicit_min_ctx: Option, min_throughput: Option, ) -> RequestRequirements { + if is_decision_chat_request(request) { + let (_, input_tokens) = decision_input_token_bounds(request); + let output_tokens = decision_output_unit_upper_bound(request); + let min_ctx = explicit_min_ctx + .unwrap_or(0) + .max(u32::try_from(decision_sequence_token_limit(request)).unwrap_or(u32::MAX)); + return RequestRequirements { + current_rules_ver: state.receipt_config.rules_ver, + requires_transport_peer: !state.dev_session_shim, + requires_prefix_caching: false, + requires_json: true, + compatible_execution_modes: Some(state.compatible_execution_modes( + direct_chat_endpoint_family(request), + request.endpoint_request.as_ref(), + )), + min_ctx, + input_tokens, + output_tokens, + usage: ReceiptUsage::text(input_tokens, output_tokens), + min_throughput, + now_millis, + max_price_au, + heartbeat_ttl_millis: state.provider_heartbeat_ttl_millis, + ..RequestRequirements::default() + }; + } let prompt_text = chat_prompt_text(request); let input_tokens = rough_tokens(&prompt_text); let output_tokens = chat_output_headroom_tokens(request); @@ -27789,6 +29807,9 @@ fn request_requirements_for_chat( RequestRequirements { current_rules_ver: state.receipt_config.rules_ver, requires_transport_peer: !state.dev_session_shim, + requires_prefix_caching: endpoint_family_requires_prefix_caching( + direct_chat_endpoint_family(request), + ) && !is_needle_cache_exception(model), requires_tools: request .tools .as_ref() @@ -27802,7 +29823,11 @@ fn request_requirements_for_chat( request.endpoint_request.as_ref(), )), modality_load, - min_ctx: effective_context_floor(explicit_min_ctx, chat_context_input_tokens(request), output_tokens), + min_ctx: effective_context_floor( + explicit_min_ctx, + chat_context_input_tokens(request), + output_tokens, + ), input_tokens, output_tokens, usage, @@ -27953,13 +29978,101 @@ fn chat_context_input_tokens(request: &ChatCompletionRequest) -> u64 { tokens } +fn chat_requested_output_tokens(request: &ChatCompletionRequest) -> u32 { + request + .max_tokens + .or(request.max_completion_tokens) + .unwrap_or(DEFAULT_CHAT_OUTPUT_HEADROOM_TOKENS as u32) + .max(1) +} + fn chat_output_headroom_tokens(request: &ChatCompletionRequest) -> u64 { - u64::from( - request - .max_tokens - .unwrap_or(DEFAULT_CHAT_OUTPUT_HEADROOM_TOKENS as u32) - .max(1), + u64::from(chat_requested_output_tokens(request)) +} + +fn chat_context_ceiling( + state: &GatewayState, + model: &GatewayModel, + options: &GatewayRequestOptions, +) -> u32 { + state.refresh_provider_table_routes(model); + order_strict_preferred_routes( + state, + model, + options, + eligible_route_candidates( + model, + options.min_att_tier, + options.quant.as_deref(), + &state.receipt_config.rail, + ), ) + .into_iter() + .filter(|route| route_has_live_control_transport(state, route)) + .map(|route| state.served_ctx_for_route(model, Some(route))) + .max() + .unwrap_or_else(|| model_served_ctx(model)) + .max(1) +} + +fn route_has_live_control_transport(state: &GatewayState, route: &GatewayRouteCandidate) -> bool { + let now_millis = now_millis_u64(); + let key = route_key(route); + state + .provider_table + .lock_recover("provider table") + .entries(now_millis) + .into_iter() + .find(|entry| entry.key == key) + .is_some_and(|entry| { + matches!( + baseline_route_state(&entry, &state.baseline_route_requirements(now_millis)), + BaselineRouteState::Live + | BaselineRouteState::Saturated + | BaselineRouteState::AtCapacity + ) && entry + .heartbeat + .as_ref() + .and_then(|heartbeat| heartbeat.transport_peer.as_deref()) + .is_some_and(|peer| is_hex_len(peer, 64)) + }) +} + +/// Treat the client's output limit as an upper bound. A large fixed limit must +/// not make a growing conversation unroutable while its prompt still fits the +/// signed context window. +fn fit_chat_output_budget_to_context( + state: &GatewayState, + model: &GatewayModel, + request: &mut ChatCompletionRequest, + options: &GatewayRequestOptions, +) { + let input_tokens = chat_context_input_tokens(request); + let context_ceiling = u64::from(chat_context_ceiling(state, model, options)); + let Some(remaining) = context_ceiling.checked_sub(input_tokens) else { + return; + }; + if remaining == 0 { + return; + } + let requested = u64::from(chat_requested_output_tokens(request)); + if requested <= remaining { + return; + } + let fitted = u32::try_from(remaining).unwrap_or(u32::MAX).max(1); + match ( + request.max_tokens.is_some(), + request.max_completion_tokens.is_some(), + ) { + (true, true) => { + request.max_tokens = Some(request.max_tokens.unwrap_or(fitted).min(fitted)); + request.max_completion_tokens = + Some(request.max_completion_tokens.unwrap_or(fitted).min(fitted)); + } + (true, false) => request.max_tokens = Some(fitted), + (false, true) => request.max_completion_tokens = Some(fitted), + (false, false) => request.max_tokens = Some(fitted), + } } fn exact_conversation_floor_after_partials( @@ -28057,8 +30170,11 @@ fn request_requirements_for_image_generation( let (width, height) = parse_image_generation_size(request) .expect("validated image request has admin-signed dimensions"); let image_count = image_generation_count(request); - let reference = request.input_reference.as_deref() - .map(mayhem_proto::image_reference_metadata).transpose() + let reference = request + .input_reference + .as_deref() + .map(mayhem_proto::image_reference_metadata) + .transpose() .expect("validated image reference has bounded content"); RequestRequirements { current_rules_ver: state.receipt_config.rules_ver, @@ -28076,7 +30192,8 @@ fn request_requirements_for_image_generation( ModalityRequestLoad { item_count: image_count, max_item_bytes: reference.map_or(1, |image| image.bytes), - max_item_units: u64::from(width).saturating_mul(u64::from(height)) + max_item_units: u64::from(width) + .saturating_mul(u64::from(height)) .max(reference.map_or(0, |image| image.pixels)), }, )]), @@ -28649,6 +30766,10 @@ fn record_route_attempt_error( { record_capacity_mismatch_if_advertised(state, route); } + if err.safe_same_route_retry { + record_route_observation(state, route, observation_sample_from_error(elapsed)); + return; + } if !err.clean_refusal { record_route_failure_attempt(state, route, elapsed); } @@ -28850,17 +30971,17 @@ fn redispatch_request_with_partials( extra: BTreeMap::new(), }); } - if let Some(max_tokens) = request.max_tokens { - let delivered = partials - .iter() - .map(|partial| partial.output.usage.completion_tokens) - .sum::(); - request.max_tokens = Some( - max_tokens - .saturating_sub(u32::try_from(delivered).unwrap_or(u32::MAX)) - .max(1), - ); - } + let delivered = partials + .iter() + .map(|partial| partial.output.usage.completion_tokens) + .sum::(); + let delivered = u32::try_from(delivered).unwrap_or(u32::MAX); + request.max_tokens = request + .max_tokens + .map(|max_tokens| max_tokens.saturating_sub(delivered).max(1)); + request.max_completion_tokens = request + .max_completion_tokens + .map(|max_tokens| max_tokens.saturating_sub(delivered).max(1)); request } @@ -28964,12 +31085,7 @@ fn selector_route_exclusion_reason( requirements: &RequestRequirements, now_millis: u64, ) -> Option<&'static str> { - if !route_matches_selector_filters( - candidate, - min_att_tier, - quant, - &state.receipt_config.rail, - ) { + if !route_matches_selector_filters(candidate, min_att_tier, quant, &state.receipt_config.rail) { return Some("selector_filter"); } if state.route_provider_in_cooloff(candidate, now_millis) { @@ -29278,9 +31394,15 @@ fn responses_value_from_chat(response: Value) -> Result { .ok_or_else(|| ApiError::bad_gateway("chat provider returned no choices", Some("model")))?; let message = choice.get("message").cloned().unwrap_or_else(|| json!({})); let mut output = Vec::new(); - if let Some(text) = message.get("reasoning_content").and_then(Value::as_str).filter(|s| !s.is_empty()) { - output.push(json!({"id":make_id("rs"),"type":"reasoning","status":"completed", - "summary":[],"content":[{"type":"reasoning_text","text":text}]})); + if let Some(text) = message + .get("reasoning_content") + .and_then(Value::as_str) + .filter(|s| !s.is_empty()) + { + output.push( + json!({"id":make_id("rs"),"type":"reasoning","status":"completed", + "summary":[],"content":[{"type":"reasoning_text","text":text}]}), + ); } if let Some(text) = message.get("content").and_then(Value::as_str) { @@ -29369,6 +31491,167 @@ fn responses_value_from_chat(response: Value) -> Result { "mayhem": response.get("mayhem").cloned().unwrap_or_else(|| json!({})), })) } +fn decision_chat_request(request: &DecisionRequest) -> Result { + let contract_request = request + .endpoint_request + .clone() + .ok_or_else(|| ApiError::internal_message("normalized decision request is missing"))?; + let prompt = stable_json_value(&contract_request).to_string(); + Ok(ChatCompletionRequest { + model: request.model.clone(), + messages: vec![ChatMessage { + role: "user".to_owned(), + content: json!(prompt), + name: None, + extra: BTreeMap::new(), + }], + user: request.user.clone(), + metadata: BTreeMap::new(), + stream: false, + stream_options: None, + tools: None, + tool_choice: None, + parallel_tool_calls: None, + response_format: None, + temperature: None, + top_p: None, + top_k: None, + min_p: None, + repeat_penalty: None, + frequency_penalty: None, + presence_penalty: None, + seed: None, + stop: None, + max_tokens: Some(1), + max_completion_tokens: None, + reasoning_effort: None, + speciality_values: BTreeMap::new(), + effective_specialities: BTreeMap::new(), + preserve_reasoning_content: false, + endpoint_family: Some(mayhem_proto::ENDPOINT_MAYHEM_DECISIONS.to_owned()), + endpoint_request: Some(contract_request), + }) +} + +fn validated_decision_result(output: &ChatOutput) -> Result { + let content = output + .content + .as_deref() + .filter(|content| !content.trim().is_empty()) + .ok_or_else(|| { + ApiError::bad_gateway("decision provider returned no result", Some("model")) + })?; + let result: Value = serde_json::from_str(content).map_err(|err| { + ApiError::bad_gateway( + format!("decision provider returned invalid JSON: {err}"), + Some("model"), + ) + })?; + if !result.get("answers").is_some_and(Value::is_object) { + return Err(ApiError::bad_gateway( + "decision provider result is missing an answers object", + Some("model"), + )); + } + if !result.get("routing").is_some_and(Value::is_object) { + return Err(ApiError::bad_gateway( + "decision provider result is missing a routing object", + Some("model"), + )); + } + Ok(result) +} + +async fn build_decision( + state: &GatewayState, + request: DecisionRequest, + options: GatewayRequestOptions, +) -> Result { + let model = require_model(state, &request.model)?; + if model.mayhem.model_class != "decision" + || !model + .mayhem + .adapter + .endpoint_families + .iter() + .any(|contract| contract.family == mayhem_proto::ENDPOINT_MAYHEM_DECISIONS) + { + return Err(ApiError::bad_request( + "model does not support typed decisions", + Some("model"), + )); + } + let chat_request = decision_chat_request(&request)?; + let id = make_id("decision"); + let created = now_secs(); + let GatewaySessionRun { + model, + result: + GatewaySessionResult { + output, + backend, + direct_session, + provider_receipt, + token_ids: _, + quality, + }, + invocation, + metering_request, + metering_output, + } = run_chat_with_route_retry(state, &model, &chat_request, options).await?; + let result = validated_decision_result(&output)?; + let receipt = if state.dev_session_shim { + None + } else { + let receipt = state.meter_chat_session( + &model, + &metering_request, + &metering_output, + &invocation, + provider_receipt.as_ref(), + )?; + state + .maybe_run_canary_probe_after_session(&model, &invocation) + .await; + Some(receipt_summary(&receipt)) + }; + if let Some(job) = invocation.job.as_ref() { + job.persist_completed_if_active( + chat_job_result_for_request(&chat_request, &output), + Vec::new(), + receipt.clone(), + ) + .await?; + } + let mut response = json!({ + "id": id, + "object": "decision", + "created": created, + "model": model.id, + "answers": result.get("answers").cloned().unwrap_or_else(|| json!({})), + "routing": result.get("routing").cloned().unwrap_or_else(|| json!({})), + "usage": output.usage, + "mayhem": { + "backend": backend, + "direct_session": direct_session, + "billable": !state.dev_session_shim, + "dev_session": state.dev_session_shim, + "quality": quality.map(|quality| json!({ + "ttft_ms": quality.ttft_ms, + "tok_s": quality.tok_s, + })), + "receipt": receipt, + }, + }); + if let Some(shortlist) = result.get("shortlist") { + response["shortlist"] = shortlist.clone(); + } + if let Some(preprocessing) = result.get("preprocessing") { + response["preprocessing"] = preprocessing.clone(); + } + Ok(response) +} + async fn build_embedding( state: &GatewayState, request: EmbeddingRequest, @@ -29386,6 +31669,7 @@ async fn build_embedding( let id = make_id("embd"); let created = now_secs(); let GatewayEmbeddingRun { + model, result: GatewayEmbeddingResult { output, @@ -29469,6 +31753,7 @@ async fn build_image_generation( let id = make_id("img"); let created = now_secs(); let GatewayImageGenerationRun { + model, result: GatewayImageGenerationResult { output, @@ -29563,6 +31848,7 @@ async fn build_artifact_generation( )); } let GatewayArtifactGenerationRun { + model, result: GatewayArtifactGenerationResult { output, @@ -29698,6 +31984,7 @@ async fn build_audio_speech( } validate_audio_speech_request(&request)?; let GatewayAudioSpeechRun { + model, result: GatewayAudioSpeechResult { output, @@ -29958,6 +32245,7 @@ async fn build_audio_transcription( } validate_audio_transcription_response_request(&request)?; let GatewayAudioTranscriptionRun { + model, result: GatewayAudioTranscriptionResult { output, @@ -31344,6 +33632,18 @@ impl GatewayState { return; } } + CANARY_VERIFICATION_DECISION_FINGERPRINT => { + if model.mayhem.model_class != "decision" + || !model + .mayhem + .adapter + .endpoint_families + .iter() + .any(|contract| contract.family == mayhem_proto::ENDPOINT_MAYHEM_DECISIONS) + { + return; + } + } _ => return, } let route_key = canary_route_key(model, invocation); @@ -31379,6 +33679,10 @@ impl GatewayState { .run_video_av_fingerprint_probe_for_route(model, invocation, &config) .await .map(|probe| vec![probe]), + CANARY_VERIFICATION_DECISION_FINGERPRINT => self + .run_decision_fingerprint_probe_for_route(model, invocation, &config) + .await + .map(|probe| vec![probe]), _ => return, }; match probes { @@ -31723,8 +34027,13 @@ impl GatewayState { .run_chat(model, &request, &invocation) .await .map_err(|err| provider_session_api_error(&err))?; - let token_fingerprint = token_fingerprint(result.token_ids.iter().copied()).digest; - observed_tokens.insert(prompt.id.clone(), result.token_ids.clone()); + let token_ids = catalog_canary_result_units( + config, + route.as_ref().map(|route| route.artifact_root.as_str()), + &result, + ); + let token_fingerprint = token_fingerprint(token_ids.iter().copied()).digest; + observed_tokens.insert(prompt.id.clone(), token_ids.clone()); let receipt = self.meter_chat_session( model, &request, @@ -31738,8 +34047,8 @@ impl GatewayState { prompt_reports.push(json!({ "prompt_id": prompt.id, "request": request, - "token_count": result.token_ids.len(), - "token_ids": result.token_ids, + "token_count": token_ids.len(), + "token_ids": token_ids, "token_fingerprint": token_fingerprint, "session_id": invocation.session_id, "receipt_hash": receipt_hash, @@ -32017,8 +34326,12 @@ impl GatewayState { Some("model"), ) })?; - let observed_prefix = result - .token_ids + let token_ids = catalog_canary_result_units( + config, + Some(route.artifact_root.as_str()), + &result, + ); + let observed_prefix = token_ids .iter() .copied() .take(expected_prefix.len()) @@ -32026,7 +34339,7 @@ impl GatewayState { let observed_prompt_fingerprint = token_fingerprint(observed_prefix.iter().copied()).digest; let full_output_fingerprint = - token_fingerprint(result.token_ids.iter().copied()).digest; + token_fingerprint(token_ids.iter().copied()).digest; observed_prefixes.insert(prompt.id.clone(), observed_prefix); let receipt = self.meter_chat_session( model, @@ -32052,8 +34365,8 @@ impl GatewayState { "prompt_id": prompt.id, "request": request, "selected_specialities": invocation.spend_voucher.body.required_specialities, - "token_count": result.token_ids.len(), - "token_ids": result.token_ids, + "token_count": token_ids.len(), + "token_ids": token_ids, "stable_prefix_fingerprint": observed_prompt_fingerprint, "full_output_fingerprint": full_output_fingerprint, "session_id": invocation.session_id, @@ -32997,6 +35310,111 @@ impl GatewayState { )) } + async fn run_decision_fingerprint_probe_for_route( + &self, + model: &GatewayModel, + served_invocation: &GatewaySessionInvocation, + config: &GatewayCanaryModelConfig, + ) -> Result { + let expected_fingerprints = + canary_expected_decision_fingerprints(config, served_invocation).ok_or_else(|| { + ApiError::bad_gateway( + "no catalog canary decision fingerprints for served artifact", + Some("model"), + ) + })?; + let route = canary_served_route(model, served_invocation); + let mut prompt_reports = Vec::with_capacity(expected_fingerprints.len()); + let mut receipt_hashes = Vec::with_capacity(expected_fingerprints.len()); + let mut stored_receipts = Vec::with_capacity(expected_fingerprints.len()); + let mut observed_fingerprints = BTreeMap::new(); + + for prompt in &config.prompts { + if !expected_fingerprints.contains_key(&prompt.id) { + continue; + } + let request = canary_decision_request(model, prompt)?; + let chat_request = decision_chat_request(&request)?; + let invocation = self.prepare_chat_invocation_for_route( + model, + &chat_request, + route.as_ref(), + &canary_request_options(served_invocation), + )?; + let result = self + .session_backend + .run_chat(model, &chat_request, &invocation) + .await + .map_err(|err| provider_session_api_error(&err))?; + let decision = validated_decision_result(&result.output)?; + let fingerprint = stable_value_hash(&stable_json_value(&decision)); + observed_fingerprints.insert(prompt.id.clone(), fingerprint.clone()); + let receipt = self.meter_chat_session( + model, + &chat_request, + &result.output, + &invocation, + result.provider_receipt.as_ref(), + )?; + let receipt_hash = stable_value_hash(&json!(receipt)); + receipt_hashes.push(receipt_hash.clone()); + stored_receipts.push(receipt); + prompt_reports.push(json!({ + "prompt_id": prompt.id, + "request": request.endpoint_request, + "decision": decision, + "decision_fingerprint": fingerprint, + "session_id": invocation.session_id, + "receipt_hash": receipt_hash, + })); + } + + if observed_fingerprints.is_empty() { + return Err(ApiError::bad_gateway( + "no canary decision prompts matched expected fingerprints", + Some("model"), + )); + } + let spec = CanaryProbeSpec { + model: model.id.clone(), + canary_set: config.canary_set.clone(), + prompt_id: format!("aggregate:{}", expected_fingerprints.len()), + prompt: config + .prompts + .iter() + .filter(|prompt| expected_fingerprints.contains_key(&prompt.id)) + .map(|prompt| prompt.id.as_str()) + .collect::>() + .join(","), + seed: self.canary_policy.seed, + max_tokens: 1, + sampling: Default::default(), + }; + let evaluation = evaluate_catalog_canary_decision_fingerprint_probe( + &spec, + &expected_fingerprints, + &observed_fingerprints, + ); + let evidence = json!({ + "schema_version": 1, + "kind": "mayhem-automatic-decision-canary-probe-evidence", + "catalog_expected_decision_fingerprints": expected_fingerprints, + "observed_decision_fingerprints": observed_fingerprints, + "evaluation": evaluation, + "prompts": prompt_reports, + "receipt_hashes": receipt_hashes, + }); + Ok(self.content_canary_probe_event( + model, + served_invocation, + config, + evaluation, + evidence, + receipt_hashes, + stored_receipts, + )) + } + fn content_canary_probe_event( &self, model: &GatewayModel, @@ -33111,7 +35529,7 @@ impl GatewayState { format!( "route {} cannot verify Tier {} locally: {}", candidate.provider, - candidate.att_tier, + route_enclave_attestation_tier(candidate), readiness .reason .as_deref() @@ -33136,7 +35554,7 @@ impl GatewayState { manifest_hash: candidate.manifest_hash.clone(), binary_hash: candidate.binary_hash.clone(), launch_measurements: candidate.launch_measurements.clone(), - att_tier: candidate.att_tier, + att_tier: route_enclave_attestation_tier(candidate), caps: candidate.caps.clone(), }, policy, @@ -33224,7 +35642,8 @@ impl GatewayState { options: &GatewayRequestOptions, ) -> Result { let prompt_text = chat_prompt_text(request); - let failover = self.failover_thresholds_for_model(model, options, chat_context_input_tokens(request)); + let failover = + self.failover_thresholds_for_model(model, options, chat_context_input_tokens(request)); let session_id = session_id_for(&model.id, &prompt_text); let billing = options .billing @@ -33261,7 +35680,7 @@ impl GatewayState { self.ctx_bracket_terms_for_model_served_ctx(model, served_ctx, opened_at)?; let checkpoint_every = self.receipt_checkpoint_every_for_request(request); let voucher_body = SpendVoucherBody { - schema_version: SESSION_RECEIPT_SCHEMA_VERSION, + schema_version: SPEND_VOUCHER_SCHEMA_VERSION, session_id: session_id.clone(), billing_id: billing.billing_id, billing_attempt: billing.billing_attempt, @@ -33400,7 +35819,7 @@ impl GatewayState { let (ctx_bracket, ctx_bracket_table_ver) = self.ctx_bracket_terms_for_model_served_ctx(model, served_ctx, opened_at)?; let voucher_body = SpendVoucherBody { - schema_version: SESSION_RECEIPT_SCHEMA_VERSION, + schema_version: SPEND_VOUCHER_SCHEMA_VERSION, session_id: session_id.clone(), billing_id: billing.billing_id, billing_attempt: billing.billing_attempt, @@ -33511,7 +35930,7 @@ impl GatewayState { let (ctx_bracket, ctx_bracket_table_ver) = self.ctx_bracket_terms_for_model_served_ctx(model, served_ctx, opened_at)?; let voucher_body = SpendVoucherBody { - schema_version: SESSION_RECEIPT_SCHEMA_VERSION, + schema_version: SPEND_VOUCHER_SCHEMA_VERSION, session_id: session_id.clone(), billing_id: billing.billing_id, billing_attempt: billing.billing_attempt, @@ -33622,7 +36041,7 @@ impl GatewayState { let (ctx_bracket, ctx_bracket_table_ver) = self.ctx_bracket_terms_for_model_served_ctx(model, served_ctx, opened_at)?; let voucher_body = SpendVoucherBody { - schema_version: SESSION_RECEIPT_SCHEMA_VERSION, + schema_version: SPEND_VOUCHER_SCHEMA_VERSION, session_id: session_id.clone(), billing_id: billing.billing_id, billing_attempt: billing.billing_attempt, @@ -33733,7 +36152,7 @@ impl GatewayState { let (ctx_bracket, ctx_bracket_table_ver) = self.ctx_bracket_terms_for_model_served_ctx(model, served_ctx, opened_at)?; let voucher_body = SpendVoucherBody { - schema_version: SESSION_RECEIPT_SCHEMA_VERSION, + schema_version: SPEND_VOUCHER_SCHEMA_VERSION, session_id: session_id.clone(), billing_id: billing.billing_id, billing_attempt: billing.billing_attempt, @@ -33848,7 +36267,7 @@ impl GatewayState { let (ctx_bracket, ctx_bracket_table_ver) = self.ctx_bracket_terms_for_model_served_ctx(model, served_ctx, opened_at)?; let voucher_body = SpendVoucherBody { - schema_version: SESSION_RECEIPT_SCHEMA_VERSION, + schema_version: SPEND_VOUCHER_SCHEMA_VERSION, session_id: session_id.clone(), billing_id: billing.billing_id, billing_attempt: billing.billing_attempt, @@ -33935,6 +36354,14 @@ impl GatewayState { model: &GatewayModel, options: &GatewayRequestOptions, ) -> Option { + if model.mayhem.model_class != DEFAULT_MODEL_CLASS { + return options + .failover_overrides + .min_tok_s + .or(model.mayhem.failover.min_tok_s) + .or(self.failover_policy.min_tok_s) + .filter(|value| value.is_finite() && *value > 0.0); + } self.throughput_floor_for_model(model, options, DEFAULT_LLM_GENERATION_FLOOR_TOK_S) } @@ -33964,7 +36391,6 @@ impl GatewayState { invocation: &GatewaySessionInvocation, provider_receipt: Option<&ProviderSignedReceipt>, ) -> Result { - let prompt_text = chat_prompt_text(request); if !self.receipt_config.cosign_enabled { self.pause_session(PausedSession { session_id: invocation.session_id.clone(), @@ -34009,7 +36435,7 @@ impl GatewayState { final_receipt: true, usage: usage.clone(), au_owed_cum, - prompt_hash: blake3_hex(prompt_text.as_bytes()), + prompt_hash: direct_chat_prompt_hash(request), }, )? } else { @@ -34060,6 +36486,8 @@ impl GatewayState { locked_per_req_au: invocation.spend_voucher.body.locked_per_req_au, locked_min_session_au: invocation.spend_voucher.body.locked_min_session_au, served_ctx: invocation.served_ctx, + compute_ms: 1, + capacity_slots: 1, ctx_bracket: invocation.ctx_bracket.clone(), ctx_bracket_table_ver: invocation.ctx_bracket_table_ver, rules_ver: invocation.rules_ver, @@ -34068,7 +36496,7 @@ impl GatewayState { usage, usage_attribution: BTreeMap::new(), au_owed_cum, - prompt_hash: blake3_hex(prompt_text.as_bytes()), + prompt_hash: direct_chat_prompt_hash(request), ts: now_millis_u64(), }; let receipt_payload = receipt_signing_bytes(&body).map_err(ApiError::internal)?; @@ -34175,6 +36603,8 @@ impl GatewayState { locked_per_req_au: invocation.spend_voucher.body.locked_per_req_au, locked_min_session_au: invocation.spend_voucher.body.locked_min_session_au, served_ctx: invocation.served_ctx, + compute_ms: 1, + capacity_slots: 1, ctx_bracket: invocation.ctx_bracket.clone(), ctx_bracket_table_ver: invocation.ctx_bracket_table_ver, rules_ver: invocation.rules_ver, @@ -34290,6 +36720,8 @@ impl GatewayState { locked_per_req_au: invocation.spend_voucher.body.locked_per_req_au, locked_min_session_au: invocation.spend_voucher.body.locked_min_session_au, served_ctx: invocation.served_ctx, + compute_ms: 1, + capacity_slots: 1, ctx_bracket: invocation.ctx_bracket.clone(), ctx_bracket_table_ver: invocation.ctx_bracket_table_ver, rules_ver: invocation.rules_ver, @@ -34412,6 +36844,8 @@ impl GatewayState { locked_per_req_au: invocation.spend_voucher.body.locked_per_req_au, locked_min_session_au: invocation.spend_voucher.body.locked_min_session_au, served_ctx: invocation.served_ctx, + compute_ms: 1, + capacity_slots: 1, ctx_bracket: invocation.ctx_bracket.clone(), ctx_bracket_table_ver: invocation.ctx_bracket_table_ver, rules_ver: invocation.rules_ver, @@ -34497,7 +36931,7 @@ impl GatewayState { final_receipt: false, au_owed_cum, usage, - prompt_hash: blake3_hex(chat_prompt_text(request).as_bytes()), + prompt_hash: direct_chat_prompt_hash(request), }, )?; let receipt_ack = ReceiptAck { @@ -34595,6 +37029,8 @@ impl GatewayState { locked_per_req_au: invocation.spend_voucher.body.locked_per_req_au, locked_min_session_au: invocation.spend_voucher.body.locked_min_session_au, served_ctx: invocation.served_ctx, + compute_ms: 1, + capacity_slots: 1, ctx_bracket: invocation.ctx_bracket.clone(), ctx_bracket_table_ver: invocation.ctx_bracket_table_ver, rules_ver: invocation.rules_ver, @@ -34710,6 +37146,8 @@ impl GatewayState { locked_per_req_au: invocation.spend_voucher.body.locked_per_req_au, locked_min_session_au: invocation.spend_voucher.body.locked_min_session_au, served_ctx: invocation.served_ctx, + compute_ms: 1, + capacity_slots: 1, ctx_bracket: invocation.ctx_bracket.clone(), ctx_bracket_table_ver: invocation.ctx_bracket_table_ver, rules_ver: invocation.rules_ver, @@ -34825,6 +37263,22 @@ fn canary_served_route( .cloned() } +fn catalog_canary_result_units( + config: &GatewayCanaryModelConfig, + artifact_root: Option<&str>, + result: &GatewaySessionResult, +) -> Vec { + if artifact_root.is_some_and(|root| config.openai_compatible_artifact_roots.contains(root)) { + let reconstructed = mayhem_proto::openai_compatible_canary_output( + &result.output.reasoning_content, + result.output.content.as_deref().unwrap_or_default(), + ); + mayhem_proto::openai_compatible_canary_units(&reconstructed) + } else { + result.token_ids.clone() + } +} + fn canary_request_options(invocation: &GatewaySessionInvocation) -> GatewayRequestOptions { GatewayRequestOptions { execution_mode_expectation: Some(match invocation.expected_execution_mode.as_ref() { @@ -35005,6 +37459,28 @@ fn canary_expected_video_fingerprints( }) } +fn canary_expected_decision_fingerprints( + config: &GatewayCanaryModelConfig, + invocation: &GatewaySessionInvocation, +) -> Option> { + invocation + .attestation + .as_ref() + .and_then(|attestation| { + config + .decision_fingerprints_by_artifact_root + .get(&attestation.contract.artifact_root) + .cloned() + }) + .or_else(|| { + invocation + .expected_execution_mode + .is_none() + .then(|| config.default_decision_fingerprints.clone()) + .flatten() + }) +} + #[derive(Clone, Debug)] struct ContextNeedleSpec { answer: String, @@ -35328,6 +37804,41 @@ fn canary_embedding_request( Ok(request) } +fn canary_decision_request( + model: &GatewayModel, + prompt: &GatewayCanaryPrompt, +) -> Result { + let mut raw = Map::from_iter(prompt.endpoint_attributes.clone()); + raw.insert("model".to_owned(), json!(model.id)); + let raw = Value::Object(raw); + let contract = model + .mayhem + .adapter + .endpoint_families + .iter() + .find(|contract| contract.family == mayhem_proto::ENDPOINT_MAYHEM_DECISIONS) + .ok_or_else(|| { + ApiError::bad_gateway("Canary endpoint contract is unavailable.", Some("model")) + })?; + let normalized = normalize_endpoint_request_for_provider(contract, &raw).map_err(|_| { + ApiError::bad_gateway( + "Canary request does not satisfy its signed endpoint contract.", + Some("model"), + ) + })?; + let mut request = serde_json::from_value::( + normalized.normalized_request.clone(), + ) + .map_err(|error| { + ApiError::bad_gateway( + format!("signed decision canary request cannot be decoded: {error}"), + Some("model"), + ) + })?; + request.endpoint_request = Some(normalized.normalized_request); + Ok(request) +} + fn canary_audio_speech_request( model: &GatewayModel, prompt: &GatewayCanaryPrompt, @@ -35959,7 +38470,9 @@ fn chat_response_value( "content": output.content.clone().unwrap_or_default(), }) }; - if !output.reasoning_content.is_empty() { message["reasoning_content"] = json!(output.reasoning_content); } + if !output.reasoning_content.is_empty() { + message["reasoning_content"] = json!(output.reasoning_content); + } json!({ "id": id, "object": "chat.completion", @@ -36009,7 +38522,16 @@ fn chat_stream_chunks( None, )]; for part in stream_parts(&output.reasoning_content) { - if !part.is_empty() { chunks.push(chat_chunk(id, created, model, json!({"reasoning_content":part}), None, None)); } + if !part.is_empty() { + chunks.push(chat_chunk( + id, + created, + model, + json!({"reasoning_content":part}), + None, + None, + )); + } } if !output.tool_calls.is_empty() { chunks.push(chat_chunk( @@ -37131,6 +39653,62 @@ fn wants_json(value: &Option) -> bool { ) } +fn validate_requested_response_schema(format: Option<&Value>) -> Result<(), ApiError> { + let Some(format) = format else { + return Ok(()); + }; + if format.get("type").and_then(Value::as_str) != Some("json_schema") { + return Ok(()); + } + let schema = format + .get("json_schema") + .and_then(|wrapper| wrapper.get("schema")) + .or_else(|| format.get("schema")) + .ok_or_else(|| { + ApiError::bad_request( + "response_format.json_schema.schema is required", + Some("response_format"), + ) + .with_public_error( + "unsupported_response_schema", + "request_validation", + false, + ) + })?; + structured_schema::prepare(schema).map_err(|error| { + ApiError::bad_request( + format!("Unsupported response JSON schema: {error}"), + Some("response_format"), + ) + .with_public_error("unsupported_response_schema", "request_validation", false) + })?; + Ok(()) +} + +fn validate_structured_chat_output( + request: &ChatCompletionRequest, + output: &ChatOutput, +) -> Result<(), GatewaySessionError> { + if !output.tool_calls.is_empty() { + return Ok(()); + } + let Some(format) = request.response_format.as_ref() else { + return Ok(()); + }; + if format.get("type").and_then(Value::as_str) != Some("json_schema") { + return Ok(()); + } + let schema = format + .get("json_schema") + .and_then(|wrapper| wrapper.get("schema")) + .or_else(|| format.get("schema")) + .ok_or_else(|| GatewaySessionError::buyer_local("request_invalid: missing JSON schema"))?; + let content = output.content.as_deref().unwrap_or_default(); + structured_schema::validate_output(schema, content).map_err(|error| { + GatewaySessionError::request_scoped(format!("model_output_invalid: {error}")) + }) +} + fn last_tool_result(messages: &[ChatMessage]) -> Option { messages .iter() @@ -37261,11 +39839,20 @@ fn validate_image_generation_request( mayhem_proto::image_reference_metadata(reference) .map_err(|message| ApiError::bad_request(message, Some("input_reference")))?; if request.strength.is_none() { - return Err(ApiError::bad_request("input_reference requires strength", Some("strength"))); + return Err(ApiError::bad_request( + "input_reference requires strength", + Some("strength"), + )); } } - if request.strength.is_some_and(|value| !value.is_finite() || !(0.0..=1.0).contains(&value)) { - return Err(ApiError::bad_request("strength must be between 0 and 1", Some("strength"))); + if request + .strength + .is_some_and(|value| !value.is_finite() || !(0.0..=1.0).contains(&value)) + { + return Err(ApiError::bad_request( + "strength must be between 0 and 1", + Some("strength"), + )); } if request.prompt.trim().is_empty() { return Err(ApiError::bad_request( @@ -37909,6 +40496,31 @@ fn estimate_max_spend_au( billing: &GatewayBillingContext, protocol_prompt_tokens: Option, ) -> MoneyAu { + if is_decision_chat_request(request) { + let (_, max_input_tokens) = decision_input_token_bounds(request); + let max_output_tokens = decision_output_unit_upper_bound(request); + let usage = if billing.prior_au_owed_cum == 0 { + ReceiptUsage::text(max_input_tokens, max_output_tokens) + } else { + billing + .prior_usage + .saturating_add(&ReceiptUsage::from_units([( + USAGE_OUTPUT_TOKEN, + max_output_tokens, + )])) + }; + return logical_cumulative_priced_usage_au( + &price.rate_map, + price.per_req_au, + price.min_session_au, + &billing.prior_usage, + billing.prior_au_owed_cum, + &usage, + ) + .and_then(|cumulative| cumulative.checked_sub(billing.prior_au_owed_cum)) + .unwrap_or(MoneyAu::MAX) + .max(1_000); + } let served_ctx = u64::from(served_ctx).max(1); let max_input_tokens = protocol_prompt_tokens.unwrap_or(served_ctx).min(served_ctx); let max_output_tokens = request @@ -37942,8 +40554,20 @@ fn estimate_max_spend_au( .max(1_000) } +fn embedding_input_token_upper_bound(inputs: &[String]) -> u64 { + // UTF-8 bytes bound byte-fallback tokens; the allowance covers model-added + // special tokens. Share this bound between voucher and receipt validation. + inputs.iter().fold(0_u64, |total, input| { + total.saturating_add( + u64::try_from(input.len()) + .unwrap_or(u64::MAX) + .saturating_add(EMBEDDING_SPECIAL_TOKEN_ALLOWANCE_PER_INPUT), + ) + }) +} + fn estimate_embedding_max_spend_au(price: &PriceRefAu, inputs: &[String]) -> MoneyAu { - let usage = ReceiptUsage::text(embedding_input_token_count(inputs), 0); + let usage = ReceiptUsage::text(embedding_input_token_upper_bound(inputs), 0); calculate_au_owed(price, &usage).max(1_000) } @@ -38212,6 +40836,129 @@ mod tests { AttestationSigner, CTX_BRACKET_TABLE_VERSION, }; + #[test] + fn schema_preflight_is_request_scoped_for_chat_and_responses() { + let valid = json!({"type":"json_schema","json_schema":{"schema":{ + "type":"object","properties":{"items":{"type":"array","uniqueItems":true, + "items":{"type":"string"}}} + }}}); + validate_requested_response_schema(Some(&valid)).unwrap(); + let responses_format = + json!({"type":"json_schema","schema":valid["json_schema"]["schema"]}); + validate_requested_response_schema(Some(&responses_format)).unwrap(); + + let unsupported = json!({"type":"json_schema","json_schema":{"schema":{ + "type":"array","unknownConstraint":true + }}}); + let error = validate_requested_response_schema(Some(&unsupported)).unwrap_err(); + assert_eq!(error.status, StatusCode::BAD_REQUEST); + assert_eq!(public_error_code(&error), "unsupported_response_schema"); + assert!(!public_error_retryable(&error)); + } + + #[test] + fn buyer_checks_original_schema_before_accepting_model_output() { + let mut request = test_chat_request("test-model"); + request.response_format = Some(json!({"type":"json_schema","json_schema":{"schema":{ + "type":"object","required":["evidenceIds"],"properties":{ + "evidenceIds":{"type":"array","uniqueItems":true,"minItems":2, + "items":{"type":"string"}} + } + }}})); + let mut output = ChatOutput { + reasoning_content: String::new(), + content: Some(r#"{"evidenceIds":["E1","E1"]}"#.to_owned()), + tool_calls: Vec::new(), + artifacts: Vec::new(), + finish_reason: "stop".to_owned(), + usage: Usage { + prompt_tokens: 1, + completion_tokens: 1, + total_tokens: 2, + }, + }; + let error = validate_structured_chat_output(&request, &output).unwrap_err(); + let api_error = request_scoped_api_error(&error).expect("request-scoped output error"); + assert_eq!(api_error.status, StatusCode::BAD_GATEWAY); + assert_eq!( + public_error_code(&api_error), + "provider_model_output_invalid" + ); + output.content = Some(r#"{"evidenceIds":["E1","E2"]}"#.to_owned()); + validate_structured_chat_output(&request, &output).unwrap(); + } + + #[tokio::test] + async fn invalid_schema_returns_400_without_starting_a_gateway_job() { + use tower::ServiceExt; + + let mut model = GatewayState::fixture() + .models_snapshot() + .first() + .cloned() + .unwrap(); + model.mayhem.adapter.endpoint_families.push( + mayhem_proto::endpoint_family_contract_template( + mayhem_proto::ENDPOINT_OPENAI_RESPONSES, + ) + .unwrap(), + ); + let model_id = model.id.clone(); + let state = GatewayState::from_models(vec![model]).with_dev_session_shim(); + let app = openai_router(state); + for stream in [false, true] { + let schema = json!({"type":"array","unknownConstraint":true}); + let cases = [ + ( + "/v1/chat/completions", + json!({ + "model":model_id, + "messages":[{"role":"user","content":"Return JSON"}], + "stream":stream, + "response_format":{"type":"json_schema","json_schema":{"name":"bad","schema":schema}} + }), + ), + ( + "/v1/responses", + json!({ + "model":model_id, + "input":"Return JSON", + "stream":stream, + "text":{"format":{"type":"json_schema","name":"bad","schema":schema}} + }), + ), + ]; + for (path, body) in cases { + let response = app + .clone() + .oneshot( + axum::http::Request::builder() + .method("POST") + .uri(path) + .header(header::CONTENT_TYPE, "application/json") + .body(Body::from(body.to_string())) + .unwrap(), + ) + .await + .unwrap(); + assert_eq!( + response.status(), + StatusCode::BAD_REQUEST, + "{path} stream={stream}" + ); + assert!(response.headers().get("x-mayhem-job-id").is_none()); + let body = axum::body::to_bytes(response.into_body(), 1024 * 1024) + .await + .unwrap(); + let body: Value = serde_json::from_slice(&body).unwrap(); + assert_eq!( + body["error"]["code"], "unsupported_response_schema", + "{path} stream={stream}: {body}" + ); + } + } + } + fn public_error_code(error: &ApiError) -> String { error .public_error_value() @@ -38362,8 +41109,7 @@ mod tests { let error = route_attempts_failed_error( 1, Some( - "provider rejected session abc123 with BALANCE: spend reservation did not complete" - .to_owned(), + "provider rejected session abc123 [reservation_relay_phase=admin_ack] with BALANCE: spend reservation did not complete".to_owned(), ), "pre_spend", ); @@ -38376,6 +41122,10 @@ mod tests { assert!(!message.contains("provider rejected")); assert_eq!(value["error"]["safe_detail"]["attempts"], 1); assert_eq!(value["error"]["safe_detail"]["phase"], "pre_spend"); + assert_eq!( + value["error"]["safe_detail"]["reservation_relay_phase"], + "admin_ack" + ); } #[test] @@ -39349,17 +42099,25 @@ mod tests { fn opencode_cache_hints_preserve_inference_contract_and_reject_storage() { let contract = mayhem_proto::endpoint_family_contract_template( mayhem_proto::ENDPOINT_OPENAI_CHAT_COMPLETIONS, - ).unwrap(); + ) + .unwrap(); let raw = json!({"model":"test/model", "messages":[{"role":"user","content":"hello"}]}); let expected = normalize_endpoint_request_for_provider(&contract, &raw).unwrap(); for cache_key in [json!("session-a"), json!("session-b"), Value::Null] { let mut with_hints = raw.clone(); with_hints["store"] = json!(false); with_hints["prompt_cache_key"] = cache_key; - assert_eq!(normalize_endpoint_request_for_provider(&contract, &with_hints).unwrap(), expected); + assert_eq!( + normalize_endpoint_request_for_provider(&contract, &with_hints).unwrap(), + expected + ); } - for (key, value) in [("store",json!(true)), ("store",json!("false")), - ("prompt_cache_key",json!({"arbitrary":"object"})), ("unknown_unsigned_field",json!(true))] { + for (key, value) in [ + ("store", json!(true)), + ("store", json!("false")), + ("prompt_cache_key", json!({"arbitrary":"object"})), + ("unknown_unsigned_field", json!(true)), + ] { let mut invalid = raw.clone(); invalid[key] = value; assert!(normalize_endpoint_request_for_provider(&contract, &invalid).is_err()); @@ -39784,9 +42542,45 @@ mod tests { } #[test] - fn generation_throughput_uses_token_intervals_and_ignores_one_token_turns() { + fn generation_throughput_uses_subsecond_samples_only_when_output_is_substantial() { assert_eq!(generated_tokens_per_second(1, 100, 10_000), None); + assert_eq!(generated_tokens_per_second(5, 100, 10_000), None); + assert_eq!(generated_tokens_per_second(6, 100, 1_099), None); assert_eq!(generated_tokens_per_second(6, 100, 1_100), Some(5.0)); + assert_eq!(generated_tokens_per_second(32, 100, 600), Some(62.0)); + } + + #[test] + fn admission_attempt_budget_preserves_time_for_alternate_routes() { + let recovery = RouteAdmissionRecovery::new(RouteWaitDeadline::new(12_000), 4); + let budget = recovery + .admission_attempt_budget(2) + .expect("alternate routes require a bounded admission attempt"); + assert!(budget <= Duration::from_secs(6)); + assert!(budget > Duration::from_secs(5)); + + let failover = + GatewayFailoverInvocation::default().with_admission_attempt_budget(Some(budget)); + assert!(failover.open_timeout() <= Duration::from_secs(3)); + assert_eq!( + failover.session_accept_timeout(), + Some(Duration::from_millis(DEFAULT_OPEN_TIMEOUT_MILLIS)) + ); + assert!(failover.session_accept_timeout().unwrap() > budget); + } + + #[test] + fn sole_route_keeps_its_configured_admission_timeouts() { + let recovery = RouteAdmissionRecovery::new(RouteWaitDeadline::new(10_000), 4); + let budget = recovery.admission_attempt_budget(1); + assert_eq!(budget, None); + + let failover = GatewayFailoverInvocation::default().with_admission_attempt_budget(budget); + assert_eq!( + failover.open_timeout(), + Duration::from_millis(DEFAULT_OPEN_TIMEOUT_MILLIS) + ); + assert_eq!(failover.session_accept_timeout(), None); } #[test] @@ -41967,13 +44761,23 @@ mod tests { let model = test_model(); let request = test_chat_request(&model.id); let invocation = test_invocation(); - let receipt = test_provider_receipt_with_finality(&model, &request, &test_chat_output(), &invocation, 18, false); + let receipt = test_provider_receipt_with_finality( + &model, + &request, + &test_chat_output(), + &invocation, + 18, + false, + ); let ack = receipt_ack_for_body(&invocation.receipt_user_seed, &receipt.body).unwrap(); let binding = json!(receipt.body); let proof = failure_recovery::test_closed_proof(&receipt, &ack); - assert!(failure_recovery::verify_closed(&binding, &proof).unwrap().is_some()); + assert!(failure_recovery::verify_closed(&binding, &proof) + .unwrap() + .is_some()); for (pointer, wrong) in [ - ("/close/confirmed", json!(false)), ("/head/confirmed", json!(false)), + ("/close/confirmed", json!(false)), + ("/head/confirmed", json!(false)), ("/reservation/value/status", json!("active")), ("/close/value/session_id", json!("ef".repeat(32))), ("/close/value/retained_au", json!("0")), @@ -41983,10 +44787,112 @@ mod tests { ] { let mut invalid = proof.clone(); *invalid.pointer_mut(pointer).unwrap() = wrong; - assert!(failure_recovery::verify_closed(&binding, &invalid).is_err(), "accepted {pointer}"); + assert!( + failure_recovery::verify_closed(&binding, &invalid).is_err(), + "accepted {pointer}" + ); } } + #[tokio::test] + async fn canonical_reservation_sweep_expires_orphaned_hold_without_local_job() { + let seed = test_user_seed(); + let state = GatewayState::fixture().with_receipt_user_seed(seed); + let invocation = test_invocation(); + let mut session = serde_json::to_value(&invocation.spend_voucher.body).unwrap(); + session["type"] = json!("targeted_spend_session"); + let user = verifying_key_hex(&seed); + let rail = invocation.spend_voucher.body.rail.clone(); + let reservation_id = invocation.spend_voucher.body.reservation_id.clone(); + let billing_id = invocation.spend_voucher.body.billing_id.clone(); + let billing_attempt = invocation.spend_voucher.body.billing_attempt; + let prefix = format!("hold/targeted-session/{rail}/{user}/"); + let session_key = format!("{prefix}{}", invocation.session_id); + let mut reservation = session.clone(); + reservation["status"] = json!("active"); + let submitted = Arc::new(Mutex::new(Vec::::new())); + let submitted_handler = submitted.clone(); + let app = axum::Router::new() + .route( + "/v1/state", + axum::routing::get( + move |axum::extract::Query(query): axum::extract::Query< + BTreeMap, + >| { + let session = session.clone(); + let reservation = reservation.clone(); + let prefix = prefix.clone(); + let session_key = session_key.clone(); + let reservation_id = reservation_id.clone(); + let billing_id = billing_id.clone(); + async move { + assert_eq!(query.get("confirmed").map(String::as_str), Some("true")); + let response = if query.get("key").map(String::as_str) + == Some("epoch/apply/state") + { + json!({"key": "epoch/apply/state", "confirmed": true, + "signed_length": 123, "value": {"updated_epoch": 37}}) + } else if query.get("prefix").map(String::as_str) + == Some(prefix.as_str()) + { + assert_eq!(query.get("signed_length").map(String::as_str), Some("123")); + json!({"prefix": prefix, "confirmed": true, "signed_length": 123, + "values": [{"key": session_key, "value": session}], + "truncated": false}) + } else if query.get("key").map(String::as_str) + == Some(format!("receipt/reservation/{reservation_id}").as_str()) + { + json!({"key": format!("receipt/reservation/{reservation_id}"), + "confirmed": true, "signed_length": 123, "value": reservation}) + } else if query.get("key").map(String::as_str) + == Some(format!("receipt/head/{billing_id}/{billing_attempt}").as_str()) + { + json!({"key": format!("receipt/head/{billing_id}/{billing_attempt}"), + "confirmed": true, "signed_length": 123, "value": null}) + } else { + panic!("unexpected canonical state query: {query:?}"); + }; + axum::Json(response) + } + }, + ), + ) + .route( + "/v1/contract/feature", + axum::routing::post(move |axum::Json(body): axum::Json| { + let submitted = submitted_handler.clone(); + async move { + submitted + .lock_recover("submitted reservation expiries") + .push(body); + axum::Json(json!({"ok": true})) + } + }), + ); + let listener = TcpListener::bind((Ipv4Addr::LOCALHOST, 0)).await.unwrap(); + let address = listener.local_addr().unwrap(); + let server = tokio::spawn(async move { axum::serve(listener, app).await.unwrap() }); + let rpc = PeerRpcClient::new(format!("http://{address}/v1")).unwrap(); + + let pass = failure_recovery::sweep_ledger_reservations_once(&state, &rpc, 32) + .await + .unwrap(); + assert_eq!(pass.discovered, 1); + assert_eq!(pass.eligible, 1); + assert_eq!(pass.submitted, 1); + let submitted = submitted.lock_recover("submitted reservation expiries"); + assert_eq!(submitted.len(), 1); + assert_eq!(submitted[0]["value"]["op"], "expire_usage_reservation"); + assert_eq!(submitted[0]["value"]["reason"], "gateway_ledger_sweep"); + assert_eq!(submitted[0]["value"]["actor"], user); + assert_eq!(submitted[0]["value"]["actor_role"], "user"); + assert_eq!( + submitted[0]["value"]["actor_sig"].as_str().map(str::len), + Some(128) + ); + server.abort(); + } + #[tokio::test] async fn failed_generation_wire_handoff_survives_lost_reply_and_restart() { use futures_util::{SinkExt, StreamExt}; @@ -41995,12 +44901,22 @@ mod tests { let root = tempfile::tempdir().unwrap(); let dir = root.path().join("jobs"); let seed = test_user_seed(); - let state = GatewayState::fixture().with_receipt_user_seed(seed) - .with_job_store_dir(dir.clone()).unwrap(); + let state = GatewayState::fixture() + .with_receipt_user_seed(seed) + .with_job_store_dir(dir.clone()) + .unwrap(); let model = test_model(); - let job = match prepare_gateway_job(&state, &HeaderMap::new(), - mayhem_proto::ENDPOINT_OPENAI_CHAT_COMPLETIONS, &model.id, - &json!({"model": model.id, "messages": []}), &None).await.unwrap() { + let job = match prepare_gateway_job( + &state, + &HeaderMap::new(), + mayhem_proto::ENDPOINT_OPENAI_CHAT_COMPLETIONS, + &model.id, + &json!({"model": model.id, "messages": []}), + &None, + ) + .await + .unwrap() + { PreparedGatewayJob::Started(job) => job, _ => panic!("fresh job expected"), }; @@ -42012,8 +44928,22 @@ mod tests { invocation.receipt_recorder.settlement_publisher = Arc::new(Some(publisher.clone())); let request = test_chat_request(&model.id); let output = test_chat_output(); - let checkpoint = test_provider_receipt_with_finality(&model, &request, &output, &invocation, 18, false); - let terminal = test_provider_receipt_with_finality(&model, &request, &output, &invocation, 19, true); + let checkpoint = test_provider_receipt_with_finality( + &model, + &request, + &output, + &invocation, + 18, + false, + ); + let terminal = test_provider_receipt_with_finality( + &model, + &request, + &output, + &invocation, + 19, + true, + ); let ack = receipt_ack_for_body(&seed, &terminal.body).unwrap(); let feature = test_receipt_settlement_feature(&terminal, &ack); let mut wire = json!(terminal.body); @@ -42029,40 +44959,123 @@ mod tests { let server = tokio::spawn(async move { let (stream, _) = listener.accept().await.unwrap(); let mut socket = tokio_tungstenite::accept_async(stream).await.unwrap(); - let auth: Value = serde_json::from_str(socket.next().await.unwrap().unwrap().to_text().unwrap()).unwrap(); - socket.send(Message::Text(json!({"id": auth["id"], "type": "auth_ok"}).to_string().into())).await.unwrap(); - let sent: Value = serde_json::from_str(socket.next().await.unwrap().unwrap().to_text().unwrap()).unwrap(); + let auth: Value = + serde_json::from_str(socket.next().await.unwrap().unwrap().to_text().unwrap()) + .unwrap(); + socket + .send(Message::Text( + json!({"id": auth["id"], "type": "auth_ok"}) + .to_string() + .into(), + )) + .await + .unwrap(); + let sent: Value = + serde_json::from_str(socket.next().await.unwrap().unwrap().to_text().unwrap()) + .unwrap(); assert_eq!(sent["type"], "session_send"); assert_eq!(sent["frame"]["t"], "s.receipt_ack"); assert_eq!(sent["frame"]["user_sig"], expected_ack.user_sig); assert_eq!(sent["frame"]["seq"], 19); - socket.send(Message::Text(json!({"id": sent["id"], "type": "session_sent"}).to_string().into())).await.unwrap(); - let frame = if lost_handoff { json!({"t": "s.close", "session_id": session}) } - else { json!({"t": "s.receipt_settlement", "session_id": session, "seq": 19, "feature": sent_feature}) }; - socket.send(Message::Text(json!({"type": "session_frame", "remote": remote, - "session_id": session, "frame": frame}).to_string().into())).await.unwrap(); + socket + .send(Message::Text( + json!({"id": sent["id"], "type": "session_sent"}) + .to_string() + .into(), + )) + .await + .unwrap(); + let frame = if lost_handoff { + json!({"t": "s.close", "session_id": session}) + } else { + json!({"t": "s.receipt_settlement", "session_id": session, "seq": 19, "feature": sent_feature}) + }; + socket + .send(Message::Text( + json!({"type": "session_frame", "remote": remote, + "session_id": session, "frame": frame}) + .to_string() + .into(), + )) + .await + .unwrap(); }); - let mut bridge = ScBridgeClient::connect(ScBridgeConfig::new(format!("ws://{address}"), "test-token").unwrap()).await.unwrap(); - let result = settle_failed_direct_session_frame(&mut bridge, &invocation, &model, - &terminal.enclave_pubkey, &failure, Some(&checkpoint), blake3_hex(chat_prompt_text(&request).as_bytes())).await; + let mut bridge = ScBridgeClient::connect( + ScBridgeConfig::new(format!("ws://{address}"), "test-token").unwrap(), + ) + .await + .unwrap(); + let result = settle_failed_direct_session_frame( + &mut bridge, + &invocation, + &model, + &terminal.enclave_pubkey, + &failure, + Some(&checkpoint), + blake3_hex(chat_prompt_text(&request).as_bytes()), + ) + .await; server.await.unwrap(); if lost_handoff { let error = result.unwrap_err(); assert!(!error.retryable); - assert_eq!(provider_session_api_error(&error).public_code, "provider_model_output_invalid"); - } else { result.unwrap(); } - let stored = state.jobs.lock_recover("test jobs").get(&id, now_secs()).unwrap().unwrap(); - assert_eq!(stored.status, if lost_handoff { GatewayJobStatus::ReconciliationPending } else { GatewayJobStatus::Failed }); - assert_eq!(stored.error_info.as_ref().unwrap().code, "provider_model_output_invalid"); - assert_eq!(stored.receipt.as_ref().unwrap()["body"]["usage"], json!(checkpoint.body.usage)); - drop(invocation); drop(state); - let restarted = GatewayState::fixture().with_receipt_user_seed(seed) - .with_job_store_dir(dir).unwrap().with_receipt_settlement_publisher(publisher.clone()); - let transport = RecordingReceiptAckRecoveryTransport { expected_ack: ack, feature, - deliveries: Arc::new(Mutex::new(Vec::new())) }; - reconcile_pending_gateway_job_once(&restarted, &id, &transport).await.unwrap(); - reconcile_pending_gateway_job_once(&restarted, &id, &transport).await.unwrap(); - assert_eq!(restarted.jobs.lock_recover("test jobs").get(&id, now_secs()).unwrap().unwrap().status, GatewayJobStatus::Failed); + assert_eq!( + provider_session_api_error(&error).public_code, + "provider_model_output_invalid" + ); + } else { + result.unwrap(); + } + let stored = state + .jobs + .lock_recover("test jobs") + .get(&id, now_secs()) + .unwrap() + .unwrap(); + assert_eq!( + stored.status, + if lost_handoff { + GatewayJobStatus::ReconciliationPending + } else { + GatewayJobStatus::Failed + } + ); + assert_eq!( + stored.error_info.as_ref().unwrap().code, + "provider_model_output_invalid" + ); + assert_eq!( + stored.receipt.as_ref().unwrap()["body"]["usage"], + json!(checkpoint.body.usage) + ); + drop(invocation); + drop(state); + let restarted = GatewayState::fixture() + .with_receipt_user_seed(seed) + .with_job_store_dir(dir) + .unwrap() + .with_receipt_settlement_publisher(publisher.clone()); + let transport = RecordingReceiptAckRecoveryTransport { + expected_ack: ack, + feature, + deliveries: Arc::new(Mutex::new(Vec::new())), + }; + reconcile_pending_gateway_job_once(&restarted, &id, &transport) + .await + .unwrap(); + reconcile_pending_gateway_job_once(&restarted, &id, &transport) + .await + .unwrap(); + assert_eq!( + restarted + .jobs + .lock_recover("test jobs") + .get(&id, now_secs()) + .unwrap() + .unwrap() + .status, + GatewayJobStatus::Failed + ); assert_eq!(publisher.features.lock_recover("test publisher").len(), 1); } } @@ -42073,36 +45086,64 @@ mod tests { let request = test_chat_request(&model.id); let invocation = test_invocation(); let output = test_chat_output(); - let checkpoint = test_provider_receipt_with_finality( - &model, &request, &output, &invocation, 18, false, - ); - let terminal = test_provider_receipt_with_finality( - &model, &request, &output, &invocation, 19, true, - ); + let checkpoint = + test_provider_receipt_with_finality(&model, &request, &output, &invocation, 18, false); + let terminal = + test_provider_receipt_with_finality(&model, &request, &output, &invocation, 19, true); let prompt_hash = blake3_hex(chat_prompt_text(&request).as_bytes()); let ack = failed_direct_session_receipt_ack( - &model, &invocation, &terminal, Some(&checkpoint), prompt_hash.clone(), - ).unwrap(); + &model, + &invocation, + &terminal, + Some(&checkpoint), + prompt_hash.clone(), + ) + .unwrap(); assert_eq!(ack.seq, 19); - for mutation in ["usage", "amount", "sequence", "finality", "attribution", "session"] { + for mutation in [ + "usage", + "amount", + "sequence", + "finality", + "attribution", + "session", + ] { let mut invalid = terminal.clone(); match mutation { "usage" => invalid.body.usage = ReceiptUsage::text(1, 99), "amount" => invalid.body.au_owed_cum += 1, "sequence" => invalid.body.seq += 1, "finality" => invalid.body.final_receipt = false, - "attribution" => { invalid.body.usage_attribution.insert("reasoning_output_tokens".into(), 1); }, + "attribution" => { + invalid + .body + .usage_attribution + .insert("reasoning_output_tokens".into(), 1); + } "session" => invalid.body.session_id = "ef".repeat(32), _ => unreachable!(), } - invalid.enclave_sig = sign_hex(&test_enclave_seed(), &receipt_signing_bytes(&invalid.body).unwrap()); - assert!(failed_direct_session_receipt_ack( - &model, &invocation, &invalid, Some(&checkpoint), prompt_hash.clone(), - ).is_err(), "accepted {mutation}"); + invalid.enclave_sig = sign_hex( + &test_enclave_seed(), + &receipt_signing_bytes(&invalid.body).unwrap(), + ); + assert!( + failed_direct_session_receipt_ack( + &model, + &invocation, + &invalid, + Some(&checkpoint), + prompt_hash.clone(), + ) + .is_err(), + "accepted {mutation}" + ); } - assert!(failed_direct_session_receipt_ack( - &model, &invocation, &terminal, None, prompt_hash, - ).is_err(), "unacknowledged output must not become a charge"); + assert!( + failed_direct_session_receipt_ack(&model, &invocation, &terminal, None, prompt_hash,) + .is_err(), + "unacknowledged output must not become a charge" + ); } #[test] @@ -42256,6 +45297,8 @@ mod tests { locked_per_req_au: invocation.spend_voucher.body.locked_per_req_au, locked_min_session_au: invocation.spend_voucher.body.locked_min_session_au, served_ctx: invocation.served_ctx, + compute_ms: 1, + capacity_slots: 1, ctx_bracket: invocation.ctx_bracket.clone(), ctx_bracket_table_ver: invocation.ctx_bracket_table_ver, rules_ver: invocation.rules_ver, @@ -42347,16 +45390,118 @@ mod tests { } #[test] - fn embedding_provider_receipt_must_match_gateway_observed_usage() { + fn embedding_usage_accepts_exact_single_and_batch_token_counts() { + for (inputs, exact_tokens) in [ + (vec!["a".to_owned()], 2), + // Public acceptance input: its provider tokenizer exceeds the five + // whitespace words; this exercises the previously rejected branch. + ( + vec!["OpenMayhem embedding acceptance: single input".to_owned()], + 9, + ), + (vec!["alpha".to_owned(), "beta gamma".to_owned()], 7), + (vec!["你好".to_owned()], 3), + ] { + let mut output = EmbeddingOutput { + embeddings: vec![vec![0.1]; inputs.len()], + usage: Usage { + prompt_tokens: exact_tokens, + completion_tokens: 0, + total_tokens: exact_tokens, + }, + }; + assert_eq!( + authoritative_embedding_usage(&inputs, &output).unwrap(), + ReceiptUsage::text(exact_tokens, 0), + ); + // Both inclusive boundaries use the same envelope as reservations. + for tokens in [ + embedding_input_token_count(&inputs), + embedding_input_token_upper_bound(&inputs), + ] { + output.usage.prompt_tokens = tokens; + output.usage.total_tokens = tokens; + assert_eq!( + authoritative_embedding_usage(&inputs, &output).unwrap(), + ReceiptUsage::text(tokens, 0), + ); + } + } + } + + #[test] + fn embedding_session_uses_signed_tokenizer_count_instead_of_whitespace_estimate() { + let inputs = vec!["OpenMayhem embedding acceptance: single input".to_owned()]; + assert_eq!(embedding_input_token_count(&inputs), 5); + let exact = Usage { + prompt_tokens: 9, + completion_tokens: 0, + total_tokens: 9, + }; + let signed = ReceiptUsage::text(9, 0); + + assert_eq!( + verified_embedding_session_usage(&inputs, Some(&exact), &signed).unwrap(), + exact, + ); + assert_eq!( + verified_embedding_session_usage(&inputs, None, &signed).unwrap(), + exact, + ); + + let stale_whitespace_usage = embedding_usage_for_inputs(&inputs); + let mismatch = + verified_embedding_session_usage(&inputs, Some(&stale_whitespace_usage), &signed) + .expect_err("delta usage must agree with the signed receipt"); + assert!(mismatch + .message + .contains("provider-reported embedding session usage")); + } + + #[test] + fn embedding_usage_rejects_invalid_counts_and_output_cardinality() { + let inputs = vec!["alpha beta".to_owned(), "gamma".to_owned()]; + let upper_bound = embedding_input_token_upper_bound(&inputs); + for (prompt_tokens, completion_tokens, total_tokens) in [ + (0, 0, 0), + (2, 0, 2), // Below the conservative whitespace lower bound. + (upper_bound + 1, 0, upper_bound + 1), + (7, 1, 8), + (7, 0, 8), + (7, 0, 6), + ] { + let output = EmbeddingOutput { + embeddings: vec![vec![0.1]; inputs.len()], + usage: Usage { + prompt_tokens, + completion_tokens, + total_tokens, + }, + }; + assert!(authoritative_embedding_usage(&inputs, &output).is_err()); + } + let output = EmbeddingOutput { + embeddings: vec![vec![0.1]], + usage: Usage { + prompt_tokens: 7, + completion_tokens: 0, + total_tokens: 7, + }, + }; + assert!(authoritative_embedding_usage(&inputs, &output).is_err()); + } + + #[test] + fn embedding_provider_receipt_must_match_bounded_reported_usage() { let state = GatewayState::fixture(); let model = test_model(); let inputs = vec!["alpha".to_owned(), "beta gamma".to_owned()]; let output = EmbeddingOutput { embeddings: vec![vec![0.1, 0.2, 0.3], vec![0.2, 0.3, 0.4]], usage: Usage { - prompt_tokens: 3, + prompt_tokens: 7, completion_tokens: 0, - total_tokens: 3, + total_tokens: 7, }, }; let invocation = test_invocation(); @@ -42581,7 +45726,7 @@ mod tests { caps: json!({}), }; let voucher_body = SpendVoucherBody { - schema_version: SESSION_RECEIPT_SCHEMA_VERSION, + schema_version: SPEND_VOUCHER_SCHEMA_VERSION, session_id: session_id.clone(), billing_id: session_id.clone(), billing_attempt: 0, @@ -43365,11 +46510,13 @@ mod tests { prompts: Vec::new(), fingerprints_by_artifact_root: BTreeMap::new(), token_prefixes_by_artifact_root: BTreeMap::new(), + openai_compatible_artifact_roots: BTreeSet::new(), perceptual_hashes_by_artifact_root: BTreeMap::new(), embedding_vectors_by_artifact_root: BTreeMap::new(), transcripts_by_artifact_root: BTreeMap::new(), audio_fingerprints_by_artifact_root: BTreeMap::new(), video_fingerprints_by_artifact_root: BTreeMap::new(), + decision_fingerprints_by_artifact_root: BTreeMap::new(), speciality_calibrations_by_artifact_root: BTreeMap::new(), default_fingerprint: Some("baseline-only".to_owned()), default_token_prefixes: None, @@ -43378,6 +46525,7 @@ mod tests { default_transcripts: None, default_audio_fingerprints: None, default_video_fingerprints: None, + default_decision_fingerprints: None, } } @@ -43787,15 +46935,21 @@ mod tests { "model": model.id, "prompt": "A blue sculpture", "width": 64, "height": 64, "n": 1, "steps": 9, "cfg_scale": 0.0, "response_format": "b64_json", "input_reference": reference, "strength": 0.5, - })).unwrap(); + })) + .unwrap(); validate_image_generation_request(&model, &request).unwrap(); let transport = direct_session_image_generation_request_body(&request); assert_eq!(transport["input_reference"], reference); assert_eq!(transport["strength"], 0.5); - let load = request_requirements_for_image_generation(&state, &model, &request, 0, None, None); + let load = + request_requirements_for_image_generation(&state, &model, &request, 0, None, None); assert_eq!(load.modality_load["image"].max_item_units, 96 * 80); - assert_eq!(load.modality_load["image"].max_item_bytes, - mayhem_proto::image_reference_metadata(&reference).unwrap().bytes); + assert_eq!( + load.modality_load["image"].max_item_bytes, + mayhem_proto::image_reference_metadata(&reference) + .unwrap() + .bytes + ); let hash = image_generation_prompt_hash(&request); let mut changed = request.clone(); changed.input_reference = Some(test_png_data_url_with_size(80, 96)); @@ -43977,6 +47131,7 @@ mod tests { price_ref_au: None, min_ask_au: 0, att_tier: 1, + enclave_att_tier: Some(1), quant: DEFAULT_QUANT_BUCKET.to_owned(), served_ctx: None, hardware_fingerprint: None, @@ -44355,6 +47510,66 @@ mod tests { } } + #[derive(Debug)] + struct PreSpendAdmissionThenSuccessBackend { + providers: Arc>>, + billing_attempts: Arc>>, + } + + impl GatewaySessionBackend for PreSpendAdmissionThenSuccessBackend { + fn name(&self) -> &str { + "test-pre-spend-admission-then-success" + } + + fn run_chat<'a>( + &'a self, + _model: &'a GatewayModel, + request: &'a ChatCompletionRequest, + invocation: &'a GatewaySessionInvocation, + ) -> GatewaySessionFuture<'a> { + Box::pin(async move { + self.billing_attempts + .lock() + .expect("billing attempts lock") + .push(invocation.spend_voucher.body.billing_attempt); + let attempt = { + let mut providers = self.providers.lock().expect("providers lock"); + providers.push(invocation.provider_pubkey.clone().unwrap_or_default()); + providers.len() + }; + if attempt == 1 { + return Err(GatewaySessionError::retryable( + "simulated direct-session admission timeout before request frames", + ) + .into_safe_same_route_retry()); + } + let prompt_tokens = rough_tokens(&chat_prompt_text(request)); + Ok(GatewaySessionResult { + output: ChatOutput { + reasoning_content: String::new(), + content: Some("recovered before spend".to_owned()), + tool_calls: Vec::new(), + artifacts: Vec::new(), + finish_reason: "stop".to_owned(), + usage: Usage { + prompt_tokens, + completion_tokens: 3, + total_tokens: prompt_tokens + 3, + }, + }, + backend: self.name().to_owned(), + direct_session: true, + provider_receipt: None, + token_ids: vec![1, 2, 3], + quality: Some(GatewaySessionQuality { + ttft_ms: 10, + tok_s: Some(40.0), + }), + }) + }) + } + } + #[derive(Debug)] struct AcceptThenCloseThenSuccessBackend { providers: Arc>>, @@ -44367,6 +47582,7 @@ mod tests { struct ProviderReportedFailureBackend { code: &'static str, attempts: Arc>, + failure_receipt: bool, } impl ProviderReportedFailureBackend { @@ -44383,15 +47599,15 @@ mod tests { Some(self.code), ); } - provider_reported_session_error( - &json!({ - "t": "s.error", - "code": self.code, - "message": "focused route-runner failure" - }), - context, - retryable, - ) + let mut frame = json!({ + "t": "s.error", + "code": self.code, + "message": "focused route-runner failure" + }); + if self.failure_receipt { + frame["receipt"] = json!({"settled": true}); + } + provider_reported_session_error(&frame, context, retryable, false) } } @@ -44827,6 +48043,61 @@ mod tests { }) }) } + + fn run_tokenize<'a>( + &'a self, + invocation: &'a GatewayTokenizeInvocation, + ) -> GatewayTokenizeFuture<'a> { + Box::pin(async move { + assert!(invocation.request.get("mayhem_contract").is_some()); + assert!(invocation.request.get("contract_request").is_some()); + Ok(GatewayTokenizeResult { + count: 3, + tokens: invocation.return_tokens.then_some(vec![11, 22, 33]), + provider: invocation.provider_pubkey.clone(), + }) + }) + } + } + + #[tokio::test] + async fn tokenize_endpoint_uses_live_model_route_without_creating_inference_work() { + use tower::ServiceExt; + + let model = test_routed_model(1); + let model_id = model.id.clone(); + let backend = Arc::new(SuccessBackend { + providers: Arc::new(Mutex::new(Vec::new())), + }); + let state = test_gateway_state_from_models(vec![model]).with_session_backend(backend); + let response = openai_router(state) + .oneshot( + axum::http::Request::builder() + .method("POST") + .uri("/v1/tokenize") + .header(header::CONTENT_TYPE, "application/json") + .body(Body::from( + json!({ + "model": model_id, + "prompt": "count these exact model tokens", + "return_tokens": true + }) + .to_string(), + )) + .unwrap(), + ) + .await + .unwrap(); + assert_eq!(response.status(), StatusCode::OK); + assert!(response.headers().get("x-mayhem-job-id").is_none()); + let body = axum::body::to_bytes(response.into_body(), 1024 * 1024) + .await + .unwrap(); + let body: Value = serde_json::from_slice(&body).unwrap(); + assert_eq!(body["object"], "tokenization"); + assert_eq!(body["count"], 3); + assert_eq!(body["tokens"], json!([11, 22, 33])); + assert_eq!(body["model"], model_id); } #[test] @@ -45664,6 +48935,34 @@ mod tests { assert_eq!(invocation.spend_voucher.body.locked_min_session_au, 456); } + #[test] + fn t4_identity_preserves_underlying_execution_attestation() { + let mut tier1_model = test_routed_model(1); + tier1_model.mayhem.route_candidates[0].att_tier = 4; + tier1_model.mayhem.route_candidates[0].enclave_att_tier = Some(1); + assert!(canonical_route_candidate( + &tier1_model.mayhem.route_candidates[0] + )); + let tier1_state = test_gateway_state_from_models(vec![tier1_model.clone()]); + let tier1 = tier1_state + .session_attestation_for_route(&tier1_model, &tier1_model.mayhem.route_candidates[0]) + .expect("T4 identity does not replace T1 execution attestation"); + assert_eq!(tier1.contract.att_tier, 1); + + let mut tier3_model = test_routed_model(1); + tier3_model.mayhem.route_candidates[0].att_tier = 4; + tier3_model.mayhem.route_candidates[0].enclave_att_tier = Some(3); + let tier3_state = test_gateway_state_from_models(vec![tier3_model.clone()]); + let error = tier3_state + .session_attestation_for_route(&tier3_model, &tier3_model.mayhem.route_candidates[0]) + .expect_err("T4 identity must not bypass T3 policy verification"); + assert!(error.message.contains("Tier 3"), "{}", error.message); + + let mut unbound = tier1_model.mayhem.route_candidates[0].clone(); + unbound.enclave_att_tier = None; + assert!(!canonical_route_candidate(&unbound)); + } + #[test] fn chat_invocation_uses_signed_heartbeat_transport_peer() { let model = test_routed_model(1); @@ -45929,6 +49228,218 @@ mod tests { ); } + #[test] + fn decision_routes_do_not_require_autoregressive_prefix_caching() { + let mut model = test_routed_model(1); + model.mayhem.model_class = "decision".to_owned(); + for contract in &mut model.mayhem.adapter.endpoint_families { + contract.family = mayhem_proto::ENDPOINT_MAYHEM_DECISIONS.to_owned(); + } + let route = &model.mayhem.route_candidates[0]; + let now = now_millis_u64(); + let mut heartbeat = heartbeat_for_route(&model, route, now); + heartbeat.prefix_caching = None; + heartbeat.sig = "aa".repeat(64); + let state = GatewayState::from_models(vec![model.clone()]) + .with_provider_heartbeats(vec![heartbeat]); + + let reporting = gateway_reporting_requirements_for_route(&state, &model, route, now); + assert_eq!(reporting.len(), 1); + assert!(!reporting[0].requires_prefix_caching); + assert_eq!(reporting[0].input_tokens, 1); + assert_eq!(reporting[0].output_tokens, 1); + + let mut request = test_chat_request(&model.id); + request.endpoint_family = Some(mayhem_proto::ENDPOINT_MAYHEM_DECISIONS.to_owned()); + request.endpoint_request = Some(json!({"state": "hello", "questions": {}})); + request.max_tokens = Some(1); + let transport = direct_session_request_body(&request); + assert_eq!(transport["kind"], "decision"); + assert_eq!( + transport["endpoint_family"], + mayhem_proto::ENDPOINT_MAYHEM_DECISIONS + ); + let request_requirements = + request_requirements_for_chat(&state, &model, &request, now, None, None, None); + assert!(!request_requirements.requires_prefix_caching); + + let entries = state + .provider_table + .lock_recover("provider table") + .entries(now); + assert_eq!( + gateway_model_live_route_keys(&state, &model, &entries, now), + BTreeSet::from([route_key(route)]) + ); + } + + #[test] + fn needle_routes_without_prefix_cache_but_other_chat_routes_still_require_it() { + let mut needle = test_routed_model(1); + needle.id = "Cactus-Compute/needle".to_owned(); + let route = &needle.mayhem.route_candidates[0]; + let now = now_millis_u64(); + let mut heartbeat = heartbeat_for_route(&needle, route, now); + heartbeat.prefix_caching = Some(false); + heartbeat.sig = "aa".repeat(64); + let state = GatewayState::from_models(vec![needle.clone()]) + .with_provider_heartbeats(vec![heartbeat]); + + let reporting = gateway_reporting_requirements_for_route(&state, &needle, route, now); + assert_eq!(reporting.len(), 1); + assert!(!reporting[0].requires_prefix_caching); + let request = test_chat_request(&needle.id); + let requirements = + request_requirements_for_chat(&state, &needle, &request, now, None, None, None); + assert!(!requirements.requires_prefix_caching); + let entries = state + .provider_table + .lock_recover("provider table") + .entries(now); + assert_eq!( + gateway_model_live_route_keys(&state, &needle, &entries, now), + BTreeSet::from([route_key(route)]) + ); + + let other = test_routed_model(1); + let other_requirements = + request_requirements_for_chat(&state, &other, &request, now, None, None, None); + assert!(other_requirements.requires_prefix_caching); + } + + #[test] + fn decision_transport_uses_bounded_structured_output_and_exact_provider_usage() { + let mut criteria = serde_json::Map::new(); + for index in 0..25 { + criteria.insert( + format!("option-{index}"), + json!(format!("criterion {index}")), + ); + } + let body = json!({ + "model": "convaiinnovations/laya", + "state": {"body": "A customer cannot sign in."}, + "questions": { + "intent": { + "type": "choice", + "instructions": "Choose the closest category.", + "criteria": Value::Object(criteria), + }, + "urgent": { + "type": "noul", + "instructions": "Is it urgent?" + } + }, + "checkpoint": "english", + "shortlist": {"k": 20, "max_length": 512, "batch_size": 16}, + "limits": {"max_len": 512, "head_max_len": 192} + }); + let mut request = test_chat_request("convaiinnovations/laya"); + request.endpoint_family = Some(mayhem_proto::ENDPOINT_MAYHEM_DECISIONS.to_owned()); + request.endpoint_request = Some(body.clone()); + request.messages[0].content = json!(stable_json_value(&body).to_string()); + request.max_tokens = Some(1); + + let (lower, upper) = decision_input_token_bounds(&request); + assert_eq!(lower, 2); + assert_eq!(upper, 2 * 512 + 26 * 512); + + let invocation = test_invocation(); + let output_limit = direct_session_chat_output_byte_limit(&request, &invocation); + assert!(output_limit > DEFAULT_SESSION_OUTPUT_BYTES_PER_REQUEST_TOKEN); + assert_eq!(output_limit, decision_output_byte_upper_bound(&request)); + + let state = GatewayState::fixture(); + let model = test_model(); + let requirements = request_requirements_for_chat( + &state, + &model, + &request, + now_millis_u64(), + None, + None, + None, + ); + assert_eq!(requirements.min_ctx, 512); + assert_eq!(requirements.input_tokens, upper); + assert_eq!( + requirements.output_tokens, + decision_output_unit_upper_bound(&request) + ); + + let exact = ReceiptUsage::text(463, 382); + let verified = expected_chat_usage_for_provider( + &request, + Some(&exact), + 1, + 382, + &text_generation_rate_map(20, 60), + None, + ) + .expect("exact Laya tokenizer usage inside the signed envelope is accepted"); + assert_eq!(verified, exact); + + let wrong_output = ReceiptUsage::text(463, 383); + let error = expected_chat_usage_for_provider( + &request, + Some(&wrong_output), + 1, + 382, + &text_generation_rate_map(20, 60), + None, + ) + .expect_err("decision output units remain buyer-verifiable"); + assert!(error.message.contains("output units")); + + let excessive_input = ReceiptUsage::text(upper.saturating_add(1), 382); + let error = expected_chat_usage_for_provider( + &request, + Some(&excessive_input), + 1, + 382, + &text_generation_rate_map(20, 60), + None, + ) + .expect_err("provider input usage above the model-derived envelope is rejected"); + assert!(error.message.contains("invalid input usage")); + + let billing = GatewayBillingContext::initial("decision-test".to_owned()); + let max_spend = + estimate_max_spend_au(&model.mayhem.price_ref_au, &request, 512, &billing, None); + let exact_spend = calculate_au_owed(&model.mayhem.price_ref_au, &exact); + assert!(max_spend >= exact_spend); + + let mut invocation = invocation; + invocation.spend_voucher.body.max_spend_au = MoneyAu::MAX; + let output = ChatOutput { + reasoning_content: String::new(), + content: Some("x".repeat(382 * mayhem_proto::VISIBLE_OUTPUT_BYTES_PER_UNIT as usize)), + tool_calls: Vec::new(), + artifacts: Vec::new(), + finish_reason: "stop".to_owned(), + usage: Usage { + prompt_tokens: 463, + completion_tokens: 382, + total_tokens: 845, + }, + }; + let mut receipt = test_provider_receipt(&model, &request, &output, &invocation); + receipt.body.prompt_hash = direct_chat_prompt_hash(&request); + receipt.enclave_sig = sign_hex( + &test_enclave_seed(), + &receipt_signing_bytes(&receipt.body).unwrap(), + ); + direct_session_receipt_ack( + &request, + &output, + &invocation, + &receipt, + invocation.provider_pubkey.as_deref().unwrap(), + &model, + ) + .expect("valid decision receipt fits the signed voucher and receives an ack"); + } + #[test] fn route_selection_excludes_circuit_open_provider_and_readmits_after_expiry() { let model = test_routed_model(3); @@ -46015,6 +49526,123 @@ mod tests { assert_eq!(state.served_ctx_for_route(&model, Some(routes[0])), 262_144); } + #[test] + fn growing_chat_fits_large_output_limit_to_remaining_context_for_streaming_and_json() { + let mut model = test_routed_model(2); + model.mayhem.caps.ctx = 262_144; + for route in &mut model.mayhem.route_candidates { + route.served_ctx = Some(262_144); + } + let state = test_gateway_state_from_models(vec![model.clone()]); + let options = GatewayRequestOptions::default(); + + for stream in [false, true] { + let mut previous_limit = 226_921; + for bytes in [5_000, 60_000, 120_000, 150_000] { + let mut request = test_chat_request(&model.id); + request.stream = stream; + request.messages[0].content = json!("x".repeat(bytes)); + request.max_tokens = Some(226_921); + + let input_tokens = chat_context_input_tokens(&request); + fit_chat_output_budget_to_context(&state, &model, &mut request, &options); + + let expected = 226_921_u64 + .min(262_144_u64.saturating_sub(input_tokens)) + .max(1) as u32; + assert_eq!(request.max_tokens, Some(expected)); + assert!(expected <= previous_limit); + previous_limit = expected; + assert!( + effective_context_floor( + None, + chat_context_input_tokens(&request), + chat_output_headroom_tokens(&request), + ) <= 262_144 + ); + assert_eq!( + ordered_route_candidates_for_request_with_options( + &state, &model, &request, &options, + ) + .len(), + 2 + ); + } + } + } + + #[test] + fn context_fit_honors_completion_and_responses_output_aliases() { + let mut model = test_routed_model(1); + model.mayhem.caps.ctx = 262_144; + model.mayhem.route_candidates[0].served_ctx = Some(262_144); + let responses_contract = mayhem_proto::endpoint_family_contract_template( + mayhem_proto::ENDPOINT_OPENAI_RESPONSES, + ) + .expect("Responses contract"); + model.mayhem.adapter.endpoint_families = vec![responses_contract.clone()]; + let state = test_gateway_state_from_models(vec![model.clone()]); + let options = GatewayRequestOptions::default(); + let raw = json!({ + "model": model.id, + "input": [{"role": "user", "content": "x".repeat(150_000)}], + "max_output_tokens": 226_921, + "stream": false + }); + let normalized = normalize_endpoint_request_for_provider(&responses_contract, &raw) + .expect("Responses request normalizes"); + let responses: ResponsesRequest = + serde_json::from_value(normalized.normalized_request.clone()).unwrap(); + let mut request = responses_chat_request(responses, normalized.normalized_request).unwrap(); + + fit_chat_output_budget_to_context(&state, &model, &mut request, &options); + synchronize_effective_chat_contract_request(&model, &mut request).unwrap(); + + let fitted = request + .max_tokens + .expect("Responses output limit remains present"); + assert!(fitted < 226_921); + assert_eq!( + request.endpoint_request.as_ref().unwrap()["max_output_tokens"], + json!(fitted) + ); + + request.max_tokens = None; + request.max_completion_tokens = Some(226_921); + request.endpoint_family = None; + request.endpoint_request = None; + assert_eq!(chat_output_headroom_tokens(&request), 226_921); + fit_chat_output_budget_to_context(&state, &model, &mut request, &options); + assert!(request.max_completion_tokens.unwrap() < 226_921); + } + + #[tokio::test] + async fn preferred_provider_context_failure_is_immediate_and_not_masked_by_other_route() { + let mut model = test_routed_model(2); + model.mayhem.caps.ctx = 262_144; + model.mayhem.route_candidates[0].served_ctx = Some(8_192); + model.mayhem.route_candidates[1].served_ctx = Some(262_144); + let state = test_gateway_state_from_models(vec![model.clone()]); + let mut request = test_chat_request(&model.id); + request.messages[0].content = json!("word ".repeat(9_000)); + request.max_tokens = Some(64); + let options = GatewayRequestOptions { + preferred_providers: Some(vec![model.mayhem.route_candidates[0].provider.clone()]), + max_wait_ms: 60_000, + ..GatewayRequestOptions::default() + }; + + let started = Instant::now(); + let error = match run_chat_with_route_retry(&state, &model, &request, options).await { + Ok(_) => panic!("preferred 8k route cannot hold this prompt"), + Err(error) => error, + }; + + assert_eq!(error.public_code, "context_capacity_unavailable"); + assert!(started.elapsed() < Duration::from_secs(1)); + assert!(!error.message.contains("preferred provider")); + } + #[test] fn context_estimate_includes_compact_text_tools_history_and_reasoning() { let mut request = test_chat_request("test/model"); @@ -46157,12 +49785,13 @@ mod tests { async fn context_exhaustion_is_terminal_and_does_not_penalize_provider() { let model = test_routed_model(3); let attempts = Arc::new(Mutex::new(0)); - let state = test_gateway_state_from_models(vec![model.clone()]).with_session_backend(Arc::new( - ProviderReportedFailureBackend { + let state = test_gateway_state_from_models(vec![model.clone()]).with_session_backend( + Arc::new(ProviderReportedFailureBackend { code: "context_length_exceeded", attempts: Arc::clone(&attempts), - }, - )); + failure_receipt: false, + }), + ); let error = focused_route_runner_error( run_chat_with_route_retry( &state, @@ -46181,7 +49810,9 @@ mod tests { assert_eq!(error.public_code, "context_length_exceeded"); assert!(!error.retryable); assert!(error.message.contains("Compact")); - assert!(!state.route_provider_in_cooloff(&model.mayhem.route_candidates[0], now_millis_u64())); + assert!( + !state.route_provider_in_cooloff(&model.mayhem.route_candidates[0], now_millis_u64()) + ); assert!(state.reputation_events().is_empty()); } @@ -46773,6 +50404,107 @@ mod tests { assert!(err.contains("Capabilities"), "unexpected error: {err}"); } + #[test] + fn modality_admission_deduplicates_heartbeat_and_local_activity() { + let mut model = test_routed_model(1); + model.mayhem.caps.vision = true; + model.mayhem.adapter.modality_set = vec!["text".to_owned(), "image".to_owned()]; + model.mayhem.route_candidates[0].served_modalities = + vec!["text".to_owned(), "image".to_owned()]; + let route = &model.mayhem.route_candidates[0]; + let state = test_gateway_state_from_models(vec![model.clone()]); + let now = now_millis_u64(); + let mut heartbeat = heartbeat_for_route(&model, route, now); + heartbeat.caps.modality_capacity.insert( + "image".to_owned(), + HeartbeatModalityCapacity { + unit: "pixel".to_owned(), + max_inflight_items: 256, + active_items: 128, + max_items_per_request: 128, + max_item_bytes: 1024 * 1024, + max_item_units: 1024 * 1024, + working_set_bytes_per_item: 1024, + }, + ); + heartbeat.sig = "aa".repeat(64); + state.ingest_provider_heartbeat(heartbeat.clone(), now); + + let mut request = test_chat_request(&model.id); + request.messages[0].content = json!([ + { "type": "text", "text": "describe" }, + { "type": "image_url", "image_url": { "url": test_png_data_url() } } + ]); + let mut requirements = + request_requirements_for_chat(&state, &model, &request, now, None, None, None); + requirements + .modality_load + .get_mut("image") + .expect("image load") + .item_count = 128; + + let first = state + .try_acquire_modality_admission(Some(route), &requirements) + .expect("remote 128 plus request 128 fits") + .expect("first admission guard"); + let second = state + .try_acquire_modality_admission(Some(route), &requirements) + .expect("overlapping remote 128 and local 128 plus request 128 fits") + .expect("second admission guard"); + drop(first); + drop(second); + + heartbeat + .caps + .modality_capacity + .get_mut("image") + .expect("image capacity") + .active_items = 256; + state.ingest_provider_heartbeat(heartbeat.clone(), now.saturating_add(1)); + assert!(matches!( + state.try_acquire_modality_admission(Some(route), &requirements), + Err(ModalityAdmissionError::RemoteCapacity(_)) + )); + + heartbeat + .caps + .modality_capacity + .get_mut("image") + .expect("image capacity") + .active_items = 128; + state.ingest_provider_heartbeat(heartbeat.clone(), now.saturating_add(2)); + let refreshed = state + .try_acquire_modality_admission(Some(route), &requirements) + .expect("heartbeat refresh restores remote capacity") + .expect("refreshed admission guard"); + drop(refreshed); + + heartbeat + .caps + .modality_capacity + .get_mut("image") + .expect("image capacity") + .active_items = 0; + state.ingest_provider_heartbeat(heartbeat, now.saturating_add(3)); + let local_first = state + .try_acquire_modality_admission(Some(route), &requirements) + .expect("first local admission") + .expect("first local guard"); + let local_second = state + .try_acquire_modality_admission(Some(route), &requirements) + .expect("second local admission") + .expect("second local guard"); + assert!(matches!( + state.try_acquire_modality_admission(Some(route), &requirements), + Err(ModalityAdmissionError::LocalCapacity { .. }) + )); + drop(local_first); + assert!(state + .try_acquire_modality_admission(Some(route), &requirements) + .is_ok()); + drop(local_second); + } + #[tokio::test] async fn video_generation_executes_through_the_common_artifact_path() { let mut model = test_model(); @@ -47857,6 +51589,77 @@ mod tests { .contains_key(&job.id)); } + #[tokio::test] + async fn terminal_handoff_error_defers_to_durable_reconciliation() { + let mut state = GatewayState::fixture(); + state.jobs = Arc::new(Mutex::new(GatewayJobStore::in_memory( + [29_u8; 32], + 8, + 64 * 1024 * 1024, + 24 * 60 * 60, + ))); + let job = match prepare_gateway_job( + &state, + &HeaderMap::new(), + mayhem_proto::ENDPOINT_OPENAI_CHAT_COMPLETIONS, + "mayhem/test", + &json!({"model": "mayhem/test", "messages": [{"role": "user", "content": "recover"}]}), + &None, + ) + .await + .unwrap() + { + PreparedGatewayJob::Started(job) => job, + _ => panic!("fresh request must start a job"), + }; + let mut invocation = test_invocation(); + invocation.transport_peer = Some("ab".repeat(32)); + invocation.job = Some(job.clone()); + let model = test_model(); + let request = test_chat_request(&model.id); + let output = test_chat_output(); + let provider_receipt = test_provider_receipt(&model, &request, &output, &invocation); + let receipt_ack = + receipt_ack_for_body(&invocation.receipt_user_seed, &provider_receipt.body).unwrap(); + stage_completed_invocation_job( + &invocation, + chat_job_result(&output), + &[], + &provider_receipt, + &receipt_ack, + ) + .await + .unwrap(); + + let recovery_job = job.clone(); + let recovery = tokio::spawn(async move { + tokio::time::sleep(Duration::from_millis(25)).await; + recovery_job + .finish_reconciliation(GatewayJobStatus::Completed, None) + .await + .unwrap(); + }); + finish_completed_invocation_after_handoff( + &invocation, + Err(GatewaySessionError::new( + "receipt settlement publisher rejected the local enqueue", + )), + Duration::from_secs(2), + ) + .await + .expect("recovered terminal handoff must remain successful"); + recovery.await.unwrap(); + + let completed = state + .jobs + .lock_recover("gateway job vault") + .get(&job.id, now_secs()) + .unwrap() + .unwrap(); + assert_eq!(completed.status, GatewayJobStatus::Completed); + assert_eq!(completed.result, Some(chat_job_result(&output))); + } + #[tokio::test] async fn ack_delivery_failure_preserves_artifact_and_retry_cannot_double_bill() { use tower::ServiceExt; @@ -48142,45 +51945,90 @@ mod tests { invocation.transport_peer = Some("ab".repeat(32)); invocation.job = Some(job); let output = test_chat_output(); - let provider_receipt = test_provider_receipt( - &model, &test_chat_request(&model.id), &output, &invocation, - ); + let provider_receipt = + test_provider_receipt(&model, &test_chat_request(&model.id), &output, &invocation); let ack = receipt_ack_for_body(&seed, &provider_receipt.body).unwrap(); let result = chat_job_result(&output); stage_completed_invocation_job( - &invocation, result.clone(), &output.artifacts, &provider_receipt, &ack, + &invocation, + result.clone(), + &output.artifacts, + &provider_receipt, + &ack, ) .await .unwrap(); let mut feature = test_receipt_settlement_feature(&provider_receipt, &ack); feature["value"]["contract_version"] = json!(RECOVERABLE_RECEIPT_CONTRACT_VERSION - 1); - assert!(validate_stored_receipt_settlement_feature(&provider_receipt, &ack, &feature).is_err()); - let historical_receipt = parse_record_usage_receipt_envelope(&feature["value"]["receipt"]).unwrap(); + assert!( + validate_stored_receipt_settlement_feature(&provider_receipt, &ack, &feature).is_err() + ); + let historical_receipt = + parse_record_usage_receipt_envelope(&feature["value"]["receipt"]).unwrap(); feature["key"] = json!(record_usage_receipt_feature_key_for_contract( - &historical_receipt, RECOVERABLE_RECEIPT_CONTRACT_VERSION - 1, + &historical_receipt, + RECOVERABLE_RECEIPT_CONTRACT_VERSION - 1, )); feature["value"]["provider_sig"] = json!(sign_hex( &test_provider_seed(), - &record_usage_receipt_signing_bytes(feature["key"].as_str().unwrap(), &feature["value"]).unwrap(), + &record_usage_receipt_signing_bytes( + feature["key"].as_str().unwrap(), + &feature["value"] + ) + .unwrap(), )); - assert!(validate_stored_receipt_settlement_feature(&provider_receipt, &ack, &feature).is_ok()); - assert!(validate_receipt_settlement_feature_for_receipt(&provider_receipt, &ack, &feature).is_err()); - invocation.job.as_ref().unwrap() - .persist_reconciliation_settlement_feature(feature.clone()).await.unwrap(); + assert!( + validate_stored_receipt_settlement_feature(&provider_receipt, &ack, &feature).is_ok() + ); + assert!( + validate_receipt_settlement_feature_for_receipt(&provider_receipt, &ack, &feature) + .is_err() + ); + invocation + .job + .as_ref() + .unwrap() + .persist_reconciliation_settlement_feature(feature.clone()) + .await + .unwrap(); // An incompatible durable job cannot take down otherwise healthy admission. - let startup = state.clone() - .with_receipt_settlement_publisher(Arc::new(RecordingReceiptSettlementPublisher::default())) + let startup = state + .clone() + .with_receipt_settlement_publisher(Arc::new( + RecordingReceiptSettlementPublisher::default(), + )) .with_session_backend(Arc::new(ScBridgeGatewaySessionBackend::new( ScBridgeGatewaySessionConfig::new("ws://127.0.0.1:1", "test-token"), ))); - let stored = startup.jobs.lock_recover("test jobs").get(&id, now_secs()).unwrap().unwrap(); + let stored = startup + .jobs + .lock_recover("test jobs") + .get(&id, now_secs()) + .unwrap() + .unwrap(); let mut malformed = stored.receipt.unwrap(); - malformed["reconciliation"]["settlement_feature"]["value"]["contract_version"] = json!(CONTRACT_VERSION + 1); - startup.jobs.lock_recover("test jobs").update_reconciliation_receipt(&id, malformed, now_secs()).unwrap(); + malformed["reconciliation"]["settlement_feature"]["value"]["contract_version"] = + json!(CONTRACT_VERSION + 1); + startup + .jobs + .lock_recover("test jobs") + .update_reconciliation_receipt(&id, malformed, now_secs()) + .unwrap(); assert!(spawn_pending_gateway_job_reconciliation(&startup).is_ok()); - let mut restored = startup.jobs.lock_recover("test jobs").get(&id, now_secs()).unwrap().unwrap().receipt.unwrap(); + let mut restored = startup + .jobs + .lock_recover("test jobs") + .get(&id, now_secs()) + .unwrap() + .unwrap() + .receipt + .unwrap(); restored["reconciliation"]["settlement_feature"] = feature.clone(); - startup.jobs.lock_recover("test jobs").update_reconciliation_receipt(&id, restored, now_secs()).unwrap(); + startup + .jobs + .lock_recover("test jobs") + .update_reconciliation_receipt(&id, restored, now_secs()) + .unwrap(); drop(startup); drop(invocation); drop(state); @@ -48193,10 +52041,19 @@ mod tests { .with_receipt_settlement_publisher(publisher.clone()); let deliveries = Arc::new(Mutex::new(Vec::new())); let transport = RecordingReceiptAckRecoveryTransport { - expected_ack: ack, feature: feature.clone(), deliveries: deliveries.clone(), + expected_ack: ack, + feature: feature.clone(), + deliveries: deliveries.clone(), }; - assert!(reconcile_pending_gateway_job_once(&restarted, &id, &transport).await.is_err()); - let key = format!("receipt/head/{}/{}", provider_receipt.body.billing_id, provider_receipt.body.billing_attempt); + assert!( + reconcile_pending_gateway_job_once(&restarted, &id, &transport) + .await + .is_err() + ); + let key = format!( + "receipt/head/{}/{}", + provider_receipt.body.billing_id, provider_receipt.body.billing_attempt + ); let confirmed = json!({ "key": key, "confirmed": true, "signed_length": 123, "value": { @@ -48206,17 +52063,26 @@ mod tests { }); let response = Arc::new(Mutex::new(confirmed.clone())); let handler_response = response.clone(); - let app = axum::Router::new().route("/v1/state", axum::routing::get(move |axum::extract::Query(query): axum::extract::Query>| { - let response = handler_response.clone(); - async move { - assert_eq!(query.get("confirmed").map(String::as_str), Some("true")); - axum::Json(response.lock_recover("test canonical response").clone()) - } - })); + let app = axum::Router::new().route( + "/v1/state", + axum::routing::get( + move |axum::extract::Query(query): axum::extract::Query< + BTreeMap, + >| { + let response = handler_response.clone(); + async move { + assert_eq!(query.get("confirmed").map(String::as_str), Some("true")); + axum::Json(response.lock_recover("test canonical response").clone()) + } + }, + ), + ); let listener = TcpListener::bind((Ipv4Addr::LOCALHOST, 0)).await.unwrap(); let address = listener.local_addr().unwrap(); let server = tokio::spawn(async move { axum::serve(listener, app).await.unwrap() }); - let restarted = restarted.with_canary_probe_contract_rpc(PeerRpcClient::new(format!("http://{address}/v1")).unwrap()); + let restarted = restarted.with_canary_probe_contract_rpc( + PeerRpcClient::new(format!("http://{address}/v1")).unwrap(), + ); let balance = restarted.ledger_balance_au(); for (pointer, wrong) in [ ("/confirmed", json!(false)), @@ -48229,28 +52095,112 @@ mod tests { let mut invalid = confirmed.clone(); *invalid.pointer_mut(pointer).unwrap() = wrong; *response.lock_recover("test canonical response") = invalid; - assert!(reconcile_pending_gateway_job_once(&restarted, &id, &transport).await.is_err()); - assert_eq!(restarted.jobs.lock_recover("test jobs").get(&id, now_secs()).unwrap().unwrap().status, GatewayJobStatus::ReconciliationPending); + assert!( + reconcile_pending_gateway_job_once(&restarted, &id, &transport) + .await + .is_err() + ); + assert_eq!( + restarted + .jobs + .lock_recover("test jobs") + .get(&id, now_secs()) + .unwrap() + .unwrap() + .status, + GatewayJobStatus::ReconciliationPending + ); } *response.lock_recover("test canonical response") = confirmed; - reconcile_pending_gateway_job_once(&restarted, &id, &transport).await.unwrap(); - reconcile_pending_gateway_job_once(&restarted, &id, &transport).await.unwrap(); - let completed = restarted.jobs.lock_recover("test jobs").get(&id, now_secs()).unwrap().unwrap(); + reconcile_pending_gateway_job_once(&restarted, &id, &transport) + .await + .unwrap(); + reconcile_pending_gateway_job_once(&restarted, &id, &transport) + .await + .unwrap(); + let completed = restarted + .jobs + .lock_recover("test jobs") + .get(&id, now_secs()) + .unwrap() + .unwrap(); assert_eq!(completed.status, GatewayJobStatus::Completed); assert_eq!(completed.result, Some(result)); - assert_eq!(completed.receipt.as_ref().unwrap().pointer("/reconciliation/settlement_feature"), Some(&feature)); - assert!(publisher.features.lock_recover("test published features").is_empty()); + assert_eq!( + completed + .receipt + .as_ref() + .unwrap() + .pointer("/reconciliation/settlement_feature"), + Some(&feature) + ); + assert!(publisher + .features + .lock_recover("test published features") + .is_empty()); assert!(deliveries.lock_recover("test deliveries").is_empty()); assert_eq!(restarted.ledger_balance_au(), balance); drop(restarted); - let reopened = GatewayState::fixture().with_receipt_user_seed(seed).with_job_store_dir(jobs_dir).unwrap(); - assert_eq!(reopened.jobs.lock_recover("test jobs").get(&id, now_secs()).unwrap().unwrap().status, GatewayJobStatus::Completed); + let reopened = GatewayState::fixture() + .with_receipt_user_seed(seed) + .with_job_store_dir(jobs_dir) + .unwrap(); + assert_eq!( + reopened + .jobs + .lock_recover("test jobs") + .get(&id, now_secs()) + .unwrap() + .unwrap() + .status, + GatewayJobStatus::Completed + ); server.abort(); } + #[test] + fn retained_contract_26_schema_11_receipt_keeps_exact_signed_envelope() { + let model = test_model(); + let invocation = test_invocation(); + let output = test_chat_output(); + let mut provider_receipt = + test_provider_receipt(&model, &test_chat_request(&model.id), &output, &invocation); + provider_receipt.body.schema_version = + mayhem_proto::RECOVERABLE_SESSION_RECEIPT_SCHEMA_VERSION; + provider_receipt.body.compute_ms = 0; + provider_receipt.body.capacity_slots = 0; + provider_receipt.enclave_sig = sign_hex( + &test_enclave_seed(), + &receipt_signing_bytes(&provider_receipt.body).unwrap(), + ); + let ack = receipt_ack_for_body(&test_user_seed(), &provider_receipt.body).unwrap(); + let mut feature = test_receipt_settlement_feature(&provider_receipt, &ack); + feature["value"]["contract_version"] = json!(CONTRACT_VERSION - 1); + let envelope = feature["value"]["receipt"].clone(); + feature["key"] = json!(record_usage_receipt_feature_key_from_envelope_for_contract( + &envelope, + CONTRACT_VERSION - 1, + ) + .unwrap()); + feature["value"]["provider_sig"] = json!(sign_hex( + &test_provider_seed(), + &record_usage_receipt_signing_bytes( + feature["key"].as_str().unwrap(), + &feature["value"], + ) + .unwrap(), + )); + + validate_stored_receipt_settlement_feature(&provider_receipt, &ack, &feature).unwrap(); + validate_receipt_settlement_feature_for_receipt(&provider_receipt, &ack, &feature).unwrap(); + assert_eq!(feature["value"]["receipt"], envelope); + assert!(envelope["body"].get("compute_ms").is_none()); + assert!(envelope["body"].get("capacity_slots").is_none()); + } + #[tokio::test] async fn contract_upgrade_resubmits_v191_context_receipts_without_rewriting() { - for recovery_version in [23, 24] { + for recovery_version in [23, 24, 25, 26] { let root = tempfile::tempdir().unwrap(); let jobs_dir = root.path().join("jobs"); let seed = test_user_seed(); @@ -48278,61 +52228,223 @@ mod tests { invocation.transport_peer = Some("ab".repeat(32)); invocation.job = Some(job); let output = test_chat_output(); - let mut provider_receipt = test_provider_receipt( - &model, &test_chat_request(&model.id), &output, &invocation, - ); + let mut provider_receipt = + test_provider_receipt(&model, &test_chat_request(&model.id), &output, &invocation); + // These contracts predate utilization fields and emitted schema 11. + provider_receipt.body.schema_version = + mayhem_proto::RECOVERABLE_SESSION_RECEIPT_SCHEMA_VERSION; + provider_receipt.body.compute_ms = 0; + provider_receipt.body.capacity_slots = 0; let billed_usage = provider_receipt.body.usage.clone(); - provider_receipt.body.usage_attribution.insert("context_input_tokens".into(), 1200); + provider_receipt + .body + .usage_attribution + .insert("context_input_tokens".into(), 1200); provider_receipt.enclave_sig = sign_hex( - &test_enclave_seed(), &receipt_signing_bytes(&provider_receipt.body).unwrap(), + &test_enclave_seed(), + &receipt_signing_bytes(&provider_receipt.body).unwrap(), ); let ack = receipt_ack_for_body(&seed, &provider_receipt.body).unwrap(); let result = chat_job_result(&output); stage_completed_invocation_job( - &invocation, result.clone(), &output.artifacts, &provider_receipt, &ack, + &invocation, + result.clone(), + &output.artifacts, + &provider_receipt, + &ack, ) .await .unwrap(); let mut feature = test_receipt_settlement_feature(&provider_receipt, &ack); feature["value"]["contract_version"] = json!(recovery_version); - assert!(validate_stored_receipt_settlement_feature(&provider_receipt, &ack, &feature).is_err()); - let historical_receipt = parse_record_usage_receipt_envelope(&feature["value"]["receipt"]).unwrap(); + assert!( + validate_stored_receipt_settlement_feature(&provider_receipt, &ack, &feature) + .is_err() + ); + let historical_receipt = + parse_record_usage_receipt_envelope(&feature["value"]["receipt"]).unwrap(); feature["key"] = json!(record_usage_receipt_feature_key_for_contract( - &historical_receipt, recovery_version, + &historical_receipt, + recovery_version, )); feature["value"]["provider_sig"] = json!(sign_hex( &test_provider_seed(), - &record_usage_receipt_signing_bytes(feature["key"].as_str().unwrap(), &feature["value"]).unwrap(), + &record_usage_receipt_signing_bytes( + feature["key"].as_str().unwrap(), + &feature["value"] + ) + .unwrap(), )); - assert!(validate_stored_receipt_settlement_feature(&provider_receipt, &ack, &feature).is_ok()); - assert!(validate_receipt_settlement_feature_for_receipt(&provider_receipt, &ack, &feature).is_err()); - invocation.job.as_ref().unwrap() - .persist_reconciliation_settlement_feature(feature.clone()).await.unwrap(); + assert!( + validate_stored_receipt_settlement_feature(&provider_receipt, &ack, &feature) + .is_ok() + ); + assert!(validate_receipt_settlement_feature_for_receipt( + &provider_receipt, + &ack, + &feature + ) + .is_ok()); + invocation + .job + .as_ref() + .unwrap() + .persist_reconciliation_settlement_feature(feature.clone()) + .await + .unwrap(); drop(invocation); drop(state); let publisher = Arc::new(RecordingReceiptSettlementPublisher::default()); let restarted = GatewayState::fixture() .with_receipt_user_seed(seed) - .with_job_store_dir(jobs_dir.clone()).unwrap() + .with_job_store_dir(jobs_dir.clone()) + .unwrap() .with_receipt_settlement_publisher(publisher.clone()); let deliveries = Arc::new(Mutex::new(Vec::new())); let transport = RecordingReceiptAckRecoveryTransport { - expected_ack: ack, feature: feature.clone(), deliveries: deliveries.clone(), + expected_ack: ack, + feature: feature.clone(), + deliveries: deliveries.clone(), }; let balance = restarted.ledger_balance_au(); - reconcile_pending_gateway_job_once(&restarted, &id, &transport).await.unwrap(); - reconcile_pending_gateway_job_once(&restarted, &id, &transport).await.unwrap(); - let completed = restarted.jobs.lock_recover("test jobs").get(&id, now_secs()).unwrap().unwrap(); + reconcile_pending_gateway_job_once(&restarted, &id, &transport) + .await + .unwrap(); + reconcile_pending_gateway_job_once(&restarted, &id, &transport) + .await + .unwrap(); + let completed = restarted + .jobs + .lock_recover("test jobs") + .get(&id, now_secs()) + .unwrap() + .unwrap(); assert_eq!(completed.status, GatewayJobStatus::Completed); assert_eq!(completed.result, Some(result)); - assert_eq!(completed.receipt.as_ref().unwrap().pointer("/reconciliation/settlement_feature"), Some(&feature)); - assert_eq!(*publisher.features.lock_recover("test published features"), vec![feature]); - assert!(deliveries.lock_recover("test deliveries").is_empty(), "no model or ACK redispatch"); + assert_eq!( + completed + .receipt + .as_ref() + .unwrap() + .pointer("/reconciliation/settlement_feature"), + Some(&feature) + ); + assert_eq!( + *publisher.features.lock_recover("test published features"), + vec![feature] + ); + assert!( + deliveries.lock_recover("test deliveries").is_empty(), + "no model or ACK redispatch" + ); assert_eq!(restarted.ledger_balance_au(), balance); assert_eq!(provider_receipt.body.usage, billed_usage); } } + #[tokio::test] + async fn checkpoint_recovery_requeues_only_contract_supported_features() { + for (contract_version, should_queue) in [ + (22_u32, false), + (23_u32, true), + (24_u32, true), + (25_u32, true), + ] { + let publisher = Arc::new(RecordingReceiptSettlementPublisher::default()); + let state = + GatewayState::fixture().with_receipt_settlement_publisher(publisher.clone()); + let model = test_model(); + let job = match prepare_gateway_job( + &state, + &HeaderMap::new(), + mayhem_proto::ENDPOINT_OPENAI_CHAT_COMPLETIONS, + &model.id, + &json!({"model": model.id, "messages": []}), + &None, + ) + .await + .unwrap() + { + PreparedGatewayJob::Started(job) => job, + _ => panic!("fresh job expected"), + }; + let id = job.id.clone(); + let mut invocation = test_invocation(); + invocation.transport_peer = Some("ab".repeat(32)); + invocation.job = Some(job.clone()); + let output = test_chat_output(); + let provider_receipt = test_provider_receipt_with_finality( + &model, + &test_chat_request(&model.id), + &output, + &invocation, + 1, + false, + ); + let ack = receipt_ack_for_body(&invocation.receipt_user_seed, &provider_receipt.body) + .unwrap(); + let mut feature = test_receipt_settlement_feature(&provider_receipt, &ack); + feature["value"]["contract_version"] = json!(contract_version); + let receipt = + parse_record_usage_receipt_envelope(&feature["value"]["receipt"]).unwrap(); + feature["key"] = json!(record_usage_receipt_feature_key_for_contract( + &receipt, + contract_version, + )); + feature["value"]["provider_sig"] = json!(sign_hex( + &test_provider_seed(), + &record_usage_receipt_signing_bytes( + feature["key"].as_str().unwrap(), + &feature["value"], + ) + .unwrap(), + )); + job.persist_reconciliation_pending( + None, + Vec::new(), + Some( + gateway_job_settled_receipt( + &invocation, + &provider_receipt, + &ack, + GatewayJobStatus::Cancelled, + Some("stream interrupted before terminal receipt".to_owned()), + Some("checkpoint".to_owned()), + ) + .unwrap(), + ), + Some("checkpoint reconciliation is pending".to_owned()), + ) + .await + .unwrap(); + job.persist_reconciliation_settlement_feature(feature.clone()) + .await + .unwrap(); + let transport = RecordingReceiptAckRecoveryTransport { + expected_ack: ack, + feature: feature.clone(), + deliveries: Arc::new(Mutex::new(Vec::new())), + }; + + let error = reconcile_pending_gateway_job_once(&state, &id, &transport) + .await + .unwrap_err(); + assert!(error.message.contains("canonical ledger access")); + assert_eq!( + publisher + .features + .lock_recover("test published features") + .as_slice(), + if should_queue { + std::slice::from_ref(&feature) + } else { + &[] + }, + "contract version {contract_version}", + ); + } + } + #[tokio::test] async fn restart_receipt_ack_recovery_is_bounded_per_pass() { let root = tempfile::tempdir().unwrap(); @@ -48388,9 +52500,10 @@ mod tests { .with_receipt_settlement_publisher(publisher.clone()); let transport = RecordingAnyReceiptAckRecoveryTransport::default(); - let first = reconcile_pending_gateway_jobs_pass(&restarted, &transport, 1, &mut String::new()) - .await - .unwrap(); + let first = + reconcile_pending_gateway_jobs_pass(&restarted, &transport, 1, &mut String::new()) + .await + .unwrap(); assert_eq!( first, GatewayReceiptAckRecoveryPass { @@ -48424,9 +52537,10 @@ mod tests { 1 ); - let second = reconcile_pending_gateway_jobs_pass(&restarted, &transport, 8, &mut String::new()) - .await - .unwrap(); + let second = + reconcile_pending_gateway_jobs_pass(&restarted, &transport, 8, &mut String::new()) + .await + .unwrap(); assert_eq!(second.pending, 2); assert_eq!(second.attempted, 2); assert_eq!(second.completed, 2); @@ -49158,6 +53272,47 @@ mod tests { } } + #[test] + fn embedding_spend_ceiling_covers_special_and_subword_tokens() { + let price = PriceRefAu { + denom: "au_usd".to_owned(), + ver: 1, + rate_map: vec![RateMapEntry { + unit: USAGE_INPUT_TOKEN.to_owned(), + per_unit_au: 60_000_000_000_000, + granularity: 1_000, + }], + per_req_au: 0, + min_session_au: 0, + derivation: None, + history: Vec::new(), + }; + let cases = [ + (vec!["a".to_owned()], 2), + ( + vec![ + "OpenMayhem embedding rollout smoke A".to_owned(), + "OpenMayhem embedding rollout smoke B".to_owned(), + ], + 16, + ), + ]; + + for (inputs, exact_provider_tokens) in cases { + let whitespace_ceiling = calculate_au_owed( + &price, + &ReceiptUsage::text(embedding_input_token_count(&inputs), 0), + ); + let exact_provider_receipt = + calculate_au_owed(&price, &ReceiptUsage::text(exact_provider_tokens, 0)); + assert!(exact_provider_receipt > whitespace_ceiling); + assert!( + exact_provider_receipt <= estimate_embedding_max_spend_au(&price, &inputs), + "exact provider token usage must fit the signed spend ceiling" + ); + } + } + #[test] fn automatic_music_duration_uses_the_signed_contract_ceiling() { let mut contract = mayhem_proto::endpoint_family_contract_template( @@ -50523,6 +54678,7 @@ mod tests { }], fingerprints_by_artifact_root: BTreeMap::new(), token_prefixes_by_artifact_root: BTreeMap::new(), + openai_compatible_artifact_roots: BTreeSet::new(), perceptual_hashes_by_artifact_root: BTreeMap::from([( "ab".repeat(32), BTreeMap::from([("fixed-workflow-image".to_owned(), expected_hash)]), @@ -50531,6 +54687,7 @@ mod tests { transcripts_by_artifact_root: BTreeMap::new(), audio_fingerprints_by_artifact_root: BTreeMap::new(), video_fingerprints_by_artifact_root: BTreeMap::new(), + decision_fingerprints_by_artifact_root: BTreeMap::new(), speciality_calibrations_by_artifact_root: BTreeMap::new(), default_fingerprint: None, default_token_prefixes: None, @@ -50539,6 +54696,7 @@ mod tests { default_transcripts: None, default_audio_fingerprints: None, default_video_fingerprints: None, + default_decision_fingerprints: None, }; let served_request = artifact_generation_request_with_workflow_policy( &model.id, @@ -50940,6 +55098,43 @@ mod tests { .any(|candidate| candidate.provider == providers[0])); } + #[tokio::test] + async fn sole_route_retries_pre_spend_admission_failure_without_cooling_provider() { + let model = test_routed_model(1); + let providers = Arc::new(Mutex::new(Vec::new())); + let billing_attempts = Arc::new(Mutex::new(Vec::new())); + let state = test_gateway_state_from_models(vec![model.clone()]).with_session_backend( + Arc::new(PreSpendAdmissionThenSuccessBackend { + providers: providers.clone(), + billing_attempts: billing_attempts.clone(), + }), + ); + let request = test_chat_request(&model.id); + + let run = + run_chat_with_route_retry(&state, &model, &request, GatewayRequestOptions::default()) + .await + .expect("a sole healthy route should recover from cold-session admission failure"); + + assert_eq!( + run.result.output.content.as_deref(), + Some("recovered before spend") + ); + let providers = providers.lock().expect("providers lock").clone(); + assert_eq!(providers.len(), 2); + assert_eq!(providers[0], providers[1]); + assert_eq!( + billing_attempts + .lock() + .expect("billing attempts lock") + .as_slice(), + &[0, 0] + ); + assert!( + !state.route_provider_in_cooloff(&model.mayhem.route_candidates[0], now_millis_u64()) + ); + } + #[test] fn buyer_local_direct_session_limits_are_modality_neutral_and_unpenalized() { let receive_rate = direct_session_transport_closed_error( @@ -51079,6 +55274,11 @@ mod tests { mayhem_proto::ENDPOINT_HF_TEXT_TO_AUDIO, true, ), + ( + "workflow", + mayhem_proto::ENDPOINT_MAYHEM_COMFY_WORKFLOWS, + true, + ), ]; for (collector, endpoint_family, collector_retryable) in collectors { @@ -51088,7 +55288,10 @@ mod tests { for (code, expected_class) in [ ("request_invalid", GatewaySessionFailureClass::BuyerLocal), - ("context_length_exceeded", GatewaySessionFailureClass::BuyerLocal), + ( + "context_length_exceeded", + GatewaySessionFailureClass::BuyerLocal, + ), ( "request_chunk_failed", GatewaySessionFailureClass::BuyerLocal, @@ -51106,10 +55309,12 @@ mod tests { &json!({ "t": "s.error", "code": code, - "message": "request-specific failure" + "message": "request-specific failure", + "receipt": {"settled": true} }), &format!("{collector} {endpoint_family} session"), collector_retryable, + false, ); assert_eq!(error.failure_class, expected_class, "{collector}"); assert!(!error.retryable, "{collector}"); @@ -51119,9 +55324,15 @@ mod tests { "{collector} cooled the route for {code}" ); let api_error = request_scoped_api_error(&error).expect("request-scoped API error"); - assert_eq!(api_error.status, - if code == "model_output_invalid" { StatusCode::BAD_GATEWAY } else { StatusCode::BAD_REQUEST }, - "{collector}"); + assert_eq!( + api_error.status, + if code == "model_output_invalid" { + StatusCode::BAD_GATEWAY + } else { + StatusCode::BAD_REQUEST + }, + "{collector}" + ); } let entry = state @@ -51142,6 +55353,7 @@ mod tests { }), &format!("{collector} {endpoint_family} session"), collector_retryable, + false, ); assert_eq!( provider_fault.failure_class, @@ -51170,6 +55382,60 @@ mod tests { } } + #[test] + fn lowercase_capacity_error_is_clean_only_before_execution_evidence() { + let collectors = [ + ("chat and multimodal chat", false), + ("embedding", true), + ("image", true), + ("speech", true), + ("transcription", true), + ("artifact and workflow", true), + ("video", true), + ]; + let frame = json!({ + "t": "s.error", + "code": "capacity", + "message": "provider lane filled before atomic admission" + }); + + for (collector, normally_retryable) in collectors { + let clean = provider_reported_session_error( + &frame, + &format!("{collector} session"), + normally_retryable, + false, + ); + assert!(clean.clean_refusal, "{collector}"); + assert_eq!(clean.clean_refusal_code.as_deref(), Some("CAPACITY")); + assert!(clean.retryable, "{collector}"); + assert!(!clean.safe_same_route_retry, "{collector}"); + + let after_output = provider_reported_session_error( + &frame, + &format!("{collector} session"), + normally_retryable, + true, + ); + assert!(!after_output.clean_refusal, "{collector}"); + assert!(!after_output.retryable, "{collector}"); + assert!(!after_output.before_first_output, "{collector}"); + assert!(!after_output.safe_same_route_retry, "{collector}"); + + let mut with_receipt = frame.clone(); + with_receipt["receipt"] = json!({"execution": "observed"}); + let after_receipt = provider_reported_session_error( + &with_receipt, + &format!("{collector} session"), + normally_retryable, + false, + ); + assert!(!after_receipt.clean_refusal, "{collector}"); + assert!(!after_receipt.retryable, "{collector}"); + assert!(!after_receipt.safe_same_route_retry, "{collector}"); + } + } + #[derive(Clone, Copy, Debug)] enum FocusedRouteRunner { Chat, @@ -51179,10 +55445,11 @@ mod tests { Transcription, AudioArtifact, VideoArtifact, + WorkflowArtifact, } impl FocusedRouteRunner { - const ALL: [Self; 7] = [ + const ALL: [Self; 8] = [ Self::Chat, Self::Embedding, Self::Image, @@ -51190,6 +55457,7 @@ mod tests { Self::Transcription, Self::AudioArtifact, Self::VideoArtifact, + Self::WorkflowArtifact, ]; fn modalities(self) -> &'static [&'static str] { @@ -51201,12 +55469,13 @@ mod tests { Self::Transcription => &["audio", "text"], Self::AudioArtifact => &["audio"], Self::VideoArtifact => &["video"], + Self::WorkflowArtifact => &["image"], } } } - fn focused_route_runner_model(runner: FocusedRouteRunner) -> GatewayModel { - let mut model = test_routed_model(1); + fn focused_route_runner_model(runner: FocusedRouteRunner, provider_count: u8) -> GatewayModel { + let mut model = test_routed_model(provider_count); let modalities = runner .modalities() .iter() @@ -51224,6 +55493,7 @@ mod tests { FocusedRouteRunner::Speech | FocusedRouteRunner::Transcription => "audio", FocusedRouteRunner::AudioArtifact => "audio", FocusedRouteRunner::VideoArtifact => "video", + FocusedRouteRunner::WorkflowArtifact => "image", FocusedRouteRunner::Chat => "text", } .to_owned(), @@ -51246,17 +55516,55 @@ mod tests { runner: FocusedRouteRunner, code: &'static str, ) -> (GatewayState, GatewayModel, ApiError) { - let model = focused_route_runner_model(runner); - let state = test_gateway_state_from_models(vec![model.clone()]) - .with_session_backend(Arc::new(ProviderReportedFailureBackend { code, attempts: Arc::default() })); + let (state, model, error, _) = + run_focused_route_runner_failure_with_options(runner, code, false, 1).await; + (state, model, error) + } + + async fn run_focused_route_runner_failure_with_options( + runner: FocusedRouteRunner, + code: &'static str, + failure_receipt: bool, + provider_count: u8, + ) -> (GatewayState, GatewayModel, ApiError, usize) { + let model = focused_route_runner_model(runner, provider_count); + let attempts = Arc::new(Mutex::new(0)); + let state = test_gateway_state_from_models(vec![model.clone()]).with_session_backend( + Arc::new(ProviderReportedFailureBackend { + code, + attempts: Arc::clone(&attempts), + failure_receipt, + }), + ); let options = GatewayRequestOptions { max_wait_ms: 0, ..GatewayRequestOptions::default() }; - let error = match runner { - FocusedRouteRunner::Chat => focused_route_runner_error( - run_chat_with_route_retry(&state, &model, &test_chat_request(&model.id), options) - .await, + let error = focused_route_runner_error( + run_focused_route_runner(runner, &state, &model, options).await, + ); + let attempt_count = *attempts.lock().unwrap(); + (state, model, error, attempt_count) + } + + fn discard_route_result(result: Result) -> Result<(), ApiError> { + result.map(|_| ()) + } + + async fn run_focused_route_runner( + runner: FocusedRouteRunner, + state: &GatewayState, + model: &GatewayModel, + options: GatewayRequestOptions, + ) -> Result<(), ApiError> { + match runner { + FocusedRouteRunner::Chat => discard_route_result( + run_chat_with_route_retry(state, model, &test_chat_request(&model.id), options) + .await.map(|run| { + assert_eq!(run.model.mayhem.price_ref_au.ver, run.invocation.price_ver, + "post-session metering/probes must retain the accepted catalog snapshot"); + run + }), ), FocusedRouteRunner::Embedding => { let inputs = vec!["embed this".to_owned()]; @@ -51269,9 +55577,12 @@ mod tests { endpoint_family: None, endpoint_request: None, }; - focused_route_runner_error( - run_embedding_with_route_retry(&state, &model, &request, &inputs, options) - .await, + discard_route_result( + run_embedding_with_route_retry(state, model, &request, &inputs, options).await.map(|run| { + assert_eq!(run.model.mayhem.price_ref_au.ver, run.invocation.price_ver, + "post-session metering/probes must retain the accepted catalog snapshot"); + run + }), ) } FocusedRouteRunner::Image => { @@ -51303,8 +55614,12 @@ mod tests { endpoint_family: None, endpoint_request: None, }; - focused_route_runner_error( - run_image_generation_with_route_retry(&state, &model, &request, options).await, + discard_route_result( + run_image_generation_with_route_retry(state, model, &request, options).await.map(|run| { + assert_eq!(run.model.mayhem.price_ref_au.ver, run.invocation.price_ver, + "post-session metering/probes must retain the accepted catalog snapshot"); + run + }), ) } FocusedRouteRunner::Speech => { @@ -51327,8 +55642,12 @@ mod tests { endpoint_family: None, endpoint_request: None, }; - focused_route_runner_error( - run_audio_speech_with_route_retry(&state, &model, &request, options).await, + discard_route_result( + run_audio_speech_with_route_retry(state, model, &request, options).await.map(|run| { + assert_eq!(run.model.mayhem.price_ref_au.ver, run.invocation.price_ver, + "post-session metering/probes must retain the accepted catalog snapshot"); + run + }), ) } FocusedRouteRunner::Transcription => { @@ -51347,35 +55666,61 @@ mod tests { endpoint_family: mayhem_proto::ENDPOINT_OPENAI_AUDIO_TRANSCRIPTIONS.to_owned(), contract_request: json!({}), }; - focused_route_runner_error( - run_audio_transcription_with_route_retry(&state, &model, &request, options) - .await, + discard_route_result( + run_audio_transcription_with_route_retry(state, model, &request, options).await.map(|run| { + assert_eq!(run.model.mayhem.price_ref_au.ver, run.invocation.price_ver, + "post-session metering/probes must retain the accepted catalog snapshot"); + run + }), ) } - FocusedRouteRunner::AudioArtifact | FocusedRouteRunner::VideoArtifact => { + FocusedRouteRunner::AudioArtifact + | FocusedRouteRunner::VideoArtifact + | FocusedRouteRunner::WorkflowArtifact => { let is_video = matches!(runner, FocusedRouteRunner::VideoArtifact); + let is_workflow = matches!(runner, FocusedRouteRunner::WorkflowArtifact); let request = ArtifactGenerationRequest { model: model.id.clone(), prompt: if is_video { "a precise test video" + } else if is_workflow { + "a precise test workflow artifact" } else { "a precise test audio artifact" } .to_owned(), endpoint_family: if is_video { mayhem_proto::ENDPOINT_OPENAI_VIDEOS + } else if is_workflow { + mayhem_proto::ENDPOINT_MAYHEM_COMFY_WORKFLOWS } else { mayhem_proto::ENDPOINT_HF_TEXT_TO_AUDIO } .to_owned(), contract_request: json!({}), workflow: None, - workflow_output: None, + workflow_output: is_workflow.then(|| WorkflowOutputBinding { + output_modalities: vec!["image".to_owned()], + metrics: BTreeMap::from([ + ("artifact_count".to_owned(), 1), + ("width".to_owned(), 64), + ("height".to_owned(), 64), + ]), + }), workflow_input_files: WorkflowInputFileStats::default(), effective_specialities: BTreeMap::new(), - output_modality: if is_video { "video" } else { "audio" }.to_owned(), + output_modality: if is_video { + "video" + } else if is_workflow { + "image" + } else { + "audio" + } + .to_owned(), transport_kind: if is_video { "video_generation" + } else if is_workflow { + "workflow_generation" } else { "audio_generation" } @@ -51394,15 +55739,601 @@ mod tests { input_audio_count: 0, input_audio_max_bytes: 0, input_audio_max_seconds: 0, - response_format: "mp4".to_owned(), + response_format: if is_workflow { "artifact" } else { "mp4" }.to_owned(), }; - focused_route_runner_error( - run_artifact_generation_with_route_retry(&state, &model, &request, options) - .await, + discard_route_result( + run_artifact_generation_with_route_retry(state, model, &request, options).await.map(|run| { + assert_eq!(run.model.mayhem.price_ref_au.ver, run.invocation.price_ver, + "post-session metering/probes must retain the accepted catalog snapshot"); + run + }), ) } + } + } + + #[derive(Debug)] + struct PriceAttempt { + session_id: String, + provider_pubkey: Option, + price_ver: u64, + spend_voucher: SpendVoucher, + } + + #[derive(Debug)] + struct PriceTransitionBackend { + expected_price_ver: u64, + partial_first: bool, + attempts: Arc>>, + chat: Arc, + } + + impl PriceTransitionBackend { + fn admit(&self, invocation: &GatewaySessionInvocation) -> Result<(), GatewaySessionError> { + self.attempts.lock().unwrap().push(PriceAttempt { + session_id: invocation.session_id.clone(), + provider_pubkey: invocation.provider_pubkey.clone(), + price_ver: invocation.price_ver, + spend_voucher: invocation.spend_voucher.clone(), + }); + if invocation.price_ver != self.expected_price_ver + && !(self.partial_first && self.attempts.lock().unwrap().len() == 1) + { + return Err(provider_reject_session_error( + &json!({ + "t": "s.reject", "code": "PRICE_VER", "reason": "price version mismatch" + }), + &invocation.session_id, + ) + .into_safe_same_route_retry()); + } + Ok(()) + } + } + + impl GatewaySessionBackend for PriceTransitionBackend { + fn name(&self) -> &str { + "price-transition" + } + fn run_chat<'a>( + &'a self, + model: &'a GatewayModel, + request: &'a ChatCompletionRequest, + invocation: &'a GatewaySessionInvocation, + ) -> GatewaySessionFuture<'a> { + Box::pin(async move { + self.admit(invocation)?; + self.chat.run_chat(model, request, invocation).await + }) + } + fn run_embedding<'a>( + &'a self, + _model: &'a GatewayModel, + request: &'a EmbeddingRequest, + invocation: &'a GatewaySessionInvocation, + ) -> GatewayEmbeddingFuture<'a> { + Box::pin(async move { + self.admit(invocation)?; + let inputs = embedding_input_texts(request).unwrap(); + let count = rough_tokens(&embedding_prompt_text(&inputs)); + Ok(GatewayEmbeddingResult { + output: EmbeddingOutput { + embeddings: inputs.iter().map(|_| vec![1.0, 0.0]).collect(), + usage: Usage { + prompt_tokens: count, + completion_tokens: 0, + total_tokens: count, + }, + }, + backend: self.name().to_owned(), + direct_session: true, + provider_receipt: None, + quality: None, + }) + }) + } + fn run_image_generation<'a>( + &'a self, + model: &'a GatewayModel, + request: &'a ImageGenerationRequest, + invocation: &'a GatewaySessionInvocation, + ) -> GatewayImageGenerationFuture<'a> { + Box::pin(async move { + self.admit(invocation)?; + ArtifactGenerationSuccessBackend + .run_image_generation(model, request, invocation) + .await + }) + } + fn run_audio_speech<'a>( + &'a self, + _model: &'a GatewayModel, + _request: &'a AudioSpeechRequest, + invocation: &'a GatewaySessionInvocation, + ) -> GatewayAudioSpeechFuture<'a> { + Box::pin(async move { + self.admit(invocation)?; + let bytes = test_wav_with_samples(8_000); + Ok(GatewayAudioSpeechResult { + output: AudioSpeechOutput { + artifacts: vec![GatewayArtifactOutput { + id: "speech".to_owned(), + content_type: "audio/wav".to_owned(), + blake3: blake3_hex(&bytes), + bytes, + }], + usage: ReceiptUsage::from_units([(USAGE_AUDIO_SECOND, 1)]), + }, + backend: self.name().to_owned(), + direct_session: true, + provider_receipt: None, + quality: None, + }) + }) + } + fn run_audio_transcription<'a>( + &'a self, + _model: &'a GatewayModel, + request: &'a AudioTranscriptionRequest, + invocation: &'a GatewaySessionInvocation, + ) -> GatewayAudioTranscriptionFuture<'a> { + Box::pin(async move { + self.admit(invocation)?; + Ok(GatewayAudioTranscriptionResult { + output: AudioTranscriptionOutput { + transcription: TranscriptionResult::text("test audio"), + usage: audio_transcription_usage_for_request(request), + }, + backend: self.name().to_owned(), + direct_session: true, + provider_receipt: None, + quality: None, + }) + }) + } + fn run_artifact_generation<'a>( + &'a self, + model: &'a GatewayModel, + request: &'a ArtifactGenerationRequest, + invocation: &'a GatewaySessionInvocation, + ) -> GatewayArtifactGenerationFuture<'a> { + Box::pin(async move { + self.admit(invocation)?; + ArtifactGenerationSuccessBackend + .run_artifact_generation(model, request, invocation) + .await + }) + } + } + + fn price_transition_backend(price_ver: u64) -> Arc { + Arc::new(PriceTransitionBackend { + expected_price_ver: price_ver, + partial_first: false, + attempts: Arc::new(Mutex::new(Vec::new())), + chat: Arc::new(SuccessBackend { + providers: Arc::new(Mutex::new(Vec::new())), + }), + }) + } + + fn refreshed_price_model(model: &GatewayModel, multiplier: u128) -> GatewayModel { + let mut model = model.clone(); + model.mayhem.price_ref_au.ver += 1; + for rate in &mut model.mayhem.price_ref_au.rate_map { + rate.per_unit_au *= multiplier; + } + for route in &mut model.mayhem.route_candidates { + route.price_ver = model.mayhem.price_ref_au.ver; + route.price_ref_au = None; + } + model + } + + fn serve_one_catalog_refresh( + state: GatewayState, + model: GatewayModel, + ) -> tokio::task::JoinHandle<()> { + tokio::spawn(async move { + tokio::time::timeout( + Duration::from_secs(5), + state.wait_for_catalog_refresh_request(), + ) + .await + .expect("route must request catalog refresh"); + state.replace_model_catalog(vec![model.clone()]); + state.complete_catalog_refresh(); + }) + } + + #[tokio::test] + async fn price_transition_recovers_all_route_runners_and_preserves_billing() { + for runner in FocusedRouteRunner::ALL { + for provider_count in [1, 2] { + for rail in ["fiat", "tnk", "tap"] { + let model = focused_route_runner_model(runner, provider_count); + let refreshed = refreshed_price_model(&model, 1); + let backend = price_transition_backend(refreshed.mayhem.price_ref_au.ver); + let state = test_gateway_state_from_models(vec![model.clone()]) + .with_session_backend(backend.clone()) + .with_receipt_rail(rail); + let refresh = serve_one_catalog_refresh(state.clone(), refreshed); + let result = run_focused_route_runner( + runner, + &state, + &model, + GatewayRequestOptions::default(), + ) + .await; + assert!( + result.is_ok(), + "{runner:?}, {provider_count}: {:?}", + result.err() + ); + refresh.await.unwrap(); + let attempts = backend.attempts.lock().unwrap(); + assert_eq!(attempts.len(), 2, "{runner:?}"); + assert_ne!(attempts[0].session_id, attempts[1].session_id); + assert_eq!(attempts[0].price_ver + 1, attempts[1].price_ver); + assert_eq!( + attempts[0].spend_voucher.body.billing_id, + attempts[1].spend_voucher.body.billing_id + ); + assert_eq!( + attempts[0].spend_voucher.body.billing_attempt, + attempts[1].spend_voucher.body.billing_attempt + ); + assert_eq!( + attempts[0].spend_voucher.body.locked_rate_map, + attempts[1].spend_voucher.body.locked_rate_map + ); + if provider_count == 2 { + assert_ne!( + attempts[0].provider_pubkey, attempts[1].provider_pubkey, + "{runner:?}: untried provider must remain available after refresh" + ); + } + assert_eq!(attempts[1].spend_voucher.body.rail, rail); + assert!(state.wallet_spend.lock().unwrap().reservations.is_empty()); + assert!(state.receipts().is_empty()); + assert!(state.reputation_events().is_empty()); + for route in &model.mayhem.route_candidates { + assert!( + !state.route_provider_in_cooloff(route, now_millis_u64()), + "{runner:?}" + ); + } + } + } + } + } + + #[tokio::test] + async fn price_transition_does_not_bypass_explicit_price_ceiling() { + let model = test_routed_model(1); + let refreshed = refreshed_price_model(&model, 100); + let backend = price_transition_backend(refreshed.mayhem.price_ref_au.ver); + let state = test_gateway_state_from_models(vec![model.clone()]) + .with_session_backend(backend.clone()); + let refresh = serve_one_catalog_refresh(state.clone(), refreshed); + let request = test_chat_request(&model.id); + let options = GatewayRequestOptions { + max_price_au: Some(rate_gate_basis_au( + &model.mayhem.price_ref_au.rate_map, + model.mayhem.price_ref_au.per_req_au, + model.mayhem.price_ref_au.min_session_au, + )), + ..GatewayRequestOptions::default() }; - (state, model, error) + let result = run_chat_with_route_retry(&state, &model, &request, options).await; + assert!(result.is_err()); + refresh.await.unwrap(); + assert_eq!( + backend.attempts.lock().unwrap().len(), + 1, + "must not sign a voucher beyond caller's ceiling" + ); + assert!(state.receipts().is_empty()); + } + + #[tokio::test] + async fn price_transition_refresh_coalesces_waiters_and_honors_cancellation() { + let model = test_model(); + let state = test_gateway_state_from_models(vec![model.clone()]); + let cancellation = GatewayRequestCancellation::new(); + let mut invocation = test_invocation(); + invocation.client_cancellation = Some(cancellation.clone()); + let deadline = RouteWaitDeadline::new(5_000); + let refresh = refresh_model_after_price_refusal(&state, &model, &invocation, deadline); + let drive = async { + state.wait_for_catalog_refresh_request().await; + let generation = state.request_catalog_refresh(); + assert_eq!(generation, state.request_catalog_refresh()); + assert!(tokio::time::timeout( + Duration::from_millis(10), + state.wait_for_catalog_refresh_request() + ) + .await + .is_err()); + cancellation.cancel(); + }; + let (result, _) = tokio::join!(refresh, drive); + assert_eq!( + result.unwrap_err().status, + StatusCode::from_u16(499).unwrap() + ); + state.complete_catalog_refresh(); + assert!(state.request_catalog_refresh() > 0); + tokio::time::timeout( + Duration::from_millis(50), + state.wait_for_catalog_refresh_request(), + ) + .await + .unwrap(); + } + + #[tokio::test] + async fn price_transition_preserves_receipted_partial_and_only_reprices_new_usage() { + let model = test_routed_model(2); + let refreshed = refreshed_price_model(&model, 2); + let backend = Arc::new(PriceTransitionBackend { + expected_price_ver: refreshed.mayhem.price_ref_au.ver, + partial_first: true, + attempts: Arc::new(Mutex::new(Vec::new())), + chat: Arc::new(PartialThenSuccessBackend { + attempts: Arc::new(Mutex::new(Vec::new())), + providers: Arc::new(Mutex::new(Vec::new())), + }), + }); + let state = test_gateway_state_from_models(vec![model.clone()]) + .with_session_backend(backend.clone()); + let refresh = serve_one_catalog_refresh(state.clone(), refreshed); + let mut request = test_chat_request(&model.id); + request.max_tokens = Some(8); + let run = + run_chat_with_route_retry(&state, &model, &request, GatewayRequestOptions::default()) + .await + .unwrap(); + refresh.await.unwrap(); + assert_eq!(run.result.output.content.as_deref(), Some("hello world")); + let attempts = backend.attempts.lock().unwrap(); + assert_eq!(attempts.len(), 3); + let refused = &attempts[1].spend_voucher.body; + let resumed = &attempts[2].spend_voucher.body; + assert!(resumed.billing_prior_au_owed_cum > 0); + assert_eq!(refused.billing_prior_usage, resumed.billing_prior_usage); + assert_eq!( + refused.billing_prior_au_owed_cum, + resumed.billing_prior_au_owed_cum + ); + assert_eq!(refused.billing_attempt, resumed.billing_attempt); + assert_eq!(resumed.billing_attempt, 1); + let receipt = run.result.provider_receipt.as_ref().unwrap(); + let increment = + ReceiptUsage::saturating_delta(&resumed.billing_prior_usage, &receipt.body.usage); + assert_eq!( + receipt.body.au_owed_cum, + resumed.billing_prior_au_owed_cum + + crate::pricing::usage_map_au(&resumed.locked_rate_map, &increment) + ); + assert_eq!( + state.receipts().len(), + 1, + "only the completed partial is recorded so far" + ); + } + + #[tokio::test] + async fn price_transition_streaming_admission_reopens_with_refreshed_signed_voucher() { + use futures_util::{SinkExt, StreamExt}; + use tokio_tungstenite::tungstenite::Message; + let mut model = test_model(); + let contract = test_invocation().attestation.unwrap().contract; + let mut route = test_route_candidate(0); + route.provider = verifying_key_hex(&test_provider_seed()); + route.enclave_id = contract.enclave_id; + route.admin_pubkey = contract.admin_pubkey; + route.artifact_root = contract.artifact_root; + route.artifact_sidecar_roots = contract.artifact_sidecar_roots; + route.manifest_hash = contract.manifest_hash; + route.binary_hash = contract.binary_hash; + model.mayhem.route_candidates = vec![route]; + let refreshed = refreshed_price_model(&model, 1); + let new_version = refreshed.mayhem.price_ref_au.ver; + let state = test_gateway_state_from_models(vec![model.clone()]); + let refresh = serve_one_catalog_refresh(state.clone(), refreshed.clone()); + let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap(); + let address = listener.local_addr().unwrap(); + let server_state = state.clone(); + let request = test_chat_request(&model.id); + let server_request = request.clone(); + let server = tokio::spawn(async move { + let mut opens = Vec::new(); + for attempt in 0..2 { + let (stream, _) = listener.accept().await.unwrap(); + let mut socket = tokio_tungstenite::accept_async(stream).await.unwrap(); + while let Some(Ok(message)) = socket.next().await { + let Ok(text) = message.to_text() else { + continue; + }; + let wire: Value = serde_json::from_str(text).unwrap(); + let response_type = match wire["type"].as_str().unwrap() { + "auth" => "auth_ok", + "session_subscribe" => "session_subscribed", + "peer_connect" => "peer_connected", + "session_open" => "session_opened", + "session_send" => "session_sent", + "session_close" => "session_closed", + other => panic!("unexpected bridge operation {other}"), + }; + socket + .send(Message::Text( + json!({"id":wire["id"], "type":response_type, + "direct":true, "relayed":false}) + .to_string() + .into(), + )) + .await + .unwrap(); + if wire["type"] != "session_send" { + continue; + } + match wire["frame"]["t"].as_str().unwrap() { + "s.open" => { + let open = &wire["frame"]; + opens.push(open.clone()); + let frame = if attempt == 0 { + assert_ne!(open["price_ver"], new_version); + json!({"t":"s.reject", "session_id":open["session_id"], + "code":"PRICE_VER", "reason":"price version mismatch"}) + } else { + assert_eq!(open["price_ver"], new_version); + let mut invocation = server_state + .prepare_chat_invocation_for_route( + &refreshed, + &server_request, + Some(&refreshed.mayhem.route_candidates[0]), + &GatewayRequestOptions::default(), + ) + .unwrap(); + invocation.session_id = + open["session_id"].as_str().unwrap().to_owned(); + let nonce = open["att_nonce"].as_str().unwrap().to_owned(); + let report = test_attestation_report_with_mutation( + &invocation, + nonce.clone(), + |report| { + report.report_ts = now_secs(); + }, + ); + let mut accept = json!({"t":"s.accept", "v":1, + "contract_version":invocation.contract_version, + "session_id":invocation.session_id, + "open_head":session_frame_head(open).unwrap(), "att_nonce":nonce, + "att_report":report, "ts":now_secs(), "nonce":"ab".repeat(32)}); + sign_accept_frame(&mut accept); + validate_direct_session_accept( + &accept, + &invocation, + &session_frame_head(open).unwrap(), + open["att_nonce"].as_str().unwrap(), + now_secs(), + ) + .unwrap(); + accept + }; + socket + .send(Message::Text( + json!({"type":"session_frame", "remote":wire["remote"], + "session_id":wire["session_id"], "frame":frame}) + .to_string() + .into(), + )) + .await + .unwrap(); + } + "s.req" => { + assert_eq!( + attempt, 1, + "never send inference work after the price refusal" + ); + return opens; + } + "s.close" => {} + other => panic!("unexpected session frame {other}"), + } + } + } + panic!("fresh price admission never dispatched work"); + }); + let session = tokio::time::timeout( + Duration::from_secs(10), + prepare_live_direct_chat_session( + Arc::new(state.clone()), + model.clone(), + request, + GatewayRequestOptions::default(), + "stream-price-transition".to_owned(), + now_secs(), + ScBridgeGatewaySessionConfig::new(format!("ws://{address}"), "test-token"), + ), + ) + .await + .unwrap() + .unwrap(); + assert_eq!(session.invocation.price_ver, new_version); + let opens = server.await.unwrap(); + refresh.await.unwrap(); + assert_eq!(opens.len(), 2); + assert_ne!(opens[0]["session_id"], opens[1]["session_id"]); + assert!(opens[0]["voucher"]["billing_id"].is_string()); + assert_eq!( + opens[0]["voucher"]["billing_id"], + opens[1]["voucher"]["billing_id"] + ); + assert_eq!( + opens[0]["voucher"]["billing_attempt"], + opens[1]["voucher"]["billing_attempt"] + ); + assert!( + !state.route_provider_in_cooloff(&model.mayhem.route_candidates[0], now_millis_u64()) + ); + } + + #[tokio::test] + async fn price_transition_unchanged_catalog_is_bounded_and_keeps_provider_healthy() { + let model = test_routed_model(1); + let backend = price_transition_backend(model.mayhem.price_ref_au.ver + 1); + let state = test_gateway_state_from_models(vec![model.clone()]) + .with_session_backend(backend.clone()); + let watcher_state = state.clone(); + let watcher = tokio::spawn(async move { + loop { + watcher_state.wait_for_catalog_refresh_request().await; + watcher_state.complete_catalog_refresh(); + } + }); + let error = focused_route_runner_error( + run_chat_with_route_retry( + &state, + &model, + &test_chat_request(&model.id), + GatewayRequestOptions::default(), + ) + .await, + ); + watcher.abort(); + assert_eq!(public_error_code(&error), "catalog_price_refresh_pending"); + assert_eq!(error.status, StatusCode::SERVICE_UNAVAILABLE); + assert_eq!( + backend.attempts.lock().unwrap().len(), + usize::from(DEFAULT_MAX_OPEN_ATTEMPTS) + ); + assert!(state.wallet_spend.lock().unwrap().reservations.is_empty()); + assert!( + !state.route_provider_in_cooloff(&model.mayhem.route_candidates[0], now_millis_u64()) + ); + } + + #[tokio::test] + async fn price_transition_missing_refresh_expires_without_provider_penalty() { + let model = test_routed_model(1); + let backend = price_transition_backend(model.mayhem.price_ref_au.ver + 1); + let state = test_gateway_state_from_models(vec![model.clone()]) + .with_session_backend(backend.clone()); + let options = GatewayRequestOptions { + max_wait_ms: 10, + ..GatewayRequestOptions::default() + }; + let error = focused_route_runner_error( + run_chat_with_route_retry(&state, &model, &test_chat_request(&model.id), options).await, + ); + assert_eq!(public_error_code(&error), "catalog_price_refresh_pending"); + assert_eq!(error.status, StatusCode::SERVICE_UNAVAILABLE); + assert_eq!(backend.attempts.lock().unwrap().len(), 1); + assert!( + !state.route_provider_in_cooloff(&model.mayhem.route_candidates[0], now_millis_u64()) + ); } #[tokio::test] @@ -51434,6 +56365,43 @@ mod tests { } } + #[tokio::test] + async fn request_and_model_output_failures_do_not_penalize_any_route_runner() { + for (code, status, public_code) in [ + ( + "request_invalid", + StatusCode::BAD_REQUEST, + "request_rejected_by_provider_contract", + ), + ( + "model_output_invalid", + StatusCode::BAD_GATEWAY, + "provider_model_output_invalid", + ), + ] { + for runner in FocusedRouteRunner::ALL { + let (state, model, error) = run_focused_route_runner_failure(runner, code).await; + assert_eq!(error.status, status, "{runner:?} {code}"); + assert_eq!(public_error_code(&error), public_code, "{runner:?} {code}"); + let route = &model.mayhem.route_candidates[0]; + assert!( + !state.route_provider_in_cooloff(route, now_millis_u64()), + "{runner:?} cooled the route for {code}" + ); + let entry = state + .provider_table + .lock_recover("provider table") + .entries(now_millis_u64()) + .into_iter() + .find(|entry| entry.key == route_key(route)) + .expect("route remains in provider table"); + assert_eq!(entry.observed.samples, 0, "{runner:?} {code}"); + assert_eq!(entry.observed.consecutive_failures, 0, "{runner:?} {code}"); + assert!(state.reputation_events().is_empty(), "{runner:?} {code}"); + } + } + } + #[tokio::test] async fn provider_faults_still_penalize_every_route_runner() { for runner in FocusedRouteRunner::ALL { @@ -51457,6 +56425,99 @@ mod tests { } } + #[tokio::test] + async fn settled_failure_receipt_is_terminal_for_every_route_runner() { + for runner in FocusedRouteRunner::ALL { + let (state, model, error, attempts) = run_focused_route_runner_failure_with_options( + runner, + "provider_response_failed", + true, + 2, + ) + .await; + assert_eq!(attempts, 1, "{runner:?} retried a settled failure"); + assert_eq!(error.status, StatusCode::BAD_GATEWAY, "{runner:?}"); + assert_eq!(public_error_code(&error), "provider_error", "{runner:?}"); + assert_eq!( + public_error_category(&error), + "provider_response", + "{runner:?}" + ); + assert!(!public_error_retryable(&error), "{runner:?}"); + let entries = state + .provider_table + .lock_recover("provider table") + .entries(now_millis_u64()); + assert_eq!( + entries + .iter() + .filter(|entry| { + model + .mayhem + .route_candidates + .iter() + .any(|route| entry.key == route_key(route)) + }) + .map(|entry| entry.observed.consecutive_failures) + .sum::(), + 1, + "{runner:?} must penalize exactly the provider that returned the failure receipt" + ); + } + } + + #[tokio::test] + async fn settled_failure_receipt_preserves_request_scoped_codes_for_every_route_runner() { + for (code, expected_public_code, expected_status) in [ + ( + "request_invalid", + "request_rejected_by_provider_contract", + StatusCode::BAD_REQUEST, + ), + ( + "context_length_exceeded", + "context_length_exceeded", + StatusCode::BAD_REQUEST, + ), + ( + "request_chunk_failed", + "request_media_reassembly_failed", + StatusCode::BAD_REQUEST, + ), + ( + "request_reassembly_failed", + "request_media_reassembly_failed", + StatusCode::BAD_REQUEST, + ), + ( + "model_output_invalid", + "provider_model_output_invalid", + StatusCode::BAD_GATEWAY, + ), + ] { + for runner in FocusedRouteRunner::ALL { + let (state, model, error, attempts) = + run_focused_route_runner_failure_with_options(runner, code, true, 2).await; + assert_eq!(attempts, 1, "{runner:?} retried {code}"); + assert_eq!(error.status, expected_status, "{runner:?} {code}"); + assert_eq!( + public_error_code(&error), + expected_public_code, + "{runner:?} {code}" + ); + assert!(!public_error_retryable(&error), "{runner:?} {code}"); + assert!( + model + .mayhem + .route_candidates + .iter() + .all(|route| { !state.route_provider_in_cooloff(route, now_millis_u64()) }), + "{runner:?} cooled a route for {code}" + ); + } + } + } + #[test] fn provider_reject_session_error_marks_self_protection_codes_clean() { for code in [ @@ -51465,6 +56526,7 @@ mod tests { "RATE", "QUOTA", "PRICE_FLOOR", + "PRICE_VER", "DRAINING", "BALANCE", "EXECUTION_MODE", @@ -51495,6 +56557,23 @@ mod tests { assert_eq!(err.clean_refusal_code, None); } + #[test] + fn route_attempt_summary_keeps_payment_failure_over_generic_failure() { + let mut error = None; + retain_most_specific_route_attempt_error( + &mut error, + "spend reservation did not complete before serving".to_owned(), + ); + retain_most_specific_route_attempt_error( + &mut error, + "provider failed without a classified reason".to_owned(), + ); + assert_eq!( + route_attempt_error_code(error.as_deref()).0, + "payment_reservation_failed" + ); + } + #[test] fn balance_reject_aborts_all_route_attempts_with_payment_required() { let err = provider_reject_session_error( @@ -51526,7 +56605,6 @@ mod tests { for reason in [ "spend reservation did not complete within the 90000 ms provider admission budget; no work was served", "contract spend reservation rejected before serving: Mayhem feature relay accepted the append but no canonical result appeared before the relay result budget.", - "provider has no active verified fiat payout binding: missing payout binding", ] { let err = provider_reject_session_error( &json!({ @@ -51538,9 +56616,33 @@ mod tests { ); assert!( terminal_balance_refusal(&err).is_none(), - "{reason} must stay route-retryable" + "{reason} is not an insufficient-credit rejection" ); + assert!(!err.retryable, "{reason} must not create reservation #2"); + assert!(!err.clean_refusal, "{reason} is outcome-ambiguous"); } + + let payout = provider_reject_session_error( + &json!({ + "t": "s.reject", + "code": "BALANCE", + "reason": "provider has no active verified fiat payout binding: missing payout binding", + }), + "session-a", + ); + assert!(payout.retryable); + assert!(payout.clean_refusal); + + let pending = provider_reject_session_error( + &json!({ + "t": "s.reject", + "code": "RESERVATION_PENDING", + "reason": "exact reservation is pending", + }), + "session-a", + ); + assert!(!pending.retryable); + assert!(!pending.clean_refusal); } #[test] @@ -51854,23 +56956,47 @@ mod tests { for pending in [Ok(false), Err("unreadable durable evidence".to_owned())] { let publisher: Arc = Arc::new(PendingPublisher(pending)); - assert!(!wait_for_pending_receipt_settlement( - Some(&publisher), "buyer", "fiat", RouteWaitDeadline::new(5000), - ).await); + assert!( + !wait_for_pending_receipt_settlement( + Some(&publisher), + "buyer", + "fiat", + RouteWaitDeadline::new(5000), + ) + .await + ); } let publisher: Arc = Arc::new(PendingPublisher(Ok(true))); - assert!(!wait_for_pending_receipt_settlement( - Some(&publisher), "buyer", "fiat", RouteWaitDeadline::new(0), - ).await); + assert!( + !wait_for_pending_receipt_settlement( + Some(&publisher), + "buyer", + "fiat", + RouteWaitDeadline::new(0), + ) + .await + ); let started = Instant::now(); - assert!(wait_for_pending_receipt_settlement( - Some(&publisher), "buyer", "fiat", RouteWaitDeadline::new(5000), - ).await); + assert!( + wait_for_pending_receipt_settlement( + Some(&publisher), + "buyer", + "fiat", + RouteWaitDeadline::new(5000), + ) + .await + ); assert!(started.elapsed() >= Duration::from_secs(1)); - assert!(!wait_for_pending_receipt_settlement( - Some(&publisher), "buyer", "fiat", RouteWaitDeadline::new(20), - ).await); + assert!( + !wait_for_pending_receipt_settlement( + Some(&publisher), + "buyer", + "fiat", + RouteWaitDeadline::new(20), + ) + .await + ); } #[test] @@ -51910,43 +57036,45 @@ mod tests { #[tokio::test] async fn capacity_and_balance_semantics_cover_every_non_streaming_endpoint_runner() { for runner in FocusedRouteRunner::ALL { - let (state, model, capacity_error) = - run_focused_route_runner_failure(runner, "CAPACITY").await; - assert_eq!( - capacity_error.status, - StatusCode::SERVICE_UNAVAILABLE, - "{runner:?}" - ); - assert_eq!( - public_error_code(&capacity_error), - "provider_admission_no_capacity", - "{runner:?}" - ); - assert_eq!( - public_error_category(&capacity_error), - "provider_admission", - "{runner:?}" - ); - assert!(public_error_retryable(&capacity_error), "{runner:?}"); - let route = &model.mayhem.route_candidates[0]; - assert!( - !state.route_provider_in_cooloff(route, now_millis_u64()), - "{runner:?} cooled an honest capacity refusal" - ); - let entry = state - .provider_table - .lock_recover("provider table") - .entries(now_millis_u64()) - .into_iter() - .find(|entry| entry.key == route_key(route)) - .expect("route remains in provider table"); - assert_eq!(entry.observed.samples, 0, "{runner:?}"); - assert_eq!(entry.observed.consecutive_failures, 0, "{runner:?}"); - assert!(state - .wallet_spend - .lock_recover("gateway wallet spend state") - .reservations - .is_empty()); + for code in ["CAPACITY", "capacity"] { + let (state, model, capacity_error) = + run_focused_route_runner_failure(runner, code).await; + assert_eq!( + capacity_error.status, + StatusCode::SERVICE_UNAVAILABLE, + "{runner:?} {code}" + ); + assert_eq!( + public_error_code(&capacity_error), + "provider_admission_no_capacity", + "{runner:?} {code}" + ); + assert_eq!( + public_error_category(&capacity_error), + "provider_admission", + "{runner:?} {code}" + ); + assert!(public_error_retryable(&capacity_error), "{runner:?} {code}"); + let route = &model.mayhem.route_candidates[0]; + assert!( + !state.route_provider_in_cooloff(route, now_millis_u64()), + "{runner:?} cooled an honest {code} refusal" + ); + let entry = state + .provider_table + .lock_recover("provider table") + .entries(now_millis_u64()) + .into_iter() + .find(|entry| entry.key == route_key(route)) + .expect("route remains in provider table"); + assert_eq!(entry.observed.samples, 0, "{runner:?} {code}"); + assert_eq!(entry.observed.consecutive_failures, 0, "{runner:?} {code}"); + assert!(state + .wallet_spend + .lock_recover("gateway wallet spend state") + .reservations + .is_empty()); + } let (_, _, balance_error) = run_focused_route_runner_failure(runner, "BALANCE").await; assert_eq!( @@ -52315,6 +57443,51 @@ mod tests { assert_eq!(restored, body); } + #[test] + fn tokenize_request_frames_chunk_large_context_under_transport_limit() { + let request = json!({ + "contract_request": { + "messages": [{"role": "user", "content": "context ".repeat(40_000)}] + } + }); + let invocation = GatewayTokenizeInvocation { + session_id: "11".repeat(32), + provider_pubkey: "22".repeat(32), + transport_peer: "33".repeat(32), + enclave_id: "44".repeat(32), + room_id: "55".repeat(16), + model: "qwen/example".to_owned(), + served_ctx: 262_144, + request: request.clone(), + return_tokens: false, + }; + let frames = tokenize_request_frames( + &invocation, + DEFAULT_SESSION_MAX_FRAME_BYTES, + 4 * 1024 * 1024, + ) + .unwrap(); + + assert!(frames.len() > 2); + assert!(frames.iter().all(|frame| { + session_frame_json_len(frame).expect("frame serializes") + <= DEFAULT_SESSION_MAX_FRAME_BYTES + })); + assert!(frames[..frames.len() - 1].iter().all(|frame| { + frame.get("t").and_then(Value::as_str) == Some(TOKENIZE_REQUEST_CHUNK_FRAME_TYPE) + })); + let final_frame: TokenizeRequestFrame = + serde_json::from_value(frames.last().unwrap().clone()).unwrap(); + assert!(final_frame.request.is_none()); + let manifest = final_frame.request_ref.unwrap(); + let chunks = frames[..frames.len() - 1] + .iter() + .map(|frame| serde_json::from_value::(frame["chunk"].clone()).unwrap()) + .collect::>(); + let restored = reassemble_json_payload(&manifest, &chunks).unwrap(); + assert_eq!(restored, request); + } + #[test] fn direct_session_request_budget_expands_to_the_admitted_body() { let body_bytes = DEFAULT_SESSION_MAX_REASSEMBLED_PAYLOAD_BYTES + 32 * 1024 * 1024; @@ -52831,6 +58004,8 @@ mod tests { locked_per_req_au: invocation.spend_voucher.body.locked_per_req_au, locked_min_session_au: invocation.spend_voucher.body.locked_min_session_au, served_ctx: invocation.served_ctx, + compute_ms: 1, + capacity_slots: 1, ctx_bracket: invocation.ctx_bracket.clone(), ctx_bracket_table_ver: invocation.ctx_bracket_table_ver, rules_ver: invocation.rules_ver, @@ -52885,6 +58060,8 @@ mod tests { locked_per_req_au: invocation.spend_voucher.body.locked_per_req_au, locked_min_session_au: invocation.spend_voucher.body.locked_min_session_au, served_ctx: invocation.served_ctx, + compute_ms: 1, + capacity_slots: 1, ctx_bracket: invocation.ctx_bracket.clone(), ctx_bracket_table_ver: invocation.ctx_bracket_table_ver, rules_ver: invocation.rules_ver, @@ -52955,6 +58132,8 @@ mod tests { locked_per_req_au: invocation.spend_voucher.body.locked_per_req_au, locked_min_session_au: invocation.spend_voucher.body.locked_min_session_au, served_ctx: invocation.served_ctx, + compute_ms: 1, + capacity_slots: 1, ctx_bracket: invocation.ctx_bracket.clone(), ctx_bracket_table_ver: invocation.ctx_bracket_table_ver, rules_ver: invocation.rules_ver, diff --git a/crates/mayhem-gateway/src/openai/dashboard_pages.rs b/crates/mayhem-gateway/src/openai/dashboard_pages.rs index 357f3b6a..a8fe4177 100644 --- a/crates/mayhem-gateway/src/openai/dashboard_pages.rs +++ b/crates/mayhem-gateway/src/openai/dashboard_pages.rs @@ -6868,7 +6868,7 @@ mod tests { ) -> StoredReceipt { let voucher = SpendVoucher { body: SpendVoucherBody { - schema_version: SESSION_RECEIPT_SCHEMA_VERSION, + schema_version: SPEND_VOUCHER_SCHEMA_VERSION, session_id: session_id.to_owned(), billing_id: format!("billing-{session_id}"), billing_attempt: 0, @@ -6927,6 +6927,8 @@ mod tests { locked_per_req_au: 0, locked_min_session_au: 0, served_ctx: 4_096, + compute_ms: 1, + capacity_slots: 1, ctx_bracket: None, ctx_bracket_table_ver: None, rules_ver: 1, diff --git a/crates/mayhem-gateway/src/openai/dashboard_workbench.rs b/crates/mayhem-gateway/src/openai/dashboard_workbench.rs index 2e84afef..823b1700 100644 --- a/crates/mayhem-gateway/src/openai/dashboard_workbench.rs +++ b/crates/mayhem-gateway/src/openai/dashboard_workbench.rs @@ -1034,7 +1034,7 @@ fn scale_state() -> GatewayState { .route_candidates .first() .expect("scale workbench model has a provider route"); - state.record_probe(workbench_probe( + state.record_workbench_probe(workbench_probe( model, candidate, format!("workbench-scale-probe-{:02}", index + 1), @@ -1054,7 +1054,32 @@ fn showcase_state_with_receipts(model_count: usize, receipt_count: usize) -> Gat fn workbench_playground_models(count: usize) -> Vec { let mut models = workbench_models(count); + let embedded = GatewayState::from_embedded_catalog().models_snapshot(); + if let Some(model) = models.get_mut(1) { + if let Some(template) = embedded + .iter() + .find(|candidate| candidate.id == "google/gemma-4-E4B-it") + { + model.id = template.id.clone(); + model.owned_by = template.owned_by.clone(); + model.mayhem.family = template.mayhem.family.clone(); + model.mayhem.model_class = template.mayhem.model_class.clone(); + model.mayhem.adapter = template.mayhem.adapter.clone(); + model.mayhem.caps = template.mayhem.caps.clone(); + model.mayhem.sampling = template.mayhem.sampling.clone(); + for candidate in &mut model.mayhem.route_candidates { + candidate.served_modalities = model.mayhem.caps.output_modalities.clone(); + } + } + } if let Some(model) = models.get_mut(2) { + if let Some(template) = embedded + .iter() + .find(|candidate| candidate.id == "tongyi/z-image-turbo") + { + model.mayhem.adapter = template.mayhem.adapter.clone(); + model.mayhem.caps = template.mayhem.caps.clone(); + } model.id = "tongyi/z-image-turbo".to_owned(); model.owned_by = "Tongyi-MAI".to_owned(); model.mayhem.family = "z-image".to_owned(); @@ -1069,6 +1094,13 @@ fn workbench_playground_models(count: usize) -> Vec { } } if let Some(model) = models.get_mut(3) { + if let Some(template) = embedded + .iter() + .find(|candidate| candidate.id == "ResembleAI/chatterbox") + { + model.mayhem.adapter = template.mayhem.adapter.clone(); + model.mayhem.caps = template.mayhem.caps.clone(); + } model.id = "hexgrad/kokoro-82m".to_owned(); model.owned_by = "Hexgrad".to_owned(); model.mayhem.family = "kokoro".to_owned(); @@ -1159,7 +1191,7 @@ fn showcase_state_from_models_with_receipts( .first() .map(|candidate| (model, candidate)) }) { - state.record_probe(workbench_probe( + state.record_workbench_probe(workbench_probe( model, candidate, "workbench-probe-latest".to_owned(), @@ -1287,11 +1319,17 @@ fn workbench_fixture_dir(name: &str) -> PathBuf { fn workbench_models(count: usize) -> Vec { let embedded = GatewayState::from_embedded_catalog().models_snapshot(); - let seeds = if embedded.is_empty() { - GatewayState::fixture().models_snapshot() + let mut seeds = if embedded.is_empty() { + GatewayState::fixture().models_snapshot().as_ref().clone() } else { - embedded + embedded.as_ref().clone() }; + if let Some(index) = seeds + .iter() + .position(|model| model.id == "Qwen/Qwen3.8-27B") + { + seeds.swap(0, index); + } (0..count) .map(|index| { let mut model = seeds[index % seeds.len()].clone(); @@ -1408,6 +1446,7 @@ fn fixture_candidate( price_ref_au: Some(model.mayhem.price_ref_au.clone()), min_ask_au: 0, att_tier: tier, + enclave_att_tier: Some(if tier == 4 { 1 } else { tier }), quant: if route_index % 2 == 0 { "int4" } else { "int8" }.to_owned(), served_ctx: Some(model.mayhem.caps.ctx), hardware_fingerprint: Some(hex_fill(0x90_u8.wrapping_add(seed))), @@ -1478,19 +1517,22 @@ fn fixture_price(base: &PriceRefAu, model_index: usize) -> PriceRefAu { .collect::>(); json!({ "epoch": epoch, - "price_source": "market_activity_momentum", + "price_source": "market_utilization", "ctx_bracket": if epoch % 4 == 0 { "32k" } else { "base" }, "usage": { "active_demand_au": (650_000_000_000_000_000_u128 + u128::from(epoch - 40) * 95_000_000_000_000_000).to_string(), "settled_usage": { "input_token": (4_000 + (epoch - 40) * 700).to_string() }, + "compute_ms": (1_800_000 + (epoch - 40) * 120_000).to_string(), + "capacity_slot_count": 1, + "legacy_receipt_count": 0, "session_count": 7 + epoch - 40, }, "controller": { - "source": "canonical_settled_work", + "source": "canonical_signed_slot_time", "active_supply": 3 + model_index, - "momentum_bps": 9_500 + (epoch - 40) * 155, - "activity_basis": "relative_dimension_vector_v1", - "frozen": false, + "utilization_bps": 5_000 + (epoch - 40) * 250, + "multiplier_bps": if epoch >= 52 { 11_000 } else { 10_000 }, + "activity_basis": "signed_slot_time_v1", }, "seed_price": {"ver": 1, "rate_map": historical_rates}, "result_price": {"ver": epoch, "rate_map": historical_rates}, @@ -1590,7 +1632,7 @@ fn fixture_receipt( let usage = ReceiptUsage::text(640 + index as u64 * 175, 210 + index as u64 * 95); let cost = 120_000_000_000_000_000_u128.saturating_add(index as u128 * 47_000_000_000_000_000); let voucher_body = SpendVoucherBody { - schema_version: SESSION_RECEIPT_SCHEMA_VERSION, + schema_version: SPEND_VOUCHER_SCHEMA_VERSION, session_id: session_id.clone(), billing_id: session_id.clone(), billing_attempt: 0, @@ -1614,6 +1656,7 @@ fn fixture_receipt( served_ctx: model.mayhem.caps.ctx, required_modalities: Vec::new(), required_specialities: BTreeMap::new(), + workflow: None, ctx_bracket: Some("base".to_owned()), ctx_bracket_table_ver: Some(1), max_spend_au: 5 * AU_PER_USD, @@ -1646,9 +1689,13 @@ fn fixture_receipt( locked_per_req_au: model.mayhem.price_ref_au.per_req_au, locked_min_session_au: model.mayhem.price_ref_au.min_session_au, served_ctx: model.mayhem.caps.ctx, + compute_ms: 1, + capacity_slots: 1, ctx_bracket: Some("base".to_owned()), ctx_bracket_table_ver: Some(1), rules_ver: 1, + workflow: None, + workflow_output: None, usage, usage_attribution: BTreeMap::from([( "reasoning_tokens".to_owned(), diff --git a/crates/mayhem-gateway/src/openai/durable_streaming_tests.rs b/crates/mayhem-gateway/src/openai/durable_streaming_tests.rs index e06df5f7..695f7c91 100644 --- a/crates/mayhem-gateway/src/openai/durable_streaming_tests.rs +++ b/crates/mayhem-gateway/src/openai/durable_streaming_tests.rs @@ -93,6 +93,31 @@ fn adapt(events: SseEventStream, family: &str, model: String) -> ChatResponse { } } +#[tokio::test] +async fn idempotency_lookup_recovers_non_streaming_jobs_without_changing_stream_receipt_rules() { + let root = tempfile::tempdir().unwrap(); + let state = GatewayState::from_models(vec![model()]) + .with_job_store_dir(root.path().join("jobs")) + .unwrap(); + let app = openai_router(state.clone()); + for family in [ + mayhem_proto::ENDPOINT_OPENAI_EMBEDDINGS, + mayhem_proto::ENDPOINT_MAYHEM_DECISIONS, + mayhem_proto::ENDPOINT_OPENAI_IMAGE_GENERATIONS, + ] { + let headers = headers(family); + let raw = json!({"model": model().id, "input": "test"}); + let job = start(&state, family, &raw, &headers).await; + let found = app.clone().oneshot(lookup_request(family, &headers)).await.unwrap(); + assert_eq!(found.status(), StatusCode::ACCEPTED, "{family}"); + let found = body(found).await; + assert_eq!(found["id"], job.id, "{family}"); + assert_eq!(found["endpoint_family"], family, "{family}"); + assert!(!durable_streaming_endpoint_family(family)); + } + assert!(!lookupable_gateway_job_family("unknown_family")); +} + #[tokio::test] async fn content_precedes_receipt_and_terminal_waits_for_durable_ack_on_all_surfaces() { for (_, family) in SURFACES { @@ -434,22 +459,49 @@ async fn checkpoint_evidence_survives_failure_restart_and_history_rotation() { .body .final_receipt ); - assert!(reconcile_pending_gateway_job_once(&restarted, &job.id, &NoDelivery).await.is_err()); - assert_eq!(restarted.jobs.lock_recover("jobs").get(&job.id, now_secs()).unwrap().unwrap().status, - GatewayJobStatus::ReconciliationPending, "a partial receipt alone is not terminal accounting"); + assert!( + reconcile_pending_gateway_job_once(&restarted, &job.id, &NoDelivery) + .await + .is_err() + ); + assert_eq!( + restarted + .jobs + .lock_recover("jobs") + .get(&job.id, now_secs()) + .unwrap() + .unwrap() + .status, + GatewayJobStatus::ReconciliationPending, + "a partial receipt alone is not terminal accounting" + ); let proof = failure_recovery::test_closed_proof(&receipt, &ack); - let app = axum::Router::new().route("/v1/state", axum::routing::get( - move |axum::extract::Query(query): axum::extract::Query>| { - let proof = proof.clone(); - async move { - let key = query.get("key").unwrap(); - axum::Json(proof.as_object().unwrap().values().find(|state| state["key"].as_str() == Some(key.as_str())).unwrap().clone()) - } - })); + let app = axum::Router::new().route( + "/v1/state", + axum::routing::get( + move |axum::extract::Query(query): axum::extract::Query>| { + let proof = proof.clone(); + async move { + let key = query.get("key").unwrap(); + axum::Json( + proof + .as_object() + .unwrap() + .values() + .find(|state| state["key"].as_str() == Some(key.as_str())) + .unwrap() + .clone(), + ) + } + }, + ), + ); let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap(); let address = listener.local_addr().unwrap(); let server = tokio::spawn(async move { axum::serve(listener, app).await.unwrap() }); - let restarted = restarted.with_canary_probe_contract_rpc(PeerRpcClient::new(format!("http://{address}/v1")).unwrap()); + let restarted = restarted.with_canary_probe_contract_rpc( + PeerRpcClient::new(format!("http://{address}/v1")).unwrap(), + ); let publisher = Arc::new(TestPublisher::default()); let restarted = restarted.with_receipt_settlement_publisher(publisher.clone()); reconcile_pending_gateway_job_once(&restarted, &job.id, &NoDelivery) diff --git a/crates/mayhem-gateway/src/openai/failure_recovery.rs b/crates/mayhem-gateway/src/openai/failure_recovery.rs index 374e503c..cc5d1065 100644 --- a/crates/mayhem-gateway/src/openai/failure_recovery.rs +++ b/crates/mayhem-gateway/src/openai/failure_recovery.rs @@ -4,6 +4,232 @@ use mayhem_proto::{ usage_reservation_close_signing_bytes, usage_reservation_close_value, }; +const LEDGER_RESERVATION_SWEEP_LIMIT: usize = 32; +const LEDGER_RESERVATION_SWEEP_INTERVAL: Duration = Duration::from_secs(30); + +#[derive(Debug, Default, Eq, PartialEq)] +pub(super) struct LedgerReservationSweepPass { + pub(super) discovered: usize, + pub(super) eligible: usize, + pub(super) submitted: usize, + pub(super) already_closed: usize, + pub(super) finalized: usize, +} + +async fn confirmed_at( + rpc: &PeerRpcClient, + key: &str, + signed_length: Option, +) -> Result { + let result = rpc + .state_at(Some(key), Some(true), signed_length) + .await + .map_err(|error| GatewaySessionError::retryable(error.to_string()))?; + if result["confirmed"] != true + || result["key"] != key + || signed_length.is_some_and(|expected| result["signed_length"] != expected) + { + return Err(GatewaySessionError::retryable( + "reservation sweep requires one exact confirmed ledger view", + )); + } + Ok(result) +} + +/// Expire reservations from canonical session shards rather than relying only +/// on the gateway job vault. The shard is sufficient signed authority for the +/// buyer to close its own expired hold after the provider grace period, so a +/// lost/replaced local store cannot strand credit forever. +pub(super) async fn sweep_ledger_reservations_once( + state: &GatewayState, + rpc: &PeerRpcClient, + limit: usize, +) -> Result { + let user = verifying_key_hex(&state.receipt_config.user_seed); + let rail = state.receipt_config.rail.as_str(); + if let Some(publisher) = state.receipt_settlement_publisher.as_ref().as_ref() { + if publisher + .has_pending_final_receipts(&user, rail) + .map_err(GatewaySessionError::retryable)? + { + return Ok(LedgerReservationSweepPass::default()); + } + } + + let epoch = confirmed_at(rpc, "epoch/apply/state", None).await?; + let current_epoch = epoch["value"]["updated_epoch"] + .as_u64() + .ok_or_else(|| GatewaySessionError::retryable("canonical epoch is unavailable"))?; + let signed_length = epoch["signed_length"] + .as_u64() + .ok_or_else(|| GatewaySessionError::retryable("canonical signed length is unavailable"))?; + let prefix = format!("hold/targeted-session/{rail}/{user}/"); + let mut after = None::; + let mut pass = LedgerReservationSweepPass::default(); + + loop { + let page = rpc + .state_prefix_page( + &prefix, + Some(true), + Some(500), + Some(signed_length), + after.as_deref(), + ) + .await + .map_err(|error| GatewaySessionError::retryable(error.to_string()))?; + if page["confirmed"] != true + || page["prefix"] != prefix + || page["signed_length"] != signed_length + { + return Err(GatewaySessionError::retryable( + "reservation sweep prefix snapshot changed", + )); + } + let entries = page["values"].as_array().ok_or_else(|| { + GatewaySessionError::retryable("reservation sweep returned an invalid state page") + })?; + for entry in entries { + let key = entry["key"].as_str().unwrap_or_default(); + let session = &entry["value"]; + if !key.starts_with(&prefix) + || session["type"] != "targeted_spend_session" + || session["user"] != user + || session["rail"] != rail + { + return Err(GatewaySessionError::new( + "reservation sweep returned an invalid session shard", + )); + } + pass.discovered += 1; + let Some(eligible_epoch) = session["reservation_expires_after_epoch"] + .as_u64() + .and_then(|expiry| { + session["reservation_receipt_grace_epochs"] + .as_u64() + .and_then(|grace| expiry.checked_add(grace)) + }) + else { + return Err(GatewaySessionError::new( + "reservation sweep found an invalid expiry", + )); + }; + if current_epoch < eligible_epoch { + continue; + } + pass.eligible += 1; + if pass.submitted >= limit.max(1) { + continue; + } + let id = session["reservation_id"] + .as_str() + .filter(|id| is_lower_hex_len(id, 64)) + .ok_or_else(|| GatewaySessionError::new("invalid reservation identity"))?; + let reservation = confirmed_at( + rpc, + &format!("receipt/reservation/{id}"), + Some(signed_length), + ) + .await?; + if reservation["value"]["status"] == "closed" { + pass.already_closed += 1; + continue; + } + if reservation["value"]["status"] != "active" + || !reservation_binding_matches(session, &reservation["value"]) + { + return Err(GatewaySessionError::new( + "reservation sweep binding does not match canonical state", + )); + } + let billing_id = session["billing_id"] + .as_str() + .filter(|id| is_lower_hex_len(id, 64)) + .ok_or_else(|| GatewaySessionError::new("invalid reservation billing identity"))?; + let billing_attempt = session["billing_attempt"] + .as_u64() + .ok_or_else(|| GatewaySessionError::new("invalid reservation billing attempt"))?; + let head = confirmed_at( + rpc, + &format!("receipt/head/{billing_id}/{billing_attempt}"), + Some(signed_length), + ) + .await?; + if head["value"]["settlement_ready"] == true + || head["value"]["receipt"]["body"]["final"] == true + { + pass.finalized += 1; + continue; + } + let mut value = usage_reservation_close_value( + session, + (!head["value"].is_null()).then_some(&head["value"]), + true, + now_secs(), + "gateway_ledger_sweep", + ) + .map_err(GatewaySessionError::new)?; + value["actor_sig"] = json!(sign_hex( + &state.receipt_config.user_seed, + &usage_reservation_close_signing_bytes(&value).map_err(GatewaySessionError::new)?, + )); + let feature = + usage_reservation_close_feature(value).map_err(GatewaySessionError::new)?; + let response = rpc + .submit_feature(feature) + .await + .map_err(|error| GatewaySessionError::retryable(error.to_string()))?; + if response["ok"] != true { + return Err(GatewaySessionError::retryable( + "canonical reservation expiry submission remains pending", + )); + } + pass.submitted += 1; + } + + if page["truncated"] != true { + break; + } + let cursor = page["next_cursor"] + .as_str() + .filter(|cursor| { + entries.last().and_then(|entry| entry["key"].as_str()) == Some(*cursor) + }) + .ok_or_else(|| GatewaySessionError::retryable("reservation sweep cursor is invalid"))?; + if after.as_deref().is_some_and(|previous| previous >= cursor) { + return Err(GatewaySessionError::retryable( + "reservation sweep cursor did not advance", + )); + } + after = Some(cursor.to_owned()); + } + Ok(pass) +} + +pub(super) fn spawn_ledger_reservation_sweep(state: &GatewayState) { + let Some(rpc) = state.canary_probe_contract_rpc.as_ref().as_ref().cloned() else { + return; + }; + let state = state.clone(); + tokio::spawn(async move { + loop { + match sweep_ledger_reservations_once(&state, &rpc, LEDGER_RESERVATION_SWEEP_LIMIT).await + { + Ok(pass) if pass.submitted > 0 => eprintln!( + "Gateway canonical reservation sweep submitted {} of {} eligible holds", + pass.submitted, pass.eligible + ), + Ok(_) => {} + Err(error) => eprintln!( + "Gateway canonical reservation sweep remains pending: {}", + error.message + ), + } + tokio::time::sleep(LEDGER_RESERVATION_SWEEP_INTERVAL).await; + } + }); +} + pub(super) fn persist_reservation( invocation: &GatewaySessionInvocation, ) -> Result<(), GatewaySessionError> { diff --git a/crates/mayhem-gateway/src/openai/incremental_output.rs b/crates/mayhem-gateway/src/openai/incremental_output.rs index 95198f47..024061ea 100644 --- a/crates/mayhem-gateway/src/openai/incremental_output.rs +++ b/crates/mayhem-gateway/src/openai/incremental_output.rs @@ -1,4 +1,4 @@ -use super::{ChatCompletionRequest, GatewaySessionError, ToolCallOutput, Value, json}; +use super::{json, ChatCompletionRequest, GatewaySessionError, ToolCallOutput, Value}; /// Presentation only: receipt accounting continues to use the exact evidence /// bytes, including native delimiters, and never charges this second view twice. @@ -292,11 +292,9 @@ mod tests { json!({"index":0,"name":"write","arguments":"{"}), json!({"index":0,"id":"x","name":"write","arguments":"x".repeat(1024)}), ] { - assert!( - ToolStream::default() - .push(&json!({"tool_calls_delta":[delta]}), &request, 1024) - .is_err() - ); + assert!(ToolStream::default() + .push(&json!({"tool_calls_delta":[delta]}), &request, 1024) + .is_err()); } let mut stream = ToolStream::default(); stream diff --git a/crates/mayhem-gateway/src/openai/response_stream.rs b/crates/mayhem-gateway/src/openai/response_stream.rs index 9538ecd8..b9ff71ac 100644 --- a/crates/mayhem-gateway/src/openai/response_stream.rs +++ b/crates/mayhem-gateway/src/openai/response_stream.rs @@ -1,6 +1,6 @@ use super::{ - AtomicU64, BTreeMap, Ordering, SseEventStream, StreamExt, Value, VecDeque, json, make_id, - now_secs, stream, + json, make_id, now_secs, stream, AtomicU64, BTreeMap, Ordering, SseEventStream, StreamExt, + Value, VecDeque, }; fn response_item_id(prefix: &str) -> String { @@ -81,8 +81,15 @@ impl ResponseStream { fn push(&mut self, chunk: Value) -> VecDeque { if let Some(error) = chunk.get("error") { return self.fail_with_error( - error.get("message").and_then(Value::as_str).unwrap_or("stream failed"), - error.get("code").and_then(Value::as_str).filter(|code| !code.is_empty()).unwrap_or("server_error"), + error + .get("message") + .and_then(Value::as_str) + .unwrap_or("stream failed"), + error + .get("code") + .and_then(Value::as_str) + .filter(|code| !code.is_empty()) + .unwrap_or("server_error"), error.get("retryable").and_then(Value::as_bool), ); } @@ -325,11 +332,9 @@ mod tests { "response.reasoning_text.delta" ); assert_eq!(events.back().unwrap()["delta"], "Consider "); - assert!( - !events - .iter() - .any(|event| event["type"] == "response.completed") - ); + assert!(!events + .iter() + .any(|event| event["type"] == "response.completed")); events.extend(adapter.push(chunk( json!({"reasoning_content":"the choices."}), Value::Null, @@ -340,11 +345,9 @@ mod tests { assert_eq!(output[0]["type"], "reasoning"); assert_eq!(output[0]["content"][0]["text"], "Consider the choices."); assert_eq!(output[1]["content"][0]["text"], "Hello"); - assert!( - events - .iter() - .any(|event| event["type"] == "response.reasoning_text.done") - ); + assert!(events + .iter() + .any(|event| event["type"] == "response.reasoning_text.done")); } fn chunk(delta: Value, finish: Value) -> Value { @@ -367,11 +370,9 @@ mod tests { ))); let meta = json!({"billable": true, "receipt": {"session_id": "server-session", "au_owed_cum": "123"}}); events.extend(adapter.push(json!({"choices": [], "usage": {"prompt_tokens": 7, "completion_tokens": 5, "total_tokens": 12}, "mayhem": meta}))); - assert!( - !events - .iter() - .any(|event| event["type"] == "response.completed") - ); + assert!(!events + .iter() + .any(|event| event["type"] == "response.completed")); events.extend(adapter.finish()); for (sequence, event) in events.iter().enumerate() { assert_eq!(event["sequence_number"], sequence); @@ -425,11 +426,9 @@ mod tests { events.back().unwrap()["response"]["incomplete_details"]["reason"], expected ); - assert!( - !events - .iter() - .any(|event| event["type"] == "response.completed") - ); + assert!(!events + .iter() + .any(|event| event["type"] == "response.completed")); } let mut adapter = ResponseStream::new("model".to_owned()); adapter.push(chunk(json!({"content": "partial"}), Value::Null)); diff --git a/crates/mayhem-gateway/src/provider_table.rs b/crates/mayhem-gateway/src/provider_table.rs index e6ddb89d..dc328974 100644 --- a/crates/mayhem-gateway/src/provider_table.rs +++ b/crates/mayhem-gateway/src/provider_table.rs @@ -24,6 +24,10 @@ pub const DEFAULT_UNDERDELIVERY_EVENT_STREAK: u32 = 2; pub const DEFAULT_THROUGHPUT_FACTOR_FLOOR: f64 = 0.1; pub const DEFAULT_TRANSIENT_THROUGHPUT_FACTOR_FLOOR: f64 = 0.5; pub const DEFAULT_LLM_GENERATION_FLOOR_TOK_S: f64 = 5.0; +/// A measured throughput sample is an admission hint, not a permanent route +/// verdict. Providers keep publishing fresh heartbeat performance, so a stale +/// gateway-local sample must eventually yield to that live signal. +pub const DEFAULT_THROUGHPUT_OBSERVATION_TTL_MILLIS: u64 = 60_000; pub const DEFAULT_LLM_PREFILL_FLOOR_TOK_S: f64 = 100.0; pub const DEFAULT_EMBEDDING_INPUT_TOKENS_FLOOR_PER_S: f64 = 10.0; pub const DEFAULT_IMAGE_FLOOR_IMAGES_PER_S: f64 = 1.0 / 300.0; @@ -114,6 +118,8 @@ pub struct ProviderObservation { pub ewma_ttft_ms: Option, pub ewma_tok_s: Option, #[serde(default, skip_serializing_if = "Option::is_none")] + pub throughput_observed_at_millis: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] pub ewma_throughput_ratio: Option, pub ewma_error_rate: f64, #[serde(default)] @@ -185,6 +191,11 @@ impl ProviderTableEntry { pub struct BaselineRouteRequirements { pub current_rules_ver: u64, pub requires_transport_peer: bool, + /// Autoregressive generation routes must prove that their runtime prefix + /// cache is enabled. Other endpoints may still bill output tokens without + /// owning a reusable KV cache (for example typed decision classifiers). + #[serde(default)] + pub requires_prefix_caching: bool, pub now_millis: u64, pub max_attestation_head_age_millis: u64, pub heartbeat_ttl_millis: u64, @@ -213,6 +224,8 @@ pub struct RequestRequirements { pub current_rules_ver: u64, pub min_reputation: f64, pub requires_transport_peer: bool, + #[serde(default)] + pub requires_prefix_caching: bool, pub requires_tools: bool, pub requires_json: bool, pub requires_vision: bool, @@ -418,6 +431,7 @@ impl Default for BaselineRouteRequirements { Self { current_rules_ver: 1, requires_transport_peer: false, + requires_prefix_caching: false, now_millis: 0, max_attestation_head_age_millis: DEFAULT_ATTESTATION_HEAD_MAX_AGE_MILLIS, heartbeat_ttl_millis: DEFAULT_PROVIDER_HEARTBEAT_TTL_MILLIS, @@ -477,6 +491,7 @@ impl From<&RequestRequirements> for BaselineRouteRequirements { Self { current_rules_ver: request.current_rules_ver, requires_transport_peer: request.requires_transport_peer, + requires_prefix_caching: request.requires_prefix_caching, now_millis: request.now_millis, max_attestation_head_age_millis: request.max_attestation_head_age_millis, heartbeat_ttl_millis: request.heartbeat_ttl_millis, @@ -514,6 +529,7 @@ impl Default for RequestRequirements { current_rules_ver: 1, min_reputation: 0.0, requires_transport_peer: false, + requires_prefix_caching: false, requires_tools: false, requires_json: false, requires_vision: false, @@ -696,6 +712,7 @@ impl ProviderTable { .filter(|value| value.is_finite() && *value >= 0.0) { observed.ewma_tok_s = Some(update_ewma(observed.ewma_tok_s, tok_s, alpha)); + observed.throughput_observed_at_millis = Some(now_millis); if let Some(advertised_tok_s) = advertised_tok_s { let ratio = (tok_s / advertised_tok_s).clamp(0.0, 10.0); observed.ewma_throughput_ratio = @@ -906,10 +923,17 @@ pub fn baseline_route_state( { return BaselineRouteState::HeartbeatStale; } - // The signed catalog's output-token pricing identifies generation routes. - // Use contract data so a heartbeat cannot evade this by hiding text capability. - if entry.contract.ref_rate_map.iter().chain(&entry.contract.rate_map) - .any(|rate| rate.unit == mayhem_proto::USAGE_OUTPUT_TOKEN) + // The endpoint contract identifies autoregressive generation. Its signed + // output-token tariff then prevents a heartbeat from evading this check by + // hiding text capability. Output-token billing alone is insufficient: + // typed decision endpoints are not KV-cache generation runtimes. + if requirements.requires_prefix_caching + && entry + .contract + .ref_rate_map + .iter() + .chain(&entry.contract.rate_map) + .any(|rate| rate.unit == mayhem_proto::USAGE_OUTPUT_TOKEN) && heartbeat.prefix_caching != Some(true) { return BaselineRouteState::PrefixCachingRequired; @@ -1065,7 +1089,9 @@ pub fn evaluate_eligibility( .min_throughput .filter(|value| value.is_finite() && *value > 0.0) { - if effective_throughput(entry).is_some_and(|throughput| throughput < floor) { + if effective_throughput(entry, request.now_millis) + .is_some_and(|throughput| throughput < floor) + { return Err(IneligibilityReason::ThroughputFloor); } } @@ -1275,10 +1301,19 @@ fn effective_ttft_ms(entry: &ProviderTableEntry) -> f64 { .unwrap_or(1.0) } -fn effective_throughput(entry: &ProviderTableEntry) -> Option { +fn effective_throughput(entry: &ProviderTableEntry, now_millis: u64) -> Option { entry .observed .ewma_tok_s + .filter(|_| { + entry + .observed + .throughput_observed_at_millis + .is_none_or(|observed_at| { + now_millis.saturating_sub(observed_at) + <= DEFAULT_THROUGHPUT_OBSERVATION_TTL_MILLIS + }) + }) .or_else(|| { entry .heartbeat @@ -1704,6 +1739,7 @@ mod tests { RequestRequirements { current_rules_ver: 3, min_reputation: 0.5, + requires_prefix_caching: true, requires_tools: true, requires_json: true, min_ctx: 4096, @@ -2358,6 +2394,7 @@ mod tests { assert_eq!(entry.observed.samples, 2); assert_eq!(entry.observed.ewma_ttft_ms, Some(180.0)); assert_eq!(entry.observed.ewma_tok_s, Some(48.0)); + assert_eq!(entry.observed.throughput_observed_at_millis, Some(0)); assert!((entry.observed.ewma_throughput_ratio.unwrap() - 1.0).abs() < 1e-12); assert_eq!(entry.observed.underdelivery_streak, 0); assert!((entry.observed.ewma_error_rate - 0.2).abs() < f64::EPSILON); @@ -2538,20 +2575,50 @@ mod tests { assert!(evaluate_eligibility(&entry, &request).is_ok()); for evidence in [None, Some(false)] { entry.heartbeat.as_mut().unwrap().prefix_caching = evidence; - assert_eq!(evaluate_eligibility(&entry, &request), - Err(IneligibilityReason::PrefixCachingRequired)); + assert_eq!( + evaluate_eligibility(&entry, &request), + Err(IneligibilityReason::PrefixCachingRequired) + ); // Withholding text in a heartbeat does not bypass the signed catalog. - entry.heartbeat.as_mut().unwrap().caps.served_modalities.clear(); - assert_eq!(baseline_route_state(&entry, &BaselineRouteRequirements::from(&request)), - BaselineRouteState::PrefixCachingRequired); + entry + .heartbeat + .as_mut() + .unwrap() + .caps + .served_modalities + .clear(); + assert_eq!( + baseline_route_state(&entry, &BaselineRouteRequirements::from(&request)), + BaselineRouteState::PrefixCachingRequired + ); } + let decision_request = RequestRequirements { + requires_prefix_caching: false, + ..request.clone() + }; + assert_eq!( + baseline_route_state(&entry, &BaselineRouteRequirements::from(&decision_request)), + BaselineRouteState::Live + ); entry.heartbeat.as_mut().unwrap().prefix_caching = Some(true); - assert_eq!(baseline_route_state(&entry, &BaselineRouteRequirements::from(&request)), BaselineRouteState::Live); + assert_eq!( + baseline_route_state(&entry, &BaselineRouteRequirements::from(&request)), + BaselineRouteState::Live + ); // Media and embedding routes do not have an output-token tariff. entry.heartbeat.as_mut().unwrap().prefix_caching = None; - entry.contract.rate_map.retain(|rate| rate.unit != mayhem_proto::USAGE_OUTPUT_TOKEN); - entry.contract.ref_rate_map.retain(|rate| rate.unit != mayhem_proto::USAGE_OUTPUT_TOKEN); - assert_eq!(baseline_route_state(&entry, &BaselineRouteRequirements::from(&request)), BaselineRouteState::Live); + entry + .contract + .rate_map + .retain(|rate| rate.unit != mayhem_proto::USAGE_OUTPUT_TOKEN); + entry + .contract + .ref_rate_map + .retain(|rate| rate.unit != mayhem_proto::USAGE_OUTPUT_TOKEN); + assert_eq!( + baseline_route_state(&entry, &BaselineRouteRequirements::from(&request)), + BaselineRouteState::Live + ); } #[test] @@ -2915,6 +2982,20 @@ mod tests { ); throughput_limited.min_throughput = Some(50.0); assert_eq!(evaluate_eligibility(&good, &throughput_limited), Ok(80)); + let mut temporarily_slow = good.clone(); + temporarily_slow.observed.ewma_tok_s = Some(2.0); + temporarily_slow.observed.throughput_observed_at_millis = Some(now); + throughput_limited.min_throughput = Some(5.0); + assert_eq!( + evaluate_eligibility(&temporarily_slow, &throughput_limited), + Err(IneligibilityReason::ThroughputFloor) + ); + throughput_limited.now_millis = now + DEFAULT_THROUGHPUT_OBSERVATION_TTL_MILLIS + 1; + assert_eq!( + evaluate_eligibility(&temporarily_slow, &throughput_limited), + Ok(80), + "a stale local throughput sample must yield to the fresh heartbeat" + ); let mut cold_start = good.clone(); cold_start.heartbeat.as_mut().expect("heartbeat").perf.tok_s = None; cold_start.observed.ewma_tok_s = None; diff --git a/crates/mayhem-gateway/src/structured_schema.rs b/crates/mayhem-gateway/src/structured_schema.rs new file mode 100644 index 00000000..a5d9f7a1 --- /dev/null +++ b/crates/mayhem-gateway/src/structured_schema.rs @@ -0,0 +1,340 @@ +//! The public JSON-schema contract is independent of the grammar compiler used +//! by any one model backend. Compile the original before dispatch, feed a safe +//! projection to the grammar compiler, and check the original after generation. + +use serde_json::Value; + +const MAX_SCHEMA_BYTES: usize = 128 * 1024; +const MAX_SCHEMA_DEPTH: usize = 64; +const MAX_SCHEMA_NODES: usize = 4096; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct SchemaError(pub String); + +impl std::fmt::Display for SchemaError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(&self.0) + } +} + +impl std::error::Error for SchemaError {} + +/// Check the real contract before a buyer reserves credit or selects a route. +/// The returned schema is only a generation hint; it must never replace the +/// original schema in the request or in final validation. +pub fn prepare(schema: &Value) -> Result { + if !schema.is_object() { + return Err(SchemaError("JSON schema must be an object".to_owned())); + } + if serde_json::to_vec(schema) + .map_err(|error| SchemaError(format!("invalid JSON schema: {error}")))? + .len() + > MAX_SCHEMA_BYTES + { + return Err(SchemaError("JSON schema exceeds 128 KiB".to_owned())); + } + let mut grammar = schema.clone(); + let mut nodes = 0; + check_and_project(schema, &mut grammar, "$", 0, &mut nodes)?; + jsonschema::draft202012::options() + .should_validate_formats(true) + .build(schema) + .map_err(|error| SchemaError(format!("invalid JSON schema: {error}")))?; + Ok(grammar) +} + +pub fn validate_output(schema: &Value, output: &str) -> Result<(), SchemaError> { + // Prepare also rejects unknown keywords, remote references and excessive + // complexity. This call is a defensive boundary for provider-side use. + prepare(schema)?; + let value: Value = serde_json::from_str(output) + .map_err(|error| SchemaError(format!("output is not valid JSON: {error}")))?; + let validator = jsonschema::draft202012::options() + .should_validate_formats(true) + .build(schema) + .map_err(|error| SchemaError(format!("invalid JSON schema: {error}")))?; + validator + .validate(&value) + .map_err(|error| SchemaError(format!("output violates JSON schema: {error}"))) +} + +fn check_and_project( + original: &Value, + grammar: &mut Value, + path: &str, + depth: usize, + nodes: &mut usize, +) -> Result<(), SchemaError> { + *nodes += 1; + if depth > MAX_SCHEMA_DEPTH || *nodes > MAX_SCHEMA_NODES { + return Err(SchemaError("JSON schema is too complex".to_owned())); + } + if original.is_boolean() { + return Ok(()); + } + let Some(object) = original.as_object() else { + return Err(SchemaError(format!("{path} must be a JSON schema object"))); + }; + let projected = grammar + .as_object_mut() + .expect("schema projection is an object"); + for (key, value) in object { + if !known_keyword(key) { + return Err(SchemaError(format!( + "unsupported JSON schema keyword {key:?} at {path}" + ))); + } + if key == "$ref" { + let reference = value + .as_str() + .ok_or_else(|| SchemaError(format!("{path}.$ref must be a string")))?; + if !reference.starts_with("#/") && reference != "#" { + return Err(SchemaError(format!( + "non-local JSON schema reference at {path}" + ))); + } + } + if key == "$schema" { + let draft = value + .as_str() + .ok_or_else(|| SchemaError(format!("{path}.$schema must be a string")))?; + if !matches!( + draft, + "https://json-schema.org/draft/2020-12/schema" + | "https://json-schema.org/draft/2020-12/schema#" + ) { + return Err(SchemaError(format!( + "unsupported JSON schema draft at {path}" + ))); + } + } + // Stateful or semantic constraints do not belong in a stateless token + // grammar. The full schema is retained and validated after generation. + if matches!( + key.as_str(), + "uniqueItems" + | "multipleOf" + | "format" + | "contains" + | "minContains" + | "maxContains" + | "patternProperties" + | "propertyNames" + | "dependentRequired" + | "dependentSchemas" + | "unevaluatedProperties" + | "unevaluatedItems" + | "if" + | "then" + | "else" + | "not" + | "title" + | "description" + | "default" + | "examples" + | "deprecated" + | "readOnly" + | "writeOnly" + | "$comment" + ) { + match key.as_str() { + "patternProperties" | "dependentSchemas" => { + let children = value + .as_object() + .ok_or_else(|| SchemaError(format!("{path}.{key} must be an object")))?; + for (name, child) in children { + let mut ignored = child.clone(); + check_and_project( + child, + &mut ignored, + &format!("{path}.{key}.{name}"), + depth + 1, + nodes, + )?; + } + } + "contains" + | "unevaluatedProperties" + | "unevaluatedItems" + | "propertyNames" + | "if" + | "then" + | "else" + | "not" => { + let mut ignored = value.clone(); + check_and_project( + value, + &mut ignored, + &format!("{path}.{key}"), + depth + 1, + nodes, + )?; + } + _ => {} + } + projected.remove(key); + continue; + } + match key.as_str() { + "properties" | "$defs" | "definitions" | "patternProperties" | "dependentSchemas" => { + let children = value + .as_object() + .ok_or_else(|| SchemaError(format!("{path}.{key} must be an object")))?; + for (name, child) in children { + check_and_project( + child, + projected + .get_mut(key) + .and_then(|v| v.get_mut(name)) + .unwrap(), + &format!("{path}.{key}.{name}"), + depth + 1, + nodes, + )?; + } + } + "items" + | "additionalProperties" + | "unevaluatedProperties" + | "unevaluatedItems" + | "contains" + | "propertyNames" + | "not" + | "if" + | "then" + | "else" => { + check_and_project( + value, + projected.get_mut(key).unwrap(), + &format!("{path}.{key}"), + depth + 1, + nodes, + )?; + } + "allOf" | "anyOf" | "oneOf" | "prefixItems" => { + let children = value + .as_array() + .ok_or_else(|| SchemaError(format!("{path}.{key} must be an array")))?; + for (index, child) in children.iter().enumerate() { + check_and_project( + child, + &mut projected + .get_mut(key) + .and_then(Value::as_array_mut) + .unwrap()[index], + &format!("{path}.{key}[{index}]"), + depth + 1, + nodes, + )?; + } + } + _ => {} + } + } + if object.contains_key("patternProperties") + && projected.get("additionalProperties") == Some(&Value::Bool(false)) + { + projected.insert("additionalProperties".to_owned(), Value::Bool(true)); + } + Ok(()) +} + +fn known_keyword(keyword: &str) -> bool { + matches!( + keyword, + "$schema" + | "$id" + | "$comment" + | "$defs" + | "$ref" + | "definitions" + | "title" + | "description" + | "default" + | "examples" + | "deprecated" + | "readOnly" + | "writeOnly" + | "type" + | "enum" + | "const" + | "properties" + | "patternProperties" + | "required" + | "additionalProperties" + | "propertyNames" + | "minProperties" + | "maxProperties" + | "dependentRequired" + | "dependentSchemas" + | "unevaluatedProperties" + | "items" + | "prefixItems" + | "contains" + | "minContains" + | "maxContains" + | "minItems" + | "maxItems" + | "uniqueItems" + | "unevaluatedItems" + | "minLength" + | "maxLength" + | "pattern" + | "format" + | "minimum" + | "maximum" + | "exclusiveMinimum" + | "exclusiveMaximum" + | "multipleOf" + | "allOf" + | "anyOf" + | "oneOf" + | "not" + | "if" + | "then" + | "else" + ) +} + +#[cfg(test)] +mod tests { + use super::*; + use serde_json::json; + + #[test] + fn nested_uniqueness_is_projected_but_finally_enforced() { + let schema = json!({"type":"object","properties":{"signals":{"type":"array","items":{ + "type":"object","properties":{"evidenceIds":{"type":"array","items":{"type":"string"}, + "minItems":2,"maxItems":4,"uniqueItems":true}} + }}}}); + let projected = prepare(&schema).unwrap(); + assert!( + projected["properties"]["signals"]["items"]["properties"]["evidenceIds"] + .get("uniqueItems") + .is_none() + ); + assert!( + schema["properties"]["signals"]["items"]["properties"]["evidenceIds"] + .get("uniqueItems") + .is_some() + ); + assert!(validate_output(&schema, r#"{"signals":[{"evidenceIds":["E1","E2"]}]}"#).is_ok()); + assert!(validate_output(&schema, r#"{"signals":[{"evidenceIds":["E1","E1"]}]}"#).is_err()); + } + + #[test] + fn schema_keywords_in_const_data_are_not_changed() { + let schema = json!({"type":"object","properties":{"value":{"const":{"uniqueItems":true}}}}); + assert_eq!(prepare(&schema).unwrap(), schema); + } + + #[test] + fn invalid_and_unknown_constraints_fail_before_dispatch() { + assert!(prepare(&json!({"type":"array","uniqueItems":"true"})).is_err()); + assert!(prepare(&json!({"type":"array","madeUpConstraint":true})).is_err()); + assert!(prepare(&json!({"$ref":"https://example.org/schema"})).is_err()); + assert!(prepare(&json!({"type":"array","contains":{"madeUpConstraint":true}})).is_err()); + assert!(prepare(&json!({"$schema":"http://json-schema.org/draft-07/schema#"})).is_err()); + assert!(prepare(&json!({"type":"object","additionalProperties":false})).is_ok()); + } +} diff --git a/crates/mayhem-gateway/tests/dashboard-browser-smoke.mjs b/crates/mayhem-gateway/tests/dashboard-browser-smoke.mjs index fad02ae4..537fbf42 100644 --- a/crates/mayhem-gateway/tests/dashboard-browser-smoke.mjs +++ b/crates/mayhem-gateway/tests/dashboard-browser-smoke.mjs @@ -404,8 +404,8 @@ try { const playgroundOutput = page.locator('[data-playground-max-tokens]'); const playgroundSend = page.locator('[data-playground-send]'); const playgroundModelTrigger = page.locator('[data-playground-model-trigger]'); - const rateOption = playgroundModel.locator('option[data-price-mode="rate"]').first(); - const fixedOption = playgroundModel.locator('option[data-price-mode="fixed"]').first(); + const rateOption = playgroundModel.locator('option[data-playground-mode="chat"][data-price-mode="rate"]').first(); + const fixedOption = playgroundModel.locator('option[data-playground-mode="chat"][data-price-mode="fixed"]').first(); const choosePlaygroundModel = async (value) => { if (await playgroundModelTrigger.getAttribute('aria-expanded') !== 'true') { await playgroundModelTrigger.click(); diff --git a/crates/mayhem-gateway/tests/openai_api.rs b/crates/mayhem-gateway/tests/openai_api.rs index 43bbb26a..e74fe86e 100644 --- a/crates/mayhem-gateway/tests/openai_api.rs +++ b/crates/mayhem-gateway/tests/openai_api.rs @@ -226,6 +226,73 @@ impl GatewaySessionBackend for EmbeddingDirectSessionBackend { } } +#[derive(Debug)] +struct DecisionDirectSessionBackend; + +impl GatewaySessionBackend for DecisionDirectSessionBackend { + fn name(&self) -> &str { + "test-decision-direct-session" + } + + fn run_chat<'a>( + &'a self, + _model: &'a GatewayModel, + request: &'a ChatCompletionRequest, + _invocation: &'a GatewaySessionInvocation, + ) -> GatewaySessionFuture<'a> { + Box::pin(async move { + assert_eq!( + request.endpoint_family.as_deref(), + Some(mayhem_proto::ENDPOINT_MAYHEM_DECISIONS) + ); + assert_eq!( + request + .endpoint_request + .as_ref() + .and_then(|body| body.get("checkpoint")) + .and_then(Value::as_str), + Some("english") + ); + let content = json!({ + "model": "laya-rl-agent", + "answers": { + "intent": { + "type": "choice", + "choice": "support", + "probabilities": {"support": 0.9, "other": 0.1}, + "confidence": 0.9, + "action": {"act_probability": 0.2} + } + }, + "routing": {"checkpoint": "english", "reason": "explicit"}, + "shortlist": {"intent": ["support", "other"]}, + "preprocessing": {"email_cleaned": false}, + "usage": {"input_tokens": 7, "output_tokens": 0} + }) + .to_string(); + Ok(GatewaySessionResult { + output: ChatOutput { + reasoning_content: String::new(), + content: Some(content), + tool_calls: Vec::new(), + artifacts: Vec::new(), + finish_reason: "stop".to_owned(), + usage: Usage { + prompt_tokens: 7, + completion_tokens: 9, + total_tokens: 16, + }, + }, + backend: self.name().to_owned(), + direct_session: true, + provider_receipt: None, + token_ids: Vec::new(), + quality: None, + }) + }) + } +} + #[derive(Debug)] struct ImageGenerationDirectSessionBackend; @@ -1927,9 +1994,18 @@ async fn av3_missing_policy_filters_tier2_and_routes_tier1_fallback() { mayhem["registered_route_candidates"][0]["dispatch_eligible"], false ); - assert_eq!(mayhem["registered_route_candidates"][0]["presence"], "online"); - assert_eq!(mayhem["registered_route_candidates"][0]["availability"], "unavailable"); - assert_eq!(mayhem["registered_route_candidates"][0]["availability_reason"], "attestation_policy"); + assert_eq!( + mayhem["registered_route_candidates"][0]["presence"], + "online" + ); + assert_eq!( + mayhem["registered_route_candidates"][0]["availability"], + "unavailable" + ); + assert_eq!( + mayhem["registered_route_candidates"][0]["availability_reason"], + "attestation_policy" + ); let tier1 = &mayhem["route_candidates"][0]["attestation_verification"]; assert_eq!(tier1["policy_required"], false); assert_eq!(tier1["locally_ready"], true); @@ -2217,8 +2293,7 @@ async fn models_endpoint_reports_busy_image_presence_and_immediate_capacity_rele .route_candidates .iter() .map(|candidate| { - let mut heartbeat = - test_provider_heartbeat(&model, candidate, 0.2, 1, 1, None, 150); + let mut heartbeat = test_provider_heartbeat(&model, candidate, 0.2, 1, 1, None, 150); heartbeat.accepting_new = false; heartbeat.q.free_slots = 0; let capacity = heartbeat.caps.modality_capacity.get_mut("image").unwrap(); @@ -2228,11 +2303,9 @@ async fn models_endpoint_reports_busy_image_presence_and_immediate_capacity_rele heartbeat }) .collect::>(); - let state = - GatewayState::from_models(vec![model]).with_provider_heartbeats(heartbeats.clone()); + let state = GatewayState::from_models(vec![model]).with_provider_heartbeats(heartbeats.clone()); let app = openai_router(state.clone()); - let (status, body) = - json_request(app.clone(), Method::GET, "/v1/models", Value::Null).await; + let (status, body) = json_request(app.clone(), Method::GET, "/v1/models", Value::Null).await; assert_eq!(status, StatusCode::OK); let mayhem = &body["data"][0]["mayhem"]; assert_eq!(mayhem["providers_online"], 2); @@ -3170,6 +3243,84 @@ async fn embeddings_endpoint_rejects_non_embedding_model() { .contains("does not expose endpoint family openai_embeddings")); } +#[tokio::test] +async fn decisions_endpoint_uses_signed_route_and_records_typed_usage() { + let state = test_gateway_state_from_models(vec![routed_decision_test_model()]) + .with_session_backend(Arc::new(DecisionDirectSessionBackend)); + let app = openai_router(state.clone()); + let request = json!({ + "model": "convaiinnovations/laya", + "state": {"body": "Please help with my account."}, + "questions": { + "intent": { + "type": "choice", + "instructions": "Choose the intent.", + "criteria": { + "support": "support request", + "other": "another topic" + } + } + }, + "checkpoint": "english" + }); + + let (status, body) = json_request(app, Method::POST, "/v1/decisions", request).await; + + assert_eq!(status, StatusCode::OK, "{body}"); + assert_eq!(body["object"], "decision"); + assert_eq!(body["model"], "convaiinnovations/laya"); + assert_eq!(body["answers"]["intent"]["choice"], "support"); + assert_eq!(body["routing"]["checkpoint"], "english"); + assert_eq!(body["shortlist"]["intent"][0], "support"); + assert_eq!(body["preprocessing"]["email_cleaned"], false); + assert_eq!(body["usage"]["prompt_tokens"], 7); + assert_eq!(body["usage"]["completion_tokens"], 9); + assert_eq!(body["mayhem"]["backend"], "test-decision-direct-session"); + assert_eq!(body["mayhem"]["direct_session"], true); + assert_eq!(body["mayhem"]["receipt"]["rail"], "fiat"); + + let receipts = state.receipts(); + assert_eq!(receipts.len(), 1); + assert_eq!(receipts[0].receipt.body.model_id, "convaiinnovations/laya"); + assert_eq!(receipts[0].receipt.body.usage.input_tokens(), 7); + assert_eq!(receipts[0].receipt.body.usage.output_tokens(), 9); +} + +#[tokio::test] +async fn decisions_endpoint_rejects_unknown_or_inapplicable_models_before_dispatch() { + let state = + test_gateway_state_from_models(vec![routed_decision_test_model(), routed_test_model()]) + .with_session_backend(Arc::new(DecisionDirectSessionBackend)); + let app = openai_router(state); + + let (status, body) = json_request( + app.clone(), + Method::POST, + "/v1/decisions", + json!({ + "model": "convaiinnovations/laya", + "state": "hello", + "questions": {"urgent": {"type": "noul", "instructions": "Urgent?"}}, + "server_path": "/tmp/escape" + }), + ) + .await; + assert_eq!(status, StatusCode::BAD_REQUEST, "{body}"); + + let (status, body) = json_request( + app, + Method::POST, + "/v1/decisions", + json!({ + "model": "mayhem/routed-test", + "state": "hello", + "questions": {"urgent": {"type": "noul", "instructions": "Urgent?"}} + }), + ) + .await; + assert_eq!(status, StatusCode::BAD_REQUEST, "{body}"); +} + #[tokio::test] async fn image_generation_endpoint_uses_routed_engine_and_records_receipt() { let state = test_gateway_state_from_models(vec![routed_image_generation_test_model()]) @@ -6095,6 +6246,51 @@ fn routed_embedding_test_model() -> GatewayModel { model } +fn routed_decision_test_model() -> GatewayModel { + let mut model = routed_test_model_with_providers(&["55".repeat(32)]); + model.id = "convaiinnovations/laya".to_owned(); + model.mayhem.model_class = "decision".to_owned(); + model.mayhem.price_ref_au = PriceRefAu { + denom: "au_usd".to_owned(), + ver: 8, + rate_map: text_generation_rate_map(10, 10), + per_req_au: 0, + min_session_au: 0, + derivation: None, + history: Vec::new(), + }; + model.mayhem.caps = ModelCaps { + tools: false, + json: true, + ctx: 1024, + vision: false, + image: false, + video: false, + audio: false, + max_image_width: None, + max_image_height: None, + max_image_steps: None, + output_modality: Some("text".to_owned()), + output_modalities: vec!["text".to_owned()], + }; + model.mayhem.adapter.modality_set = vec!["text".to_owned()]; + model.mayhem.adapter.endpoint_families = vec![mayhem_proto::endpoint_family_contract_template( + mayhem_proto::ENDPOINT_MAYHEM_DECISIONS, + ) + .unwrap()]; + for candidate in &mut model.mayhem.route_candidates { + candidate.served_modalities = vec!["text".to_owned()]; + candidate.price_ver = 8; + candidate.caps = serde_json::json!({ + "ctx_max": 1024, + "json": true, + "output_modality": "text", + "output_modalities": ["text"] + }); + } + model +} + fn routed_image_generation_test_model() -> GatewayModel { let mut model = routed_test_model_with_providers(&["55".repeat(32)]); model.id = "admin/image-fixture".to_owned(); @@ -6453,6 +6649,8 @@ fn signed_image_provider_receipt( locked_per_req_au: invocation.spend_voucher.body.locked_per_req_au, locked_min_session_au: invocation.spend_voucher.body.locked_min_session_au, served_ctx: invocation.served_ctx, + compute_ms: 1, + capacity_slots: 1, ctx_bracket: invocation.ctx_bracket.clone(), ctx_bracket_table_ver: invocation.ctx_bracket_table_ver, rules_ver: invocation.rules_ver, @@ -6548,6 +6746,8 @@ fn signed_provider_receipt_for_test( locked_per_req_au: invocation.spend_voucher.body.locked_per_req_au, locked_min_session_au: invocation.spend_voucher.body.locked_min_session_au, served_ctx: invocation.served_ctx, + compute_ms: 1, + capacity_slots: 1, ctx_bracket: invocation.ctx_bracket.clone(), ctx_bracket_table_ver: invocation.ctx_bracket_table_ver, rules_ver: invocation.rules_ver, @@ -6888,11 +7088,13 @@ fn test_canary_registry(expected_tokens: &[i32]) -> GatewayCanaryRegistry { "aa".repeat(32), BTreeMap::from([("fixed-probe".to_owned(), expected_tokens.to_vec())]), )]), + openai_compatible_artifact_roots: BTreeSet::new(), perceptual_hashes_by_artifact_root: BTreeMap::new(), embedding_vectors_by_artifact_root: BTreeMap::new(), transcripts_by_artifact_root: BTreeMap::new(), audio_fingerprints_by_artifact_root: BTreeMap::new(), video_fingerprints_by_artifact_root: BTreeMap::new(), + decision_fingerprints_by_artifact_root: BTreeMap::new(), speciality_calibrations_by_artifact_root: BTreeMap::new(), default_fingerprint: None, default_token_prefixes: None, @@ -6901,6 +7103,7 @@ fn test_canary_registry(expected_tokens: &[i32]) -> GatewayCanaryRegistry { default_transcripts: None, default_audio_fingerprints: None, default_video_fingerprints: None, + default_decision_fingerprints: None, }, )]), prompt_ids_by_set: BTreeMap::from([( @@ -6953,6 +7156,7 @@ fn test_image_canary_registry(expected_hash: String) -> GatewayCanaryRegistry { }], fingerprints_by_artifact_root: BTreeMap::new(), token_prefixes_by_artifact_root: BTreeMap::new(), + openai_compatible_artifact_roots: BTreeSet::new(), perceptual_hashes_by_artifact_root: BTreeMap::from([( "aa".repeat(32), BTreeMap::from([("fixed-image".to_owned(), expected_hash)]), @@ -6961,6 +7165,7 @@ fn test_image_canary_registry(expected_hash: String) -> GatewayCanaryRegistry { transcripts_by_artifact_root: BTreeMap::new(), audio_fingerprints_by_artifact_root: BTreeMap::new(), video_fingerprints_by_artifact_root: BTreeMap::new(), + decision_fingerprints_by_artifact_root: BTreeMap::new(), speciality_calibrations_by_artifact_root: BTreeMap::new(), default_fingerprint: None, default_token_prefixes: None, @@ -6969,6 +7174,7 @@ fn test_image_canary_registry(expected_hash: String) -> GatewayCanaryRegistry { default_transcripts: None, default_audio_fingerprints: None, default_video_fingerprints: None, + default_decision_fingerprints: None, }, )]), prompt_ids_by_set: BTreeMap::from([( @@ -7021,6 +7227,7 @@ fn test_embedding_canary_registry(expected_vector: Vec) -> GatewayCanaryReg }], fingerprints_by_artifact_root: BTreeMap::new(), token_prefixes_by_artifact_root: BTreeMap::new(), + openai_compatible_artifact_roots: BTreeSet::new(), perceptual_hashes_by_artifact_root: BTreeMap::new(), embedding_vectors_by_artifact_root: BTreeMap::from([( "aa".repeat(32), @@ -7029,6 +7236,7 @@ fn test_embedding_canary_registry(expected_vector: Vec) -> GatewayCanaryReg transcripts_by_artifact_root: BTreeMap::new(), audio_fingerprints_by_artifact_root: BTreeMap::new(), video_fingerprints_by_artifact_root: BTreeMap::new(), + decision_fingerprints_by_artifact_root: BTreeMap::new(), speciality_calibrations_by_artifact_root: BTreeMap::new(), default_fingerprint: None, default_token_prefixes: None, @@ -7037,6 +7245,7 @@ fn test_embedding_canary_registry(expected_vector: Vec) -> GatewayCanaryReg default_transcripts: None, default_audio_fingerprints: None, default_video_fingerprints: None, + default_decision_fingerprints: None, }, )]), prompt_ids_by_set: BTreeMap::from([( @@ -7093,6 +7302,7 @@ fn test_transcript_canary_registry(audio: Vec) -> GatewayCanaryRegistry { prompts: vec![runtime_prompt, calibration_prompt], fingerprints_by_artifact_root: BTreeMap::new(), token_prefixes_by_artifact_root: BTreeMap::new(), + openai_compatible_artifact_roots: BTreeSet::new(), perceptual_hashes_by_artifact_root: BTreeMap::new(), embedding_vectors_by_artifact_root: BTreeMap::new(), transcripts_by_artifact_root: BTreeMap::from([( @@ -7107,6 +7317,7 @@ fn test_transcript_canary_registry(audio: Vec) -> GatewayCanaryRegistry { )]), audio_fingerprints_by_artifact_root: BTreeMap::new(), video_fingerprints_by_artifact_root: BTreeMap::new(), + decision_fingerprints_by_artifact_root: BTreeMap::new(), speciality_calibrations_by_artifact_root: BTreeMap::new(), default_fingerprint: None, default_token_prefixes: None, @@ -7115,6 +7326,7 @@ fn test_transcript_canary_registry(audio: Vec) -> GatewayCanaryRegistry { default_transcripts: None, default_audio_fingerprints: None, default_video_fingerprints: None, + default_decision_fingerprints: None, }, )]), prompt_ids_by_set: BTreeMap::from([( @@ -7167,6 +7379,7 @@ fn test_audio_fingerprint_canary_registry(expected_fingerprint: String) -> Gatew }], fingerprints_by_artifact_root: BTreeMap::new(), token_prefixes_by_artifact_root: BTreeMap::new(), + openai_compatible_artifact_roots: BTreeSet::new(), perceptual_hashes_by_artifact_root: BTreeMap::new(), embedding_vectors_by_artifact_root: BTreeMap::new(), transcripts_by_artifact_root: BTreeMap::new(), @@ -7175,6 +7388,7 @@ fn test_audio_fingerprint_canary_registry(expected_fingerprint: String) -> Gatew BTreeMap::from([("fixed-tts".to_owned(), expected_fingerprint)]), )]), video_fingerprints_by_artifact_root: BTreeMap::new(), + decision_fingerprints_by_artifact_root: BTreeMap::new(), speciality_calibrations_by_artifact_root: BTreeMap::new(), default_fingerprint: None, default_token_prefixes: None, @@ -7183,6 +7397,7 @@ fn test_audio_fingerprint_canary_registry(expected_fingerprint: String) -> Gatew default_transcripts: None, default_audio_fingerprints: None, default_video_fingerprints: None, + default_decision_fingerprints: None, }, )]), prompt_ids_by_set: BTreeMap::from([( @@ -7251,6 +7466,7 @@ fn test_music_audio_fingerprint_canary_registry( }], fingerprints_by_artifact_root: BTreeMap::new(), token_prefixes_by_artifact_root: BTreeMap::new(), + openai_compatible_artifact_roots: BTreeSet::new(), perceptual_hashes_by_artifact_root: BTreeMap::new(), embedding_vectors_by_artifact_root: BTreeMap::new(), transcripts_by_artifact_root: BTreeMap::new(), @@ -7259,6 +7475,7 @@ fn test_music_audio_fingerprint_canary_registry( BTreeMap::from([("fixed-music".to_owned(), expected_fingerprint)]), )]), video_fingerprints_by_artifact_root: BTreeMap::new(), + decision_fingerprints_by_artifact_root: BTreeMap::new(), speciality_calibrations_by_artifact_root: BTreeMap::new(), default_fingerprint: None, default_token_prefixes: None, @@ -7267,6 +7484,7 @@ fn test_music_audio_fingerprint_canary_registry( default_transcripts: None, default_audio_fingerprints: None, default_video_fingerprints: None, + default_decision_fingerprints: None, }, )]), prompt_ids_by_set: BTreeMap::from([( @@ -7467,6 +7685,7 @@ fn routed_test_candidate(provider: &str, idx: usize) -> GatewayRouteCandidate { price_ref_au: None, min_ask_au: 0, att_tier: 1, + enclave_att_tier: Some(1), quant: "int4".to_owned(), served_ctx: None, hardware_fingerprint: None, @@ -7952,7 +8171,8 @@ async fn legacy_completions_return_text_completion_shape_and_stream() { assert_eq!(body["mayhem"]["dev_session"], true); assert_eq!(body["mayhem"]["receipt"], Value::Null); - let request = json!({ "model": openai_test_model_id().await, "prompt": "Hello", "stream": true }); + let request = + json!({ "model": openai_test_model_id().await, "prompt": "Hello", "stream": true }); let (status, headers, bytes) = raw_request(app, Method::POST, "/v1/completions", Some(request)).await; assert_eq!(status, StatusCode::OK); diff --git a/crates/mayhem-gateway/tests/video_metering_vectors.rs b/crates/mayhem-gateway/tests/video_metering_vectors.rs index 90e42266..7bd0db21 100644 --- a/crates/mayhem-gateway/tests/video_metering_vectors.rs +++ b/crates/mayhem-gateway/tests/video_metering_vectors.rs @@ -45,10 +45,8 @@ fn repo_path(relative: &str) -> PathBuf { fn read_json(relative: &str) -> Value { let path = repo_path(relative); - let bytes = fs::read(&path) - .unwrap_or_else(|err| panic!("reading {}: {err}", path.display())); - serde_json::from_slice(&bytes) - .unwrap_or_else(|err| panic!("parsing {}: {err}", path.display())) + let bytes = fs::read(&path).unwrap_or_else(|err| panic!("reading {}: {err}", path.display())); + serde_json::from_slice(&bytes).unwrap_or_else(|err| panic!("parsing {}: {err}", path.display())) } fn vectors() -> Value { @@ -73,10 +71,15 @@ fn decimal_field(case: &Value, key: &str) -> MoneyAu { /// these vectors exercise the meter, not admission. fn metering_policy(pricing_unit: &str) -> ComfyWorkflowDerivationPolicy { ComfyWorkflowDerivationPolicy { - whitelisted_nodes: ["MiniMaxH3Easy", "BasicScheduler", "CreateVideo", "SaveVideo"] - .into_iter() - .map(str::to_owned) - .collect(), + whitelisted_nodes: [ + "MiniMaxH3Easy", + "BasicScheduler", + "CreateVideo", + "SaveVideo", + ] + .into_iter() + .map(str::to_owned) + .collect(), pricing_unit: Some(pricing_unit.to_owned()), max_width: 8_192, max_height: 8_192, @@ -151,11 +154,9 @@ fn video_metering_vectors_match_derivation_and_pricing() { "{label}: pixel_frame granularity must be 1_000_000" ); - let derivation = derive_comfy_workflow( - &video_graph(case), - &metering_policy(USAGE_PIXEL_FRAME), - ) - .unwrap_or_else(|err| panic!("{label}: derivation failed: {err}")); + let derivation = + derive_comfy_workflow(&video_graph(case), &metering_policy(USAGE_PIXEL_FRAME)) + .unwrap_or_else(|err| panic!("{label}: derivation failed: {err}")); assert!( derivation @@ -281,7 +282,10 @@ fn vector_tariffs_match_the_published_grid_and_catalog() { let Some(definition) = model.pointer("/workflow/outcome_class_definition") else { continue; }; - let class_id = definition["class_id"].as_str().expect("class id").to_owned(); + let class_id = definition["class_id"] + .as_str() + .expect("class id") + .to_owned(); let pricing_unit = definition["pricing_unit"].as_str().expect("pricing unit"); catalog_tariffs.insert( class_id.clone(), @@ -471,7 +475,8 @@ fn unknown_pricing_units_fail_closed_instead_of_billing_one_unit() { let err = derive_comfy_workflow(&video_graph(&case), &policy) .expect_err("an unknown pricing unit must fail closed"); assert!( - err.to_string().contains("unsupported pricing_unit seller_second"), + err.to_string() + .contains("unsupported pricing_unit seller_second"), "unexpected error: {err}" ); // Guard the old defect directly: the fallback used to quote one unit. diff --git a/crates/mayhem-hwprobe/src/lib.rs b/crates/mayhem-hwprobe/src/lib.rs index a9f81e7d..b697bfac 100644 --- a/crates/mayhem-hwprobe/src/lib.rs +++ b/crates/mayhem-hwprobe/src/lib.rs @@ -20,6 +20,8 @@ const ACE_STEP_CUDA_FULL_OFFLOAD_FLOOR: u64 = 20 * GIB; const ACE_STEP_APPLE_UNIFIED_MEMORY_FLOOR: u64 = 16 * GIB; const CHATTERBOX_RAM_FLOOR: u64 = 8 * GIB; const CHATTERBOX_ACCELERATOR_MEMORY_FLOOR: u64 = 6 * GIB; +const LAYA_RAM_FLOOR: u64 = 8 * GIB; +const LAYA_CUDA_MEMORY_FLOOR: u64 = 8 * GIB; const NEEDLE_RAM_FLOOR: u64 = GIB; const NEEDLE_GPU_MEMORY_FLOOR: u64 = 512 * MIB; const COMFYUI_RAM_FLOOR: u64 = 4 * GIB; @@ -337,6 +339,7 @@ fn report_from_profile(profile: HardwareProfile) -> HardwareReport { fn compute_backend_verdicts(profile: &HardwareProfile) -> Vec { vec![ vllm_verdict(profile), + openai_compatible_verdict(profile), trt_llm_verdict(profile), mlx_verdict(profile), llama_cpp_verdict(profile), @@ -345,6 +348,7 @@ fn compute_backend_verdicts(profile: &HardwareProfile) -> Vec { ace_step_verdict(profile), chatterbox_verdict(profile), transformers_asr_verdict(profile), + laya_verdict(profile), whisper_cpp_verdict(profile), piper_verdict(profile), needle_cpu_verdict(profile), @@ -353,6 +357,17 @@ fn compute_backend_verdicts(profile: &HardwareProfile) -> Vec { ] } +fn openai_compatible_verdict(profile: &HardwareProfile) -> BackendVerdict { + let mut verdict = vllm_verdict(profile); + verdict.backend = "openai-compatible".to_owned(); + verdict.reason = verdict.reason.map(|reason| { + format!( + "CUDA serving envelope supports a signed managed OpenAI-compatible runtime; {reason}" + ) + }); + verdict +} + fn chatterbox_verdict(profile: &HardwareProfile) -> BackendVerdict { let host_supported = match profile.host.os.as_str() { "linux" => matches!(profile.host.arch.as_str(), "x86_64" | "aarch64" | "arm64"), @@ -1374,6 +1389,80 @@ fn transformers_asr_verdict(profile: &HardwareProfile) -> BackendVerdict { } } +fn laya_verdict(profile: &HardwareProfile) -> BackendVerdict { + if profile.host.os != "linux" { + return insufficient( + "laya", + "Laya's calibrated production runtime requires Linux CUDA", + ); + } + if profile.memory.total_bytes < LAYA_RAM_FLOOR { + return insufficient("laya", "less than 8 GiB host RAM detected"); + } + + let compatible = profile + .gpus + .iter() + .filter(|gpu| { + gpu.vendor == GpuVendor::Nvidia + && gpu.backend == GpuBackend::Nvml + && gpu + .compute_capability + .as_deref() + .and_then(parse_compute_capability) + .is_some_and(|capability| capability >= (7, 5)) + }) + .collect::>(); + if compatible.is_empty() { + return insufficient( + "laya", + "no compatible NVIDIA GPU with compute capability >= 7.5 detected", + ); + } + + // A Laya worker preloads all three checkpoints on one CUDA device. Do not + // add memory across devices: split capacity cannot satisfy that residency + // requirement. + let usable_memory = compatible + .iter() + .map(|gpu| { + if nvidia_gpu_uses_host_unified_memory(profile, gpu) { + gpu.memory_bytes.unwrap_or_else(|| { + profile + .memory + .available_bytes + .unwrap_or(profile.memory.total_bytes) + }) + } else { + gpu.dedicated_memory_bytes.or(gpu.memory_bytes).unwrap_or(0) + } + }) + .max() + .unwrap_or(0); + if usable_memory < LAYA_CUDA_MEMORY_FLOOR { + return insufficient( + "laya", + &format!( + "Laya requires at least 8 GiB usable memory on one compatible NVIDIA GPU; {} detected", + format_bytes(usable_memory) + ), + ); + } + + BackendVerdict { + backend: "laya".to_owned(), + status: VerdictStatus::FullOffload, + reason: Some( + "one compatible NVIDIA CUDA GPU can keep the complete English, multilingual, and typed-decisions checkpoint family resident" + .to_owned(), + ), + est_tok_s: None, + n_layers_gpu: None, + max_sessions: 1, + kv_cache_bytes_budget: 0, + } +} + fn whisper_cpp_verdict(profile: &HardwareProfile) -> BackendVerdict { if profile.memory.total_bytes < GIB { return insufficient( @@ -3460,6 +3549,56 @@ mod tests { assert!(verdict.reason.unwrap_or_default().contains("below 4 GiB")); } + #[test] + fn laya_requires_one_linux_cuda_device_with_the_full_memory_floor() { + for fixture in [ + FixtureProfile::LinuxNvidia, + FixtureProfile::LinuxNvidiaArm64, + ] { + let profile = fixture_profile(fixture, Path::new(".")); + let verdict = laya_verdict(&profile); + assert_eq!(verdict.status, VerdictStatus::FullOffload); + assert_eq!(verdict.max_sessions, 1); + assert_eq!(verdict.kv_cache_bytes_budget, 0); + } + + let mut split = fixture_profile(FixtureProfile::LinuxNvidia, Path::new(".")); + for gpu in &mut split.gpus { + gpu.memory_bytes = Some(6 * GIB); + gpu.dedicated_memory_bytes = Some(6 * GIB); + } + let split = laya_verdict(&split); + assert_eq!(split.status, VerdictStatus::Insufficient); + assert!(split + .reason + .unwrap_or_default() + .contains("on one compatible NVIDIA GPU")); + } + + #[test] + fn laya_rejects_uncalibrated_platforms_and_cuda_capabilities() { + for fixture in [ + FixtureProfile::AppleSilicon, + FixtureProfile::WindowsNvidia, + FixtureProfile::CpuOnly, + ] { + assert_eq!( + laya_verdict(&fixture_profile(fixture, Path::new("."))).status, + VerdictStatus::Insufficient, + "{}", + fixture.as_str() + ); + } + + let mut old_cuda = fixture_profile(FixtureProfile::LinuxNvidia, Path::new(".")); + for gpu in &mut old_cuda.gpus { + gpu.compute_capability = Some("7.0".to_owned()); + } + let old_cuda = laya_verdict(&old_cuda); + assert_eq!(old_cuda.status, VerdictStatus::Insufficient); + assert!(old_cuda.reason.unwrap_or_default().contains(">= 7.5")); + } + #[test] fn needle_cpu_only_host_gets_cpu_but_not_gpu() { let report = fixture_report(FixtureProfile::CpuOnly); diff --git a/crates/mayhem-proto/src/endpoint_contract.rs b/crates/mayhem-proto/src/endpoint_contract.rs index 40fcca6c..431e2f47 100644 --- a/crates/mayhem-proto/src/endpoint_contract.rs +++ b/crates/mayhem-proto/src/endpoint_contract.rs @@ -8,7 +8,7 @@ use crate::{ ValidatedAudioFormat, ENDPOINT_HF_AUTOMATIC_SPEECH_RECOGNITION, ENDPOINT_HF_FEATURE_EXTRACTION, ENDPOINT_HF_MULTIMODAL_CHAT, ENDPOINT_HF_TEXT_TO_AUDIO, ENDPOINT_HF_TEXT_TO_IMAGE, ENDPOINT_HF_TEXT_TO_SPEECH, ENDPOINT_HF_TEXT_TO_VIDEO, ENDPOINT_MAYHEM_AUDIO_GENERATIONS, - ENDPOINT_MAYHEM_COMFY_WORKFLOWS, ENDPOINT_MAYHEM_MUSIC_GENERATIONS, + ENDPOINT_MAYHEM_COMFY_WORKFLOWS, ENDPOINT_MAYHEM_DECISIONS, ENDPOINT_MAYHEM_MUSIC_GENERATIONS, ENDPOINT_OPENAI_AUDIO_SPEECH, ENDPOINT_OPENAI_AUDIO_TRANSCRIPTIONS, ENDPOINT_OPENAI_CHAT_COMPLETIONS, ENDPOINT_OPENAI_COMPLETIONS, ENDPOINT_OPENAI_EMBEDDINGS, ENDPOINT_OPENAI_IMAGE_GENERATIONS, ENDPOINT_OPENAI_RESPONSES, ENDPOINT_OPENAI_VIDEOS, @@ -778,6 +778,35 @@ pub fn endpoint_family_contract_template(family: &str) -> Option ( + &[ + "model", + "state", + "questions", + "checkpoint", + "task", + "lang", + "auto_task_detection", + "email", + "shortlist", + "temperature", + "limits", + "user", + ], + &["model", "state", "questions"], + &[ + "id", + "object", + "created", + "model", + "answers", + "routing", + "shortlist", + "preprocessing", + "usage", + "mayhem", + ], + ), _ => return None, }; @@ -844,6 +873,7 @@ fn endpoint_response_attribute_optional(family: &str, path: &str) -> bool { ENDPOINT_OPENAI_AUDIO_TRANSCRIPTIONS, "task" | "language" | "duration" | "words" | "segments" ) | (ENDPOINT_HF_AUTOMATIC_SPEECH_RECOGNITION, "chunks") + | (ENDPOINT_MAYHEM_DECISIONS, "shortlist" | "preprocessing") ) } @@ -1123,6 +1153,60 @@ fn request_attribute_spec(family: &str, path: &str) -> Option { enum_spec(Some(json!("artifact")), &[json!("artifact"), json!("json")]) } + "state" if family == ENDPOINT_MAYHEM_DECISIONS => union_spec( + &[ + EndpointValueType::String, + EndpointValueType::Object, + EndpointValueType::Array, + ], + &[ + json!("Mayhem calibration state"), + json!({"message":"Mayhem calibration state"}), + json!([{"role":"user","content":"Mayhem calibration state"}]), + ], + ), + "questions" if family == ENDPOINT_MAYHEM_DECISIONS => object_spec(json!({ + "intent": { + "type": "choice", + "instructions": "What is the request about?", + "criteria": {"support":"support request", "other":"another topic"} + }, + "urgent": { + "type": "noul", + "instructions": "Is the request urgent?" + } + })), + "checkpoint" if family == ENDPOINT_MAYHEM_DECISIONS => enum_spec( + None, + &[ + json!("english"), + json!("multilingual"), + json!("typed-decisions"), + ], + ), + "task" if family == ENDPOINT_MAYHEM_DECISIONS => { + enum_spec(None, &[json!("typed_decisions")]) + } + "lang" if family == ENDPOINT_MAYHEM_DECISIONS => string_spec(1, 128, json!("en")), + "auto_task_detection" if family == ENDPOINT_MAYHEM_DECISIONS => boolean_spec(Some(false)), + "email" if family == ENDPOINT_MAYHEM_DECISIONS => object_spec(json!({ + "clean": true, + "max_chars": 3000 + })), + "shortlist" if family == ENDPOINT_MAYHEM_DECISIONS => object_spec(json!({ + "k": 20, + "max_length": 512, + "batch_size": 32 + })), + "temperature" if family == ENDPOINT_MAYHEM_DECISIONS => object_spec(json!({ + "choice": 1.0, + "score": 1.0, + "noul": 1.0 + })), + "limits" if family == ENDPOINT_MAYHEM_DECISIONS => object_spec(json!({ + "max_len": 1024, + "head_max_len": 256 + })), "text" | "reasoning" | "response_format" if matches!( family, @@ -1893,7 +1977,9 @@ fn response_attribute_spec(path: &str) -> Option { } "choices" | "data" | "output" | "embeddings" | "chunks" | "words" | "segments" | "artifacts" => array_spec(0, 1_000_000, json!([])), - "usage" | "mayhem" => object_spec(json!({})), + "usage" | "mayhem" | "answers" | "routing" | "shortlist" | "preprocessing" => { + object_spec(json!({})) + } "error" => union_spec( &[EndpointValueType::Object, EndpointValueType::Null], &[json!({}), Value::Null], @@ -1906,7 +1992,12 @@ fn response_attribute_spec(path: &str) -> Option { fn boolean_leaf(leaf: &str) -> bool { matches!( leaf, - "normalize" | "truncate" | "return_timestamps" | "do_sample" | "use_cache" + "normalize" + | "truncate" + | "return_timestamps" + | "do_sample" + | "use_cache" + | "auto_task_detection" ) } @@ -4069,6 +4160,21 @@ pub fn endpoint_contract_fingerprint(contract: &EndpointFamilyContract) -> Strin blake3::hash(&encoded).to_hex().to_string() } +/// Hashes an endpoint contract after canonicalizing nested JSON objects and +/// JavaScript-roundtrippable numbers. The legacy contract fingerprint remains +/// part of the wire envelope for compatibility with existing providers; peers +/// that understand this fingerprint use it so Cargo feature unification, +/// architecture-specific builds, and JavaScript relays cannot change the +/// meaning of an otherwise identical signed contract. +#[must_use] +pub fn endpoint_contract_canonical_fingerprint(contract: &EndpointFamilyContract) -> String { + let value = serde_json::to_value(contract).expect("endpoint contracts are JSON serializable"); + let canonical = javascript_roundtrip_stable_value(&value); + blake3::hash(canonical.to_string().as_bytes()) + .to_hex() + .to_string() +} + /// Hashes the semantic JSON value while remaining stable across JavaScript relays. /// JavaScript serializes integral JSON numbers such as `1.0` as `1`; those values /// must compare equally without making changed integers or non-integral values equal. @@ -5455,6 +5561,40 @@ mod tests { ); } + #[test] + fn canonical_contract_fingerprint_ignores_nested_object_order_and_js_numbers() { + let mut rust_contract = + endpoint_family_contract_template(ENDPOINT_MAYHEM_COMFY_WORKFLOWS).unwrap(); + let mut relayed_contract = rust_contract.clone(); + rust_contract + .request_attribute_specs + .get_mut("workflow") + .unwrap() + .calibration_values = vec![serde_json::from_str( + r#"{"1":{"strength":1.0},"10":{"shift":3.0},"2":{"seed":7}}"#, + ) + .unwrap()]; + relayed_contract + .request_attribute_specs + .get_mut("workflow") + .unwrap() + .calibration_values = + vec![ + serde_json::from_str(r#"{"1":{"strength":1},"2":{"seed":7},"10":{"shift":3}}"#) + .unwrap(), + ]; + + assert_ne!( + endpoint_contract_fingerprint(&rust_contract), + endpoint_contract_fingerprint(&relayed_contract), + "legacy fingerprints reproduce the order-sensitive production failure" + ); + assert_eq!( + endpoint_contract_canonical_fingerprint(&rust_contract), + endpoint_contract_canonical_fingerprint(&relayed_contract) + ); + } + #[test] fn every_endpoint_template_has_complete_typed_specs() { for family in [ @@ -5475,6 +5615,7 @@ mod tests { ENDPOINT_MAYHEM_AUDIO_GENERATIONS, ENDPOINT_MAYHEM_MUSIC_GENERATIONS, ENDPOINT_MAYHEM_COMFY_WORKFLOWS, + ENDPOINT_MAYHEM_DECISIONS, ENDPOINT_HF_TEXT_TO_AUDIO, ] { let contract = endpoint_family_contract_template(family) @@ -5660,7 +5801,8 @@ mod tests { #[test] fn openai_image_reference_has_explicit_default_and_binds_the_reference() { - let contract = endpoint_family_contract_template(ENDPOINT_OPENAI_IMAGE_GENERATIONS).unwrap(); + let contract = + endpoint_family_contract_template(ENDPOINT_OPENAI_IMAGE_GENERATIONS).unwrap(); let raw = json!({"model":"test/image", "prompt":"a compass", "size":"1024x1024", "input_reference":"$IMAGE_DATA_URL"}); let normalized = materialize_endpoint_request_defaults(&contract, &raw).unwrap(); assert_eq!(normalized["input_reference"], raw["input_reference"]); diff --git a/crates/mayhem-proto/src/lib.rs b/crates/mayhem-proto/src/lib.rs index 76f53223..a8301ef9 100644 --- a/crates/mayhem-proto/src/lib.rs +++ b/crates/mayhem-proto/src/lib.rs @@ -19,7 +19,8 @@ pub use validated_image::{image_reference_metadata, ImageReferenceMetadata}; mod reservation_close; pub use reservation_close::{ canonical_usage_receipt_hash, reservation_binding_matches, usage_reservation_close_feature, - usage_reservation_close_signing_bytes, usage_reservation_close_value, RESERVATION_BINDING_FIELDS, + usage_reservation_close_signing_bytes, usage_reservation_close_value, + RESERVATION_BINDING_FIELDS, }; pub use comfy_workflow::{ @@ -41,7 +42,8 @@ pub use comfy_workflow_media::{ pub use endpoint_contract::{ artifact_generation_inline_audio_load, artifact_generation_input_characters, canonicalize_endpoint_request_aliases, endpoint_attribute_value_matches, - endpoint_contract_fingerprint, endpoint_family_contract_template, endpoint_request_fingerprint, + endpoint_contract_canonical_fingerprint, endpoint_contract_fingerprint, + endpoint_family_contract_template, endpoint_request_fingerprint, generate_endpoint_calibration_cases, materialize_endpoint_calibration_request, materialize_endpoint_request_defaults, openai_responses_input_to_chat_messages, validate_endpoint_attribute_value, validate_endpoint_request, validate_endpoint_response, @@ -63,9 +65,31 @@ pub use validated_audio::{ }; pub const CRATE_NAME: &str = "mayhem-proto"; -pub const CONTRACT_VERSION: u32 = 25; -/// Retained schema-11 receipt settlement features accepted across the v25 upgrade. -pub const RECOVERABLE_RECEIPT_CONTRACT_VERSIONS: &[u32] = &[23, 24]; + +/// Reconstruct the text used by OpenAI-compatible canary evidence without +/// retaining transport-specific SSE delta boundaries. +pub fn openai_compatible_canary_output(reasoning: &str, content: &str) -> String { + let mut output = String::new(); + if !reasoning.is_empty() { + output.push_str(""); + output.push_str(reasoning); + output.push_str(""); + } + output.push_str(content); + output +} + +/// Return Unicode scalar values for the reconstructed OpenAI-compatible +/// canary output. These units are evidence-only and do not affect metering. +pub fn openai_compatible_canary_units(reconstructed_output: &str) -> Vec { + reconstructed_output + .chars() + .map(|scalar| i32::try_from(u32::from(scalar)).expect("Unicode scalar fits i32")) + .collect() +} +pub const CONTRACT_VERSION: u32 = 28; +/// Retained receipt settlement features accepted across the v28 upgrade. +pub const RECOVERABLE_RECEIPT_CONTRACT_VERSIONS: &[u32] = &[23, 24, 25, 26, 27]; /// Historical fixture version; use receipt_contract_version_is_supported for admission. pub const RECOVERABLE_RECEIPT_CONTRACT_VERSION: u32 = 23; pub fn receipt_contract_version_is_supported(version: u64) -> bool { @@ -74,6 +98,19 @@ pub fn receipt_contract_version_is_supported(version: u64) -> bool { .iter() .any(|prior| version == u64::from(*prior)) } + +pub fn receipt_schema_version_is_supported_for_contract( + schema_version: u64, + contract_version: u64, +) -> bool { + if contract_version == u64::from(CONTRACT_VERSION) || contract_version == 27 { + return schema_version == u64::from(SESSION_RECEIPT_SCHEMA_VERSION); + } + RECOVERABLE_RECEIPT_CONTRACT_VERSIONS + .iter() + .any(|prior| contract_version == u64::from(*prior)) + && schema_version == u64::from(RECOVERABLE_SESSION_RECEIPT_SCHEMA_VERSION) +} pub const ATTESTATION_SCHEMA_VERSION: u32 = 2; pub const ATTESTATION_ALG: &str = "ed25519"; pub const ATTESTATION_POLICY_SCHEMA_VERSION: u32 = 1; @@ -82,9 +119,18 @@ pub const TPM_ACTIVATE_CREDENTIAL_SCHEMA_VERSION: u32 = 1; pub const TPM_ACTIVATE_CREDENTIAL_FRAME_VERSION: u32 = 1; pub const TPM_ACTIVATE_CREDENTIAL_CHALLENGE_FRAME_TYPE: &str = "tpm.activate.challenge"; pub const TPM_ACTIVATE_CREDENTIAL_RESPONSE_FRAME_TYPE: &str = "tpm.activate.response"; +pub const TOKENIZE_REQUEST_FRAME_TYPE: &str = "tokenize.request"; +pub const TOKENIZE_REQUEST_CHUNK_FRAME_TYPE: &str = "tokenize.request_chunk"; +pub const TOKENIZE_RESPONSE_FRAME_TYPE: &str = "tokenize.response"; +pub const TOKENIZE_RESPONSE_CHUNK_FRAME_TYPE: &str = "tokenize.response_chunk"; +pub const TOKENIZE_FRAME_VERSION: u32 = 1; pub const TPM_PCR_POLICY_SCHEMA_VERSION: u32 = 2; pub const TPM_QUOTE_EVIDENCE_SCHEMA_VERSION: u32 = 1; -pub const SESSION_RECEIPT_SCHEMA_VERSION: u32 = 11; +pub const SESSION_RECEIPT_SCHEMA_VERSION: u32 = 12; +/// Receipt schema emitted before signed utilization evidence was added. +/// It remains readable only so already-signed settlement evidence can drain. +pub const RECOVERABLE_SESSION_RECEIPT_SCHEMA_VERSION: u32 = 11; +pub const SPEND_VOUCHER_SCHEMA_VERSION: u32 = 11; pub const SIGNING_MESSAGE_VERSION: u32 = 2; pub const CTX_BRACKET_TABLE_VERSION: u32 = 1; pub const CTX_BRACKETS: &[(u32, &str)] = &[ @@ -353,6 +399,7 @@ pub const ENDPOINT_HF_TEXT_TO_VIDEO: &str = "hf_text_to_video"; pub const ENDPOINT_MAYHEM_AUDIO_GENERATIONS: &str = "mayhem_audio_generations"; pub const ENDPOINT_MAYHEM_MUSIC_GENERATIONS: &str = "mayhem_music_generations"; pub const ENDPOINT_MAYHEM_COMFY_WORKFLOWS: &str = "mayhem_comfy_workflows"; +pub const ENDPOINT_MAYHEM_DECISIONS: &str = "mayhem_decisions"; pub const ENDPOINT_HF_TEXT_TO_AUDIO: &str = "hf_text_to_audio"; pub const DEFAULT_SESSION_MAX_FRAME_BYTES: usize = 256 * 1024; pub const DEFAULT_SESSION_PAYLOAD_CHUNK_BYTES: usize = 16 * 1024; @@ -1358,6 +1405,53 @@ pub struct TpmActivateCredentialResponseFrame { pub response: TpmActivateCredentialResponse, } +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct TokenizeRequestFrame { + #[serde(rename = "t")] + pub frame_type: String, + #[serde(rename = "v")] + pub version: u32, + pub session_id: String, + pub provider: String, + pub enclave_id: String, + pub room_id: String, + pub model: String, + #[serde(default, skip_serializing_if = "String::is_empty", rename = "rid")] + pub request_id: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub request: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub request_ref: Option, + #[serde(default)] + pub return_tokens: bool, +} + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct TokenizeResponseFrame { + #[serde(rename = "t")] + pub frame_type: String, + #[serde(rename = "v")] + pub version: u32, + pub session_id: String, + pub provider: String, + pub enclave_id: String, + pub room_id: String, + pub model: String, + pub ok: bool, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub count: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub tokens: Option>, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub tokens_ref: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub error_code: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub error: Option, +} + #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize, Deserialize)] #[serde(rename_all = "snake_case")] pub enum TpmHashAlgorithm { @@ -1744,6 +1838,14 @@ pub fn canonical_usage_unit(unit: &str) -> Option<&'static str> { } } +fn is_zero_u64(value: &u64) -> bool { + *value == 0 +} + +fn is_zero_u32(value: &u32) -> bool { + *value == 0 +} + #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] pub struct ReceiptBody { pub schema_version: u32, @@ -1773,6 +1875,10 @@ pub struct ReceiptBody { #[serde(with = "decimal_u128")] pub locked_min_session_au: MoneyAu, pub served_ctx: u32, + #[serde(default, skip_serializing_if = "is_zero_u64")] + pub compute_ms: u64, + #[serde(default, skip_serializing_if = "is_zero_u32")] + pub capacity_slots: u32, #[serde(default)] pub ctx_bracket: Option, #[serde(default)] @@ -1821,6 +1927,27 @@ pub fn record_usage_receipt_envelope(receipt: &SessionReceipt) -> serde_json::Va pub fn parse_record_usage_receipt_envelope( value: &serde_json::Value, ) -> Result { + let schema_version = value + .pointer("/body/schema_version") + .and_then(Value::as_u64); + let has_compute_ms = value.pointer("/body/compute_ms").is_some(); + let has_capacity_slots = value.pointer("/body/capacity_slots").is_some(); + if schema_version == Some(u64::from(RECOVERABLE_SESSION_RECEIPT_SCHEMA_VERSION)) + && (has_compute_ms || has_capacity_slots) + { + return Err( + "invalid record usage receipt envelope: schema-11 receipt contains utilization fields" + .to_owned(), + ); + } + if schema_version == Some(u64::from(SESSION_RECEIPT_SCHEMA_VERSION)) + && !(has_compute_ms && has_capacity_slots) + { + return Err( + "invalid record usage receipt envelope: schema-12 receipt is missing utilization fields" + .to_owned(), + ); + } let envelope: RecordUsageReceiptEnvelope = serde_json::from_value(value.clone()) .map_err(|error| format!("invalid record usage receipt envelope: {error}"))?; let receipt = SessionReceipt { @@ -1852,11 +1979,25 @@ pub fn record_usage_receipt_feature_key_for_contract( receipt: &SessionReceipt, contract_version: u32, ) -> String { + record_usage_receipt_feature_key_from_envelope_for_contract( + &record_usage_receipt_envelope(receipt), + contract_version, + ) + .expect("serializing a receipt feature key cannot fail") +} + +/// Reconstruct a receipt key from the exact signed envelope. This preserves +/// retained schema-11 evidence rather than adding schema-12 fields to it. +pub fn record_usage_receipt_feature_key_from_envelope_for_contract( + envelope: &serde_json::Value, + contract_version: u32, +) -> Result { + let receipt = parse_record_usage_receipt_envelope(envelope)?; let evidence = serde_json::json!({ "contract_version": contract_version, "epoch": receipt.body.billing_epoch, "payout_revision": receipt.body.payout_revision, - "receipt": record_usage_receipt_envelope(receipt), + "receipt": envelope, }); let key_material = serde_json::json!({ "domain": "mayhem-record-usage-receipt-feature-v1", @@ -1864,15 +2005,15 @@ pub fn record_usage_receipt_feature_key_for_contract( }); let digest = stable_json_bytes(&key_material) .map(|bytes| blake3::hash(&bytes).to_hex().to_string()) - .expect("serializing a receipt feature key cannot fail"); - format!( + .map_err(|error| format!("serializing receipt feature key: {error}"))?; + Ok(format!( "receipt/submit/{}/{}/{}/{}/{}", receipt.body.billing_epoch, receipt.body.billing_id, receipt.body.billing_attempt, receipt.body.seq, digest - ) + )) } pub fn record_usage_receipt_signing_bytes( @@ -3454,10 +3595,9 @@ mod tests { "request_modalities": [["text"]], "proof_sha256": "33".repeat(32), }); - let profile: SerializedGenerationExecutionProfile = serde_json::from_value( - serialized_mode["generation_execution_profile"].clone(), - ) - .unwrap(); + let profile: SerializedGenerationExecutionProfile = + serde_json::from_value(serialized_mode["generation_execution_profile"].clone()) + .unwrap(); let legacy_profile_bytes = br#"{"schema_version":1,"engine":"vllm","independent_dispatch":true,"request_modalities":[["text"]]}"#; assert_eq!(profile.topology, None); assert_eq!(serde_json::to_vec(&profile).unwrap(), legacy_profile_bytes); @@ -3513,12 +3653,10 @@ mod tests { let mut hashes = BTreeSet::from([legacy.policy_hash]); for topology in ["shared_worker", "isolated_workers"] { mode["generation_execution_profile"]["topology"] = json!(topology); - let binding = - vllm_execution_mode_binding(&artifact_root, "throughput", &mode).unwrap(); + let binding = vllm_execution_mode_binding(&artifact_root, "throughput", &mode).unwrap(); assert!(hashes.insert(binding.policy_hash.clone())); let mut changed_proof = mode.clone(); - changed_proof["generation_execution_profile"]["proof_sha256"] = - json!("44".repeat(32)); + changed_proof["generation_execution_profile"]["proof_sha256"] = json!("44".repeat(32)); assert_eq!( vllm_execution_mode_binding(&artifact_root, "throughput", &changed_proof).unwrap(), binding @@ -3616,7 +3754,10 @@ mod tests { let profile: SerializedVllmExecutionProfile = serde_json::from_value(mode["profile"].clone()).unwrap(); assert_eq!(profile.runtime, None); - assert!(serde_json::to_value(profile).unwrap().get("runtime").is_none()); + assert!(serde_json::to_value(profile) + .unwrap() + .get("runtime") + .is_none()); mode["profile"]["runtime"] = Value::Null; assert_eq!( @@ -3626,15 +3767,24 @@ mod tests { let profile: SerializedVllmExecutionProfile = serde_json::from_value(mode["profile"].clone()).unwrap(); assert_eq!(profile.runtime, None); - assert!(serde_json::to_value(profile).unwrap().get("runtime").is_none()); + assert!(serde_json::to_value(profile) + .unwrap() + .get("runtime") + .is_none()); let runtime = VllmRuntime::FlashinferSpeculativeMetadataV1; - assert_eq!(serde_json::to_value(runtime).unwrap(), json!("flashinfer_speculative_metadata_v1")); + assert_eq!( + serde_json::to_value(runtime).unwrap(), + json!("flashinfer_speculative_metadata_v1") + ); mode["profile"]["runtime"] = serde_json::to_value(runtime).unwrap(); let profile: SerializedVllmExecutionProfile = serde_json::from_value(mode["profile"].clone()).unwrap(); assert_eq!(profile.runtime, Some(runtime)); - assert_eq!(serde_json::to_value(profile).unwrap()["runtime"], mode["profile"]["runtime"]); + assert_eq!( + serde_json::to_value(profile).unwrap()["runtime"], + mode["profile"]["runtime"] + ); let selected = vllm_execution_mode_binding(&artifact_root, "throughput", &mode).unwrap(); assert_ne!(selected, baseline); mode["profile"]["proof_sha256"] = json!("ff".repeat(32)); @@ -3643,7 +3793,11 @@ mod tests { selected ); - for invalid in [json!("unknown_runtime"), json!(1), json!({"runtime": "flashinfer_speculative_metadata_v1"})] { + for invalid in [ + json!("unknown_runtime"), + json!(1), + json!({"runtime": "flashinfer_speculative_metadata_v1"}), + ] { mode["profile"]["runtime"] = invalid; assert!(vllm_execution_mode_binding(&artifact_root, "throughput", &mode).is_err()); } @@ -4430,7 +4584,7 @@ mod tests { #[test] fn voucher_and_receipt_signing_payloads_are_bound_to_terms() { let voucher = SpendVoucherBody { - schema_version: SESSION_RECEIPT_SCHEMA_VERSION, + schema_version: SPEND_VOUCHER_SCHEMA_VERSION, session_id: "sess".to_owned(), billing_id: "11".repeat(32), billing_attempt: 0, @@ -4519,6 +4673,8 @@ mod tests { locked_per_req_au: 7, locked_min_session_au: 11, served_ctx: voucher.served_ctx, + compute_ms: 1, + capacity_slots: 1, ctx_bracket: voucher.ctx_bracket.clone(), ctx_bracket_table_ver: voucher.ctx_bracket_table_ver, rules_ver: 1, @@ -4591,6 +4747,36 @@ mod tests { receipt_signing_bytes(&workflow_receipt).unwrap(), receipt_signing_bytes(&changed).unwrap() ); + + let current = SessionReceipt { + body: receipt.clone(), + enclave_sig: "enclave-signature".to_owned(), + enclave_pubkey: "enclave-key".to_owned(), + user_sig: "user-signature".to_owned(), + }; + let mut incomplete_current = record_usage_receipt_envelope(¤t); + incomplete_current["body"] + .as_object_mut() + .unwrap() + .remove("compute_ms"); + assert!(parse_record_usage_receipt_envelope(&incomplete_current).is_err()); + + let mut legacy = current; + legacy.body.schema_version = RECOVERABLE_SESSION_RECEIPT_SCHEMA_VERSION; + legacy.body.compute_ms = 0; + legacy.body.capacity_slots = 0; + let legacy_envelope = record_usage_receipt_envelope(&legacy); + assert!(legacy_envelope["body"].get("compute_ms").is_none()); + assert!(legacy_envelope["body"].get("capacity_slots").is_none()); + assert_eq!( + parse_record_usage_receipt_envelope(&legacy_envelope).unwrap(), + legacy + ); + assert_eq!( + record_usage_receipt_feature_key_for_contract(&legacy, 26), + record_usage_receipt_feature_key_from_envelope_for_contract(&legacy_envelope, 26) + .unwrap() + ); } #[test] @@ -4631,7 +4817,7 @@ mod tests { #[test] fn signing_payloads_use_current_version_only() { let voucher = SpendVoucherBody { - schema_version: SESSION_RECEIPT_SCHEMA_VERSION, + schema_version: SPEND_VOUCHER_SCHEMA_VERSION, session_id: "sess".to_owned(), billing_id: "11".repeat(32), billing_attempt: 0, @@ -4693,6 +4879,8 @@ mod tests { locked_per_req_au: 7, locked_min_session_au: 11, served_ctx: voucher.served_ctx, + compute_ms: 1, + capacity_slots: 1, ctx_bracket: voucher.ctx_bracket.clone(), ctx_bracket_table_ver: voucher.ctx_bracket_table_ver, rules_ver: 1, @@ -4725,7 +4913,7 @@ mod tests { }, ]; let voucher = SpendVoucherBody { - schema_version: SESSION_RECEIPT_SCHEMA_VERSION, + schema_version: SPEND_VOUCHER_SCHEMA_VERSION, session_id: "sess-au-roundtrip".to_owned(), billing_id: "44".repeat(32), billing_attempt: 0, @@ -4810,6 +4998,8 @@ mod tests { locked_per_req_au: voucher.locked_per_req_au, locked_min_session_au: voucher.locked_min_session_au, served_ctx: voucher.served_ctx, + compute_ms: 1, + capacity_slots: 1, ctx_bracket: voucher.ctx_bracket, ctx_bracket_table_ver: voucher.ctx_bracket_table_ver, rules_ver: 7, @@ -4823,7 +5013,7 @@ mod tests { }; let expected_receipt = concat!( "{\"domain\":\"mayhem-session-receipt\",\"signing_version\":2,\"body\":{", - "\"schema_version\":11,\"session_id\":\"sess-au-roundtrip\",", + "\"schema_version\":12,\"session_id\":\"sess-au-roundtrip\",", "\"billing_id\":\"4444444444444444444444444444444444444444444444444444444444444444\",", "\"billing_attempt\":0,\"billing_prior_usage\":{},\"billing_prior_au_owed_cum\":\"0\",", "\"billing_epoch\":12,", @@ -4838,7 +5028,8 @@ mod tests { "{\"unit\":\"input_token\",\"per_unit_au\":\"10000000\",\"granularity\":1},", "{\"unit\":\"output_token\",\"per_unit_au\":\"2500000000000000\",\"granularity\":1000}", "],\"locked_per_req_au\":\"1\",\"locked_min_session_au\":\"2000000000000000000000000\",", - "\"served_ctx\":131072,\"ctx_bracket\":\"le128k\",\"ctx_bracket_table_ver\":1,", + "\"served_ctx\":131072,\"compute_ms\":1,\"capacity_slots\":1,", + "\"ctx_bracket\":\"le128k\",\"ctx_bracket_table_ver\":1,", "\"rules_ver\":7,\"usage\":{\"input_token\":3,\"output_token\":5},", "\"au_owed_cum\":\"2000000000000000000000001\",", "\"prompt_hash\":\"3333333333333333333333333333333333333333333333333333333333333333\",", @@ -5390,17 +5581,77 @@ mod tests { PayloadChunkError::ChunkAfterFinal { .. } )); } + + #[test] + fn tokenize_control_frames_round_trip_with_route_bindings() { + let request = TokenizeRequestFrame { + frame_type: TOKENIZE_REQUEST_FRAME_TYPE.to_owned(), + version: TOKENIZE_FRAME_VERSION, + session_id: "11".repeat(32), + provider: "22".repeat(32), + enclave_id: "33".repeat(32), + room_id: "room-1".to_owned(), + model: "qwen/example".to_owned(), + request_id: String::new(), + request: Some(json!({"contract_request": {"messages": []}})), + request_ref: None, + return_tokens: true, + }; + let encoded = serde_json::to_value(&request).unwrap(); + assert_eq!( + serde_json::from_value::(encoded).unwrap(), + request + ); + + let response = TokenizeResponseFrame { + frame_type: TOKENIZE_RESPONSE_FRAME_TYPE.to_owned(), + version: TOKENIZE_FRAME_VERSION, + session_id: request.session_id, + provider: request.provider, + enclave_id: request.enclave_id, + room_id: request.room_id, + model: request.model, + ok: true, + count: Some(3), + tokens: Some(vec![1, 2, 3]), + tokens_ref: None, + error_code: None, + error: None, + }; + let encoded = serde_json::to_value(&response).unwrap(); + assert_eq!( + serde_json::from_value::(encoded).unwrap(), + response + ); + } } #[cfg(test)] mod market_version_bridge_tests { #[test] - fn receipt_recovery_accepts_v23_v24_and_v25_only() { - for version in [23, 24, 25] { + fn receipt_recovery_accepts_v23_through_current_only() { + for version in [23, 24, 25, 26, 27, 28] { assert!(super::receipt_contract_version_is_supported(version)); } - for version in [0, 22, 26, u64::MAX] { + for version in [0, 22, 29, u64::MAX] { assert!(!super::receipt_contract_version_is_supported(version)); } + for version in [23, 24, 25, 26] { + assert!(super::receipt_schema_version_is_supported_for_contract( + 11, version + )); + assert!(!super::receipt_schema_version_is_supported_for_contract( + 12, version + )); + } + assert!(super::receipt_schema_version_is_supported_for_contract( + 12, 27 + )); + assert!(super::receipt_schema_version_is_supported_for_contract( + 12, 28 + )); + assert!(!super::receipt_schema_version_is_supported_for_contract( + 11, 27 + )); } } diff --git a/crates/mayhem-proto/src/reservation_close.rs b/crates/mayhem-proto/src/reservation_close.rs index 91450597..59910a11 100644 --- a/crates/mayhem-proto/src/reservation_close.rs +++ b/crates/mayhem-proto/src/reservation_close.rs @@ -131,7 +131,7 @@ mod tests { #[test] fn reservation_close_matches_intercom_contract_vectors() { // Generated by the actual contract's closeUsageReservationFeatureKey, - // including its stable JSON and actor-signature binding (contract 24). + // including its stable JSON and actor-signature binding (contract 28). // The close evidence carries CONTRACT_VERSION, so these digests change // whenever the contract version changes; regenerate them from // intercom/contract/contract.js, never from this crate. @@ -142,11 +142,11 @@ mod tests { for (expiry, digest) in [ ( false, - "cbed1f274171d0dad41fa156dc6e7b46f6aac1f0e69487fb27022877f5a70505", + "b2fde61528a908aa70660f7c0ecf07ee0fcefb9fefc50c8f0b1620cdb7163a2c", ), ( true, - "6cc97e5463ee99dfbee804ed30561fca98b4e8ccb060733ef3ab300c19218abf", + "9dbb792758e31729ef4029d2d91454610683104547e117152ef111b729e61372", ), ] { let mut value = usage_reservation_close_value( diff --git a/docs/contract-history-replay.md b/docs/contract-history-replay.md index f4844b0e..d1192f03 100644 --- a/docs/contract-history-replay.md +++ b/docs/contract-history-replay.md @@ -17,7 +17,7 @@ must contain the same envelope. The signed canonical read view must contain eith A dispatch field or RPC caller cannot provide this capability. Unaccepted, altered, optimistic, forged, reused and cross-batch evidence cannot enable compatibility. -Only v23 and v24 are eligible. Fresh operations continue through current admission; +Only v23, v24 and v25 are eligible. Fresh operations continue through current admission; the existing evidence-bound prepared checkpoint path remains separate. Accepted historical calls execute serially with retained source implementations: @@ -26,12 +26,13 @@ Accepted historical calls execute serially with retained source implementations: |---|---|---| | 23 | v0.2.191 (identical in v0.2.183–v0.2.191) | `365330d5a94be2c8a3afdbeb2766e052c1ab3324f39880d7940d0a9a91b9a0bc` | | 24 | v0.2.193 (identical in v0.2.192–v0.2.193) | `695c8010aa8f61fcedeb277f4251f0c6bd670bdb1c2a133b2da175900f45dd08` | +| 25 | v0.2.242 | `31d570bd8ab6e89b469e67f1d035f1cb943ff99ded8ed7824b490841ed733a4a` | These sources use no mutable protocol business helpers. Their only protocol access is wallet signature verification and immutable subnet/MSB network identity for admin signing contexts. The wallet implementation is unchanged across these source releases. Each retained contract supplies its own version, signing domains, schemas -and economic methods. The current release identity includes both retained files and +and economic methods. The current release identity includes all retained files and the replay admission/consensus transport sources. Regression fixtures use real Autobase/Corestore history, close and reopen persisted diff --git a/docs/knowledge/architecture/contract.md b/docs/knowledge/architecture/contract.md index 71606bd3..1490167d 100644 --- a/docs/knowledge/architecture/contract.md +++ b/docs/knowledge/architecture/contract.md @@ -96,12 +96,12 @@ from being truncated, partially applied, or silently reinterpreted during settle fraud}/*`, `rate/latest`, `tap/rate/latest`, `payout/*`, `settle/*`, `dep/*`, `fr/`. ## Key constants -`CONTRACT_VERSION 21`, `SESSION_RECEIPT_SCHEMA_VERSION 11`, `SIGNING_MESSAGE_VERSION 2`, +`CONTRACT_VERSION 27`, `SESSION_RECEIPT_SCHEMA_VERSION 12`, `SIGNING_MESSAGE_VERSION 2`, `epoch_seconds 3600`, `challenge_epochs 6`, holdback 24 / new-provider 168, fee cap 1500 bps, TAP -burn 1000 bps, fraud slash 10000 bps, dispute-lost slash 2000 bps, payout_min $1. Market: target -activity EMA alpha 2500, gain 5000, max step 1000, hard reference bounds 2500–40000 bps. Provider count does not gate pricing. Rails exactly +burn 1000 bps, fraud slash 10000 bps, dispute-lost slash 2000 bps, payout_min $1. Market: low/high +utilization thresholds 2000/8000 bps, fixed step 1000 bps, hard reference bounds 2500–40000 bps. Provider count does not gate pricing. Rails exactly {fiat, tap, tnk}. **No staking** — dispute deposits + reputation holdbacks are the only economic bonds. Ctx brackets le8k/le32k/le128k/le256k/gt256k. No secrets in these files — only public constants and signing-domain strings. -Contract v25 adds admin `migrate_market_pricing` and optional complete `activity_calibration` on model references. Canonical settled usage drives aggregate market activity; a dimension-relative fallback covers legacy references. Recovery accepts original signed v23 and v24 receipts. See [the coordinated upgrade procedure](../../market-activity-pricing-v25.md). +Contract v27 uses signed provider compute time and execution-slot capacity to derive absolute utilization for every model class. Prices move by a fixed 10% at the inclusive 20% and 80% thresholds while retaining the existing seed bounds. Receipt schema 12 carries the evidence; model recalibration is not required. Recovery accepts retained signed receipt evidence from contract versions 23 through 26. See [the coordinated upgrade procedure](../../market-utilization-pricing-v27.md). diff --git a/docs/knowledge/index.md b/docs/knowledge/index.md index b76d74d1..9cc1404f 100644 --- a/docs/knowledge/index.md +++ b/docs/knowledge/index.md @@ -26,7 +26,7 @@ custody model, and every credential. * [OKF maintenance rules](/okf-maintenance.md) - how to keep this bundle compliant with Google OKF v0.2 and avoid local wiki drift. ## Market and money -* [Market and pricing](/market/index.md) - the activity-momentum clearing price, reservation bands, epochs and provenance. +* [Market and pricing](/market/index.md) - the utilization-indexed clearing price, reservation bands, epochs and provenance. * [Payments, rails, and settlement](/payments/index.md) - the three rails, payouts, epoch settlement, and fraud proofs. ## How it works diff --git a/docs/knowledge/market/epochs-and-settlement.md b/docs/knowledge/market/epochs-and-settlement.md index ad2562c5..bf66e619 100644 --- a/docs/knowledge/market/epochs-and-settlement.md +++ b/docs/knowledge/market/epochs-and-settlement.md @@ -13,7 +13,7 @@ An epoch is the settlement window, `epoch_seconds` default **3600s (1 hour)**, a 60–86400. It is the unit of: evidence root computation, market-price recomputation, reputation folds, and holdback maturation. At the end of each epoch all signed receipts settle, and that settlement doubles as the signed workload input for the next epoch's price. See -[The Activity Momentum Pricing Controller](/market/pricing-controller.md) and [Epoch Settlement and Fraud Proofs](/payments/settlement-and-fraud-proofs.md). +[The Utilization-Indexed Pricing Controller](/market/pricing-controller.md) and [Epoch Settlement and Fraud Proofs](/payments/settlement-and-fraud-proofs.md). ## Per-session price lock (I3-F3) This is the load-bearing fix that makes a floating price safe. At session open, the actual resolved @@ -27,10 +27,10 @@ price. Without the lock the float would invalidate in-flight sessions; without t would freeze the market — they exist as a pair (CONCEPTS.md §2). ## Price provenance (I3-F8) — every price is recomputable -Each completed bounded epoch hashes consensus-derived price updates into its market price evidence root. Derivations bind settled workload dimensions, calibration or dimension-vector basis, previous actual activity, telemetry EMA, epoch duration, constants, seed, previous terms and result. The ordinary roller commits an empty external price root; the contract computes the actual market-price evidence after validating all canonical receipt pages. Empty seals also bind their zero-activity price evidence. +Each completed bounded epoch hashes consensus-derived price updates into its market price evidence root. Derivations bind signed compute time, execution-slot capacity, utilization, epoch duration, thresholds, multiplier, seed, previous terms and result. The ordinary roller commits an empty external price root; the contract computes the actual market-price evidence after validating all canonical receipt pages. Empty seals also bind their zero-utilization price evidence. ## Price fraud proof -Explicit nonempty price commitments require one market and at most 128 canonical final receipt heads. At commit time the contract verifies the frozen signed usage root and pins its expected activity derivation. A challenger can prove a contradictory price root within the challenge window without relying on later mutable price/calibration state. Larger epochs use bounded consensus computation. Historical monetary-price commitments must be resolved before upgrading to v25; see [the rollout procedure](../../market-activity-pricing-v25.md). +Explicit nonempty price commitments require one market and at most 128 canonical final receipt heads. At commit time the contract verifies the frozen signed usage root and pins its expected utilization derivation. A challenger can prove a contradictory price root within the challenge window without relying on later mutable price state. Larger epochs use bounded consensus computation. Historical monetary-price commitments must be resolved before upgrading; see [the v27 rollout procedure](../../market-utilization-pricing-v27.md). ## Where the money math lives Settlement itself (`epochApply`) runs the fee (15%), the TAP burn (10%), per-rail conservation diff --git a/docs/knowledge/market/index.md b/docs/knowledge/market/index.md index dbfb7456..63ae00ad 100644 --- a/docs/knowledge/market/index.md +++ b/docs/knowledge/market/index.md @@ -1,10 +1,10 @@ # Market and Pricing -How OpenMayhem sets prices: one automated, activity-momentum clearing price per market per epoch, +How OpenMayhem sets prices: one automated, utilization-indexed clearing price per market per epoch, seeded once by the admin and then floated by the contract on verified settled work. Nobody types the running price. -* [Pricing controller](/market/pricing-controller.md) - the activity-momentum controller: calibrated work, the previous-epoch baseline, the per-epoch step, and hard reference bounds. +* [Pricing controller](/market/pricing-controller.md) - the utilization controller: signed compute time, execution-slot capacity, fixed per-epoch steps, and hard reference bounds. * [Reservation bands: min-ask and max-bid](/market/bands-min-ask-max-bid.md) - how providers and users gate participation without naming a price. * [Epochs, settlement, and price provenance](/market/epochs-and-settlement.md) - the hourly epoch, the price lock, and how every published price carries a recomputable derivation. diff --git a/docs/knowledge/market/pricing-controller.md b/docs/knowledge/market/pricing-controller.md index 4b1e6707..10441373 100644 --- a/docs/knowledge/market/pricing-controller.md +++ b/docs/knowledge/market/pricing-controller.md @@ -1,41 +1,50 @@ --- type: Reference -title: "The Activity Momentum Pricing Controller" -description: "Contract v25 prices follow aggregate signed settled work, with a previous-epoch baseline, bounded steps, and hard 25%-400% reference bands." +title: "The Utilization Pricing Controller" +description: "Contract v27 changes prices from signed provider slot utilization, with fixed 10% steps and hard 25%-400% reference bands." tags: [pricing, market, controller, contract, au] -timestamp: 2026-09-13T00:00:00Z +timestamp: 2026-09-20T00:00:00Z --- -# The Activity Momentum Pricing Controller +# The Utilization Pricing Controller -Contract v25 compares each market's aggregate settled activity per second with the immediately previous epoch’s actual activity. Rising work raises its next price; falling work lowers it. Spend and active-provider counts remain settlement evidence but do not determine activity. There is no monetary target or minimum-provider gate. +Contract v27 changes each enclave/context market price once per settled epoch from its absolute utilization: -## Signed work and calibration +- utilization at or above 80%: increase every price term by 10%; +- utilization at or below 20%: decrease every price term by 10%; +- utilization between 20% and 80%: keep the price unchanged. -Canonical final receipts supply the usage increment above their signed billing baseline. Only dimensions priced by the receipt's locked rate map contribute. Checkpoints, retries and already billed work do not count twice. The controller aggregates across providers within the existing enclave/context market; it never divides by provider count. Separate enclave or context markets retain independent histories. +The boundary values are inclusive. Repeated high-utilization epochs keep raising the price and repeated low-utilization or empty epochs keep lowering it until an existing bound stops movement. The controller does not compare the current hour with the previous hour and has no dollar revenue target. -An admin model reference may carry `activity_calibration` with schema version 1, an evidence `source_hash`, and sorted dimensions `{unit, units, work_us}`. Every priced unit must have a positive calibration. For text, input tokens are normalized by calibrated prefill capacity and output tokens by calibrated decode capacity. Media and workflows use calibrated reference work for their billed dimensions. Work is summed with integer arithmetic as `floor(count × work_us × 1000000 / units)` picoseconds per axis, then divided by epoch seconds. This estimates reference work, not actual provider GPU occupancy. +## Signed utilization evidence -Existing model references without machine-readable calibration use `relative_dimension_vector_v1`: compare each signed dimension's per-second count against its own immediately previous epoch, then average those dimension ratios with equal weight. Zero/zero axes are omitted. This makes all existing model classes responsive without inventing throughput or adding incompatible raw units. A supplied partial or invalid calibration is rejected. Omission preserves an existing calibration; explicit `activity_calibration: null` clears it and returns to the dimension-vector mode. +Every schema-12 final receipt commits two provider-measured values: -## Epoch update +- `compute_ms`: elapsed execution time for that request; checkpoint values may only increase; +- `capacity_slots`: the execution concurrency offered by that provider for the signed attempt; it is immutable within the attempt. -1. The first v25 epoch establishes a baseline and holds the current price. Changing the calibration or activity basis also establishes a fresh baseline for one epoch. -2. Compare the current activity rate against the immediately previous epoch’s actual rate. Ratios use basis points and cap at 50000 (5×); a positive current value above a zero baseline uses that cap. Zero activity below a positive previous epoch has ratio zero. Equal nonzero activity has ratio 10000. Initialized empty epochs, including zero-to-zero, use ratio zero so prices continue stepping down toward their hard floor. -3. Set the desired price to the current price multiplied by that ratio. Move toward it using `market_gain_bps` (default 5000) and the existing `market_max_step_bps` clamp (default 1000, or 10%). Integer rounding retains a minimum one-atto movement where required. -4. Clamp rate-map terms to 25%-400% of the model reference. Clamp fixed `per_req_au` and `min_session_au` to 25%-400% of their immutable admin seed; a zero seed stays zero. Hard safety corrections take precedence over the step limit. -5. Update the telemetry-only EMA with `market_ema_alpha_bps` (default 2500) and publish `market_activity_momentum` or `market_activity_hold` with the complete derivation. EMA never determines price direction. +The buyer acknowledges the signed provider receipt. Settlement sums `compute_ms` from canonical final receipts and, for each distinct provider observed in a market during the epoch, takes the maximum signed `capacity_slots`. Available slot time is: -For example, doubling calibrated work relative to the previous epoch produces momentum 20000. With the default gain and step, a price of 100 becomes 110. Halving activity produces a downward step. One provider behaves the same as many providers doing the same aggregate work. Stable nonzero work has momentum 10000 regardless of the price paid. Every initialized empty epoch causes a downward step until the hard floor. Hard bounds and integer price granularity still limit movement. +`capacity_slot_count × epoch_seconds × 1000` -## Coverage, bounds and participation +Utilization is `min(100%, compute_ms / available_slot_time)`. A provider with two slots therefore needs twice as much aggregate compute time as a one-slot provider to reach the same utilization. Text, embeddings, images, audio, video and workflows use the same evidence and thresholds. Catalog `activity_calibration` metadata remains valid, but no longer controls price direction and no recalibration is required for this upgrade. -`migrate_market_pricing` seeds every active base/context schedule into the bounded canonical activity index. Completed settlement and empty-epoch seals update indexed dormant markets with zero activity. There is no permanent thin-market freeze. Operators must complete every migration batch before resuming epoch settlement; see [the v25 rollout procedure](../../market-activity-pricing-v25.md). +Only canonical settled work enters utilization. Checkpoints, duplicate receipts, redispatch baselines and already billed work do not count twice. Unserved requests and abandoned reservations do not create utilization evidence. -The shipped mainnet configuration and template both use `price_min_bps=2500` and `price_max_bps=40000`. v25 rejects wider bounds and sanitizes historical unsafe active or pending parameter records before they can take effect. Admin seed updates retain existing scheduling and rate limits. Historical wider settings are not the v25 policy. +Receipts retained from before contract v27 do not contain signed slot-time fields. They still settle normally. If an epoch contains one, that market records a `legacy_receipt_hold_v1` derivation and keeps its price unchanged for that epoch rather than inventing utilization. The next epoch containing only schema-12 receipts resumes the normal rule automatically. -Min-ask and max-bid still gate participation. Unserved requests and unspent reservations do not create verified completed work: current signed evidence does not establish unmet demand or distinguish withholding from absent demand. Consequently a saturated market with flat completed work need not rise merely because a queue grows. Adding funded admitted/unmet demand requires a separately specified signed, deduplicated, expiring evidence protocol. +## Price update and bounds -Funded wash activity can influence prices within the step and reference bounds. Signed settlement makes that activity accountable and costly; it cannot prove independent economic intent. Provider-advertised capacities, claims of demand and AU totals cannot directly change the controller. +The selected multiplier, 9000, 10000 or 11000 basis points, is applied directly to every rate-map term plus `per_req_au` and `min_session_au`. Integer rounding preserves a minimum one-atto term when a nonzero price moves. -Session price locks, rail conservation, evidence locks and deterministic consensus remain in force. See [price provenance](epochs-and-settlement.md) and [settlement proofs](../payments/settlement-and-fraud-proofs.md). +The result remains clamped to 25%-400% of the immutable admin seed. A zero fixed-term seed stays zero. Admin seed scheduling, session price locks, provider min asks, buyer max bids, rail conservation and fraud-proof roots remain unchanged. + +The price derivation records schema 3, the signed compute total, capacity slot count, utilization, selected multiplier, epoch duration, previous price, seed bounds and result. Empty indexed markets use zero utilization and receive the same 10% downward step. + +## Migration and operation + +`migrate_market_pricing` must run at a completed epoch boundary after all old nonempty price commitments are resolved. It indexes every active base/context market, preserves current prices and seeds, retains the 25%-400% bounds, and records the 20%/80%/10% policy. The offline generator is `intercom/scripts/prepare-market-activity-upgrade.mjs`. + +The upgrade requires contract v27 and receipt schema 12 across the writer, gateways, providers and settlement workers. Contract v27 can settle retained signed receipt evidence from contract versions 23 through 26 without rewriting it; the affected market holds price for that settlement epoch because those receipts have no signed slot-time data. Do not mix contract versions while accepting new work. + +Utilization proves completed slot occupancy, not queued unmet demand. A fully queued provider can stay below 80% if jobs fail before producing canonical receipts. Signed evidence makes completed activity accountable; it does not prove independent economic intent. diff --git a/docs/knowledge/operations/configuration-and-admin-params.md b/docs/knowledge/operations/configuration-and-admin-params.md index 6c52b062..7b901f7a 100644 --- a/docs/knowledge/operations/configuration-and-admin-params.md +++ b/docs/knowledge/operations/configuration-and-admin-params.md @@ -52,10 +52,14 @@ when no record exists. Full default/bounds tables: `docs/reference/intercom-epoc (5000), `param_activation_delay_seconds` (86400), `rules_grace_seconds` (1209600), `rate_staleness_seconds` (2700). -**Market controller** (see [Activity Momentum Pricing](../market/pricing-controller.md)) — -`price_rate_limit_seconds`, `market_ema_alpha_bps` (2500), `market_gain_bps` (5000), -`market_max_step_bps` (1000), and hard bounds `price_min_bps` (2500) / `price_max_bps` (40000). -The old utilization target, provider-dollar target, minimum-provider gate, utilization cap and two curve-slope knobs are deprecated readable compatibility fields; v25 rejects new updates to them. Run the bounded admin `migrate_market_pricing` plan before resuming settlement after upgrade. Both shipped mainnet manifests use the exact hard bounds. +**Market controller** (see [The Utilization Pricing Controller](../market/pricing-controller.md)) — +`price_rate_limit_seconds` governs admin seed changes, while hard bounds remain +`price_min_bps` (2500) / `price_max_bps` (40000). Contract v27 applies fixed protocol rules: +utilization at or above 80% raises price 10%, utilization at or below 20% lowers price 10%, and +the middle band holds. The former utilization target, EMA, gain, configurable step, +provider-dollar target, minimum-provider gate, utilization cap and curve-slope knobs are readable +historical fields and reject new updates. Run the bounded admin `migrate_market_pricing` plan before +resuming settlement after upgrade. **Trust and economics** — `fee_bps` (1500, hard-capped), probation set (`probation_successful_sessions`, `probation_seconds`, diff --git a/docs/knowledge/operations/model-roster.md b/docs/knowledge/operations/model-roster.md new file mode 100644 index 00000000..77a1ae14 --- /dev/null +++ b/docs/knowledge/operations/model-roster.md @@ -0,0 +1,43 @@ +# Model roster decisions + +The signed catalog is the source of truth for the current model and workflow +roster. This document records durable operating decisions that should not be +inferred from a temporary provider state. + +## Extensible model support + +Existing model classes and workflows remain in Core for compatibility. Future +models, workflows, and model types should move toward signed, versioned add-ons +that declare their endpoint contract, artifacts, runtime adapter, canaries, +resource limits, and billing dimensions without requiring every participant to +install a new Core contract. Core should retain only the stable verifier, +sandbox, settlement, and add-on lifecycle. A new add-on must fail closed on an +older runtime and must not interrupt unrelated providers during publication or +rollback. + +Qwen3 Embedding 4B is intentionally calibrated through the existing monolithic +path for this release. Its generic vLLM pooling support belongs in Core because +the current runtime could not execute embedding catalog models. Its model +identity, artifact, endpoint limits, dimensions, canaries, price, and platform +proof remain signed catalog data. + +## Qwen3 Embedding 4B + +- Canonical model: `Qwen/Qwen3-Embedding-4B` +- Exact source revision: `5cf2132abc99cad020ac570b19d031efec650f2b` +- Exact mirror revision: `909825755dc39379f3eb31256602da9cafb29c95` +- Calibrated backend: vLLM 0.24 pooling, BF16, Linux NVIDIA compute capability + 12.1 +- Context: 32,768 model tokens +- Dimensions: native 2,560 and Matryoshka 32 through 2,560, including exact + 1,536 +- Batch input: ordered arrays up to 32 items +- Settlement rails: FIAT, TNK, and TAP with same-currency settlement +- Windows support is unavailable until a separate Windows CUDA proof exists. + +Weight-bearing calibration runs on the selected provider host. Catalog +application, signing, and publication run on the canonical admin/indexer and do +not load model weights. A Core change requires one identical release across all +providers, gateways, helpers, relays, and dependent workers. Every store must +remain on the indexer's canonical fork; stores are never deleted to repair a +rollout. diff --git a/docs/knowledge/overview.md b/docs/knowledge/overview.md index 87921150..cf126ef8 100644 --- a/docs/knowledge/overview.md +++ b/docs/knowledge/overview.md @@ -18,8 +18,8 @@ OpenMayhem is a peer-to-peer AI inference marketplace on Trac Network. Users poi OpenAI-compatible client at a local gateway (`127.0.0.1:11435`) and buy inference directly from provider machines over encrypted P2P; an admin-only replicated contract on an Intercom subnet holds the catalog, prices, balances, and settlement. Everything is priced in dollars (`au_usd`, atto-USD). -Nobody sets the running price — the admin seeds each per-enclave market once, then a -activity-momentum controller floats it. Four attestation tiers (software, TPM device identity, +Nobody sets the running price — the admin seeds each per-enclave market once, then an +absolute-utilization controller floats it. Four attestation tiers (software, TPM device identity, confidential compute, KYB business), each its own priced market, back trust with evidence: signed receipts, canary probes, holdbacks, and permissionless fraud proofs. Three isolated payment rails (Stripe, Ethereum/TAP, Trac/TNK) carry value but never mix; providers keep 85% on fiat/TNK and diff --git a/docs/knowledge/payments/settlement-and-fraud-proofs.md b/docs/knowledge/payments/settlement-and-fraud-proofs.md index 2b5cd374..6ea04d2b 100644 --- a/docs/knowledge/payments/settlement-and-fraud-proofs.md +++ b/docs/knowledge/payments/settlement-and-fraud-proofs.md @@ -49,7 +49,7 @@ that envelope, receipt application remains capped by `max_apply_batch` (default `new_provider_holdback_epochs` default 168 (one week); release additionally gated by canary-probe status and open disputes. Payable = total − held − paid_cum. - **Roots:** the final page carries per-epoch evidence roots `dep, use, earn, fee, price` validated - against recomputed totals. The market price controller ([The Activity Momentum Pricing Controller](/market/pricing-controller.md)) runs + against recomputed totals. The market price controller ([The Utilization-Indexed Pricing Controller](/market/pricing-controller.md)) runs inside the same apply. ## Fraud proofs (permissionless, no admin discretion) diff --git a/docs/laya-onboarding-2026-09-21.md b/docs/laya-onboarding-2026-09-21.md new file mode 100644 index 00000000..57593f3a --- /dev/null +++ b/docs/laya-onboarding-2026-09-21.md @@ -0,0 +1,118 @@ +# Laya onboarding and calibration + +Status: implementation and two-host calibration complete; production micro-canary and rollout remain. This plan is subordinate to `docs/CALIBRATION.md` v10. + +## Scope + +Onboard the pinned `convaiinnovations/laya` bundle as a native typed-decision model. The bundle contains the English, multilingual, and typed-decisions checkpoints. Production providers preload all three checkpoints on CUDA and use the upstream router. SemIf/Qwen3.5-4B is explicitly out of scope. + +The public surface is `POST /v1/decisions`. This is a new endpoint family and model class; Laya must not be represented as chat completion or text generation. The endpoint participates in the existing signed session, receipt, route, retry, idempotency, job, pricing, fiat, TNK, and TAP paths. + +## Pinned upstream facts + +- Repository: `https://huggingface.co/convaiinnovations/laya` +- Revision: `1c5edc17a7acd8701df6fc341c0d179f1c62c982` +- License: Apache-2.0 +- Runtime: Python 3.10+, PyTorch, Transformers 5.x, upstream `laya.Router` +- English checkpoint: ModernBERT-large, 421M, 512-token context +- Multilingual checkpoint: mmBERT-base, 322M, 1024-token context +- Typed-decisions checkpoint: ModernBERT-large, 421M, 1024-token context +- Production mode: `Router(preload=True, device="cuda")` +- Upstream measured T4 latency: 32.8-39.5 ms for one question, 72.3-158.6 ms for ten, and 337 ms for fifty on multilingual +- Architectural limit: keep `choice` questions at 20 options or fewer +- Automatic typed-workflow detection is opt-in upstream and remains opt-in here + +Research evidence is retained outside git at `.local-mayhem/laya-onboarding-20260921/research` in the parent workspace. + +## Card-to-pipeline coverage + +| Field | Source | Type / limit | Default | Mayhem disposition | +|---|---|---|---|---| +| `state` | README, `Agent.system_one` | string, object, or array; serialized into the selected checkpoint context | required | exposed, required | +| `questions` | README, `Agent.system_one` | object keyed by question id | required | exposed, required; bounded count and byte size | +| `questions.*.type` | README, `common.QTYPES` | `choice`, `score`, or `noul` | required | exposed, required | +| `questions.*.instructions` | README, `_to_internal` | string or JSON-serializable value | required | exposed, required and bounded | +| `questions.*.criteria` for choice | README, `render_options` | object or list; at most 20 options | required | exposed, required, max 20 | +| `questions.*.criteria` for score | README, `render_options` | ordered array | required | exposed, required and bounded | +| `questions.*.criteria` for noul | `render_options` | optional object with `false` and `true` descriptions | omitted | exposed, optional | +| `model` override | README, `Router.predict` | `english`, `multilingual`, `typed-decisions`, plus documented aliases | automatic routing | exposed as `checkpoint`; canonical values only | +| `task` | README, `Router.route` | `typed_decisions` or checkpoint alias | omitted | exposed, optional | +| `lang` | README, `Router.route` | language hint | detected | exposed, optional | +| typed workflow auto-detection | `Router(auto_task_detection=...)` | boolean | false | exposed as `auto_task_detection`, default false | +| router default | `Router(default=...)` | canonical checkpoint name | english | provider serving knob fixed to upstream default | +| preload | README, `Router(preload=True)` | boolean | false upstream generic SDK | provider serving knob fixed true to avoid 7-10 s reloads | +| device | README, `Router(..., device="cuda")` | runtime device | auto | provider serving knob fixed CUDA; CPU fallback is rejected | +| response `answers` | README, `Agent.system_one` | typed object containing probability, confidence, and action data | n/a | returned unchanged after finite-number validation | +| response `routing` | README, `Router.predict` | selected checkpoint and reason | n/a | returned unchanged after local-path/token redaction checks | +| response `usage` | `Agent.system_one` plus Mayhem's bounded response meter | exact processed input tokens plus serialized result units (one unit per four visible JSON bytes) | n/a | returned and reconciled through the standard receipt path; no per-request or minimum-session charge | + +## Measured serving envelope and price + +The pinned three-checkpoint bundle was loaded on two independent NVIDIA GB10 +CUDA hosts with the exact frozen Python runtime. Both runs reported all three +checkpoints resident on CUDA and a peak worker allocation of 5,671 MiB. A +five-request warm probe measured 20.889 ms p95 on the first host and 20.444 ms +p95 on the second. The fuller mixed calibration probes measured 84.531 ms and +84.237 ms p95 respectively. Production load now fails closed if the device is +not CUDA or any checkpoint is absent. + +The proposed Tier-1 reference rate is 10,000,000,000 au per input token and per +serialized result unit, equal to $0.01 per million units. There is no fixed +request charge and no minimum-session charge. With the platform's hard +25%-400% activity band, the full range is $0.0025-$0.04 per million units. The +floor remains a positive 2,500,000,000 au per unit, so a one-unit request is +still billable and neither fiat nor TNK/TAP accounting rounds the work to zero. +The reference price is below the upstream managed-API comparison of $0.042 per +million tokens while preserving room for every downward market step. Tier 2 +uses the same calibration evidence and receives its required higher seed when +the enclaves are registered. + +## Final two-host calibration evidence + +The final catalog fingerprints were produced from Core commit `f72df193` with +the release-mode executable whose SHA-256 is +`845be261b273eb564fc37a22edd9f28344a612390026bc45974df689fa24f862`. +Both selected CUDA hosts independently ran all eight decision canaries and the +complete 64-case `mayhem_decisions` endpoint matrix with `--require-match`. +Both runs passed with zero endpoint failures and reported: + +- catalog fingerprint: `f0e70feb5f9da6ad5e273a109c7131c54c01414e96c9a404dffb225054af98c1` +- text-modality fingerprint: `1977665478c278ee738983b310f7fdec25a43d5db6f415732340a5053c2a4ab1` +- endpoint-matrix fingerprint: `327ab16b78ac6c24314d73ee0b47cb31945b5cfcc2a8885b5136c197eacce76f` +- eight matching prompt fingerprints and 64 passing endpoint cases per host + +The retained reports are +`.local-mayhem/laya-onboarding-20260921/reports/f72df193/spark41/report.json` +and +`.local-mayhem/laya-onboarding-20260921/reports/f72df193/spark42/report.json`. +Their SHA-256 values are respectively +`5e524549bd3f158cd54172e6b4272cd90729b292d2f51ca8eec6379f18931f28` +and +`1d909312822a90bc0e84425100d81f6cad7b3b1113ea7a289477e5d0b9e8c0fb`. + +The upstream checkpoint contains a `choice:11+` temperature of `0.1006`, +outside Laya's supported `[0.5, 5]` range. The pinned upstream runtime clamps +that value and warns that confidence for the affected bucket is uncalibrated. +Mayhem preserves that upstream behavior and does not silently reinterpret the +confidence value. + +## Implementation order + +1. Add `mayhem_decisions`, model class `decision`, endpoint contract, request validation, compatibility validation, and exact canonical JSON decision fingerprinting. +2. Add a persistent `laya` engine backend and embedded JSON-line Python worker. Load only the pinned local snapshot, preload all three checkpoints, require CUDA, reject silent CPU fallback, and preserve upstream routing behavior. +3. Add provider dispatch, signed session result handling, route retry, idempotent job storage, exact input/result-unit metering, and the gateway `/v1/decisions` response. +4. Add CLI backend lifecycle, runtime preparation, artifact verification, calibration canary support, Tier 1/Tier 2 registration, and catalog validation. +5. Download through the configured Hugging Face token, mirror/pin exact artifacts, calibrate on suitable operator hardware, and capture latency, memory, deterministic output, concurrency, and failure evidence. +6. Prove peak co-residency before selecting the two Sparks. Current candidates are Spark41 and Spark42; momentary free unified memory is not proof. Preserve at least 15-20 GiB at observed concurrent peak and do not disturb existing providers. +7. Publish the catalog row and lowest sustainable nonzero unit-rate anchor, enable the established fiat/TNK/TAP provider identities, then perform a paid micro-canary on both providers including cross-provider deterministic output and fallback. +8. Only after the canary is clean, roll the contract-bearing release across Core, gateways, helpers, workers, providers, and fleet stores under the established canonical-indexer rollout rules. Verify every process reports the same release and canonical fork before declaring completion. +9. Add the model page, API documentation, examples, and discoverability to the site after the live API is proven. Release Core and site separately. + +## Acceptance gates + +- English, non-English, explicit typed-decisions, all three question primitives, and invalid-shape cases pass through the paid public API. +- No request downloads or cold-loads weights. +- Both selected providers run CUDA and remain within the measured co-residency reserve under existing provider peak use. +- Automatic route, explicit checkpoint route, measured unit billing, retries, async jobs, idempotency, cancellation, Tier 1/Tier 2, and all three payment rails are verified. +- Exact canary output matches on both providers at the pinned artifact/backend fingerprint. +- No fleet-wide rollout begins before the two-provider micro-canary is complete. diff --git a/docs/market-utilization-pricing-v27.md b/docs/market-utilization-pricing-v27.md new file mode 100644 index 00000000..bd852a21 --- /dev/null +++ b/docs/market-utilization-pricing-v27.md @@ -0,0 +1,29 @@ +# Contract v27 utilization pricing rollout + +This change requires a coordinated Core contract upgrade. Contract v27 adds signed `compute_ms` and `capacity_slots` to schema-12 receipts and replaces previous-epoch activity momentum with absolute slot utilization. Prices move +10% at or above 80%, -10% at or below 20%, and hold between those thresholds. Existing 25%-400% seed bounds remain. + +## Prepare offline + +Export a complete canonical snapshot with `at`, `epoch_apply_state`, `pending_price_commits`, `modelrefs`, `enclaves`, and the full `prices` array. Finish any pending paged epoch and resolve prior-version nonempty price commitments first. + +```sh +node intercom/scripts/prepare-market-activity-upgrade.mjs canonical-snapshot.json unsigned-plan.json +``` + +The generator writes unsigned `migrate_market_pricing` batches of at most 128 markets. It does not sign or submit anything. Verify `required_final_index` against the canonical store before resuming settlement. + +## Coordinated cutover + +1. Build and authenticate one release containing contract v27, receipt schema 12, the writer recompute changes, and all gateway/provider receipt changes. +2. Drain a canary gateway and provider without discarding pending receipts or outboxes. Upgrade the canonical writer and canary components under the established contract-transition procedure. +3. At a completed epoch boundary, submit every reviewed `migrate_market_pricing` batch. Confirm the activity index, migration-v3 record, contract digest, and canonical fork. +4. Prove streaming and non-streaming receipts, checkpoint monotonicity, multi-slot capacity, final epoch recompute, the 20%/80% boundaries, and retained v26 receipt recovery on the canary. +5. Only after that proof, drain and upgrade every remaining gateway, provider, helper, payment watcher, and settlement worker to the identical release. Preserve all stores and outboxes. +6. Verify every process reports the same release/hash and every follower store is on the writer/indexer canonical fork. The writer/indexer store is the source of truth and must never be wiped. +7. Resume normal admissions and verify the first completed production epoch publishes schema-3 utilization derivations. + +If canary proof fails, keep the rest of the fleet on the prior release and correct the candidate. Once v27 operations or schema-12 receipts are accepted, use a state-aware forward correction rather than blindly rolling the contract back. + +Public release notes must describe behavior without naming hosts, operating systems, credentials, or internal fleet topology. + +Retained schema-10/11 receipts remain payable after cutover. Because they predate signed `compute_ms` and `capacity_slots`, any market containing one records a legacy-evidence hold and leaves price unchanged for that epoch. Never synthesize utilization for them. Normal utilization pricing resumes automatically once an epoch contains only schema-12 receipts. diff --git a/docs/reference/intercom-epoch-admin-params.md b/docs/reference/intercom-epoch-admin-params.md index 68c41b86..549bc3d4 100644 --- a/docs/reference/intercom-epoch-admin-params.md +++ b/docs/reference/intercom-epoch-admin-params.md @@ -12,7 +12,7 @@ also exported as `contractEpochAdminParamKeys()` / `contractEpochAdminParamDefin can assert the live map, not a hand-maintained prose list. The values listed here are defaults and bounds, not provider-settable terms. -The current writable source map is exported by `contractEpochAdminParamKeys()` in `intercom/contract/contract.js`; deprecated compatibility records listed below are readable but cannot be set under v25. +The current writable source map is exported by `contractEpochAdminParamKeys()` in `intercom/contract/contract.js`; deprecated compatibility records listed below remain readable but cannot be set under v27. ## Epoch, Settlement, And Governance @@ -36,9 +36,9 @@ The current writable source map is exported by `contractEpochAdminParamKeys()` i |---|---:|---:|---| | `price_rate_limit_seconds` | `21600` | `0 .. 31536000` | Admin seed `P0` change throttle only; market floats are exempt. | | `market_target_utilization_bps` | historical | read-only | Deprecated since v25; ignored by activity pricing. | -| `market_ema_alpha_bps` | `2500` | `1 .. 10000` | Telemetry activity EMA weight per epoch; does not determine price direction. | -| `market_gain_bps` | `5000` | `1 .. 10000` | Dampening gain toward the desired price. | -| `market_max_step_bps` | `1000` | `1 .. 10000` | Per-epoch max price movement clamp. | +| `market_ema_alpha_bps` | historical | read-only | Deprecated in v27; ignored by utilization pricing. | +| `market_gain_bps` | historical | read-only | Deprecated in v27; ignored by utilization pricing. | +| `market_max_step_bps` | historical | read-only | Deprecated in v27; the contract uses a fixed 10% step. | | `market_cold_start_min_providers` | historical | read-only | Deprecated since v25; ignored by activity pricing. | | `market_provider_epoch_target_au` | historical | read-only | Deprecated since v25; ignored by activity pricing. | | `market_max_utilization_bps` | historical | read-only | Deprecated since v25; ignored by activity pricing. | @@ -56,7 +56,7 @@ The current writable source map is exported by `contractEpochAdminParamKeys()` i | Intercom contract `epochCommit` / `fraudProof` | `challenge_epochs`, `epoch_seconds`, `fraud_slash_bps` | Stores `provisional_until_epoch`; challenges remain epoch-count based, not wall-clock based. Commit hashes/records and fraud-proof replay bind the active epoch timing. Provider-committer penalties read the active admin slash percentage. | | Intercom contract probe/dispute slashing | `fraud_slash_bps`, `dispute_lost_slash_bps` | Canary mismatch, fraud proof, direct dispute-loss reputation events, and dispute resolution all read active admin slash percentages at the event timestamp. | | Intercom contract rate gates | `rate_staleness_seconds` | TNK/TAP oracle freshness window. | -| Intercom contract market tick | `epoch_seconds`, all `market_*` params | Price derivation evidence records the active constants and epoch timing used for replay. | +| Intercom contract market tick | `epoch_seconds`, `price_min_bps`, `price_max_bps` | Signed receipt slot time determines utilization. The 20%/80% thresholds and 10% step are deterministic protocol constants recorded in derivation evidence. | | Context bracket governance | `param_activation_delay_seconds` plus `ctx_brackets` schedule | Admin-only `setCtxBrackets` publishes versioned `current`/`pending` tables. Gateway spend vouchers and providers now read `ctx_brackets` from contract state and settle against the pinned table version instead of a hardcoded runtime table. | | Canonical gateway launched by `mayhem use` | `epoch_seconds`, `ctx_brackets` | Gateway-generated reputation-event commands derive their contract epoch from the active admin value, not a fixed one-hour epoch. Gateway session vouchers derive `ctx_bracket`/`ctx_bracket_table_ver` from the active admin context table. | | Fiat paygate | `epoch_seconds` | Stripe evidence derives `fiat_deposit` / `fiat_chargeback` epochs from the active admin contract value. Service config `contract.epoch_seconds` is a fallback before a contract param record exists, not the live authority. | @@ -131,4 +131,4 @@ These are not operating knobs: schema versions, fixed rail names (`fiat`, `tap`, Simulation-only values such as `intercom/scripts/market-sim.mjs`'s default epoch length do not drive contract state or production evidence. -Contract v25 removes the six deprecated monetary-utilization fields from writable epoch parameters. Active pricing uses activity EMA alpha, gain, step clamp and hard bounds 2500–40000 bps. Both mainnet manifests use exactly 2500/40000. Existing unsafe active/pending records are suppressed and audited by `migrate_market_pricing`. See [the v25 upgrade](../market-activity-pricing-v25.md). +Contract v27 also removes the activity EMA, gain and configurable step from the active controller. Absolute signed slot utilization uses fixed inclusive thresholds of 20% and 80% and a fixed 10% step. Bounds remain writable within 2500–40000 bps. Existing unsafe active/pending records are suppressed and audited by `migrate_market_pricing`. See [the v27 upgrade](../market-utilization-pricing-v27.md). diff --git a/docs/release-notes-0.2.198.md b/docs/release-notes-0.2.198.md new file mode 100644 index 00000000..909f931c --- /dev/null +++ b/docs/release-notes-0.2.198.md @@ -0,0 +1,12 @@ +# 0.2.198 + +Adds signed, managed OpenAI-compatible generation runtimes. Catalog artifacts can +now bind an exact model snapshot, runtime recipe, platform wheel, container image, +hardware envelope, concurrency limit, and capability preflight. Core verifies those +bindings before a provider advertises the route and owns the runtime through cleanup +or recovery. + +Streaming, reasoning, tools, structured output, prefix caching, cancellation, and +concurrent generation are admitted only when the signed runtime proves the claimed +behavior. Existing engines and catalog entries retain their prior behavior. Contract +version remains 25 and this release requires no pricing migration. diff --git a/docs/release-notes-0.2.199.md b/docs/release-notes-0.2.199.md new file mode 100644 index 00000000..475a3bd0 --- /dev/null +++ b/docs/release-notes-0.2.199.md @@ -0,0 +1,11 @@ +# 0.2.199 + +Fixes offline installation of signed managed-runtime wheels. Core now preserves +the recipe-validated wheel filename when mounting it into the runtime installer, +allowing package tooling to validate the wheel name, version, ABI, and platform +tags before installation. + +The managed runtime remains fail closed: artifact hashes, the signed filename, +offline installation, and the installed package identity must all match before a +provider can advertise the route. Contract version remains 25 and this release +requires no pricing migration. diff --git a/docs/release-notes-0.2.200.md b/docs/release-notes-0.2.200.md new file mode 100644 index 00000000..fdf2efa2 --- /dev/null +++ b/docs/release-notes-0.2.200.md @@ -0,0 +1,11 @@ +# 0.2.200 + +Fixes ownership of files created by managed-runtime preparation containers. +Offline wheel installation, model preparation, and verification now run with +the managed runtime directory's numeric user and group, so their outputs remain +readable and removable by the provider service after each container exits. + +Preparation remains isolated from the network and keeps its signed command and +artifact checks. The persistent model service is started separately after those +checks pass. Contract version remains 25 and this release requires no pricing +migration. diff --git a/docs/release-notes-0.2.201.md b/docs/release-notes-0.2.201.md new file mode 100644 index 00000000..0c3cb36a --- /dev/null +++ b/docs/release-notes-0.2.201.md @@ -0,0 +1,10 @@ +# 0.2.201 + +Runs the persistent managed model service with the same numeric user and group as +its Core-owned runtime directory. Signed source, prepared model files, caches, and +the long-running model process therefore share one ownership boundary from setup +through serving and cleanup. + +The service remains constrained by its signed container image, resource limits, +network and IPC profile, seccomp policy, launch arguments, and artifact proofs. +Contract version remains 25 and this release requires no pricing migration. diff --git a/docs/release-notes-0.2.202.md b/docs/release-notes-0.2.202.md new file mode 100644 index 00000000..60207aab --- /dev/null +++ b/docs/release-notes-0.2.202.md @@ -0,0 +1,8 @@ +# 0.2.202 + +OpenAI-compatible runtime preflight now gives every signed chat-template +control a unique parameter identity derived from its native path. Profiles that +carry several reasoning controls pass request validation while preserving each +exact signed control value. + +Contract version remains 25 and this release requires no pricing migration. diff --git a/docs/release-notes-0.2.203.md b/docs/release-notes-0.2.203.md new file mode 100644 index 00000000..8b6980b3 --- /dev/null +++ b/docs/release-notes-0.2.203.md @@ -0,0 +1,8 @@ +# 0.2.203 + +OpenAI-compatible concurrency preflight now polls the signed scheduler metric +while its bounded request workers are live. Admission requires both observed +scheduler overlap at the signed concurrency and first streamed content from +every worker before Core cancels the probes. + +Contract version remains 25 and this release requires no pricing migration. diff --git a/docs/release-notes-0.2.204.md b/docs/release-notes-0.2.204.md new file mode 100644 index 00000000..8db1f11a --- /dev/null +++ b/docs/release-notes-0.2.204.md @@ -0,0 +1,8 @@ +# 0.2.204 + +OpenAI-compatible stream adapters now derive deterministic, content-bound +pseudo-token IDs when upstream SSE omits portable token IDs. Token-fingerprint +calibration can distinguish equal-length streams by their delta content while +preserving the existing streamed text and chunk delivery. + +Contract version remains 25 and this release requires no pricing migration. diff --git a/docs/release-notes-0.2.205.md b/docs/release-notes-0.2.205.md new file mode 100644 index 00000000..06f7a6ea --- /dev/null +++ b/docs/release-notes-0.2.205.md @@ -0,0 +1,11 @@ +# 0.2.205 + +OpenAI-compatible catalog canaries now derive portable fingerprint units from +the reconstructed reasoning and visible response text. Fingerprints remain +stable when an upstream stream divides identical output into different deltas, +without changing streamed responses or usage accounting. + +Catalog validation now accepts signed OpenAI-compatible KV-cache metadata and +distinguishes multimodal video input from generated video output. + +Contract version remains 25 and this release requires no pricing migration. diff --git a/docs/release-notes-0.2.206.md b/docs/release-notes-0.2.206.md new file mode 100644 index 00000000..f247c2ed --- /dev/null +++ b/docs/release-notes-0.2.206.md @@ -0,0 +1,8 @@ +# 0.2.206 + +Calibration memory measurement now tolerates managed descendant processes that +exit between in-flight sampling and the final RSS sample. It still requires a +live measured process and a successful in-flight sample, and operation errors +remain authoritative. + +Contract version remains 25 and this release requires no pricing migration. diff --git a/docs/release-notes-0.2.207.md b/docs/release-notes-0.2.207.md new file mode 100644 index 00000000..d9883d1d --- /dev/null +++ b/docs/release-notes-0.2.207.md @@ -0,0 +1,8 @@ +# 0.2.207 + +The managed Qwen3.8 Flash-Next profile now uses deterministic inference with +the compatible Triton linear-attention prefill backend. Its signed launch +profile continues to use FlashInfer decode and the existing radix and +hierarchical cache configuration. + +Contract version remains 25 and this release requires no pricing migration. diff --git a/docs/release-notes-0.2.208.md b/docs/release-notes-0.2.208.md new file mode 100644 index 00000000..64959cf1 --- /dev/null +++ b/docs/release-notes-0.2.208.md @@ -0,0 +1,8 @@ +# 0.2.208 + +The managed Qwen3.8 Flash-Next profile now selects the Triton main-attention +backend when deterministic inference is enabled. The signed launch profile +continues to use Triton linear-attention prefill, FlashInfer linear-attention +decode, and the existing radix and hierarchical cache configuration. + +Contract version remains 25 and this release requires no pricing migration. diff --git a/docs/release-notes-0.2.209.md b/docs/release-notes-0.2.209.md new file mode 100644 index 00000000..b245e2bb --- /dev/null +++ b/docs/release-notes-0.2.209.md @@ -0,0 +1,7 @@ +# 0.2.209 + +OpenAI-compatible prefix-cache preflight now supports Prometheus counters that +are created lazily on the first cache hit. The identical-prefix replay must +still expose the signed counter and prove that its value increased. + +Contract version remains 25 and this release requires no pricing migration. diff --git a/docs/release-notes-0.2.210.md b/docs/release-notes-0.2.210.md new file mode 100644 index 00000000..1f4c58fd --- /dev/null +++ b/docs/release-notes-0.2.210.md @@ -0,0 +1,8 @@ +# 0.2.210 + +Endpoint calibration now extracts base64 video fixtures from standard +`data:video/*;base64,...` URLs, including nested OpenAI-compatible +`video_url` inputs. This makes video-capable endpoint contracts testable +without model-specific fixture handling. + +Contract version remains 25 and this release requires no pricing migration. diff --git a/docs/release-notes-0.2.211.md b/docs/release-notes-0.2.211.md new file mode 100644 index 00000000..99913f80 --- /dev/null +++ b/docs/release-notes-0.2.211.md @@ -0,0 +1,7 @@ +# 0.2.211 + +Calibration resume now validates content-derived token witnesses against their +recorded unit sequence. Backend usage counters remain independent because +OpenAI-compatible tokenizers and canonical reproducibility units can differ. + +Contract version remains 25 and this release requires no pricing migration. diff --git a/docs/release-notes-0.2.212.md b/docs/release-notes-0.2.212.md new file mode 100644 index 00000000..c9bb35ad --- /dev/null +++ b/docs/release-notes-0.2.212.md @@ -0,0 +1,8 @@ +# 0.2.212 + +OpenAI-compatible requests now translate Hugging Face video content into the +backend's native video URL form. Endpoint calibration supplies signed companion +fields for partial accepted video probes and gives required tool calls the +model's signed tool-output budget with a direct invocation prompt. + +Contract version remains 25 and this release requires no pricing migration. diff --git a/docs/release-notes-0.2.214.md b/docs/release-notes-0.2.214.md new file mode 100644 index 00000000..9f518689 --- /dev/null +++ b/docs/release-notes-0.2.214.md @@ -0,0 +1,8 @@ +# 0.2.214 + +Direct peer channels now remove transport state on every terminal event and +recover a half-open connection only after the existing bidirectional health +protocol proves it dead. Healthy transports and unrelated peers are preserved, +and relay failures retain an allowlisted delivery phase for diagnosis. + +Contract version remains 25 and this release requires no pricing migration. diff --git a/docs/release-notes-0.2.215.md b/docs/release-notes-0.2.215.md new file mode 100644 index 00000000..98ecbd5a --- /dev/null +++ b/docs/release-notes-0.2.215.md @@ -0,0 +1,8 @@ +# 0.2.215 + +Direct peer channels now remove transport state on every terminal event and +recover a half-open connection only after the existing bidirectional health +protocol proves it dead. Healthy transports and unrelated peers are preserved, +and relay failures retain an allowlisted delivery phase for diagnosis. + +Contract version remains 25 and this release requires no pricing migration. diff --git a/docs/release-notes-0.2.216.md b/docs/release-notes-0.2.216.md new file mode 100644 index 00000000..25eb6a78 --- /dev/null +++ b/docs/release-notes-0.2.216.md @@ -0,0 +1,5 @@ +# OpenMayhem 0.2.216 + +- Admit signed managed OpenAI-compatible runtimes from their calibrated peak GPU and host-memory envelopes instead of treating downloaded artifact bytes as resident memory. +- Budget managed CUDA runtimes against detected NVIDIA memory while preserving explicit host-memory headroom. +- Keep older signed runtime bindings compatible; calibrated envelopes are enforced when present. diff --git a/docs/release-notes-0.2.217.md b/docs/release-notes-0.2.217.md new file mode 100644 index 00000000..8feee7ff --- /dev/null +++ b/docs/release-notes-0.2.217.md @@ -0,0 +1,7 @@ +# OpenMayhem 0.2.217 + +- ComfyUI providers can apply a calibrated GPU-memory reserve with + `MAYHEM_COMFYUI_RESERVE_VRAM_GB`, allowing the runtime to offload more model + state while preserving capacity for another local workload. +- Invalid, non-finite, negative, or unreasonably large reserve values fail + provider startup instead of reaching the ComfyUI runtime. diff --git a/docs/release-notes-0.2.218.md b/docs/release-notes-0.2.218.md new file mode 100644 index 00000000..6148207f --- /dev/null +++ b/docs/release-notes-0.2.218.md @@ -0,0 +1,6 @@ +# 0.2.218 + +- Stream native OpenAI-compatible tool calls that follow assistant commentary, + preserve the commentary, and fail closed on incomplete tool envelopes. +- Create ComfyUI request journals with inherited Windows sandbox permissions so + reference-file cleanup and subsequent workflow requests remain reliable. diff --git a/docs/release-notes-0.2.219.md b/docs/release-notes-0.2.219.md new file mode 100644 index 00000000..4297f08d --- /dev/null +++ b/docs/release-notes-0.2.219.md @@ -0,0 +1,8 @@ +# 0.2.219 + +- Admit signed workflow memory from the maximum parts selectable by one request + while continuing to verify every advertised part in the provider inventory. +- Stream native OpenAI-compatible tool calls that follow assistant commentary, + preserve the commentary, and fail closed on incomplete tool envelopes. +- Keep ComfyUI request journals removable by the managed sandbox after each + reference-file workflow. diff --git a/docs/release-notes-0.2.220.md b/docs/release-notes-0.2.220.md new file mode 100644 index 00000000..4e765ca9 --- /dev/null +++ b/docs/release-notes-0.2.220.md @@ -0,0 +1,6 @@ +# OpenMayhem Core 0.2.220 + +- Recover expired inference reservations from canonical ledger state even when a gateway's local job record is unavailable. +- Preserve any confirmed partial receipt when closing an expired reservation, so delivered work remains accounted for. + +This release does not change the Intercom contract. diff --git a/docs/release-notes-0.2.221.md b/docs/release-notes-0.2.221.md new file mode 100644 index 00000000..f884edab --- /dev/null +++ b/docs/release-notes-0.2.221.md @@ -0,0 +1,7 @@ +# OpenMayhem Core 0.2.221 + +- Let idle workflow engines release retained model and allocator memory when the provider runtime floor activates. +- Use ComfyUI's supported unload and free-memory controls without interrupting active workflow sessions. +- Keep unrelated backends unchanged when they do not support in-place memory reclamation. + +This release does not change the Intercom contract. diff --git a/docs/release-notes-0.2.222.md b/docs/release-notes-0.2.222.md new file mode 100644 index 00000000..81fc4dc9 --- /dev/null +++ b/docs/release-notes-0.2.222.md @@ -0,0 +1,7 @@ +# OpenMayhem Core 0.2.222 + +- Add a canonical endpoint-contract fingerprint alongside the existing legacy fingerprint. +- Prefer the canonical fingerprint on updated providers while retaining legacy mixed-version compatibility. +- Keep semantically identical workflow contracts valid across object reordering, JavaScript relays, and architecture-specific builds. + +This release does not change the Intercom contract. diff --git a/docs/release-notes-0.2.223.md b/docs/release-notes-0.2.223.md new file mode 100644 index 00000000..834cb0fd --- /dev/null +++ b/docs/release-notes-0.2.223.md @@ -0,0 +1,5 @@ +# OpenMayhem Core 0.2.223 + +- Refresh matured provider holdbacks before validating a prepared payout against its revision liability. +- Keep payout amount, binding, watermark, signature, epoch-plan, and external-effect validation unchanged. +- Preserve canonical endpoint-contract fingerprints introduced in 0.2.222. diff --git a/docs/release-notes-0.2.224.md b/docs/release-notes-0.2.224.md new file mode 100644 index 00000000..3d7941b2 --- /dev/null +++ b/docs/release-notes-0.2.224.md @@ -0,0 +1,5 @@ +# OpenMayhem Core 0.2.224 + +- Resumes canonical same-currency Stripe payouts without applying expired valuation quotes to transfers. +- Renews pre-attempt Stripe valuation quotes inside their active lock window. +- Settles older operator-fee tranches while preserving fees accrued by newer epochs. diff --git a/docs/release-notes-0.2.225.md b/docs/release-notes-0.2.225.md new file mode 100644 index 00000000..6fc5b697 --- /dev/null +++ b/docs/release-notes-0.2.225.md @@ -0,0 +1,4 @@ +# OpenMayhem 0.2.225 + +- Pins HyperDHT 6.29.6 across the bundled Intercom, settlement-bus, and peer runtime to prevent long-running nodes from crashing when persistent DHT handlers receive traffic while the node is ephemeral. +- Rejects release packages that contain a stale or duplicate HyperDHT or settlement-bus dependency tree. diff --git a/docs/release-notes-0.2.226.md b/docs/release-notes-0.2.226.md new file mode 100644 index 00000000..7c7d512c --- /dev/null +++ b/docs/release-notes-0.2.226.md @@ -0,0 +1,7 @@ +# OpenMayhem 0.2.226 + +- Keeps the TNK ledger reader synchronized between purchases so confirmation does not replay an idle reader’s entire backlog. +- Runs TAP and TNK discovery and health reporting independently from long-running payment settlement work. +- Bounds TNK confirmation scans from the caught-up ledger frontier. + +The Intercom contract remains at version 25; this release requires no ledger migration. diff --git a/docs/release-notes-0.2.227.md b/docs/release-notes-0.2.227.md new file mode 100644 index 00000000..947e81df --- /dev/null +++ b/docs/release-notes-0.2.227.md @@ -0,0 +1,7 @@ +# OpenMayhem 0.2.227 + +- Keeps the TNK ledger reader synchronized between purchases so confirmation does not replay an idle reader’s entire backlog. +- Runs TAP and TNK discovery and health reporting independently from long-running payment settlement work. +- Bounds TNK confirmation scans from the caught-up ledger frontier. + +The Intercom contract remains at version 25; this release requires no ledger migration. diff --git a/docs/release-notes-0.2.228.md b/docs/release-notes-0.2.228.md new file mode 100644 index 00000000..b3578490 --- /dev/null +++ b/docs/release-notes-0.2.228.md @@ -0,0 +1,6 @@ +# OpenMayhem 0.2.228 + +- Prevents a confirmed TNK settlement from remaining in progress when the temporary ledger transport hangs during shutdown. +- Includes the persistent TNK reader, bounded confirmation scans, and independent TAP/TNK worker loops from 0.2.227. + +The Intercom contract remains at version 25; this release requires no ledger migration. diff --git a/docs/release-notes-0.2.229.md b/docs/release-notes-0.2.229.md new file mode 100644 index 00000000..f1982e45 --- /dev/null +++ b/docs/release-notes-0.2.229.md @@ -0,0 +1,7 @@ +# OpenMayhem 0.2.229 + +- Posts and persists matched TNK deposits before shutting down the temporary ledger reader. +- Bounds ledger-reader shutdown so a transport close failure cannot suppress confirmed TNK credit. +- Includes the retail TNK/TAP worker reliability fixes from 0.2.228. + +The Intercom contract remains at version 25; this release requires no ledger migration. diff --git a/docs/release-notes-0.2.230.md b/docs/release-notes-0.2.230.md new file mode 100644 index 00000000..add0858b --- /dev/null +++ b/docs/release-notes-0.2.230.md @@ -0,0 +1,5 @@ +# OpenMayhem Core 0.2.230 + +- TNK deposit readers now catch up to the canonical peer's current MSB signed height before scanning incoming transfers. +- The TNK watcher allows a bounded five-minute reader catch-up window, preventing a retained sparse reader store from repeatedly scanning an obsolete frontier. +- Contract version and contract code digest remain unchanged. diff --git a/docs/release-notes-0.2.231.md b/docs/release-notes-0.2.231.md new file mode 100644 index 00000000..1d34057e --- /dev/null +++ b/docs/release-notes-0.2.231.md @@ -0,0 +1,6 @@ +# OpenMayhem Core 0.2.231 + +- Retail TAP bridge retries preserve the first locked backing amount instead of repricing an already verified customer transfer before broadcast. +- Retail TNK bridge retries recover the existing canonical deposit-rate lock and reject any change to its locked transfer amount. +- TAP and TNK collection funding shortfalls are reported distinctly from network failures so operators can restore the affected rail directly. +- Contract version and contract code digest remain unchanged. diff --git a/docs/release-notes-0.2.232.md b/docs/release-notes-0.2.232.md new file mode 100644 index 00000000..3201ac74 --- /dev/null +++ b/docs/release-notes-0.2.232.md @@ -0,0 +1,10 @@ +# OpenMayhem Core 0.2.232 + +This release adds generic vLLM pooling support for embedding catalog models. +Providers can serve ordered batch embeddings through the existing OpenAI and +Hugging Face endpoint families with exact backend token accounting, bounded +dimensions, cancellation, and shared-worker concurrency. Signed independent +dispatch profiles now support the embedding modality while retaining the +existing text-generation rules. + +The Intercom contract remains version 25 with unchanged contract bytes. diff --git a/docs/release-notes-0.2.233.md b/docs/release-notes-0.2.233.md new file mode 100644 index 00000000..d220ddd2 --- /dev/null +++ b/docs/release-notes-0.2.233.md @@ -0,0 +1,8 @@ +# OpenMayhem Core 0.2.233 + +This release restores receipt recovery across supported contract versions and +prevents obsolete checkpoint evidence from blocking current inference. A +confirmed canonical receipt head can now safely retire a superseded non-final +checkpoint when its attempt, terms, usage, and sequence prove continuity. + +The Intercom contract remains version 25 with unchanged contract bytes. diff --git a/docs/release-notes-0.2.234.md b/docs/release-notes-0.2.234.md new file mode 100644 index 00000000..6f573bfb --- /dev/null +++ b/docs/release-notes-0.2.234.md @@ -0,0 +1,9 @@ +# OpenMayhem Core 0.2.234 + +This release bounds embedding spend reservations by UTF-8 input bytes plus a +fixed per-input special-token allowance. Provider receipts whose exact +tokenizer usage exceeds the routing estimate now remain within the signed +spend ceiling. + +The Intercom contract remains version 25 with unchanged contract bytes. The +signed model catalog is unchanged. diff --git a/docs/release-notes-0.2.235.md b/docs/release-notes-0.2.235.md new file mode 100644 index 00000000..113ca7c3 --- /dev/null +++ b/docs/release-notes-0.2.235.md @@ -0,0 +1,10 @@ +# OpenMayhem Core 0.2.235 + +This release validates embedding providers' exact tokenizer usage within the +same conservative bounds used by the signed spend voucher. Valid signed +embedding receipts no longer fail when exact tokenizer counts differ from the +gateway's routing estimate, while incoherent or out-of-bounds usage remains +rejected. + +The Intercom contract remains version 25 with unchanged contract bytes. The +signed model catalog is unchanged. diff --git a/docs/release-notes-0.2.236.md b/docs/release-notes-0.2.236.md new file mode 100644 index 00000000..bcb6e479 --- /dev/null +++ b/docs/release-notes-0.2.236.md @@ -0,0 +1,10 @@ +# OpenMayhem Core 0.2.236 + +This release preserves the embedding provider's signed tokenizer usage through +response collection and validates any streamed usage against it. Embedding +receipts now settle when the model tokenizer count legitimately differs from +the gateway's routing estimate, while mismatched, incoherent, or out-of-bounds +usage remains rejected. + +The Intercom contract remains version 25 with unchanged contract bytes. The +signed model catalog is unchanged. diff --git a/docs/release-notes-0.2.237.md b/docs/release-notes-0.2.237.md new file mode 100644 index 00000000..71333fdd --- /dev/null +++ b/docs/release-notes-0.2.237.md @@ -0,0 +1,9 @@ +# OpenMayhem Core 0.2.237 + +Structured JSON output now checks requested schemas before dispatch. Constraints +that a generation grammar cannot enforce, including `uniqueItems`, remain in the +request contract and are validated against the completed output. Invalid or +unsupported schemas return a request error without cooling a healthy provider. + +The Intercom contract remains version 25 with unchanged contract bytes. The +signed model catalog is unchanged. diff --git a/docs/release-notes-0.2.238.md b/docs/release-notes-0.2.238.md new file mode 100644 index 00000000..0669f623 --- /dev/null +++ b/docs/release-notes-0.2.238.md @@ -0,0 +1,11 @@ +# OpenMayhem Core 0.2.238 + +Final usage receipts now remain in the durable settlement outbox until the +canonical ledger proves the exact receipt landed. A request arriving +immediately after a completed turn waits for that confirmation instead of +failing payment admission while the provider already appears available. +Streaming, non-streaming, embedding, and media routes use the same recovery +rule, and mixed provider failures preserve the most specific error category. + +The Intercom contract remains version 25 with unchanged contract bytes. The +signed model catalog is unchanged. diff --git a/docs/release-notes-0.2.239.md b/docs/release-notes-0.2.239.md new file mode 100644 index 00000000..3f0ac133 --- /dev/null +++ b/docs/release-notes-0.2.239.md @@ -0,0 +1,18 @@ +# OpenMayhem Core 0.2.239 + +Large client output limits are now treated as upper bounds and reduced to the +context that remains after each accumulated prompt. Streaming and non-streaming +chat therefore keep using eligible providers as conversations grow instead of +failing route selection when the requested output allowance alone fills the +model context window. Context failures are reported directly without a route +wait or provider cooldown. + +Authenticated clients can obtain the serving runtime's exact, template-aware +token count through `POST /v1/tokenize` or `POST /v1/count_tokens`. The control +request accepts chat `messages` or a single `prompt`, can optionally return token +IDs, and creates no inference charge, receipt, or capacity reservation. A +runtime without an exact tokenizer returns `token_count_unsupported` instead of +an estimate. + +The Intercom contract remains version 25 with unchanged contract bytes. The +signed model catalog is unchanged; no recalibration is required. diff --git a/docs/release-notes-0.2.240.md b/docs/release-notes-0.2.240.md new file mode 100644 index 00000000..f57ea6c1 --- /dev/null +++ b/docs/release-notes-0.2.240.md @@ -0,0 +1,19 @@ +# OpenMayhem Core 0.2.240 + +Large client output limits are treated as upper bounds and reduced to the +context that remains after each accumulated prompt. Streaming and non-streaming +chat therefore keep using eligible providers as conversations grow instead of +failing route selection when the requested output allowance alone fills the +model context window. Context failures are reported directly without a route +wait or provider cooldown. + +Authenticated clients can obtain the serving runtime's exact, template-aware +token count through `POST /v1/tokenize` or `POST /v1/count_tokens`. The control +request accepts chat `messages` or a single `prompt`, can optionally return token +IDs, and creates no inference charge, receipt, or capacity reservation. vLLM +providers answer token-count requests while generations are active instead of +holding them behind the generation queue. A runtime without an exact tokenizer +returns `token_count_unsupported` instead of an estimate. + +The Intercom contract remains version 25 with unchanged contract bytes. The +signed model catalog is unchanged; no recalibration is required. diff --git a/docs/release-notes-0.2.241.md b/docs/release-notes-0.2.241.md new file mode 100644 index 00000000..6fed1f31 --- /dev/null +++ b/docs/release-notes-0.2.241.md @@ -0,0 +1,14 @@ +# OpenMayhem Core 0.2.241 + +Valid terminal inference results remain deliverable while a transient receipt +settlement handoff is recovered from the durable job record. The gateway waits +for the exact job's existing reconciliation instead of reporting a provider +failure after output and a signed receipt have already been accepted. It does +not rerun inference or duplicate settlement. + +The behavior applies to streaming and non-streaming requests across the shared +gateway execution path. Genuine model-output, request-contract, and permanent +settlement failures remain errors. + +The Intercom contract remains version 25 with unchanged contract bytes. The +signed model catalog is unchanged; no recalibration is required. diff --git a/docs/release-notes-0.2.242.md b/docs/release-notes-0.2.242.md new file mode 100644 index 00000000..186b91ce --- /dev/null +++ b/docs/release-notes-0.2.242.md @@ -0,0 +1,13 @@ +# OpenMayhem Core 0.2.242 + +Verified provider identity and execution attestation are now represented +separately throughout gateway routing. A verified identity can advertise Tier 4 +accountability while the gateway still verifies the provider's underlying Tier +1, Tier 2, or Tier 3 execution evidence. + +This preserves Tier 2 and Tier 3 policy enforcement and prevents valid provider +reports from being rejected because their execution tier does not equal the +identity tier. + +The Intercom contract remains version 25 with unchanged contract bytes. The +signed model catalog is unchanged; no recalibration is required. diff --git a/docs/release-notes-0.2.243.md b/docs/release-notes-0.2.243.md new file mode 100644 index 00000000..4ebefb0a --- /dev/null +++ b/docs/release-notes-0.2.243.md @@ -0,0 +1,16 @@ +# OpenMayhem Core 0.2.243 + +Tier 4 provider identity is now independent from execution attestation. Routing can +require verified Tier 4 accountability while session admission continues to verify +the provider's underlying Tier 1, Tier 2, or Tier 3 evidence. Tier 2 and Tier 3 +policies remain fail-closed. + +Contract version 26 adds an administrator-audited recovery path for provider KYB +revocations that were explicitly recorded as reversible. Recovery requires the +original provider identity and all three bound KYB ban indexes; it does not weaken +provider, device, fingerprint, or committer bans. Re-verification still requires a +valid administrator signature. + +The exact version 25 contract implementation is retained for authenticated canonical +history replay. Existing schema-11 receipt evidence from versions 23 through 25 +remains recoverable without rewriting signatures or billing. diff --git a/docs/release-notes-0.2.244.md b/docs/release-notes-0.2.244.md new file mode 100644 index 00000000..a29ea0ab --- /dev/null +++ b/docs/release-notes-0.2.244.md @@ -0,0 +1,8 @@ +# OpenMayhem Core 0.2.244 + +Streaming providers now classify a provisional tool call that fails final +validation as request-specific model output. The gateway reports the existing +`provider_model_output_invalid` error without cooling or withdrawing a healthy +provider route. + +Contract version 26 and its authenticated history are unchanged. diff --git a/docs/release-notes-0.2.246.md b/docs/release-notes-0.2.246.md new file mode 100644 index 00000000..1ee15cec --- /dev/null +++ b/docs/release-notes-0.2.246.md @@ -0,0 +1,14 @@ +# OpenMayhem Core 0.2.246 + +Completed durable inference jobs now remain successful when a concurrent +receipt-settlement recovery finishes after the original handoff reports a +local error. The terminal job and its exact signed receipt take precedence, +so streaming clients receive the completed result instead of a contradictory +provider failure. + +Qwen3 Embedding 4B now accepts up to 128 inputs per request and advertises +up to 256 in-flight items from signed large-batch calibration evidence. Bulk +imports can use two full batches concurrently without repeating per-request +settlement overhead for every 32 inputs. + +Contract version 26 and its authenticated history are unchanged. diff --git a/docs/release-notes-0.2.247.md b/docs/release-notes-0.2.247.md new file mode 100644 index 00000000..6cea9b3e --- /dev/null +++ b/docs/release-notes-0.2.247.md @@ -0,0 +1,11 @@ +# OpenMayhem Core 0.2.247 + +Concurrent embedding runtimes now expose their measured session capacity to +provider admission. Independent embedding requests can use available runtime +slots concurrently, while runtimes without concurrent execution remain +exclusive. + +This release also includes the durable terminal-result recovery and calibrated +large-batch embedding limits introduced in 0.2.246. + +Contract version 26 and its authenticated history are unchanged. diff --git a/docs/release-notes-0.2.248.md b/docs/release-notes-0.2.248.md new file mode 100644 index 00000000..25df2e48 --- /dev/null +++ b/docs/release-notes-0.2.248.md @@ -0,0 +1,11 @@ +# OpenMayhem Core 0.2.248 + +Concurrent embedding runtimes now expose and use their signed shared-scheduler +capacity. The scheduler is bounded by the catalog limit and the provider's +configured session limit without being reduced by full-model replica capacity. +Runtimes without a concurrent backend remain exclusive. + +This release also includes the durable terminal-result recovery and calibrated +large-batch embedding limits introduced in 0.2.246. + +Contract version 26 and its authenticated history are unchanged. diff --git a/docs/release-notes-0.2.249.md b/docs/release-notes-0.2.249.md new file mode 100644 index 00000000..4566beb6 --- /dev/null +++ b/docs/release-notes-0.2.249.md @@ -0,0 +1,9 @@ +# OpenMayhem Core 0.2.249 + +Shared vLLM embedding runtimes now size provider-session capacity from their +signed scheduler limit and operator limit. They no longer apply the decoder KV +cache reservation used for concurrent text generation. Embedding-only profiles +remain bounded by the signed batch ceiling; text, mixed-modality and isolated +worker profiles retain their existing memory admission rules. + +Contract version 26 and its authenticated history are unchanged. diff --git a/docs/release-notes-0.2.250.md b/docs/release-notes-0.2.250.md new file mode 100644 index 00000000..d3012af7 --- /dev/null +++ b/docs/release-notes-0.2.250.md @@ -0,0 +1,9 @@ +# OpenMayhem Core 0.2.250 + +Gateway modality admission now avoids double-counting provider heartbeat load +and the gateway's overlapping local reservations. Provider-side atomic +capacity refusals remain the final authority and are treated as clean capacity +only when no execution or billing evidence exists, so healthy routes are not +cooled for ordinary contention while ambiguous work remains fail-closed. + +Contract version 26 and its authenticated history are unchanged. diff --git a/docs/release-notes-0.2.252.md b/docs/release-notes-0.2.252.md new file mode 100644 index 00000000..2c39fe57 --- /dev/null +++ b/docs/release-notes-0.2.252.md @@ -0,0 +1,12 @@ +# OpenMayhem Core 0.2.252 + +Provider admission now keeps canonical spend-reservation confirmation separate +from route-discovery timing. Pending reservations retain their exact signed +identity and durable recovery evidence, preventing a delayed acknowledgement +from creating a conflicting retry or an incorrect capacity failure. + +Intercom feature relays preserve accepted-but-pending status, and reservation +recovery treats locally absent canonical state as pending propagation rather +than a binding mismatch. + +Contract version 26 and its authenticated history are unchanged. diff --git a/docs/release-notes-0.2.253.md b/docs/release-notes-0.2.253.md new file mode 100644 index 00000000..5a87d936 --- /dev/null +++ b/docs/release-notes-0.2.253.md @@ -0,0 +1,11 @@ +# OpenMayhem Core 0.2.253 + +Provider admission now treats an accepted reservation awaiting canonical state +as recoverable work. An identical session replay resumes the same signed +reservation, while changed requests and terminal rejections remain rejected. + +Admission timeouts retain the exact reservation identity for recovery instead +of reporting a balance failure. This prevents delayed writer acknowledgements +from making an available provider appear unavailable. + +Contract version 26 and its authenticated history are unchanged. diff --git a/docs/release-notes-0.2.254.md b/docs/release-notes-0.2.254.md new file mode 100644 index 00000000..f4740913 --- /dev/null +++ b/docs/release-notes-0.2.254.md @@ -0,0 +1,9 @@ +# OpenMayhem Core 0.2.254 + +Market prices now follow absolute settled utilization instead of comparing activity with the previous epoch. A market at or above 80% utilization increases every price term by 10%, a market at or below 20% decreases every term by 10%, and the middle band holds. The existing 25%-400% reference-price bounds remain. + +Contract version 27 and receipt schema 12 bind provider compute time and execution-slot capacity to signed receipts. The same controller applies to text, embeddings, image, video, audio and workflow markets without model recalibration. + +Retained receipts from contract versions 23 through 26 continue to settle without signature rewriting. Because those receipts predate signed slot-time evidence, their market holds price for that settlement epoch and resumes utilization pricing with subsequent schema-12-only evidence. + +The release also exposes utilization derivations in price reports and retires the former EMA, gain and configurable-step parameters from the writable admin surface. diff --git a/docs/release-notes-0.2.255.md b/docs/release-notes-0.2.255.md new file mode 100644 index 00000000..93a5091f --- /dev/null +++ b/docs/release-notes-0.2.255.md @@ -0,0 +1,8 @@ +# OpenMayhem Core 0.2.255 + +Receipt recovery now reads retained schema-11 settlement evidence across the +contract-27 upgrade without changing its signed envelope, feature key, or +signatures. New contract-27 receipts remain strictly bound to schema 12 and its +signed utilization fields. + +Contract version 27 and its authenticated history are unchanged. diff --git a/docs/release-notes-0.2.256.md b/docs/release-notes-0.2.256.md new file mode 100644 index 00000000..baad8ad8 --- /dev/null +++ b/docs/release-notes-0.2.256.md @@ -0,0 +1,7 @@ +# OpenMayhem Core 0.2.256 + +- Classify invalid provider requests and invalid model output without penalizing healthy routes. +- Treat signed provider failure receipts as terminal so paid attempts are never dispatched twice. +- Apply the corrected failure handling consistently across text, embedding, image, audio, video, and workflow requests. + +This is a wire-compatible code release. Contract version 27 and its contract digest are unchanged. diff --git a/docs/release-notes-0.2.257.md b/docs/release-notes-0.2.257.md new file mode 100644 index 00000000..4b71f8a8 --- /dev/null +++ b/docs/release-notes-0.2.257.md @@ -0,0 +1,8 @@ +# OpenMayhem Core 0.2.257 + +- Allow cumulative compute evidence to advance across streamed receipt checkpoints while preserving monotonic validation. +- Route terminal stream-flush failures through signed failure receipts so interrupted work can settle without ambiguous reconciliation. +- Preserve the original provider stream error when an engine callback aborts, keeping request and provider failures correctly classified. +- Accept schema-12 receipts in the epoch and TAP settlement workers. + +This is a wire-compatible code release. Contract version 27 and its contract digest are unchanged. diff --git a/docs/release-notes-0.2.258.md b/docs/release-notes-0.2.258.md new file mode 100644 index 00000000..9757d921 --- /dev/null +++ b/docs/release-notes-0.2.258.md @@ -0,0 +1,10 @@ +# OpenMayhem Core 0.2.258 + +- Allow cumulative compute evidence to advance across streamed receipt checkpoints while preserving monotonic validation. +- Consume a transmitted checkpoint sequence before waiting for its acknowledgement, ensuring terminal recovery always uses a fresh sequence when an acknowledgement is lost. +- Reject different signed receipt evidence that reuses an already transmitted sequence. +- Route terminal stream-flush failures through signed failure receipts so interrupted work can settle without ambiguous reconciliation. +- Preserve the original provider stream error when an engine callback aborts, keeping request and provider failures correctly classified. +- Accept schema-12 receipts in the epoch and TAP settlement workers. + +This is a wire-compatible code release. Contract version 27 and its contract digest are unchanged. diff --git a/docs/release-notes-0.2.259.md b/docs/release-notes-0.2.259.md new file mode 100644 index 00000000..ead31422 --- /dev/null +++ b/docs/release-notes-0.2.259.md @@ -0,0 +1,7 @@ +# OpenMayhem 0.2.259 + +- Retire confirmed partial receipt deliveries independently of final settlement, allowing failed-session reservation recovery to progress while preserving signed usage evidence. +- Avoid repeatedly validating unrelated pending receipts on every streaming checkpoint. Keep cross-process locking, durable writes, monotonic receipt validation, and full background validation. +- Allow existing receipt attempts to advance when the outbox reaches its capacity limit. + +The contract version, model catalog, calibration requirements, and receipt storage layout are unchanged. diff --git a/docs/release-notes-0.2.260.md b/docs/release-notes-0.2.260.md new file mode 100644 index 00000000..19f24e39 --- /dev/null +++ b/docs/release-notes-0.2.260.md @@ -0,0 +1,9 @@ +# OpenMayhem 0.2.260 + +Gateway admission now refreshes authenticated catalog prices after an explicit pre-execution price-version refusal. Streaming chat, non-streaming chat, embeddings, image generation, speech, transcription, and artifact/workflow requests can retry with current terms without penalizing a healthy provider. + +Retries preserve caller price limits, provider filters, cancellation, retry budgets, and prior signed usage. Concurrent refusals share catalog refresh work. Previously billed usage retains its original charge when a continuation uses a newer price. + +Post-session metering and health probes retain the catalog snapshot accepted for the completed session. TNK and TAP deposit launchers now honor the configured installed binary and fail clearly if it is missing. + +This release does not change the contract, catalog calibration requirements, receipt schema, or stored ledger data. diff --git a/docs/release-notes-0.2.261.md b/docs/release-notes-0.2.261.md new file mode 100644 index 00000000..fd2a85cb --- /dev/null +++ b/docs/release-notes-0.2.261.md @@ -0,0 +1,11 @@ +# Mayhem 0.2.261 + +This release adds native typed decision inference through `POST /v1/decisions` and the +`convaiinnovations/laya` model family. It includes signed endpoint metadata, exact structured +canaries, deterministic request binding, asynchronous jobs, cancellation, retry and paid receipt +support across the existing payment rails. + +The Laya runtime pins and preloads its English, multilingual and typed-decision checkpoints on +CUDA. Request-local routing, email preprocessing, temperatures, token limits and bounded +shortlisting are validated without mutating shared model state. Existing model classes keep their +prior contract and runtime behavior. diff --git a/docs/release-notes-0.2.262.md b/docs/release-notes-0.2.262.md new file mode 100644 index 00000000..4fc3390f --- /dev/null +++ b/docs/release-notes-0.2.262.md @@ -0,0 +1,11 @@ +# OpenMayhem 0.2.262 + +OpenAI-compatible providers no longer report success when a complete tool call is emitted only inside private reasoning. When the call names an advertised tool, the response has no answer or structured call, and a signed non-thinking profile and output-token budget are available, the provider makes one bounded recovery attempt. It preserves the caller's tool choice and remaining output-token budget. If recovery cannot produce a structured call or answer, the request fails instead of silently ending without the requested action. + +Ordinary chat, reasoning, and structured tool calls keep their existing path. No contract code, receipt schema, or stored ledger format changes in this release. + +Provider admission now reads only the confirmed records for the selected route instead of scanning every historical price version. This removes catalog-history growth from the per-request admission path while retaining confirmed ledger checks. + +Needle's CPU and GPU runtimes can serve their fixed-context chat endpoint without claiming prefix caching. The prefix-cache admission requirement remains in force for other text-generation runtimes. + +Laya's signed catalog reference rate is reduced; its live market schedule is updated separately through the existing admin pricing process. diff --git a/docs/release-notes-0.2.263.md b/docs/release-notes-0.2.263.md new file mode 100644 index 00000000..e4b27631 --- /dev/null +++ b/docs/release-notes-0.2.263.md @@ -0,0 +1,7 @@ +# OpenMayhem 0.2.263 + +The managed Qwen3.8 Flash-Next runtime now constrains automatic tool calls so streamed tool arguments are complete JSON before the provider returns them. Invalid tool calls remain rejected rather than executed. + +OpenAI-compatible providers preserve visible answers that arrive without a separate reasoning block. Optional, bounded diagnostics record only response shape and validation results; they do not record prompts or tool arguments. + +This release does not change the contract, catalog, receipt format, or other model runtimes. diff --git a/docs/release-notes-0.2.264.md b/docs/release-notes-0.2.264.md new file mode 100644 index 00000000..57796f74 --- /dev/null +++ b/docs/release-notes-0.2.264.md @@ -0,0 +1,5 @@ +# OpenMayhem 0.2.264 + +OpenAI-compatible provider health checks now use bounded, spaced identity probes. A brief failure to read a runtime identity endpoint no longer retires an otherwise live provider; sustained unavailability still triggers recovery, and a mismatch with signed identity data still fails immediately. Health logs identify the failing endpoint without recording prompts or response bodies. + +The provider now reports a failed health check accurately instead of claiming the runtime process exited. This release does not change the contract, catalog, receipts, or inference request format. diff --git a/intercom/contract/contract.js b/intercom/contract/contract.js index 896034fc..26d744ff 100644 --- a/intercom/contract/contract.js +++ b/intercom/contract/contract.js @@ -7,12 +7,15 @@ import { consumeCanonicalReplayContext } from 'trac-peer/src/base/canonical-repl import PeerWallet from 'trac-wallet'; import ContractV23 from './history/v23.js'; import ContractV24 from './history/v24.js'; +import ContractV25 from './history/v25.js'; +import ContractV26 from './history/v26.js'; +import ContractV27 from './history/v27.js'; -export const CONTRACT_VERSION = 25; -// Recovery is limited to unchanged schema-11 receipt evidence already signed by -// v23 or v24 participants. New prior-version operations are not admitted; +export const CONTRACT_VERSION = 28; +// Recovery is limited to receipt evidence already signed by v23-v27 +// participants. New prior-version operations are not admitted; // separately authenticated canonical replay does not constitute new admission. -const RECOVERABLE_RECEIPT_CONTRACT_VERSIONS = new Set([23, 24]); +const RECOVERABLE_RECEIPT_CONTRACT_VERSIONS = new Set([23, 24, 25, 26, 27]); const SIGNING_MESSAGE_VERSION = 2; const CURRENT_RULES_KEY = 'rules/current'; const PROVIDER_ACCEPTED_RAILS = new Set(['fiat', 'tap', 'tnk']); @@ -51,6 +54,9 @@ const DEFAULT_MARKET_PRICE_TARGET_UTILIZATION_BPS = 8_500; const DEFAULT_MARKET_PRICE_EMA_ALPHA_BPS = 2_500; const DEFAULT_MARKET_PRICE_GAIN_BPS = 5_000; const DEFAULT_MARKET_PRICE_MAX_STEP_BPS = 1_000; +const MARKET_UTILIZATION_LOW_BPS = 2_000; +const MARKET_UTILIZATION_HIGH_BPS = 8_000; +const MARKET_UTILIZATION_STEP_BPS = 1_000; const DEFAULT_MARKET_PRICE_COLD_START_MIN_PROVIDERS = 2; const ZERO_AU = '0'; const ONE_USD_AU = '1000000000000000000'; @@ -79,7 +85,7 @@ const TAP_OPERATOR_BPS = 1_500; const TAP_BURN_BPS = 1_000; const DISPUTE_EVIDENCE_MAX_BYTES = 4_096; const FRAUD_PROOF_MAX_BYTES = 4_096; -export const SESSION_RECEIPT_SCHEMA_VERSION = 11; +export const SESSION_RECEIPT_SCHEMA_VERSION = 12; export const SPEND_VOUCHER_SCHEMA_VERSION = 11; const RECEIPT_EPOCH_INDEX_PAGE_SIZE = 128; const CTX_BRACKET_TABLE_VERSION = 1; @@ -131,9 +137,9 @@ const PARAM_DEFINITIONS = Object.freeze({ price_max_bps: { default: 40_000, min: 2_500, max: 40_000 }, price_rate_limit_seconds: { default: DEFAULT_PRICE_RATE_LIMIT_SECONDS, min: 0, max: 365 * DAY_SECONDS }, market_target_utilization_bps: { default: DEFAULT_MARKET_PRICE_TARGET_UTILIZATION_BPS, min: 1, max: 9_999, deprecated: true }, - market_ema_alpha_bps: { default: DEFAULT_MARKET_PRICE_EMA_ALPHA_BPS, min: 1, max: 10_000 }, - market_gain_bps: { default: DEFAULT_MARKET_PRICE_GAIN_BPS, min: 1, max: 10_000 }, - market_max_step_bps: { default: DEFAULT_MARKET_PRICE_MAX_STEP_BPS, min: 1, max: 10_000 }, + market_ema_alpha_bps: { default: DEFAULT_MARKET_PRICE_EMA_ALPHA_BPS, min: 1, max: 10_000, deprecated: true }, + market_gain_bps: { default: DEFAULT_MARKET_PRICE_GAIN_BPS, min: 1, max: 10_000, deprecated: true }, + market_max_step_bps: { default: DEFAULT_MARKET_PRICE_MAX_STEP_BPS, min: 1, max: 10_000, deprecated: true }, market_cold_start_min_providers: { default: DEFAULT_MARKET_PRICE_COLD_START_MIN_PROVIDERS, min: 0, max: 1_000_000, deprecated: true }, market_provider_epoch_target_au: { default: DEFAULT_MARKET_PRICE_PROVIDER_EPOCH_TARGET_AU, min: '1', money: true, deprecated: true }, market_max_utilization_bps: { default: DEFAULT_MARKET_PRICE_MAX_UTILIZATION_BPS, min: 1, max: 1_000_000, deprecated: true }, @@ -179,9 +185,6 @@ const EPOCH_ADMIN_PARAM_KEYS = Object.freeze([ 'price_min_bps', 'price_max_bps', 'price_rate_limit_seconds', - 'market_ema_alpha_bps', - 'market_gain_bps', - 'market_max_step_bps', 'epoch_seconds', 'reservation_max_lifetime_epochs', 'reservation_receipt_grace_epochs', @@ -217,7 +220,7 @@ const PROBE_VERIFICATION_METHODS = new Set([ 'attestation_of_compute', ]); const AUDITOR_SLASH_REASONS = new Set(['collusion', 'false_report']); -const BAN_TARGET_TYPES = new Set(['provider', 'device', 'fingerprint', 'committer']); +const BAN_TARGET_TYPES = new Set(['provider', 'device', 'fingerprint', 'committer', 'kyb']); const FRAUD_PROOF_REASONS = new Set(['over_credit', 'price_derivation']); const DISPUTE_OUTCOMES = new Set(['provider_fault', 'opener_fault', 'no_fault']); const DISPUTE_DEPOSIT_ACTIONS = new Set(['refund', 'forfeit', 'partial_forfeit']); @@ -290,6 +293,7 @@ const MODEL_CLASSES = new Set([ 'audio-generation', 'music-generation', 'workflow', + 'decision', ]); const RATE_MAP_MAX_ENTRIES = 16; const MODEL_CLASS_RATE_UNITS = Object.freeze({ @@ -305,6 +309,7 @@ const MODEL_CLASS_RATE_UNITS = Object.freeze({ stt: new Set(['audio_second']), 'audio-generation': new Set(['input_character', 'audio_second']), 'music-generation': new Set(['input_character', 'audio_second']), + decision: new Set(['input_token', 'output_token']), workflow: new Set([ 'megapixel_step', 'megapixel', @@ -332,6 +337,7 @@ const MODEL_CLASS_OUTPUT_MODALITIES = Object.freeze({ stt: new Set(['text']), 'audio-generation': new Set(['audio']), 'music-generation': new Set(['audio']), + decision: new Set(['text']), workflow: new Set(['image', 'video', 'audio']), }); const ENCLAVE_ARTIFACT_ROOT_KIND = 'blake3_merkle_v1'; @@ -525,6 +531,8 @@ const canonicalReceiptBody = (body) => { locked_min_session_au: body.locked_min_session_au, served_ctx: body.served_ctx, }; + if (hasOwn(body, 'compute_ms')) canonical.compute_ms = body.compute_ms; + if (hasOwn(body, 'capacity_slots')) canonical.capacity_slots = body.capacity_slots; if (hasOwn(body, 'ctx_bracket')) canonical.ctx_bracket = body.ctx_bracket; if (hasOwn(body, 'ctx_bracket_table_ver')) canonical.ctx_bracket_table_ver = body.ctx_bracket_table_ver; canonical.rules_ver = body.rules_ver; @@ -900,14 +908,23 @@ class MayhemContract extends Contract { const versioned = versionedMayhemOperation(op); const canonicalReplay = consumeCanonicalReplayContext(consensusContext, op, storage); const historical = versioned.present && ( - ([23, 24].includes(versioned.version) && canonicalReplay) || - (versioned.version === 24 && await this.isPreparedCheckpointReplay(op, storage)) + ([23, 24, 25, 26, 27].includes(versioned.version) && canonicalReplay) || + ([24, 25, 26, 27].includes(versioned.version) && + await this.isPreparedCheckpointReplay(op, storage)) ); if (historical) { // Replaying with today's pricing/receipt methods would produce a // different signed view. Retained implementations preserve the exact // historical transition, and never participate in new admission. - const Implementation = versioned.version === 23 ? ContractV23 : ContractV24; + const Implementation = versioned.version === 23 + ? ContractV23 + : versioned.version === 24 + ? ContractV24 + : versioned.version === 25 + ? ContractV25 + : versioned.version === 26 + ? ContractV26 + : ContractV27; this._historicalContracts ??= new Map(); if (!this._historicalContracts.has(versioned.version)) { this._historicalContracts.set(versioned.version, new Implementation(this.protocol, this.config)); @@ -3243,6 +3260,7 @@ class MayhemContract extends Contract { locked_per_req_au: body.locked_per_req_au, locked_min_session_au: body.locked_min_session_au, served_ctx: body.served_ctx, + capacity_slots: body.capacity_slots, ctx_bracket: body.ctx_bracket, ctx_bracket_table_ver: body.ctx_bracket_table_ver, rules_ver: body.rules_ver, @@ -3295,8 +3313,11 @@ class MayhemContract extends Contract { 'record usage receipt envelope' ); if (receiptShapeError) return receiptShapeError; + const targetSchemaVersion = value.contract_version === CONTRACT_VERSION || value.contract_version === 27 + ? SESSION_RECEIPT_SCHEMA_VERSION + : 11; const receipt = await this.normalizeReceiptEnvelope(value.receipt, { - targetSchemaVersion: SESSION_RECEIPT_SCHEMA_VERSION, + targetSchemaVersion, }); if (receipt instanceof Error) return receipt; const canonicalReceipt = { @@ -4314,7 +4335,8 @@ class MayhemContract extends Contract { return new Error('Higher receipt sequence changed immutable attempt terms.'); } if (!this.receiptUsageIsMonotonic(existingHead.receipt.body.usage, body.usage) || - this.compareAu(body.au_owed_cum, existingHead.receipt.body.au_owed_cum) < 0) { + this.compareAu(body.au_owed_cum, existingHead.receipt.body.au_owed_cum) < 0 || + body.compute_ms < existingHead.receipt.body.compute_ms) { return new Error('Higher receipt sequence is not monotonic.'); } } @@ -5473,9 +5495,39 @@ class MayhemContract extends Contract { current.paid_cum_au !== liability.paid_cum_au_before) { return new Error('Targeted payout preparation liability watermark mismatch.'); } + const provider = await this.get(`prov/${liability.provider}`); + if (!provider || + (provider.status !== 'active' && provider.status !== 'banned')) { + return new Error('Targeted payout preparation provider status is not payable.'); + } + const params = await this.activeParamsAt(value.prepared_at, [ + 'holdback_epochs', + 'challenge_epochs', + 'canary_probe_holdback_bps', + 'canary_probe_release_min_passes', + ]); + if (params instanceof Error) return params; + const probeGate = await this.probeGateForEarning( + liability.provider, + current, + params + ); + if (probeGate instanceof Error) return probeGate; + const lockedEpochs = this.providerLockedEarningEpochs(provider, params); + if (lockedEpochs instanceof Error) return lockedEpochs; + const disputeGate = await this.providerHasOpenDispute(liability.provider); + if (disputeGate instanceof Error) return disputeGate; + const refreshed = this.refreshEarningHoldback( + current, + value.epoch, + lockedEpochs, + probeGate, + disputeGate + ); + if (refreshed instanceof Error) return refreshed; const payable = this.safeSubAu( - this.safeSubAu(current.total_au, current.held_au), - current.paid_cum_au + this.safeSubAu(refreshed.total_au, refreshed.held_au), + refreshed.paid_cum_au ); if (payable instanceof Error) return payable; if (this.compareAu(liability.liability_au, payable) > 0) { @@ -7095,7 +7147,7 @@ class MayhemContract extends Contract { const fee = await this.feeCumRecord('tnk'); if (fee instanceof Error) return fee; const payable = this.safeSubAu(fee.cum_au, fee.swept_cum_au); - if (payable instanceof Error || payable !== planned.output.au) { + if (payable instanceof Error || this.compareAu(payable, planned.output.au) < 0) { return new Error('Targeted TNK fee output does not match canonical fee state.'); } const swept = this.safeAddAu(fee.swept_cum_au, planned.output.au); @@ -7230,7 +7282,8 @@ class MayhemContract extends Contract { const fee = await this.feeCumRecord('fiat'); if (fee instanceof Error) return fee; const payable = this.safeSubAu(fee.cum_au, fee.swept_cum_au); - if (payable instanceof Error || payable !== planned.output.liability_au || + if (payable instanceof Error || + this.compareAu(payable, planned.output.liability_au) < 0 || planned.output.paid_au !== planned.output.liability_au) { return new Error('Targeted fiat fee output does not match canonical fee state.'); } @@ -8286,6 +8339,48 @@ class MayhemContract extends Contract { if (!this.isHexBytes(this.value.target, 32)) return new Error('Invalid ban target.'); if (!this.isHexBytes(this.value.reason_hash, 32)) return new Error('Invalid unban reason hash.'); + if (targetType === 'kyb') { + const provider = await this.get(`prov/${this.value.target}`); + if (!provider) return new Error('Provider not found.'); + const kyb = await this.get(`kyb/${this.value.target}`); + if (!kyb || kyb.status !== 'revoked') { + return new Error('Revoked provider KYB not found.'); + } + const keys = await this.kybBanIndexKeys(kyb); + if (keys instanceof Error) return keys; + const records = []; + for (const key of keys) { + const current = await this.get(key); + if (!current || current.target_type !== 'kyb' || current.reversible !== true) { + return new Error('Reversible provider KYB ban index not found.'); + } + if (!['banned', 'revoked', 'unbanned'].includes(current.status)) { + return new Error('Invalid provider KYB ban index status.'); + } + if (!current.providers?.[this.value.target]) { + return new Error('Provider KYB ban index does not bind this provider.'); + } + records.push([key, current]); + } + for (const [key, current] of records) { + await this.put(key, { + ...current, + status: 'unbanned', + unbanned_at: this.tx, + unbanned_by: this.address, + unbanned_by_role: 'admin', + unban_reason_hash: this.value.reason_hash, + reversible: true, + }); + } + return { + ok: true, + op: 'unban', + target_type: targetType, + target: this.value.target, + }; + } + if (targetType === 'provider') { const provider = await this.get(`prov/${this.value.target}`); if (!provider) return new Error('Provider not found.'); @@ -10152,9 +10247,8 @@ class MayhemContract extends Contract { ctx_bracket_table_ver: update.ctx_bracket_table_ver, } : {}), ver: update.ver, - momentum_bps: update.momentum_bps, - activity_rate: update.activity_rate, - ema_activity_rate: update.ema_activity_rate, + utilization_bps: update.utilization_bps, + multiplier_bps: update.multiplier_bps, active_supply: update.active_supply, active_demand_au: update.active_demand_au, frozen: update.frozen, @@ -10283,6 +10377,21 @@ class MayhemContract extends Contract { const providers = Array.isArray(usage?.providers) ? usage.providers.slice().sort(compareCodepoint) : []; + const computeMs = this.normalizeAu( + usage?.compute_ms, + 'bounded receipt canonical market compute duration' + ); + const providerCapacities = Array.isArray(usage?.provider_capacities) + ? usage.provider_capacities.slice() + : []; + const capacityProviders = providerCapacities.map((entry) => entry?.provider); + const legacyReceiptCount = usage?.legacy_receipt_count; + const capacitySlotCount = providerCapacities.reduce( + (sum, entry) => Number.isSafeInteger(entry?.capacity_slots) + ? sum + entry.capacity_slots + : Number.MAX_SAFE_INTEGER, + 0 + ); if (!usage || !this.isSafeKeyPart(usage.enclave_id) || (ctxBracket !== null && !this.isSafeKeyPart(ctxBracket)) || @@ -10292,10 +10401,26 @@ class MayhemContract extends Contract { demandAu instanceof Error || !Number.isSafeInteger(usage.session_count) || usage.session_count < 1 || + !Number.isSafeInteger(legacyReceiptCount) || + legacyReceiptCount < 0 || + legacyReceiptCount > usage.session_count || + computeMs instanceof Error || providers.length < 1 || providers.some((provider) => !this.isSafeKeyPart(provider)) || new Set(providers).size !== providers.length || stableJson(providers) !== stableJson(usage.providers) || + providerCapacities.length > providers.length || + new Set(capacityProviders).size !== capacityProviders.length || + stableJson(capacityProviders.slice().sort(compareCodepoint)) !== + stableJson(capacityProviders) || + capacityProviders.some((provider) => !providers.includes(provider)) || + providerCapacities.some((entry) => + !entry || !Number.isSafeInteger(entry.capacity_slots) || + entry.capacity_slots < 1 || entry.capacity_slots > 1_000_000) || + !Number.isSafeInteger(capacitySlotCount) || capacitySlotCount < 0 || + (legacyReceiptCount === 0 && + (this.isZeroAu(computeMs) || capacitySlotCount < 1 || + providerCapacities.length !== providers.length)) || canonicalPageMarketUsageMap.has(marketKey)) { return new Error('Bounded receipt canonical market usage is invalid.'); } @@ -10317,8 +10442,12 @@ class MayhemContract extends Contract { } : {}), demand_au: demandAu, settled_usage: usage.settled_usage, + compute_ms: computeMs, + legacy_receipt_count: legacyReceiptCount, + capacity_slot_count: capacitySlotCount, session_count: usage.session_count, providers, + provider_capacities: providerCapacities, }); } if (this.compareAu(canonicalPageDemandAu, grossTotal) !== 0 || @@ -10699,8 +10828,11 @@ class MayhemContract extends Contract { } : {}), demand_au: ZERO_AU, settled_usage: {}, + compute_ms: ZERO_AU, + legacy_receipt_count: 0, session_count: 0, provider_count: 0, + capacity_slot_count: 0, first_page: page, last_page: page - 1, updated_at: null, @@ -10719,17 +10851,34 @@ class MayhemContract extends Contract { !Number.isSafeInteger(marker.session_count) || marker.session_count < 1 || !Number.isSafeInteger(marker.provider_count) || - marker.provider_count < 1 + marker.provider_count < 1 || + !/^(0|[1-9][0-9]*)$/.test(marker.compute_ms ?? '') || + !Number.isSafeInteger(marker.legacy_receipt_count) || + marker.legacy_receipt_count < 0 || + !Number.isSafeInteger(marker.capacity_slot_count) || + marker.capacity_slot_count < 0 ) { return new Error('Bounded receipt market usage marker is inconsistent.'); } let newProviderCount = 0; + let capacitySlotDelta = 0; for (const provider of usage.providers) { + const capacitySlots = usage.provider_capacities + .find((entry) => entry.provider === provider)?.capacity_slots ?? 0; + if (!Number.isSafeInteger(capacitySlots) || capacitySlots < 0) { + return new Error('Bounded receipt market provider capacity is invalid.'); + } const providerKey = `epoch/market-provider/${value.epoch}/${usage.enclave_id}/${ctxKey}/${provider}`; const providerMarker = await this.get(providerKey); if (providerMarker === null) { newProviderCount += 1; + capacitySlotDelta = this.safeAddCount( + capacitySlotDelta, + capacitySlots, + 'bounded receipt market capacity slots' + ); + if (capacitySlotDelta instanceof Error) return capacitySlotDelta; epochMarketProviderUpdates.push({ key: providerKey, value: { @@ -10738,6 +10887,7 @@ class MayhemContract extends Contract { enclave_id: usage.enclave_id, ...(usage.ctx_bracket ? { ctx_bracket: usage.ctx_bracket } : {}), provider, + capacity_slots: capacitySlots, first_page: page, updated_at: this.tx, }, @@ -10748,11 +10898,28 @@ class MayhemContract extends Contract { providerMarker.enclave_id !== usage.enclave_id || (providerMarker.ctx_bracket ?? null) !== (usage.ctx_bracket ?? null) || providerMarker.provider !== provider || + !Number.isSafeInteger(providerMarker.capacity_slots) || + providerMarker.capacity_slots < 0 || !Number.isSafeInteger(providerMarker.first_page) || providerMarker.first_page < 0 || providerMarker.first_page >= page ) { return new Error('Bounded receipt market provider marker is inconsistent.'); + } else if (capacitySlots > providerMarker.capacity_slots) { + capacitySlotDelta = this.safeAddCount( + capacitySlotDelta, + capacitySlots - providerMarker.capacity_slots, + 'bounded receipt market capacity slots' + ); + if (capacitySlotDelta instanceof Error) return capacitySlotDelta; + epochMarketProviderUpdates.push({ + key: providerKey, + value: { + ...providerMarker, + capacity_slots: capacitySlots, + updated_at: this.tx, + }, + }); } } const demandAu = this.safeAddAu(marker.demand_au, usage.demand_au); @@ -10766,8 +10933,21 @@ class MayhemContract extends Contract { newProviderCount, 'bounded receipt market providers' ); + const computeMs = this.safeAddAu(marker.compute_ms, usage.compute_ms); + const legacyReceiptCount = this.safeAddCount( + marker.legacy_receipt_count, + usage.legacy_receipt_count, + 'bounded receipt legacy receipt count' + ); + const capacitySlotCount = this.safeAddCount( + marker.capacity_slot_count, + capacitySlotDelta, + 'bounded receipt market capacity slots' + ); if (demandAu instanceof Error || sessionCount instanceof Error || - providerCount instanceof Error) { + providerCount instanceof Error || computeMs instanceof Error || + legacyReceiptCount instanceof Error || + capacitySlotCount instanceof Error) { return new Error('Bounded receipt market usage marker overflow.'); } const settledUsage = this.addSettledUsage(marker.settled_usage, usage.settled_usage); @@ -10776,8 +10956,11 @@ class MayhemContract extends Contract { ...marker, settled_usage: settledUsage, demand_au: demandAu, + compute_ms: computeMs, + legacy_receipt_count: legacyReceiptCount, session_count: sessionCount, provider_count: providerCount, + capacity_slot_count: capacitySlotCount, last_page: page, updated_at: this.tx, }; @@ -10828,6 +11011,9 @@ class MayhemContract extends Contract { demand_au: marker.demand_au, session_count: marker.session_count, provider_count: marker.provider_count, + compute_ms: marker.compute_ms, + legacy_receipt_count: marker.legacy_receipt_count, + capacity_slot_count: marker.capacity_slot_count, }) !== stableJson(usage)) { return new Error('Final market usage does not match canonical receipt settlement state.'); } @@ -11178,9 +11364,8 @@ class MayhemContract extends Contract { ctx_bracket_table_ver: update.ctx_bracket_table_ver, } : {}), ver: update.ver, - momentum_bps: update.momentum_bps, - activity_rate: update.activity_rate, - ema_activity_rate: update.ema_activity_rate, + utilization_bps: update.utilization_bps, + multiplier_bps: update.multiplier_bps, active_supply: update.active_supply, active_demand_au: update.active_demand_au, frozen: update.frozen, @@ -14537,12 +14722,12 @@ class MayhemContract extends Contract { if (feeError) return feeError; const payableFee = this.safeSubAu(fee.cum_au, fee.swept_cum_au); if (payableFee instanceof Error) return payableFee; - if (this.compareAu(payableFee, value.operator_fee_au) !== 0) { + if (this.compareAu(payableFee, value.operator_fee_au) < 0) { return new Error('Targeted TNK operator fee does not match fee state.'); } const operatorOutputs = outputs.filter((entry) => entry.role === 'operator_fee'); - if ((this.isZeroAu(payableFee) && operatorOutputs.length !== 0) || - (this.compareAu(payableFee, ZERO_AU) > 0 && + if ((this.isZeroAu(value.operator_fee_au) && operatorOutputs.length !== 0) || + (this.compareAu(value.operator_fee_au, ZERO_AU) > 0 && (operatorOutputs.length !== 1 || operatorOutputs[0].to !== value.operator_to))) { return new Error('Targeted TNK operator output mismatch.'); } @@ -14745,7 +14930,7 @@ class MayhemContract extends Contract { !this.isZeroAu(value.operator_fee_retained_au))) || (operatorOutputs.length === 1 && (operatorOutputs[0].to !== value.operator_to || - this.compareAu(operatorOutputs[0].liability_au, payableFee) !== 0 || + this.compareAu(operatorOutputs[0].liability_au, payableFee) > 0 || this.compareAu(operatorOutputs[0].paid_au, value.operator_fee_retained_au) !== 0))) { return new Error('Targeted fiat operator output mismatch.'); } @@ -17299,10 +17484,13 @@ class MayhemContract extends Contract { ...(receiptBody.ctx_bracket_table_ver ? { ctx_bracket_table_ver: receiptBody.ctx_bracket_table_ver, } : {}), - demand_au: ZERO_AU, - settled_usage: {}, - session_count: 0, - providers: new Set(), + demand_au: ZERO_AU, + settled_usage: {}, + compute_ms: ZERO_AU, + legacy_receipt_count: 0, + session_count: 0, + providers: new Set(), + provider_capacities: new Map(), }; if ((currentMarket.ctx_bracket_table_ver ?? null) !== (receiptBody.ctx_bracket_table_ver ?? currentMarket.ctx_bracket_table_ver ?? null)) { @@ -17322,6 +17510,31 @@ class MayhemContract extends Contract { const settledUsage = this.addSettledUsage(currentMarket.settled_usage, increment); if (settledUsage instanceof Error) return settledUsage; currentMarket.settled_usage = settledUsage; + if (receiptBody.schema_version === SESSION_RECEIPT_SCHEMA_VERSION) { + const computeMs = this.safeAddAu( + currentMarket.compute_ms, + String(receiptBody.compute_ms) + ); + if (computeMs instanceof Error) { + return new Error('Canonical receipt market compute duration overflow.'); + } + currentMarket.compute_ms = computeMs; + currentMarket.provider_capacities.set( + allocation.provider, + Math.max( + currentMarket.provider_capacities.get(allocation.provider) ?? 0, + receiptBody.capacity_slots + ) + ); + } else { + const legacyReceiptCount = this.safeAddCount( + currentMarket.legacy_receipt_count, + 1, + 'canonical legacy receipt count' + ); + if (legacyReceiptCount instanceof Error) return legacyReceiptCount; + currentMarket.legacy_receipt_count = legacyReceiptCount; + } currentMarket.demand_au = marketDemandAu; currentMarket.session_count = marketSessionCount; currentMarket.providers.add(allocation.provider); @@ -17469,8 +17682,13 @@ class MayhemContract extends Contract { } : {}), demand_au: entry.demand_au, settled_usage: entry.settled_usage, + compute_ms: entry.compute_ms, + legacy_receipt_count: entry.legacy_receipt_count, session_count: entry.session_count, providers: Array.from(entry.providers).sort(compareCodepoint), + provider_capacities: Array.from(entry.provider_capacities.entries()) + .sort(([left], [right]) => compareCodepoint(left, right)) + .map(([provider, capacity_slots]) => ({ provider, capacity_slots })), })), }; } @@ -18081,6 +18299,7 @@ class MayhemContract extends Contract { coreModalitiesForModelClass(modelClass) { switch (modelClass) { case DEFAULT_MODEL_CLASS: + case 'decision': return new Set(['text']); case 'embedding': return new Set(['embedding']); @@ -18596,7 +18815,7 @@ class MayhemContract extends Contract { !Array.isArray(this.value.markets) || this.value.markets.length > 128) { return new Error('Migration requires a timestamp and at most 128 active market rows.'); } - const key = 'market/activity/migration-v2'; + const key = 'market/activity/migration-v3'; const existing = await this.get(key); const state = await this.epochApplyStateRecord(); if (state.pending_epoch !== null && state.pending_epoch !== undefined) { @@ -18648,8 +18867,11 @@ class MayhemContract extends Contract { for (const repair of repairs) await this.put(`params/${repair.key}`, repair.after); await this.put('market/activity/index', nextIndex); await this.put(key, { - schema_version: 2, contract_version: CONTRACT_VERSION, + schema_version: 3, contract_version: CONTRACT_VERSION, hard_min_bps: 2_500, hard_max_bps: 40_000, + low_utilization_bps: MARKET_UTILIZATION_LOW_BPS, + high_utilization_bps: MARKET_UTILIZATION_HIGH_BPS, + price_step_bps: MARKET_UTILIZATION_STEP_BPS, previous_applied_epoch: state.updated_epoch ?? 0, repairs: [...(existing?.repairs ?? []), ...repairs], market_count: index.size, migrated_at: existing?.migrated_at ?? this.tx, updated_at: this.tx, migrated_by: this.address, @@ -18748,43 +18970,6 @@ class MayhemContract extends Contract { return work.toString(); // picoseconds of calibrated reference work. } - marketActivityMomentum(currentRate, previousRate, constants) { - const current = BigInt(currentRate); - const previous = BigInt(previousRate); - const raw = previous > 0n ? current * 10_000n / previous - : (current > 0n ? BigInt(constants.max_momentum_bps) : 0n); - return Number(raw > BigInt(constants.max_momentum_bps) - ? BigInt(constants.max_momentum_bps) : raw); - } - - marketActivityEma(previousRate, currentRate, constants) { - return ((BigInt(previousRate) * BigInt(10_000 - constants.ema_alpha_bps) + - BigInt(currentRate) * BigInt(constants.ema_alpha_bps)) / 10_000n).toString(); - } - - marketActivityVector(usage, epochSeconds) { - return Object.fromEntries(Object.entries(usage).map(([unit, count]) => [ - unit, (BigInt(count) * 1_000_000_000_000n / BigInt(epochSeconds)).toString(), - ])); - } - - marketVectorMomentum(current, previous, constants) { - const units = [...new Set([...Object.keys(current), ...Object.keys(previous)])] - .filter((unit) => BigInt(current[unit] ?? '0') > 0n || BigInt(previous[unit] ?? '0') > 0n) - .sort(compareCodepoint); - if (!units.length) return 0; - let sum = 0n; - for (const unit of units) sum += BigInt(this.marketActivityMomentum( - current[unit] ?? '0', previous[unit] ?? '0', constants)); - return Number(sum / BigInt(units.length)); - } - - marketVectorEma(previous, current, constants) { - return Object.fromEntries([...new Set([...Object.keys(current), ...Object.keys(previous)])] - .sort(compareCodepoint).map((unit) => [unit, this.marketActivityEma( - previous[unit] ?? '0', current[unit] ?? '0', constants)])); - } - async activityMarketEntries(usageMap, includeDormant) { const entries = this.mapMarketUsageEntriesForHash(usageMap); const known = await this.get('market/activity/index') ?? []; @@ -18794,7 +18979,16 @@ class MayhemContract extends Contract { const keys = new Set(entries.map((row) => this.priceMarketKey(row.enclave_id, row.ctx_bracket ?? null))); for (const row of known) { const key = this.priceMarketKey(row.enclave_id, row.ctx_bracket ?? null); - if (includeDormant && !keys.has(key)) entries.push({ ...row, demand_au: '0', session_count: 0, provider_count: 0, _activity_dormant: true }); + if (includeDormant && !keys.has(key)) entries.push({ + ...row, + demand_au: '0', + session_count: 0, + provider_count: 0, + compute_ms: '0', + capacity_slot_count: 0, + legacy_receipt_count: 0, + _activity_dormant: true, + }); } if (new Set([...keys, ...known.map((row) => this.priceMarketKey(row.enclave_id, row.ctx_bracket ?? null))]).size > DEFAULT_MAX_MARKET_USAGE_ENTRIES) { return new Error('Market activity index capacity exceeded.'); @@ -18822,20 +19016,45 @@ class MayhemContract extends Contract { } marketPriceParamKeys() { - return ['price_min_bps', 'price_max_bps', 'market_ema_alpha_bps', - 'market_gain_bps', 'market_max_step_bps']; + return ['price_min_bps', 'price_max_bps']; } - marketPriceConstants(params) { + marketPriceConstants() { return { - schema_version: 2, - ema_alpha_bps: params.market_ema_alpha_bps, - gain_bps: params.market_gain_bps, - max_step_bps: params.market_max_step_bps, - max_momentum_bps: 50_000, + schema_version: 3, + low_utilization_bps: MARKET_UTILIZATION_LOW_BPS, + high_utilization_bps: MARKET_UTILIZATION_HIGH_BPS, + price_step_bps: MARKET_UTILIZATION_STEP_BPS, }; } + marketUtilizationBps(computeMs, capacitySlotCount, epochSeconds) { + const busy = this.parseAu(computeMs, 'market compute duration'); + if (busy instanceof Error || !Number.isSafeInteger(capacitySlotCount) || + capacitySlotCount < 0 || !Number.isSafeInteger(epochSeconds) || epochSeconds < 1) { + return new Error('Invalid market utilization evidence.'); + } + if (capacitySlotCount === 0) { + return busy === 0n ? 0 : new Error('Market compute duration requires execution capacity.'); + } + const available = BigInt(capacitySlotCount) * BigInt(epochSeconds) * 1_000n; + const utilization = busy * 10_000n / available; + return Number(utilization > 10_000n ? 10_000n : utilization); + } + + marketUtilizationMultiplier(utilizationBps) { + if (!Number.isSafeInteger(utilizationBps) || utilizationBps < 0 || utilizationBps > 10_000) { + return new Error('Invalid market utilization.'); + } + if (utilizationBps >= MARKET_UTILIZATION_HIGH_BPS) { + return 10_000 + MARKET_UTILIZATION_STEP_BPS; + } + if (utilizationBps <= MARKET_UTILIZATION_LOW_BPS) { + return 10_000 - MARKET_UTILIZATION_STEP_BPS; + } + return 10_000; + } + scalePriceTerm(term, multiplierBps) { const amount = this.parseAu(term, 'price term'); if (amount instanceof Error || !Number.isSafeInteger(multiplierBps) || multiplierBps < 0) { @@ -18846,25 +19065,6 @@ class MayhemContract extends Contract { return this.canonicalAu(scaled > 0n ? scaled : 1n); } - stepPriceTerm(current, desired, constants) { - const currentAu = this.parseAu(current, 'current price term'); - const desiredAu = this.parseAu(desired, 'desired price term'); - if (currentAu instanceof Error || desiredAu instanceof Error) return new Error('Invalid price term.'); - if (currentAu === desiredAu) return this.canonicalAu(currentAu); - if (currentAu === 0n) return this.canonicalAu(desiredAu); - const delta = currentAu > desiredAu ? currentAu - desiredAu : desiredAu - currentAu; - const gainedRaw = (delta * BigInt(constants.gain_bps)) / 10_000n; - const maxStepRaw = (currentAu * BigInt(constants.max_step_bps)) / 10_000n; - const gained = gainedRaw > 0n ? gainedRaw : 1n; - const maxStep = maxStepRaw > 0n ? maxStepRaw : 1n; - const step = gained < maxStep ? gained : maxStep; - return this.canonicalAu( - desiredAu > currentAu - ? currentAu + step - : (step > currentAu ? 0n : currentAu - step) - ); - } - scaleRateMap(rateMap, multiplierBps) { const scaled = []; for (const entry of rateMap) { @@ -18875,21 +19075,6 @@ class MayhemContract extends Contract { return this.normalizeRateMap(scaled); } - stepRateMap(currentRateMap, desiredRateMap, constants) { - const desiredByUnit = this.rateMapByUnit(desiredRateMap); - const stepped = []; - for (const entry of currentRateMap) { - const desired = desiredByUnit.get(entry.unit); - if (!desired || desired.granularity !== entry.granularity) { - return new Error('Market price rate_map shape changed.'); - } - const perUnitAu = this.stepPriceTerm(entry.per_unit_au, desired.per_unit_au, constants); - if (perUnitAu instanceof Error) return perUnitAu; - stepped.push({ ...entry, per_unit_au: perUnitAu }); - } - return this.normalizeRateMap(stepped); - } - clampRateMapBounds(priceRateMap, referenceRateMap, params) { const priceByUnit = this.rateMapByUnit(priceRateMap); const referenceByUnit = this.rateMapByUnit(referenceRateMap); @@ -18973,60 +19158,49 @@ class MayhemContract extends Contract { const modelRef = await this.get(`modelref/${enclave.model_id}`); if (!modelRef) return new Error('Market price model reference not found.'); const previousMarket = current.market ?? {}; - if (previousMarket.schema_version === 2 && previousMarket.epoch >= epoch) { + if (Number.isSafeInteger(previousMarket.epoch) && previousMarket.epoch >= epoch) { return new Error('Market activity epoch must increase exactly once per settled epoch.'); } const canonical = context.canonicalActivity?.get(marketKey) ?? - (context.includeDormant && usage.session_count === 0 ? { settled_usage: {} } : null); - const settledUsage = canonical?.settled_usage ?? null; - const calibration = modelRef.activity_calibration ?? null; - const calibrationError = calibration && this.validateActivityCalibration(calibration, modelRef.model_class, modelRef.rate_map); - if (calibrationError) return calibrationError; - const calibrationHash = calibration - ? await this.opaqueHash('mayhem-market-activity-calibration-v1', calibration) : null; - const work = calibration && settledUsage !== null - ? this.calibratedActivityWork(settledUsage, calibration) : null; - if (work instanceof Error) return work; - const activityRate = work === null ? null : (BigInt(work) / BigInt(epochSeconds)).toString(); - const vector = settledUsage === null ? null : this.marketActivityVector(settledUsage, epochSeconds); - // With no machine-readable calibration, compare each signed dimension only - // against its own history. No synthetic GPU capacity or monetary weights. - const activityBasis = work === null ? 'relative_dimension_vector_v1' : 'calibrated_work_v1'; - const initialized = previousMarket.schema_version === 2 && - previousMarket.activity_initialized === true && - previousMarket.calibration_hash === calibrationHash && - previousMarket.activity_basis === activityBasis && - previousMarket.epoch === epoch - 1; - const previousEma = initialized ? previousMarket.ema_activity_rate : activityRate; - const activeSupply = usage.provider_count; + (context.includeDormant && usage.session_count === 0 + ? { settled_usage: {}, compute_ms: '0', capacity_slot_count: 0, + legacy_receipt_count: 0 } + : usage); + const settledUsage = canonical?.settled_usage ?? {}; + const computeMs = canonical?.compute_ms ?? usage.compute_ms; + const capacitySlotCount = canonical?.capacity_slot_count ?? usage.capacity_slot_count; + const legacyReceiptCount = canonical?.legacy_receipt_count ?? + usage.legacy_receipt_count; if (canonical && canonical.session_count !== undefined && (canonical.session_count !== usage.session_count || canonical.demand_au !== usage.demand_au || + canonical.compute_ms !== usage.compute_ms || + canonical.capacity_slot_count !== usage.capacity_slot_count || + (canonical.legacy_receipt_count ?? 0) !== usage.legacy_receipt_count || (canonical.provider_count ?? canonical.providers?.length) !== usage.provider_count)) { return new Error('Market activity totals do not match canonical receipt evidence.'); } - // Nonzero direction follows the previous epoch; empty epochs keep decaying. - // EMA is telemetry only; the bootstrap still holds for one epoch. - const rawMomentum = initialized && vector !== null - ? activityBasis === 'calibrated_work_v1' - ? this.marketActivityMomentum(activityRate, previousMarket.activity_rate, constants) - : this.marketVectorMomentum(vector, previousMarket.activity_vector, constants) - : 10_000; - const frozenReason = settledUsage === null ? 'missing_canonical_activity' - : !initialized ? 'activity_baseline_bootstrap' : null; - const frozen = frozenReason !== null; - const multiplierBps = frozen ? 10_000 : rawMomentum; - const activityInitialized = vector !== null; - const emaActivityRate = activityRate === null ? null : initialized - ? this.marketActivityEma(previousEma, activityRate, constants) : activityRate; - // Momentum moves the current price. The admin seed is provenance, not a dollar target. + if (computeMs === undefined || capacitySlotCount === undefined || + !Number.isSafeInteger(legacyReceiptCount) || legacyReceiptCount < 0) { + return new Error('Market utilization requires canonical signed compute evidence.'); + } + const legacyHold = legacyReceiptCount > 0; + const utilizationBps = legacyHold ? null : this.marketUtilizationBps( + computeMs, capacitySlotCount, epochSeconds); + if (utilizationBps instanceof Error) return utilizationBps; + const multiplierBps = legacyHold ? 10_000 : + this.marketUtilizationMultiplier(utilizationBps); + if (multiplierBps instanceof Error) return multiplierBps; + const activeSupply = usage.provider_count; + // Apply the fixed utilization step directly. No previous-hour activity, + // revenue target, EMA, or demand AU enters the direction decision. const desiredRateMap = this.scaleRateMap(current.rate_map, multiplierBps); const desiredPerReqAu = this.scalePriceTerm(current.per_req_au, multiplierBps); const desiredMinSessionAu = this.scalePriceTerm(current.min_session_au, multiplierBps); const nextTerms = { - rate_map: this.stepRateMap(current.rate_map, desiredRateMap, constants), - per_req_au: this.stepPriceTerm(current.per_req_au, desiredPerReqAu, constants), - min_session_au: this.stepPriceTerm(current.min_session_au, desiredMinSessionAu, constants), + rate_map: desiredRateMap, + per_req_au: desiredPerReqAu, + min_session_au: desiredMinSessionAu, }; for (const term of Object.values(nextTerms)) if (term instanceof Error) return term; for (const field of ['per_req_au', 'min_session_au']) { @@ -19044,24 +19218,25 @@ class MayhemContract extends Contract { set_by: seed.set_by, set_by_role: 'admin', ...(ctxMeta ? { ctx_bracket: ctxMeta.ctx_bracket, ctx_bracket_table_ver: ctxMeta.ctx_bracket_table_ver } : {}), - price_source: frozen ? 'market_activity_hold' : 'market_activity_momentum', seed, + price_source: 'market_utilization', seed, market: { - schema_version: 2, source: 'canonical_settled_work', epoch, epoch_seconds: epochSeconds, + schema_version: 3, + source: legacyHold ? 'canonical_legacy_receipt_hold' : + 'canonical_signed_slot_time', + epoch, + epoch_seconds: epochSeconds, active_supply: activeSupply, - // Gross AU remains accounting evidence only; it never enters the controller. + capacity_slot_count: capacitySlotCount, + compute_ms: computeMs, + legacy_receipt_count: legacyReceiptCount, + utilization_bps: utilizationBps, + // Gross AU and metered units remain accounting evidence only. active_demand_au: usage.demand_au, session_count: usage.session_count, - settled_usage: settledUsage, calibration_hash: calibrationHash, - activity_basis: activityBasis, activity_vector: vector, - previous_activity_vector: initialized ? previousMarket.activity_vector : vector, - previous_activity_rate: initialized ? previousMarket.activity_rate : activityRate, - previous_ema_activity_vector: initialized ? previousMarket.ema_activity_vector : vector, - ema_activity_vector: vector === null ? null : initialized - ? this.marketVectorEma(previousMarket.ema_activity_vector, vector, constants) : vector, - calibration: cloneValue(calibration), modelref_ver: modelRef.ver ?? null, - calibrated_work_ps: work, activity_rate: activityRate, - previous_ema_activity_rate: previousEma, ema_activity_rate: emaActivityRate, - activity_initialized: activityInitialized, momentum_bps: rawMomentum, - multiplier_bps: multiplierBps, frozen, frozen_reason: frozenReason, constants, + settled_usage: settledUsage, + activity_basis: legacyHold ? 'legacy_receipt_hold_v1' : + 'signed_slot_time_v1', + modelref_ver: modelRef.ver ?? null, + activity_initialized: true, multiplier_bps: multiplierBps, constants, desired_rate_map: desiredRateMap, desired_per_req_au: desiredPerReqAu, desired_min_session_au: desiredMinSessionAu, previous_price_ver: current.ver, previous_rate_map: cloneValue(current.rate_map), @@ -19074,8 +19249,10 @@ class MayhemContract extends Contract { ...(ctxMeta ? { ctx_bracket: ctxMeta.ctx_bracket, ctx_bracket_table_ver: ctxMeta.ctx_bracket_table_ver } : {}), market_key: marketKey, ver: record.ver, rate_map: record.rate_map, - momentum_bps: rawMomentum, activity_rate: activityRate, ema_activity_rate: emaActivityRate, - active_supply: activeSupply, active_demand_au: usage.demand_au, frozen, + utilization_bps: utilizationBps, multiplier_bps: multiplierBps, + active_supply: activeSupply, capacity_slot_count: capacitySlotCount, + compute_ms: computeMs, legacy_receipt_count: legacyReceiptCount, + active_demand_au: usage.demand_au, frozen: false, schedule_key: scheduleKey, schedule: { ...schedule, current: record }, record_key: this.priceRecordKey(usage.enclave_id, record.ver, ctxMeta?.ctx_bracket ?? null), record, }); @@ -19166,7 +19343,7 @@ class MayhemContract extends Contract { if (modalityError) return modalityError; const modelClass = this.modelClassFor(enclave); const required = new Set(); - if (modelClass === DEFAULT_MODEL_CLASS) { + if (modelClass === DEFAULT_MODEL_CLASS || modelClass === 'decision') { required.add('input_token'); required.add('output_token'); } else if (modelClass === 'embedding') { @@ -22695,6 +22872,10 @@ class MayhemContract extends Contract { prompt_hash: bodySource.prompt_hash, ts: bodySource.ts, }; + if (targetSchemaVersion >= 12) { + body.compute_ms = bodySource.compute_ms; + body.capacity_slots = bodySource.capacity_slots; + } if (hasOwn(bodySource, 'usage_attribution')) { body.usage_attribution = cloneValue(bodySource.usage_attribution); } @@ -22809,6 +22990,15 @@ class MayhemContract extends Contract { if (!Number.isSafeInteger(body.served_ctx) || body.served_ctx < 0) { return new Error('Invalid receipt served context.'); } + if (expectedSchemaVersion >= 12) { + if (!Number.isSafeInteger(body.compute_ms) || body.compute_ms < 1) { + return new Error('Invalid receipt compute duration.'); + } + if (!Number.isSafeInteger(body.capacity_slots) || body.capacity_slots < 1 || + body.capacity_slots > 1_000_000) { + return new Error('Invalid receipt execution capacity.'); + } + } const table = body.ctx_bracket_table_ver === null || body.ctx_bracket_table_ver === undefined ? null : await this.ctxBracketTableByVersion(body.ctx_bracket_table_ver); @@ -22995,7 +23185,7 @@ class MayhemContract extends Contract { } return { type: 'price_derivation', - schema_version: 2, + schema_version: 3, epoch, at, epoch_seconds: epochSeconds, @@ -23011,7 +23201,9 @@ class MayhemContract extends Contract { usage: { usage_root: usageRoot, settled_usage: cloneValue(market.settled_usage), - calibrated_work_ps: market.calibrated_work_ps, + compute_ms: market.compute_ms, + capacity_slot_count: market.capacity_slot_count, + legacy_receipt_count: market.legacy_receipt_count, active_demand_au: market.active_demand_au, session_count: market.session_count, ...(record.ctx_bracket ? { @@ -23023,22 +23215,10 @@ class MayhemContract extends Contract { source: market.source, active_supply: market.active_supply, activity_basis: market.activity_basis, - activity_vector: cloneValue(market.activity_vector), - previous_activity_vector: cloneValue(market.previous_activity_vector), - previous_activity_rate: market.previous_activity_rate, - previous_ema_activity_vector: cloneValue(market.previous_ema_activity_vector), - ema_activity_vector: cloneValue(market.ema_activity_vector), - momentum_bps: market.momentum_bps, - activity_rate: market.activity_rate, - previous_ema_activity_rate: market.previous_ema_activity_rate, - ema_activity_rate: market.ema_activity_rate, + utilization_bps: market.utilization_bps, activity_initialized: market.activity_initialized, - calibration_hash: market.calibration_hash, - calibration: cloneValue(market.calibration), modelref_ver: market.modelref_ver, multiplier_bps: market.multiplier_bps, - frozen: market.frozen, - frozen_reason: market.frozen_reason, constants: cloneValue(market.constants), }, seed_price: this.priceTermsSnapshot(record.seed), @@ -23152,15 +23332,31 @@ class MayhemContract extends Contract { ...(body.ctx_bracket ? { ctx_bracket: body.ctx_bracket, ctx_bracket_table_ver: body.ctx_bracket_table_ver } : {}), demand_au: '0', session_count: 0, providers: new Set(), settled_usage: {}, + compute_ms: '0', legacy_receipt_count: 0, provider_capacities: new Map(), }; const increment = this.incrementalSettledUsage(body); if (increment instanceof Error) return increment; row.settled_usage = this.addSettledUsage(row.settled_usage, increment); row.demand_au = this.safeAddAu(row.demand_au, head.incremental_au); - if (row.settled_usage instanceof Error || row.demand_au instanceof Error) { + if (body.schema_version === SESSION_RECEIPT_SCHEMA_VERSION) { + row.compute_ms = this.safeAddAu(row.compute_ms, String(body.compute_ms)); + row.provider_capacities.set( + head.provider, + Math.max(row.provider_capacities.get(head.provider) ?? 0, body.capacity_slots) + ); + } else { + row.legacy_receipt_count = this.safeAddCount( + row.legacy_receipt_count, + 1, + 'activity commitment legacy receipt count' + ); + } + if (row.settled_usage instanceof Error || row.demand_au instanceof Error || + row.compute_ms instanceof Error || row.legacy_receipt_count instanceof Error) { return new Error('Activity commitment work overflow.'); } - row.session_count++; row.providers.add(head.provider); + row.session_count++; + row.providers.add(head.provider); markets.set(marketKey, row); leaves.push(await this.usageLeafHash(head.receipt)); } @@ -23173,8 +23369,10 @@ class MayhemContract extends Contract { } const canonical = new Map(); const usage = new Map(); for (const [key, row] of markets) { - const { providers, settled_usage, ...publicRow } = row; + const { providers, provider_capacities: providerCapacities, settled_usage, ...publicRow } = row; publicRow.provider_count = providers.size; + publicRow.capacity_slot_count = Array.from(providerCapacities.values()) + .reduce((sum, slots) => sum + slots, 0); if (publicRow.demand_au !== totals.use_au) return new Error('Activity commitment gross total mismatch.'); usage.set(key, publicRow); canonical.set(key, { ...publicRow, settled_usage }); @@ -23499,12 +23697,22 @@ class MayhemContract extends Contract { 'demand_au', 'session_count', 'provider_count', + 'compute_ms', + 'capacity_slot_count', + 'legacy_receipt_count', ]); const unknown = Object.keys(entry).filter((key) => !allowed.has(key)).sort(); if (unknown.length > 0) { return new Error(`market usage entry does not accept fields: ${unknown.join(', ')}.`); } - for (const key of ['enclave_id', 'demand_au', 'session_count', 'provider_count']) { + for (const key of [ + 'enclave_id', + 'demand_au', + 'session_count', + 'provider_count', + 'compute_ms', + 'capacity_slot_count', + ]) { if (!hasOwn(entry, key)) return new Error(`market usage entry is missing ${key}.`); } const enclaveId = entry.enclave_id; @@ -23529,6 +23737,21 @@ class MayhemContract extends Contract { if (!Number.isSafeInteger(entry.provider_count) || entry.provider_count <= 0) { return new Error('Invalid market usage provider_count.'); } + const legacyReceiptCount = entry.legacy_receipt_count ?? 0; + if (!Number.isSafeInteger(legacyReceiptCount) || legacyReceiptCount < 0 || + legacyReceiptCount > entry.session_count) { + return new Error('Invalid market usage legacy_receipt_count.'); + } + const computeMs = this.normalizeAu(entry.compute_ms, 'market usage compute duration'); + if (computeMs instanceof Error || + (legacyReceiptCount === 0 && this.isZeroAu(computeMs))) { + return new Error('Invalid market usage compute duration.'); + } + if (!Number.isSafeInteger(entry.capacity_slot_count) || + entry.capacity_slot_count < 0 || + (legacyReceiptCount === 0 && entry.capacity_slot_count === 0)) { + return new Error('Invalid market usage capacity_slot_count.'); + } const key = this.priceMarketKey(enclaveId, ctxBracket); const current = out.get(key) ?? { enclave_id: enclaveId, @@ -23537,6 +23760,9 @@ class MayhemContract extends Contract { demand_au: ZERO_AU, session_count: 0, provider_count: 0, + compute_ms: ZERO_AU, + capacity_slot_count: 0, + legacy_receipt_count: 0, }; if ((current.ctx_bracket_table_ver ?? null) !== (entry.ctx_bracket_table_ver ?? current.ctx_bracket_table_ver ?? null)) { return new Error('Market usage context bracket table version mismatch.'); @@ -23547,6 +23773,23 @@ class MayhemContract extends Contract { if (sessionCount instanceof Error) return sessionCount; const providerCount = this.safeAddCount(current.provider_count, entry.provider_count, 'market usage provider_count'); if (providerCount instanceof Error) return providerCount; + const totalComputeMs = this.safeAddAu(current.compute_ms, computeMs); + if (totalComputeMs instanceof Error) return totalComputeMs; + const capacitySlotCount = this.safeAddCount( + current.capacity_slot_count, + entry.capacity_slot_count, + 'market usage capacity slot count' + ); + if (capacitySlotCount instanceof Error) return capacitySlotCount; + const totalLegacyReceiptCount = this.safeAddCount( + current.legacy_receipt_count, + legacyReceiptCount, + 'market usage legacy receipt count' + ); + if (totalLegacyReceiptCount instanceof Error || + totalLegacyReceiptCount > sessionCount) { + return new Error('Invalid market usage legacy receipt total.'); + } out.set(key, { enclave_id: enclaveId, ...(ctxBracket ? { ctx_bracket: ctxBracket } : {}), @@ -23554,6 +23797,9 @@ class MayhemContract extends Contract { demand_au: demandAu, session_count: sessionCount, provider_count: providerCount, + compute_ms: totalComputeMs, + capacity_slot_count: capacitySlotCount, + legacy_receipt_count: totalLegacyReceiptCount, }); } return out; @@ -23843,6 +24089,9 @@ class MayhemContract extends Contract { demand_au: entry.demand_au, session_count: entry.session_count, provider_count: entry.provider_count, + compute_ms: entry.compute_ms, + capacity_slot_count: entry.capacity_slot_count, + legacy_receipt_count: entry.legacy_receipt_count ?? 0, })); } diff --git a/intercom/contract/history/v25.js b/intercom/contract/history/v25.js new file mode 100644 index 00000000..d02f5f05 --- /dev/null +++ b/intercom/contract/history/v25.js @@ -0,0 +1,25374 @@ +import b4a from 'b4a'; +import { blake3 } from '@tracsystems/blake3'; +import { keccak256 } from 'ethereum-cryptography/keccak'; +import { secp256k1 } from 'ethereum-cryptography/secp256k1'; +import { Contract } from 'trac-peer'; +import { consumeCanonicalReplayContext } from 'trac-peer/src/base/canonical-replay.js'; +import PeerWallet from 'trac-wallet'; +import ContractV23 from './v23.js'; +import ContractV24 from './v24.js'; + +export const CONTRACT_VERSION = 25; +// Recovery is limited to unchanged schema-11 receipt evidence already signed by +// v23 or v24 participants. New prior-version operations are not admitted; +// separately authenticated canonical replay does not constitute new admission. +const RECOVERABLE_RECEIPT_CONTRACT_VERSIONS = new Set([23, 24]); +const SIGNING_MESSAGE_VERSION = 2; +const CURRENT_RULES_KEY = 'rules/current'; +const PROVIDER_ACCEPTED_RAILS = new Set(['fiat', 'tap', 'tnk']); +const PROVIDER_ACCEPTED_RAIL_ORDER = Object.freeze(['fiat', 'tap', 'tnk']); +const PROVIDER_RAIL_SCHEMA_VERSION = 1; +const PROVIDER_PAYOUT_BINDING_RAILS = new Set(['fiat', 'tap', 'tnk']); +const PROVIDER_PAYOUT_BINDING_RAIL_ORDER = Object.freeze(['fiat', 'tap', 'tnk']); +const PROVIDER_PAYOUT_BINDING_CONTEXT_CURRENT_KEY = 'payout/context/current'; +export const PAYOUT_INTENT_MAX_EXPIRY_EPOCHS_DEFAULT = 10_000; +const PAYOUT_PARAM_DEFINITIONS = Object.freeze({ + payout_intent_max_expiry_epochs: { + default: PAYOUT_INTENT_MAX_EXPIRY_EPOCHS_DEFAULT, + min: 1, + max: 1_000_000, + }, +}); +const FIAT_DEPOSIT_RAILS = new Set(['stripe']); +const REQUIRED_FIAT_PAYOUT_CURRENCIES = Object.freeze(['eur', 'gbp', 'usd']); +const PRICE_DENOMINATION = 'au_usd'; +const RATE_SOURCES = new Set(['gate-spot', 'mexc-spot']); +const LEGACY_TNK_DEPOSIT_RATE_DRIFT_BPS = 100n; +const CATALOG_SOURCE_KINDS = new Set(['https', 'huggingface']); +const CATALOG_RUNTIME_STATUSES = new Set(['blessed', 'deprecated', 'revoked']); +const CATALOG_OUTCOME_CLASS_STATUSES = new Set(['active', 'deprecated', 'revoked']); +const PROVIDER_LIFECYCLE_OPS = new Set([ + 'register_provider', + 'join_enclave', + 'leave_enclave', + 'join_room', + 'leave_room', + 'set_provider_rails', +]); +const DAY_SECONDS = 24 * 60 * 60; +const DEFAULT_PRICE_RATE_LIMIT_SECONDS = 6 * 60 * 60; +const DEFAULT_MARKET_PRICE_TARGET_UTILIZATION_BPS = 8_500; +const DEFAULT_MARKET_PRICE_EMA_ALPHA_BPS = 2_500; +const DEFAULT_MARKET_PRICE_GAIN_BPS = 5_000; +const DEFAULT_MARKET_PRICE_MAX_STEP_BPS = 1_000; +const DEFAULT_MARKET_PRICE_COLD_START_MIN_PROVIDERS = 2; +const ZERO_AU = '0'; +const ONE_USD_AU = '1000000000000000000'; +const FIVE_MILLI_USD_AU = '5000000000000000'; +const DEFAULT_MARKET_PRICE_PROVIDER_EPOCH_TARGET_AU = ONE_USD_AU; +const DEFAULT_MARKET_PRICE_MAX_UTILIZATION_BPS = 50_000; +const DEFAULT_MARKET_PRICE_BELOW_TARGET_DISCOUNT_BPS = 2_500; +const DEFAULT_MARKET_PRICE_ABOVE_TARGET_SLOPE_BPS = 15_000; +const DEFAULT_PARAM_ACTIVATION_DELAY_SECONDS = DAY_SECONDS; +const PROBATION_SECONDS = 7 * DAY_SECONDS; +const DEFAULT_FRAUD_SLASH_BPS = 10_000; +const DEFAULT_DISPUTE_LOST_SLASH_BPS = 2_000; +const MAX_OPERATOR_FEE_BPS = 1_500; +const MAX_LAUNCH_ENCLAVE_ATTESTATION_TIER = 3; +const DEFAULT_DISPUTE_DEPOSIT_AU = ONE_USD_AU; +const DEFAULT_DISPUTE_TIMEOUT_EPOCHS = 168; +const DEFAULT_MAX_OPEN_DISPUTES_PER_OPENER = 8; +const DEFAULT_DISPUTE_OPENER_FAULT_FORFEIT_BPS = 2_500; +const DEFAULT_MAX_APPLY_BATCH = 2_000; +const DEFAULT_MAX_MARKET_USAGE_ENTRIES = 5_000; +const DEFAULT_MAX_TAP_SETTLEMENT_OUTPUTS = 5_000; +const DEFAULT_MAX_TNK_SETTLEMENT_OUTPUTS = 5_000; +const DEFAULT_MAX_FIAT_SETTLEMENT_OUTPUTS = 5_000; +const MIN_TAP_CONFIRMATION_DEPTH = 12; +const TAP_OPERATOR_BPS = 1_500; +const TAP_BURN_BPS = 1_000; +const DISPUTE_EVIDENCE_MAX_BYTES = 4_096; +const FRAUD_PROOF_MAX_BYTES = 4_096; +export const SESSION_RECEIPT_SCHEMA_VERSION = 11; +export const SPEND_VOUCHER_SCHEMA_VERSION = 11; +const RECEIPT_EPOCH_INDEX_PAGE_SIZE = 128; +const CTX_BRACKET_TABLE_VERSION = 1; +const CTX_BRACKETS = Object.freeze([ + { id: 'le8k', max_ctx: 8_192 }, + { id: 'le32k', max_ctx: 32_768 }, + { id: 'le128k', max_ctx: 131_072 }, + { id: 'le256k', max_ctx: 262_144 }, + { id: 'gt256k', max_ctx: null }, +]); +const TNK_E18 = 1_000_000_000_000_000_000n; +const TAP_WEI = 1_000_000_000_000_000_000n; +const USD_AU = 1_000_000_000_000_000_000n; +const USD_CENT_AU = 10_000_000_000_000_000n; +const TAP_DEPOSIT_EVENT_SIGNATURE = '0xe1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d3c460751c2402c5c5cc9109c'; +const TAP_DEPOSIT_WATCHER_ID = 'tap-deposit-watcher-v1'; +const MSB_TRANSFER_EVIDENCE_VERSION = 1; +const STRIPE_TRANSFER_EVIDENCE_VERSION = 2; +const REPUTATION_HALF_LIFE_SECONDS = 14 * DAY_SECONDS; +const REPUTATION_KAPPA = 25; +const REPUTATION_RAW_NANO_SCALE = 1_000_000_000n; +const REPUTATION_DECAY_PICO_SCALE = 1_000_000_000_000n; +const REPUTATION_RAW_NANO_PER_MILLI = 1_000_000n; +const PARAM_DEFINITIONS = Object.freeze({ + probation_successful_sessions: { default: 50, min: 0, max: 1_000_000 }, + probation_seconds: { default: PROBATION_SECONDS, min: 0, max: 365 * 24 * 60 * 60 }, + probation_max_concurrent_sessions_per_user: { default: 2, min: 1, max: 1_000_000 }, + probation_price_max_bps: { default: 10_000, min: 0, max: 1_000_000 }, + probation_weight_bps: { default: 5_000, min: 0, max: 10_000 }, + auditor_min_reputation_bps: { default: 8_000, min: 0, max: 10_000 }, + auditor_min_age_seconds: { default: 30 * DAY_SECONDS, min: 0, max: 10 * 365 * DAY_SECONDS }, + canary_match_min_bps: { default: 9_000, min: 0, max: 10_000 }, + canary_probe_holdback_bps: { default: 0, min: 0, max: 10_000 }, + canary_probe_release_min_passes: { default: 2, min: 0, max: 1_000_000 }, + probe_reward_au: { default: FIVE_MILLI_USD_AU, min: ZERO_AU, money: true }, + uptime_tick_seconds: { default: 6 * 60 * 60, min: 60, max: 30 * DAY_SECONDS }, + fraud_slash_bps: { default: DEFAULT_FRAUD_SLASH_BPS, min: 0, max: 10_000 }, + dispute_lost_slash_bps: { default: DEFAULT_DISPUTE_LOST_SLASH_BPS, min: 0, max: 10_000 }, + new_provider_holdback_epochs: { default: 168, min: 0, max: 1_000_000 }, + holdback_epochs: { default: 24, min: 0, max: 1_000_000 }, + min_tier_notice_epochs: { default: 24, min: 1, max: 1_000_000 }, + fee_bps: { default: 1_500, min: 0, max: MAX_OPERATOR_FEE_BPS }, + dispute_deposit_au: { default: DEFAULT_DISPUTE_DEPOSIT_AU, min: '1', money: true }, + dispute_timeout_epochs: { default: DEFAULT_DISPUTE_TIMEOUT_EPOCHS, min: 1, max: 1_000_000 }, + max_open_disputes_per_opener: { default: DEFAULT_MAX_OPEN_DISPUTES_PER_OPENER, min: 1, max: 1_000 }, + dispute_opener_fault_forfeit_bps: { default: DEFAULT_DISPUTE_OPENER_FAULT_FORFEIT_BPS, min: 1, max: 9_999 }, + payout_min_au: { default: ONE_USD_AU, min: ZERO_AU, money: true }, + price_min_bps: { default: 2_500, min: 2_500, max: 40_000 }, + price_max_bps: { default: 40_000, min: 2_500, max: 40_000 }, + price_rate_limit_seconds: { default: DEFAULT_PRICE_RATE_LIMIT_SECONDS, min: 0, max: 365 * DAY_SECONDS }, + market_target_utilization_bps: { default: DEFAULT_MARKET_PRICE_TARGET_UTILIZATION_BPS, min: 1, max: 9_999, deprecated: true }, + market_ema_alpha_bps: { default: DEFAULT_MARKET_PRICE_EMA_ALPHA_BPS, min: 1, max: 10_000 }, + market_gain_bps: { default: DEFAULT_MARKET_PRICE_GAIN_BPS, min: 1, max: 10_000 }, + market_max_step_bps: { default: DEFAULT_MARKET_PRICE_MAX_STEP_BPS, min: 1, max: 10_000 }, + market_cold_start_min_providers: { default: DEFAULT_MARKET_PRICE_COLD_START_MIN_PROVIDERS, min: 0, max: 1_000_000, deprecated: true }, + market_provider_epoch_target_au: { default: DEFAULT_MARKET_PRICE_PROVIDER_EPOCH_TARGET_AU, min: '1', money: true, deprecated: true }, + market_max_utilization_bps: { default: DEFAULT_MARKET_PRICE_MAX_UTILIZATION_BPS, min: 1, max: 1_000_000, deprecated: true }, + market_below_target_discount_bps: { default: DEFAULT_MARKET_PRICE_BELOW_TARGET_DISCOUNT_BPS, min: 0, max: 10_000, deprecated: true }, + market_above_target_slope_bps: { default: DEFAULT_MARKET_PRICE_ABOVE_TARGET_SLOPE_BPS, min: 0, max: 1_000_000, deprecated: true }, + epoch_seconds: { default: 3_600, min: 60, max: 86_400 }, + reservation_max_lifetime_epochs: { default: 24, min: 1, max: 1_000_000 }, + reservation_receipt_grace_epochs: { default: 6, min: 0, max: 1_000_000 }, + rate_staleness_seconds: { default: 45 * 60, min: 60, max: 86_400 }, + rules_grace_seconds: { default: 14 * 24 * 60 * 60, min: 0, max: 365 * 24 * 60 * 60 }, + challenge_epochs: { default: 6, min: 0, max: 1_000_000 }, + max_apply_batch: { default: DEFAULT_MAX_APPLY_BATCH, min: 1, max: Number.MAX_SAFE_INTEGER }, + max_market_usage_entries: { default: DEFAULT_MAX_MARKET_USAGE_ENTRIES, min: 0, max: Number.MAX_SAFE_INTEGER }, + max_tap_settlement_outputs: { default: DEFAULT_MAX_TAP_SETTLEMENT_OUTPUTS, min: 1, max: Number.MAX_SAFE_INTEGER }, + max_tnk_settlement_outputs: { default: DEFAULT_MAX_TNK_SETTLEMENT_OUTPUTS, min: 1, max: Number.MAX_SAFE_INTEGER }, + max_fiat_settlement_outputs: { default: DEFAULT_MAX_FIAT_SETTLEMENT_OUTPUTS, min: 1, max: Number.MAX_SAFE_INTEGER }, + param_activation_delay_seconds: { default: DEFAULT_PARAM_ACTIVATION_DELAY_SECONDS, min: 0, max: 30 * DAY_SECONDS }, +}); +const EPOCH_ADMIN_PARAM_KEYS = Object.freeze([ + 'probation_successful_sessions', + 'probation_seconds', + 'probation_max_concurrent_sessions_per_user', + 'probation_price_max_bps', + 'probation_weight_bps', + 'auditor_min_reputation_bps', + 'auditor_min_age_seconds', + 'canary_match_min_bps', + 'canary_probe_holdback_bps', + 'canary_probe_release_min_passes', + 'probe_reward_au', + 'uptime_tick_seconds', + 'fraud_slash_bps', + 'dispute_lost_slash_bps', + 'new_provider_holdback_epochs', + 'holdback_epochs', + 'min_tier_notice_epochs', + 'fee_bps', + 'dispute_deposit_au', + 'dispute_timeout_epochs', + 'max_open_disputes_per_opener', + 'dispute_opener_fault_forfeit_bps', + 'payout_min_au', + 'price_min_bps', + 'price_max_bps', + 'price_rate_limit_seconds', + 'market_ema_alpha_bps', + 'market_gain_bps', + 'market_max_step_bps', + 'epoch_seconds', + 'reservation_max_lifetime_epochs', + 'reservation_receipt_grace_epochs', + 'rate_staleness_seconds', + 'rules_grace_seconds', + 'challenge_epochs', + 'max_apply_batch', + 'max_market_usage_entries', + 'max_tap_settlement_outputs', + 'max_tnk_settlement_outputs', + 'max_fiat_settlement_outputs', + 'param_activation_delay_seconds', +]); +const REPUTATION_EVENT_KINDS = new Set([ + 'session_ok', + 'session_partial', + 'session_fail', + 'probe_ok', + 'probe_fail', + 'uptime_tick', + 'underdelivery', + 'dispute_lost', + 'provenance_violation', +]); +const PROBE_KINDS = new Set(['canary', 'uptime_tick']); +const PROBE_VERIFICATION_METHODS = new Set([ + 'token_fingerprint', + 'context_needle', + 'seed_perceptual_hash', + 'embedding_cosine', + 'transcript_match', + 'audio_fingerprint', + 'attestation_of_compute', +]); +const AUDITOR_SLASH_REASONS = new Set(['collusion', 'false_report']); +const BAN_TARGET_TYPES = new Set(['provider', 'device', 'fingerprint', 'committer']); +const FRAUD_PROOF_REASONS = new Set(['over_credit', 'price_derivation']); +const DISPUTE_OUTCOMES = new Set(['provider_fault', 'opener_fault', 'no_fault']); +const DISPUTE_DEPOSIT_ACTIONS = new Set(['refund', 'forfeit', 'partial_forfeit']); +const EPOCH_ROOT_KEYS = ['dep', 'use', 'earn', 'fee', 'price']; +const EPOCH_TOTAL_KEYS = [ + 'dep_count', + 'dep_au', + 'use_count', + 'use_au', + 'provider_count', + 'earn_au', + 'fee_au', + 'fee_cum_au', + 'burn_au', + 'burn_cum_au', + 'price_count', +]; +const EPOCH_TOTAL_MONEY_KEYS = new Set([ + 'dep_au', + 'use_au', + 'earn_au', + 'fee_au', + 'fee_cum_au', + 'burn_au', + 'burn_cum_au', +]); +const ENCLAVE_UPDATE_FIELDS = [ + 'att_tier', + 'binary_hash', + 'approved_binary_hashes', + 'launch_measurements', + 'caps', +]; +const ENCLAVE_ARTIFACT_SIDECARS_MAX = 64; +const ENCLAVE_APPROVED_BINARY_HASHES_MAX = 64; +const TIER3_MEASUREMENT_MAX_NAMES = 32; +const TIER3_MEASUREMENT_MAX_VALUES = 128; +const ENCLAVE_BACKEND_PATTERN = /^[a-z][a-z0-9]*(?:[.-][a-z0-9]+)*$/; +const ENCLAVE_BACKEND_MAX_LENGTH = 64; +const ENCLAVE_QUANT_BUCKETS = new Set([ + 'unknown', + 'fp32', + 'fp16', + 'bf16', + 'fp8', + 'nvfp4', + 'int8', + 'int4', + 'int2', + 'int1', + 'fp64', + 'tf32', + 'mxfp8', + 'mxfp6', + 'mxfp4', + 'fp6', + 'fp4', + 'nf4', +]); +const ENCLAVE_QUANT_BUCKET_PATTERN = /^(?:unknown|binary|ternary|tf32|(?:mxfp|nvfp|uint|int|fp|bf|nf)[1-9][0-9]?(?:-[a-z0-9]+)*)$/; +const ENCLAVE_QUANT_BUCKET_MAX_LENGTH = 32; +const DEFAULT_MODEL_CLASS = 'text-generation'; +const MODEL_CLASSES = new Set([ + DEFAULT_MODEL_CLASS, + 'embedding', + 'image-generation', + 'video-generation', + 'tts', + 'stt', + 'audio-generation', + 'music-generation', + 'workflow', +]); +const RATE_MAP_MAX_ENTRIES = 16; +const MODEL_CLASS_RATE_UNITS = Object.freeze({ + [DEFAULT_MODEL_CLASS]: new Set([ + 'input_token', + 'cached_input_token', + 'output_token', + ]), + embedding: new Set(['input_token', 'embedding']), + 'image-generation': new Set(['image', 'step']), + 'video-generation': new Set(['video_second', 'frame']), + tts: new Set(['input_character', 'audio_second']), + stt: new Set(['audio_second']), + 'audio-generation': new Set(['input_character', 'audio_second']), + 'music-generation': new Set(['input_character', 'audio_second']), + workflow: new Set([ + 'megapixel_step', + 'megapixel', + // Exact integer pixel-frames (width * height * frames * artifact_count). + // Video workflow classes price in this unit; `megapixel_step` rounds each + // frame up to a whole megapixel and is image-only. + 'pixel_frame', + 'compute_second', + 'audio_second', + 'input_character', + 'frame', + 'image', + 'step', + 'video_second', + ]), +}); +const CAP_OUTPUT_MODALITIES = new Set(['text', 'embedding', 'image', 'video', 'audio']); +const ENCLAVE_MODALITIES = new Set(['text', 'embedding', 'image', 'video', 'audio']); +const MODEL_CLASS_OUTPUT_MODALITIES = Object.freeze({ + [DEFAULT_MODEL_CLASS]: new Set(['text']), + embedding: new Set(['embedding']), + 'image-generation': new Set(['image']), + 'video-generation': new Set(['video', 'audio']), + tts: new Set(['audio']), + stt: new Set(['text']), + 'audio-generation': new Set(['audio']), + 'music-generation': new Set(['audio']), + workflow: new Set(['image', 'video', 'audio']), +}); +const ENCLAVE_ARTIFACT_ROOT_KIND = 'blake3_merkle_v1'; +const ENCLAVE_CAP_BOOLEAN_FIELDS = [ + 'chat', + 'tools', + 'json', + 'embeddings', + 'vision', + 'image', + 'video', + 'audio', +]; +const ENCLAVE_CAP_INTEGER_FIELDS = [ + 'ctx', + 'ctx_max', + 'tp_degree', + 'max_batch_size', + 'max_num_tokens', + 'kv_bytes_per_token', + 'vllm_gpu_memory_utilization_pct', + 'max_image_width', + 'max_image_height', + 'max_image_steps', + 'max_video_width', + 'max_video_height', + 'max_video_frames', + 'max_video_seconds', + 'max_audio_seconds', + 'sample_rate_hz', +]; +const ENCLAVE_CAP_STRING_FIELDS = [ + 'vllm_dtype', +]; +const ENCLAVE_CAP_FIELDS = new Set([ + ...ENCLAVE_CAP_BOOLEAN_FIELDS, + ...ENCLAVE_CAP_INTEGER_FIELDS, + ...ENCLAVE_CAP_STRING_FIELDS, + 'output_modality', + 'output_modalities', + 'modality_set', + 'speciality_levels', +]); +const ROOM_POLICY_FIELDS = new Set([ + 'region_hint', + 'canary_set', + 'min_reputation', + 'max_price_mult', +]); + +export const signingMessageVersions = () => [SIGNING_MESSAGE_VERSION]; +export const consentMessage = (ver, hash, signingVersion = SIGNING_MESSAGE_VERSION) => { + if (signingVersion !== SIGNING_MESSAGE_VERSION) { + throw new Error(`Unsupported signing message version: ${signingVersion}`); + } + return JSON.stringify({ + domain: 'mayhem-consent', + signing_version: SIGNING_MESSAGE_VERSION, + rules_ver: ver, + rules_hash: hash, + }); +}; +export const providerLifecycleIntentMessage = (intent, signingVersion = SIGNING_MESSAGE_VERSION) => { + if (signingVersion !== SIGNING_MESSAGE_VERSION) { + throw new Error(`Unsupported signing message version: ${signingVersion}`); + } + return JSON.stringify({ + domain: 'mayhem-provider-lifecycle', + signing_version: SIGNING_MESSAGE_VERSION, + intent: stableValue(intent), + }); +}; +export const providerPayoutTargetBindingEvidence = (intent) => ({ + admin: intent.admin, + bootstrap: intent.bootstrap, + chain_id: intent.chain_id, + context_revision: intent.context_revision, + currency: intent.currency, + expires_after_epoch: intent.expires_after_epoch, + network: intent.network, + nonce: intent.nonce, + payment_config_version: intent.payment_config_version, + previous_revision: intent.previous_revision, + provider: intent.provider, + rail: intent.rail, + target: intent.target, + target_wallet: intent.target_wallet, +}); +export const providerPayoutTargetBindingMessage = (intent) => + `mayhem-provider-payout-target-binding-v1${stableJson( + providerPayoutTargetBindingEvidence(intent) + )}`; +export const providerPayoutBindingMessage = (intent) => + `mayhem-provider-payout-binding-v1${stableJson(intent)}`; +export const stripePayoutProcessorEvidence = (value) => ({ + account_id: value.account_id, + account_type: value.account_type, + country: value.country, + currency: value.currency, + mode: value.mode, + provider: value.provider, +}); +export const stripePayoutProcessorRevision = async (value) => { + const digest = await blake3(b4a.from(stableJson({ + domain: 'mayhem-stripe-payout-processor-evidence-v1', + evidence: stripePayoutProcessorEvidence(value), + }))); + return b4a.toString(digest, 'hex'); +}; +export const stripePayoutVerificationFeatureKey = async (value) => { + const digest = await blake3(b4a.from(stableJson({ + domain: 'mayhem-stripe-payout-verification-feature-v1', + value, + }))); + return `payout/stripe-verified/${value.provider}/${b4a.toString(digest, 'hex')}`; +}; +export const depositTnkIntentMessage = (intent) => + `mayhem-deposit-tnk-intent-v1${stableJson(intent)}`; +export const tapAccountBindingEvidence = (value) => ({ + user: value.user, + ethereum_address: value.ethereum_address.toLowerCase(), + chain_id: value.chain_id, + pool_address: value.pool_address.toLowerCase(), +}); +export const tapAccountBindingMessage = (value) => + `mayhem-tap-account-bind-v1${stableJson(tapAccountBindingEvidence(value))}`; +const canonicalSpendVoucherBody = (body) => { + if (!body || typeof body !== 'object' || Array.isArray(body)) return body; + const canonical = { + schema_version: body.schema_version, + session_id: body.session_id, + billing_id: body.billing_id, + billing_attempt: body.billing_attempt, + billing_prior_usage: body.billing_prior_usage, + billing_prior_au_owed_cum: body.billing_prior_au_owed_cum, + billing_epoch: body.billing_epoch, + reservation_id: body.reservation_id, + reservation_expires_after_epoch: body.reservation_expires_after_epoch, + reservation_receipt_grace_epochs: body.reservation_receipt_grace_epochs, + user: body.user, + provider: body.provider, + payout_revision: body.payout_revision, + rail: body.rail, + enclave_id: body.enclave_id, + model_id: body.model_id, + price_ver: body.price_ver, + locked_rate_map: body.locked_rate_map, + locked_per_req_au: body.locked_per_req_au, + locked_min_session_au: body.locked_min_session_au, + served_ctx: body.served_ctx, + }; + if (Array.isArray(body.required_modalities) && body.required_modalities.length > 0) { + canonical.required_modalities = body.required_modalities; + } + if (body.required_specialities && typeof body.required_specialities === 'object' && + !Array.isArray(body.required_specialities) && Object.keys(body.required_specialities).length > 0) { + canonical.required_specialities = body.required_specialities; + } + if (hasOwn(body, 'workflow')) canonical.workflow = canonicalWorkflowBinding(body.workflow); + if (hasOwn(body, 'ctx_bracket')) canonical.ctx_bracket = body.ctx_bracket; + if (hasOwn(body, 'ctx_bracket_table_ver')) canonical.ctx_bracket_table_ver = body.ctx_bracket_table_ver; + canonical.rules_ver = body.rules_ver; + canonical.max_spend_au = body.max_spend_au; + canonical.checkpoint_every = body.checkpoint_every; + return canonical; +}; +const canonicalReceiptBody = (body) => { + if (!body || typeof body !== 'object' || Array.isArray(body)) return body; + const canonical = { + schema_version: body.schema_version, + session_id: body.session_id, + billing_id: body.billing_id, + billing_attempt: body.billing_attempt, + billing_prior_usage: body.billing_prior_usage, + billing_prior_au_owed_cum: body.billing_prior_au_owed_cum, + billing_epoch: body.billing_epoch, + reservation_id: body.reservation_id, + reservation_expires_after_epoch: body.reservation_expires_after_epoch, + reservation_receipt_grace_epochs: body.reservation_receipt_grace_epochs, + payout_revision: body.payout_revision, + seq: body.seq, + final: body.final, + rail: body.rail, + user: body.user, + provider: body.provider, + enclave_id: body.enclave_id, + model_id: body.model_id, + price_ver: body.price_ver, + locked_rate_map: body.locked_rate_map, + locked_per_req_au: body.locked_per_req_au, + locked_min_session_au: body.locked_min_session_au, + served_ctx: body.served_ctx, + }; + if (hasOwn(body, 'ctx_bracket')) canonical.ctx_bracket = body.ctx_bracket; + if (hasOwn(body, 'ctx_bracket_table_ver')) canonical.ctx_bracket_table_ver = body.ctx_bracket_table_ver; + canonical.rules_ver = body.rules_ver; + if (hasOwn(body, 'workflow')) canonical.workflow = canonicalWorkflowBinding(body.workflow); + if (hasOwn(body, 'workflow_output')) canonical.workflow_output = canonicalWorkflowOutput(body.workflow_output); + canonical.usage = body.usage; + if (body.usage_attribution && typeof body.usage_attribution === 'object' && + !Array.isArray(body.usage_attribution) && Object.keys(body.usage_attribution).length > 0) { + canonical.usage_attribution = body.usage_attribution; + } + canonical.au_owed_cum = body.au_owed_cum; + canonical.prompt_hash = body.prompt_hash; + canonical.ts = body.ts; + return canonical; +}; +const canonicalWorkflowBinding = (workflow) => { + if (!workflow || typeof workflow !== 'object' || Array.isArray(workflow)) return workflow; + return { + endpoint_family: workflow.endpoint_family, + graph_hash: workflow.graph_hash, + runtime_id: workflow.runtime_id, + outcome_class: workflow.outcome_class, + quoted_usage: stableValue(workflow.quoted_usage), + }; +}; +const canonicalWorkflowOutput = (output) => { + if (!output || typeof output !== 'object' || Array.isArray(output)) return output; + return { + output_modalities: output.output_modalities, + metrics: stableValue(output.metrics), + }; +}; +export const receiptMessage = (body, signingVersion = SIGNING_MESSAGE_VERSION) => { + if (signingVersion !== SIGNING_MESSAGE_VERSION) { + throw new Error(`Unsupported signing message version: ${signingVersion}`); + } + return JSON.stringify({ + domain: 'mayhem-session-receipt', + signing_version: SIGNING_MESSAGE_VERSION, + body: canonicalReceiptBody(body), + }); +}; +export const spendVoucherMessage = (body, signingVersion = SIGNING_MESSAGE_VERSION) => { + if (signingVersion !== SIGNING_MESSAGE_VERSION) { + throw new Error(`Unsupported signing message version: ${signingVersion}`); + } + return JSON.stringify({ + domain: 'mayhem-spend-voucher', + signing_version: SIGNING_MESSAGE_VERSION, + body: canonicalSpendVoucherBody(body), + }); +}; +export const spendReservationEvidence = (value) => { + const voucher = stableValue(value.voucher); + if (voucher?.required_specialities && typeof voucher.required_specialities === 'object' && + !Array.isArray(voucher.required_specialities) && Object.keys(voucher.required_specialities).length === 0) { + delete voucher.required_specialities; + } + const evidence = { + contract_version: value.contract_version, + session_id: value.session_id, + reservation_id: value.reservation_id, + reservation_expires_after_epoch: value.reservation_expires_after_epoch, + reservation_receipt_grace_epochs: value.reservation_receipt_grace_epochs, + epoch: value.epoch, + at: value.at, + rail: value.rail, + user: value.user, + provider: value.provider, + enclave_id: value.enclave_id, + enclave_pubkey: value.enclave_pubkey, + model_id: value.model_id, + price_ver: value.price_ver, + rules_ver: value.rules_ver, + served_ctx: value.served_ctx, + required_modalities: value.required_modalities, + ctx_bracket: value.ctx_bracket, + ctx_bracket_table_ver: value.ctx_bracket_table_ver, + max_spend_au: value.max_spend_au, + voucher, + }; + if (value.required_specialities && typeof value.required_specialities === 'object' && + !Array.isArray(value.required_specialities) && Object.keys(value.required_specialities).length > 0) { + evidence.required_specialities = value.required_specialities; + } + if (value.workflow && typeof value.workflow === 'object' && !Array.isArray(value.workflow)) { + evidence.workflow = canonicalWorkflowBinding(value.workflow); + } + return evidence; +}; +export const spendReservationMessage = (value) => + `mayhem-spend-reservation-v1${stableJson(spendReservationEvidence(value))}`; +export const targetedSpendReservationEvidence = (value) => ({ + payout_revision: value.payout_revision, + reservation: spendReservationEvidence(value), +}); +export const targetedSpendReservationMessage = (value) => + `mayhem-targeted-spend-reservation-v1${stableJson( + targetedSpendReservationEvidence(value) + )}`; +export const recordUsageReceiptEvidence = (value) => ({ + contract_version: value.contract_version, + epoch: value.epoch, + payout_revision: value.payout_revision, + receipt: stableValue(value.receipt), +}); +export const recordUsageReceiptMessage = (value) => + `mayhem-record-usage-receipt-v1${stableJson(recordUsageReceiptEvidence(value))}`; +export const closeUsageReservationEvidence = (value) => ({ + op: value.op, + contract_version: value.contract_version, + billing_epoch: value.billing_epoch, + reservation_id: value.reservation_id, + reservation_expires_after_epoch: value.reservation_expires_after_epoch, + reservation_receipt_grace_epochs: value.reservation_receipt_grace_epochs, + billing_id: value.billing_id, + billing_attempt: value.billing_attempt, + session_id: value.session_id, + user: value.user, + rail: value.rail, + provider: value.provider, + payout_revision: value.payout_revision, + latest_receipt_seq: value.latest_receipt_seq, + latest_receipt_hash: value.latest_receipt_hash, + at: value.at, + reason: value.reason, + actor: value.actor, + actor_role: value.actor_role, +}); +export const closeUsageReservationMessage = (value) => + `mayhem-close-usage-reservation-v1${stableJson(closeUsageReservationEvidence(value))}`; +export const expireUsageReservationMessage = (value) => + `mayhem-expire-usage-reservation-v1${stableJson(closeUsageReservationEvidence(value))}`; +export const payoutPreparationEvidence = (value) => ({ + op: value.op, + contract_version: value.contract_version, + economic_op_id: value.economic_op_id, + rail: value.rail, + epoch: value.epoch, + epoch_apply_hash: value.epoch_apply_hash, + prepared_at: value.prepared_at, + kind: value.kind, + output_index: value.output_index, + payload_hash: value.payload_hash, + payload: stableValue(value.payload), + liability: stableValue(value.liability), + external_effect_ids: value.external_effect_ids, + admin: value.admin, +}); +export const payoutPreparationMessage = (value) => + `mayhem-targeted-payout-preparation-v1${stableJson(payoutPreparationEvidence(value))}`; +export const targetedPayoutControlEvidence = (value) => { + const evidence = { ...value }; + delete evidence.admin_sig; + return stableValue(evidence); +}; +export const targetedPayoutControlMessage = (value) => + `mayhem-targeted-payout-control-v1${stableJson(targetedPayoutControlEvidence(value))}`; +export const probeResultEvidence = (value, auditor) => ({ + auditor, + probe_id: value.probe_id, + probe_kind: value.probe_kind, + provider: value.provider, + enclave_id: value.enclave_id, + binary_hash: value.binary_hash, + canary_set: value.canary_set, + canary_prompt_id: value.canary_prompt_id, + challenge_epoch: value.challenge_epoch, + challenge_apply_hash: value.challenge_apply_hash, + challenge_seed: value.challenge_seed, + verification_method: value.verification_method, + session_receipt_hash: value.session_receipt_hash, + evidence_hash: value.evidence_hash, + match_bps: value.match_bps, + pass: value.pass, + epoch: value.epoch, + at: value.at, +}); +export const probeResultMessage = (value, auditor) => + `mayhem-probe-result-v1${stableJson(probeResultEvidence(value, auditor))}`; +export const providerKybEvidence = (value) => ({ + provider: value.provider, + legal_name: value.legal_name, + jurisdiction: value.jurisdiction, + proof_hash: value.proof_hash, + kyb_ref: value.kyb_ref, + verified_at: value.verified_at, + schema_version: value.schema_version, +}); +export const providerKybMessage = (value) => + `mayhem-provider-kyb-v1${stableJson(providerKybEvidence(value))}`; +export const roomSidechannelName = (roomId) => `mx/room/${roomId}`; +export const deriveRoomId = async (modelId, creator, nonce) => { + const digest = await blake3(b4a.from(`${modelId}${creator}${nonce}`)); + return b4a.toString(digest, 'hex').slice(0, 32); +}; + +const cloneValue = (value) => (value === undefined ? undefined : JSON.parse(JSON.stringify(value))); +const hasOwn = (value, key) => Object.prototype.hasOwnProperty.call(value, key); + +const versionedMayhemOperation = (op) => { + const dispatch = op?.value?.dispatch; + if (!dispatch || typeof dispatch !== 'object' || Array.isArray(dispatch)) { + return { present: false, version: null, operation: op }; + } + + if (op.type === 'feature' && dispatch.type === 'mayhem_feature' && + hasOwn(dispatch, 'contract_version')) { + const normalizedDispatch = { ...dispatch }; + const version = normalizedDispatch.contract_version; + delete normalizedDispatch.contract_version; + return { + present: true, + version, + operation: { + ...op, + value: { ...op.value, dispatch: normalizedDispatch }, + }, + }; + } + + if (op.type === 'tx' && dispatch.value && typeof dispatch.value === 'object' && + !Array.isArray(dispatch.value) && hasOwn(dispatch.value, 'contract_version')) { + const normalizedValue = { ...dispatch.value }; + const version = normalizedValue.contract_version; + delete normalizedValue.contract_version; + return { + present: true, + version, + operation: { + ...op, + value: { + ...op.value, + dispatch: { ...dispatch, value: normalizedValue }, + }, + }, + }; + } + + return { present: false, version: null, operation: op }; +}; + +export const validateMayhemOperationContractVersion = ( + op, + expectedVersion = CONTRACT_VERSION +) => { + if (!Number.isSafeInteger(expectedVersion) || expectedVersion < 1) { + throw new Error('Expected contract version must be a positive safe integer.'); + } + const versioned = versionedMayhemOperation(op); + if (versioned.present && + (!Number.isSafeInteger(versioned.version) || versioned.version !== expectedVersion)) { + const actual = Number.isSafeInteger(versioned.version) + ? versioned.version + : 'invalid'; + throw new Error( + `Contract upgrade required: expected CONTRACT_VERSION ${expectedVersion}, got ${actual}.` + ); + } + return versioned.operation; +}; +const compareCodepoint = (left, right) => { + const a = String(left); + const b = String(right); + if (a < b) return -1; + if (a > b) return 1; + return 0; +}; +const stableValue = (value) => { + if (Array.isArray(value)) return value.map((item) => stableValue(item)); + if (value && typeof value === 'object') { + return Object.fromEntries( + Object.keys(value) + .sort() + .map((key) => [key, stableValue(value[key])]) + ); + } + return value; +}; +const stableJson = (value) => JSON.stringify(stableValue(value)); + +const verifyEd25519Hex = (signature, message, publicKey) => { + const signatureHex = String(signature ?? '').toLowerCase(); + const publicKeyHex = String(publicKey ?? '').toLowerCase(); + const messageBytes = b4a.isBuffer(message) ? message : b4a.from(String(message)); + if (!/^[0-9a-f]{128}$/.test(signatureHex) || + !/^[0-9a-f]{64}$/.test(publicKeyHex) || + messageBytes.length === 0) { + return false; + } + const signatureBytes = b4a.from(signatureHex, 'hex'); + const publicKeyBytes = b4a.from(publicKeyHex, 'hex'); + try { + return PeerWallet.verify(signatureBytes, messageBytes, publicKeyBytes) === true; + } catch (_error) { + return false; + } +}; + +export const adminContractTxSigningValue = ({ + address, + context, + nonce, + prepared_command: preparedCommand, + sim, +}) => stableValue({ + address: String(address ?? '').trim().toLowerCase(), + context: { + contract_version: context?.contract_version, + msb_bootstrap: String(context?.msb_bootstrap ?? '').trim().toLowerCase(), + network_id: context?.network_id, + subnet_bootstrap: String(context?.subnet_bootstrap ?? '').trim().toLowerCase(), + }, + domain: 'mayhem-admin-contract-tx-v1', + nonce: String(nonce ?? '').trim().toLowerCase(), + prepared_command: preparedCommand, + sim: sim === true, +}); + +export const adminContractTxDigest = async (value) => b4a.toString( + await blake3(b4a.from(stableJson(adminContractTxSigningValue(value)), 'utf8')), + 'hex' +); + +const serializableFeatureResult = (value) => { + if (value === undefined) return null; + if (value instanceof Error) { + return { + name: value.name, + message: value.message, + }; + } + try { + return JSON.parse(JSON.stringify(value)); + } catch { + return String(value); + } +}; + +const ethereumPersonalMessageHash = (message) => { + const body = b4a.from(message, 'utf8'); + const prefix = b4a.from(`\x19Ethereum Signed Message:\n${body.length}`, 'utf8'); + return keccak256(b4a.concat([prefix, body])); +}; +const ethereumAddressFromPublicKey = (publicKey) => + `0x${b4a.toString(keccak256(publicKey.subarray(1)).subarray(12), 'hex')}`; + +export const contractParamDefinitions = () => cloneValue(PARAM_DEFINITIONS); +export const contractEpochAdminParamKeys = () => [...EPOCH_ADMIN_PARAM_KEYS]; +export const contractEpochAdminParamDefinitions = () => Object.fromEntries( + EPOCH_ADMIN_PARAM_KEYS.map((key) => [key, cloneValue(PARAM_DEFINITIONS[key])]) +); +export const contractCtxBracketTable = () => ({ + ver: CTX_BRACKET_TABLE_VERSION, + brackets: cloneValue(CTX_BRACKETS), +}); + +const ctxBracketForTokens = (tokens, table = CTX_BRACKETS) => { + if (!Number.isSafeInteger(tokens) || tokens < 0) return null; + const bracket = table.find((entry) => entry.max_ctx === null || tokens <= entry.max_ctx); + return bracket?.id ?? null; +}; + +class MayhemContract extends Contract { + // The base class runs one execution at a time and calls executeQueued() from + // inside that queue, so the per-operation fields set here belong to the call + // that is running and cannot be overwritten by an overlapping one. + async executeQueued(op, storage, consensusContext = null) { + // Admin Feature envelopes temporarily impersonate a command execution. Keep + // the actual consensus operation kind separately for paid-only operations. + this._mayhemExecutionType = op?.type; + try { + const versioned = versionedMayhemOperation(op); + const canonicalReplay = consumeCanonicalReplayContext(consensusContext, op, storage); + const historical = versioned.present && ( + ([23, 24].includes(versioned.version) && canonicalReplay) || + (versioned.version === 24 && await this.isPreparedCheckpointReplay(op, storage)) + ); + if (historical) { + // Replaying with today's pricing/receipt methods would produce a + // different signed view. Retained implementations preserve the exact + // historical transition, and never participate in new admission. + const Implementation = versioned.version === 23 ? ContractV23 : ContractV24; + this._historicalContracts ??= new Map(); + if (!this._historicalContracts.has(versioned.version)) { + this._historicalContracts.set(versioned.version, new Implementation(this.protocol, this.config)); + } + const previous = this._mayhemReplayStatus; + this._mayhemReplayStatus = { active: true, completed: previous?.completed ?? 0, + contractVersion: versioned.version, canonicalSignedLength: canonicalReplay?.signedLength ?? null }; + try { + const result = await this._historicalContracts.get(versioned.version).execute(op, storage); + this._mayhemReplayStatus.completed++; + return result; + } finally { this._mayhemReplayStatus.active = false; } + } + return await super.executeQueued(validateMayhemOperationContractVersion(op), storage); + } finally { + this._mayhemExecutionType = null; + } + } + + // Compatibility is attached to canonical preparation evidence, never a + // caller-supplied replay flag. TxOperation verifies the original MSB payment + // and exact dispatch hash before entering consensus execution here. + async isPreparedCheckpointReplay(op, storage) { + const dispatch = op?.value?.dispatch; + const value = dispatch?.value; + if (op?.type !== 'tx' || dispatch?.type !== 'stateCheckpoint' || + value?.op !== 'state_checkpoint' || value.contract_version !== 24 || + !Number.isSafeInteger(value.slot) || value.slot < 1 || + !this.isHexBytes(op.key, 32) || !this.isHexBytes(value.snapshot_hash, 32)) return false; + const read = async (key) => (await storage.get(key))?.value ?? null; + const admin = await read('admin'); + const snapshot = await read(`checkpoint/prepared/${value.slot}`); + if (op.value.ipk !== admin || !snapshot || + snapshot.type !== 'state_checkpoint_snapshot' || snapshot.schema_version !== 1 || + snapshot.slot !== value.slot || snapshot.prepared_by !== admin || + snapshot.state?.contract_version !== 24 || snapshot.snapshot_hash !== value.snapshot_hash) return false; + const { snapshot_hash: snapshotHash, ...body } = snapshot; + if (await this.opaqueHash('mayhem-checkpoint-state-v1', snapshot.state) !== snapshot.state_hash || + await this.opaqueHash('mayhem-checkpoint-snapshot-v1', body) !== snapshotHash) return false; + const existing = await read(`checkpoint/slot/${value.slot}`); + if (existing) return existing.tx === op.key && existing.snapshot_hash === snapshotHash && + existing.paid_by === admin; + const preparing = await read('checkpoint/preparing'); + return preparing?.slot === value.slot && preparing.snapshot_hash === snapshotHash; + } + + constructor(protocol, options = {}) { + super(protocol, options); + const self = this; + this._mayhemApplyStage = null; + + this.addFeature('mayhem_feature', async function () { + const result = await self.mayhemFeature(); + await self.recordMayhemFeatureResult(result); + return result; + }); + + this.addSchema('noop', { + value: { + $$strict: true, + $$type: 'object', + op: { type: 'string', min: 1, max: 32 }, + }, + }); + + this.addSchema('gatedNoop', { + value: { + $$strict: true, + $$type: 'object', + op: { type: 'string', min: 1, max: 32 }, + }, + }); + + this.addSchema('readKey', { + value: { + $$strict: true, + $$type: 'object', + op: { type: 'string', min: 1, max: 64 }, + key: { type: 'string', min: 1, max: 256 }, + }, + }); + + this.addSchema('setRules', { + value: { + $$strict: true, + $$type: 'object', + op: { type: 'string', min: 1, max: 64 }, + ver: { type: 'number', integer: true, min: 1 }, + hash: { type: 'string', min: 1, max: 128 }, + }, + }); + + this.addSchema('setParams', { + value: { + $$strict: true, + $$type: 'object', + op: { type: 'string', min: 1, max: 64 }, + submitted_at: { type: 'number', integer: true, min: 0 }, + effective_at: { type: 'number', integer: true, min: 0 }, + values: { type: 'any' }, + }, + }); + + this.addSchema('setPayments', { + value: { + $$strict: true, + $$type: 'object', + op: { type: 'string', min: 1, max: 64 }, + ver: { type: 'number', integer: true, min: 1 }, + fiat: { type: 'any' }, + tap: { type: 'any' }, + tnk: { type: 'any' }, + }, + }); + + this.addSchema('readParams', { + value: { + $$strict: true, + $$type: 'object', + op: { type: 'string', min: 1, max: 64 }, + at: { type: 'number', integer: true, min: 0 }, + keys: { + type: 'array', + max: 64, + items: { type: 'string', min: 1, max: 64 }, + optional: true, + }, + }, + }); + + this.addSchema('setCtxBrackets', { + value: { + $$strict: true, + $$type: 'object', + op: { type: 'string', min: 1, max: 64 }, + submitted_at: { type: 'number', integer: true, min: 0 }, + effective_at: { type: 'number', integer: true, min: 0 }, + brackets: { type: 'array', min: 1, max: 32, items: { type: 'any' } }, + }, + }); + + this.addSchema('readCtxBrackets', { + value: { + $$strict: true, + $$type: 'object', + op: { type: 'string', min: 1, max: 64 }, + at: { type: 'number', integer: true, min: 0, optional: true }, + ver: { type: 'number', integer: true, min: 1, optional: true }, + }, + }); + + this.addSchema('consent', { + value: { + $$strict: true, + $$type: 'object', + op: { type: 'string', min: 1, max: 64 }, + ver: { type: 'number', integer: true, min: 1 }, + hash: { type: 'string', min: 1, max: 128 }, + sig: { type: 'string', min: 1, max: 256 }, + }, + }); + + this.addFunction('registerProvider'); + + this.addSchema('setProviderRails', { + value: { + $$strict: true, + $$type: 'object', + op: { type: 'string', min: 1, max: 64 }, + rails: { + type: 'array', + min: 1, + max: 3, + items: { type: 'string', min: 1, max: 16 }, + }, + }, + }); + + this.addSchema('setProviderKyb', { + value: { + $$strict: true, + $$type: 'object', + op: { type: 'string', min: 1, max: 64 }, + provider: { type: 'string', min: 1, max: 128 }, + legal_name: { type: 'string', min: 1, max: 160 }, + jurisdiction: { type: 'string', min: 1, max: 64 }, + proof_hash: { type: 'string', min: 1, max: 128 }, + kyb_ref: { type: 'string', min: 1, max: 128 }, + verified_at: { type: 'number', integer: true, min: 0 }, + schema_version: { type: 'number', integer: true, min: 1, optional: true }, + admin_sig: { type: 'string', min: 1, max: 256 }, + }, + }); + + this.addSchema('revokeProviderKyb', { + value: { + $$strict: true, + $$type: 'object', + op: { type: 'string', min: 1, max: 64 }, + provider: { type: 'string', min: 1, max: 128 }, + reason_hash: { type: 'string', min: 1, max: 128, optional: true }, + }, + }); + + this.addSchema('banProvider', { + value: { + $$strict: true, + $$type: 'object', + op: { type: 'string', min: 1, max: 64 }, + provider: { type: 'string', min: 1, max: 128 }, + reason_hash: { type: 'string', min: 1, max: 128, optional: true }, + device_key: { type: 'string', min: 1, max: 128, optional: true }, + hardware_fingerprint: { type: 'string', min: 1, max: 128, optional: true }, + }, + }); + + this.addSchema('unban', { + value: { + $$strict: true, + $$type: 'object', + op: { type: 'string', min: 1, max: 64 }, + target_type: { type: 'string', min: 1, max: 32 }, + target: { type: 'string', min: 1, max: 128 }, + reason_hash: { type: 'string', min: 1, max: 128 }, + }, + }); + + this.addSchema('deviceRebind', { + value: { + $$strict: true, + $$type: 'object', + op: { type: 'string', min: 1, max: 64 }, + device_key: { type: 'string', min: 1, max: 128 }, + provider: { type: 'string', min: 1, max: 128 }, + reason_hash: { type: 'string', min: 1, max: 128 }, + }, + }); + + this.addSchema('migrateMarketPricing', { + value: { + $$strict: true, + $$type: 'object', + op: { type: 'string', min: 1, max: 64 }, + at: { type: 'number', integer: true, min: 0 }, + markets: { type: 'array', min: 0, max: 128, items: { type: 'any' } }, + }, + }); + + this.addSchema('setModelRef', { + value: { + $$strict: true, + $$type: 'object', + op: { type: 'string', min: 1, max: 64 }, + model_id: { type: 'string', min: 1, max: 256 }, + model_class: { type: 'string', min: 1, max: 64 }, + rate_map: { type: 'array', min: 1, max: RATE_MAP_MAX_ENTRIES, items: { type: 'any' } }, + source_hash: { type: 'string', min: 1, max: 128, optional: true }, + activity_calibration: { type: 'any', optional: true }, + }, + }); + + this.addSchema('publishCatalog', { + value: { + $$strict: true, + $$type: 'object', + op: { type: 'string', min: 1, max: 64 }, + catalog_id: { type: 'string', min: 1, max: 128 }, + source_kind: { type: 'string', min: 1, max: 32 }, + catalog_url: { type: 'string', min: 1, max: 512 }, + signature_url: { type: 'string', min: 1, max: 512 }, + catalog_hash: { type: 'string', min: 1, max: 128 }, + signature_hash: { type: 'string', min: 1, max: 128 }, + key_id: { type: 'string', min: 1, max: 128 }, + public_key: { type: 'string', min: 1, max: 128 }, + model_count: { type: 'number', integer: true, min: 1 }, + artifact_count: { type: 'number', integer: true, min: 1 }, + canaries: { type: 'array', max: 64, items: { type: 'any' } }, + parts_anchor: { type: 'any', optional: true }, + blessed_runtimes: { type: 'array', max: 32, optional: true, items: { type: 'any' } }, + outcome_classes: { type: 'array', max: 64, optional: true, items: { type: 'any' } }, + }, + }); + + this.addSchema('registerEnclave', { + value: { + $$strict: true, + $$type: 'object', + op: { type: 'string', min: 1, max: 64 }, + enclave_id: { type: 'string', min: 1, max: 128 }, + model_id: { type: 'string', min: 1, max: 256 }, + model_class: { type: 'string', min: 1, max: 64 }, + backend: { type: 'string', min: 1, max: 64 }, + artifact_root: { type: 'string', min: 1, max: 256 }, + artifact_root_kind: { type: 'string', min: 1, max: 64 }, + artifact_source: { type: 'any' }, + artifact_sidecars: { type: 'any', optional: true }, + source_sha256: { type: 'string', min: 1, max: 128, optional: true }, + manifest_hash: { type: 'string', min: 1, max: 128 }, + att_tier: { type: 'number', integer: true, min: 1, max: 4 }, + quant: { type: 'string', min: 1, max: 32, optional: true }, + binary_hash: { type: 'string', min: 1, max: 128 }, + launch_measurements: { type: 'any', optional: true }, + caps: { type: 'any' }, + }, + }); + + this.addSchema('updateEnclave', { + value: { + $$strict: true, + $$type: 'object', + op: { type: 'string', min: 1, max: 64 }, + enclave_id: { type: 'string', min: 1, max: 128 }, + model_class: { type: 'string', min: 1, max: 64, optional: true }, + backend: { type: 'string', min: 1, max: 64, optional: true }, + artifact_root: { type: 'string', min: 1, max: 256, optional: true }, + artifact_root_kind: { type: 'string', min: 1, max: 64, optional: true }, + artifact_source: { type: 'any', optional: true }, + artifact_sidecars: { type: 'any', optional: true }, + source_sha256: { type: 'string', min: 1, max: 128, optional: true }, + manifest_hash: { type: 'string', min: 1, max: 128, optional: true }, + att_tier: { type: 'number', integer: true, min: 1, max: 4, optional: true }, + quant: { type: 'string', min: 1, max: 32, optional: true }, + binary_hash: { type: 'string', min: 1, max: 128, optional: true }, + launch_measurements: { type: 'any', optional: true }, + caps: { type: 'any', optional: true }, + }, + }); + + this.addSchema('setEnclaveMinTier', { + value: { + $$strict: true, + $$type: 'object', + op: { type: 'string', min: 1, max: 64 }, + enclave_id: { type: 'string', min: 1, max: 128 }, + min_att_tier: { type: 'number', integer: true, min: 1, max: 4 }, + submitted_epoch: { type: 'number', integer: true, min: 0 }, + effective_epoch: { type: 'number', integer: true, min: 0 }, + submitted_at: { type: 'number', integer: true, min: 0 }, + reason_hash: { type: 'string', min: 1, max: 128 }, + }, + }); + + this.addSchema('joinEnclave', { + value: { + $$strict: true, + $$type: 'object', + op: { type: 'string', min: 1, max: 64 }, + enclave_id: { type: 'string', min: 1, max: 128 }, + att_tier: { type: 'number', integer: true, min: 1, max: 3 }, + attestation_head: { type: 'string', min: 64, max: 64 }, + hardware_fingerprint: { type: 'string', min: 1, max: 128, optional: true }, + device_key: { type: 'string', min: 1, max: 128, optional: true }, + }, + }); + + this.addSchema('leaveEnclave', { + value: { + $$strict: true, + $$type: 'object', + op: { type: 'string', min: 1, max: 64 }, + enclave_id: { type: 'string', min: 1, max: 128 }, + }, + }); + + this.addSchema('joinRoom', { + value: { + $$strict: true, + $$type: 'object', + op: { type: 'string', min: 1, max: 64 }, + room_id: { type: 'string', min: 1, max: 128 }, + enclave_id: { type: 'string', min: 1, max: 128 }, + }, + }); + + this.addSchema('leaveRoom', { + value: { + $$strict: true, + $$type: 'object', + op: { type: 'string', min: 1, max: 64 }, + room_id: { type: 'string', min: 1, max: 128 }, + enclave_id: { type: 'string', min: 1, max: 128 }, + }, + }); + + this.addSchema('retireEnclave', { + value: { + $$strict: true, + $$type: 'object', + op: { type: 'string', min: 1, max: 64 }, + enclave_id: { type: 'string', min: 1, max: 128 }, + }, + }); + + this.addSchema('openRoom', { + value: { + $$strict: true, + $$type: 'object', + op: { type: 'string', min: 1, max: 64 }, + enclave_id: { type: 'string', min: 1, max: 128 }, + model_id: { type: 'string', min: 1, max: 256, optional: true }, + nonce: { type: 'string', min: 1, max: 128 }, + label: { type: 'string', min: 1, max: 64 }, + policy: { type: 'any' }, + }, + }); + + this.addSchema('closeRoom', { + value: { + $$strict: true, + $$type: 'object', + op: { type: 'string', min: 1, max: 64 }, + room_id: { type: 'string', min: 1, max: 128 }, + }, + }); + + this.addSchema('setPrice', { + value: { + $$strict: true, + $$type: 'object', + op: { type: 'string', min: 1, max: 64 }, + enclave_id: { type: 'string', min: 1, max: 128 }, + rate_map: { type: 'array', min: 1, max: RATE_MAP_MAX_ENTRIES, items: { type: 'any' } }, + per_req_au: { type: 'string', min: 1, max: 80 }, + min_session_au: { type: 'string', min: 1, max: 80 }, + effective_at: { type: 'number', integer: true, min: 0 }, + ctx_bracket: { type: 'string', min: 1, max: 64, optional: true }, + }, + }); + + this.addSchema('readPrice', { + value: { + $$strict: true, + $$type: 'object', + op: { type: 'string', min: 1, max: 64 }, + enclave_id: { type: 'string', min: 1, max: 128 }, + at: { type: 'number', integer: true, min: 0 }, + ctx_bracket: { type: 'string', min: 1, max: 64, optional: true }, + }, + }); + + this.addSchema('recordReputationEvent', { + value: { + $$strict: true, + $$type: 'object', + op: { type: 'string', min: 1, max: 64 }, + provider: { type: 'string', min: 1, max: 128 }, + event_id: { type: 'string', min: 1, max: 128 }, + kind: { type: 'string', min: 1, max: 64 }, + epoch: { type: 'number', integer: true, min: 0 }, + at: { type: 'number', integer: true, min: 0 }, + paid_au: { type: 'string', min: 1, max: 80, optional: true }, + max_spend_au: { type: 'string', min: 1, max: 80, optional: true }, + evidence_hash: { type: 'string', min: 1, max: 128, optional: true }, + beneficiary: { type: 'string', min: 1, max: 128, optional: true }, + enclave_id: { type: 'string', min: 1, max: 128, optional: true }, + }, + }); + + this.addSchema('anchorReputation', { + value: { + $$strict: true, + $$type: 'object', + op: { type: 'string', min: 1, max: 64 }, + provider: { type: 'string', min: 1, max: 128 }, + epoch: { type: 'number', integer: true, min: 0 }, + folded_at: { type: 'number', integer: true, min: 0 }, + events_head: { type: 'string', min: 1, max: 128 }, + r_bps: { type: 'number', integer: true, min: 0, max: 10_000 }, + raw_milli: { type: 'number', integer: true }, + successful_sessions: { type: 'number', integer: true, min: 0 }, + provenance_violation: { type: 'boolean', optional: true }, + }, + }); + + this.addSchema('auditorRegister', { + value: { + $$strict: true, + $$type: 'object', + op: { type: 'string', min: 1, max: 64 }, + auditor: { type: 'string', min: 1, max: 128, optional: true }, + registered_at_seconds: { type: 'number', integer: true, min: 0, optional: true }, + }, + }); + + this.addSchema('auditorSlash', { + value: { + $$strict: true, + $$type: 'object', + op: { type: 'string', min: 1, max: 64 }, + auditor: { type: 'string', min: 64, max: 64 }, + provider: { type: 'string', min: 64, max: 64 }, + probe_id: { type: 'string', min: 1, max: 128 }, + epoch: { type: 'number', integer: true, min: 0 }, + reason: { type: 'string', min: 1, max: 64 }, + evidence_hash: { type: 'string', min: 64, max: 64 }, + at: { type: 'number', integer: true, min: 0 }, + }, + }); + + this.addSchema('probeResult', { + value: { + $$strict: true, + $$type: 'object', + op: { type: 'string', min: 1, max: 64 }, + probe_id: { type: 'string', min: 1, max: 128 }, + probe_kind: { type: 'string', min: 1, max: 64 }, + provider: { type: 'string', min: 1, max: 128 }, + enclave_id: { type: 'string', min: 1, max: 128, optional: true }, + binary_hash: { type: 'string', min: 1, max: 128, optional: true }, + epoch: { type: 'number', integer: true, min: 0 }, + at: { type: 'number', integer: true, min: 0 }, + canary_set: { type: 'string', min: 1, max: 128, optional: true }, + canary_prompt_id: { type: 'string', min: 1, max: 128, optional: true }, + challenge_epoch: { type: 'number', integer: true, min: 0, optional: true }, + challenge_apply_hash: { type: 'string', min: 64, max: 64, optional: true }, + challenge_seed: { type: 'string', min: 64, max: 64, optional: true }, + verification_method: { type: 'string', min: 1, max: 64, optional: true }, + match_bps: { type: 'number', integer: true, min: 0, max: 10_000, optional: true }, + pass: { type: 'boolean', optional: true }, + session_receipt_hash: { type: 'string', min: 1, max: 128, optional: true }, + evidence_hash: { type: 'string', min: 1, max: 128, optional: true }, + auditor_sig: { type: 'string', min: 1, max: 128, optional: true }, + }, + }); + + this.addSchema('prepareStateCheckpoint', { + value: { + $$strict: true, $$type: 'object', + op: { type: 'string', min: 1, max: 64 }, + slot: { type: 'number', integer: true, min: 1 }, + observed_at: { type: 'number', integer: true, min: 0 }, + contract_code_sha256: { type: 'string', min: 64, max: 64 }, + }, + }); + this.addSchema('stateCheckpoint', { + value: { + $$strict: true, $$type: 'object', + op: { type: 'string', min: 1, max: 64 }, + slot: { type: 'number', integer: true, min: 1 }, + snapshot_hash: { type: 'string', min: 64, max: 64 }, + }, + }); + + this.addSchema('epochFreeze', { + value: { + $$strict: true, + $$type: 'object', + op: { type: 'string', min: 1, max: 64 }, + epoch: { type: 'number', integer: true, min: 1 }, + at: { type: 'number', integer: true, min: 0 }, + }, + }); + + this.addSchema('epochCommit', { + value: { + $$strict: true, + $$type: 'object', + op: { type: 'string', min: 1, max: 64 }, + epoch: { type: 'number', integer: true, min: 1 }, + at: { type: 'number', integer: true, min: 0 }, + roots: { type: 'any' }, + totals: { type: 'any' }, + }, + }); + + this.addSchema('epochSealEmpty', { + value: { + $$strict: true, + $$type: 'object', + op: { type: 'string', min: 1, max: 64 }, + epoch: { type: 'number', integer: true, min: 1 }, + at: { type: 'number', integer: true, min: 0 }, + reason_hash: { type: 'string', min: 64, max: 64 }, + }, + }); + + this.addSchema('fraudProof', { + value: { + $$strict: true, + $$type: 'object', + op: { type: 'string', min: 1, max: 64 }, + epoch: { type: 'number', integer: true, min: 1 }, + proof_epoch: { type: 'number', integer: true, min: 1 }, + at: { type: 'number', integer: true, min: 0 }, + reason: { type: 'string', min: 1, max: 64 }, + receipt: { type: 'any', optional: true }, + claimed_au_owed_cum: { type: 'string', min: 1, max: 80, optional: true }, + previous_au_owed_cum: { type: 'string', min: 1, max: 80, optional: true }, + price_usage: { type: 'any', optional: true }, + }, + }); + + this.addSchema('dispute', { + value: { + $$strict: true, + $$type: 'object', + op: { type: 'string', min: 1, max: 64 }, + rail: { type: 'string', min: 1, max: 16 }, + session_id: { type: 'string', min: 64, max: 64 }, + reason: { type: 'string', min: 1, max: 64 }, + provider: { type: 'string', min: 64, max: 64 }, + counterparty: { type: 'string', min: 1, max: 128, optional: true }, + enclave_id: { type: 'string', min: 64, max: 64 }, + epoch: { type: 'number', integer: true, min: 0, optional: true }, + at: { type: 'number', integer: true, min: 0 }, + evidence_hash: { type: 'string', min: 1, max: 128, optional: true }, + evidence: { type: 'any', optional: true }, + }, + }); + + this.addSchema('disputeResolve', { + value: { + $$strict: true, + $$type: 'object', + op: { type: 'string', min: 1, max: 64 }, + dispute_id: { type: 'number', integer: true, min: 1 }, + outcome: { type: 'string', min: 1, max: 64 }, + deposit_action: { type: 'string', min: 1, max: 64 }, + rationale_hash: { type: 'string', min: 1, max: 128 }, + at: { type: 'number', integer: true, min: 0 }, + slash: { type: 'boolean', optional: true }, + beneficiary: { type: 'string', min: 1, max: 128, optional: true }, + }, + }); + + this.addSchema('disputeExpire', { + value: { + $$strict: true, + $$type: 'object', + op: { type: 'string', min: 1, max: 64 }, + dispute_id: { type: 'number', integer: true, min: 1 }, + at: { type: 'number', integer: true, min: 0 }, + }, + }); + + this.addSchema('fiatDeposit', { + value: { + $$strict: true, + $$type: 'object', + op: { type: 'string', min: 1, max: 64 }, + rail: { type: 'string', min: 1, max: 64 }, + who: { type: 'string', min: 1, max: 128 }, + au: { type: 'string', min: 1, max: 80 }, + ext_ref_hash: { type: 'string', min: 1, max: 128 }, + fiat_currency: { type: 'string', min: 3, max: 3 }, + fiat_amount_minor: { type: 'number', integer: true, min: 1, max: Number.MAX_SAFE_INTEGER }, + epoch: { type: 'number', integer: true, min: 1 }, + at: { type: 'number', integer: true, min: 0 }, + }, + }); + + this.addSchema('fiatChargeback', { + value: { + $$strict: true, + $$type: 'object', + op: { type: 'string', min: 1, max: 64 }, + rail: { type: 'string', min: 1, max: 64 }, + who: { type: 'string', min: 1, max: 128 }, + au: { type: 'string', min: 1, max: 80 }, + ext_ref_hash: { type: 'string', min: 1, max: 128 }, + dispute_ref_hash: { type: 'string', min: 1, max: 128 }, + fiat_currency: { type: 'string', min: 3, max: 3 }, + fiat_amount_minor: { type: 'number', integer: true, min: 1, max: Number.MAX_SAFE_INTEGER }, + epoch: { type: 'number', integer: true, min: 1 }, + at: { type: 'number', integer: true, min: 0 }, + }, + }); + + } + + async noop() { + const result = { + ok: true, + op: 'noop', + contract: 'mayhem', + version: CONTRACT_VERSION, + address: this.address, + }; + console.log('mayhem noop', result); + return result; + } + + async gatedNoop() { + const consentError = await this.requireConsent(); + if (consentError) return consentError; + + const result = { + ok: true, + op: 'gatedNoop', + contract: 'mayhem', + version: CONTRACT_VERSION, + address: this.address, + }; + console.log('mayhem gatedNoop', result); + return result; + } + + async readKey() { + const key = this.value?.key; + const value = typeof key === 'string' ? await this.get(key) : null; + console.log('mayhem readKey', key, '=>', value); + return value; + } + + async mayhemFeature() { + this._mayhemLastFeatureResult = undefined; + const rawKey = this.op?.key; + const key = typeof rawKey === 'string' && rawKey.startsWith('mayhem_') + ? rawKey.slice('mayhem_'.length) + : rawKey; + const value = this.value; + if (typeof key !== 'string' || !value || typeof value !== 'object' || Array.isArray(value)) { + return; + } + if (value.op === 'deposit_tnk') { + const result = await this.applyDepositTnkFeature(key, value); + this._mayhemLastFeatureResult = result; + return result; + } + if (value.op === 'consent') { + const result = await this.applyConsentFeature(key, value); + this._mayhemLastFeatureResult = result; + return result; + } + if (value.op === 'provider_lifecycle') { + const result = await this.applyProviderLifecycleFeature(key, value); + this._mayhemLastFeatureResult = result; + return result; + } + if (value.op === 'bind_provider_payout') { + const result = await this.applyProviderPayoutBindingFeature(key, value); + this._mayhemLastFeatureResult = result; + return result; + } + if (value.op === 'spend_reserve_targeted') { + const result = await this.applyTargetedSpendReserveFeature(key, value); + this._mayhemLastFeatureResult = result; + return result; + } + if (value.op === 'record_usage_receipt') { + const result = await this.applyRecordUsageReceiptFeature(key, value); + this._mayhemLastFeatureResult = result; + return result; + } + if (value.op === 'close_usage_reservation') { + const result = await this.applyCloseUsageReservationFeature(key, value); + this._mayhemLastFeatureResult = result; + return result; + } + if (value.op === 'expire_usage_reservation') { + const result = await this.applyCloseUsageReservationFeature(key, value, { + expiry: true, + }); + this._mayhemLastFeatureResult = result; + return result; + } + if (value.op === 'tap_account_bind') { + const result = await this.applyTapAccountBindingFeature(key, value); + this._mayhemLastFeatureResult = result; + return result; + } + const isRateFeature = value.op === 'rate_oracle' || value.op === 'tap_rate_oracle'; + if (isRateFeature) this._mayhemApplyStage = 'rate:require-admin'; + const adminError = await this.requireAdmin(this.address); + if (adminError) { + if (isRateFeature) this._mayhemApplyStage = null; + this._mayhemLastFeatureResult = adminError; + return adminError; + } + if (isRateFeature) this._mayhemApplyStage = 'rate:admin-verified'; + if (value.op === 'admin_contract_tx') { + const result = await this.applyAdminContractTxFeature(key, value); + this._mayhemLastFeatureResult = result; + return result; + } + if (value.op === 'publish_payout_context') { + const result = await this.applyPublishPayoutContextFeature(key, value); + this._mayhemLastFeatureResult = result; + return result; + } + if (value.op === 'schedule_payout_parameter') { + const result = await this.applySchedulePayoutParameterFeature(key, value); + this._mayhemLastFeatureResult = result; + return result; + } + if (value.op === 'verify_stripe_payout') { + const result = await this.applyVerifyStripePayoutFeature(key, value); + this._mayhemLastFeatureResult = result; + return result; + } + if (value.op === 'apply_targeted_epoch') { + const result = await this.applyTargetedEpochFeature(key, value); + this._mayhemLastFeatureResult = result; + return result; + } + if (value.op === 'commit_apply_targeted_epoch_page0') { + const result = await this.applyCommitTargetedEpochPageZeroFeature(key, value); + this._mayhemLastFeatureResult = result; + return result; + } + if (['tnk_deposit', 'tap_deposit', 'tap_deposit_reversal', 'fiat_deposit', 'fiat_chargeback'].includes(value.op)) { + const result = await this.applyDepositCreditFeature(key, value); + this._mayhemLastFeatureResult = result; + return result; + } + if (value.op === 'rate_oracle' || value.op === 'tap_rate_oracle') { + const result = await this.applyRateOracleFeature(key, value); + this._mayhemApplyStage = null; + this._mayhemLastFeatureResult = result; + return result; + } + if (value.op === 'prepare_targeted_payout') { + const result = await this.applyTargetedPayoutPreparationFeature(key, value); + this._mayhemLastFeatureResult = result; + return result; + } + if (value.op === 'prepare_targeted_payout_epoch') { + const result = await this.applyTargetedPayoutEpochFeature(key, value); + this._mayhemLastFeatureResult = result; + return result; + } + if (value.op === 'prepare_targeted_fiat_attempt') { + const result = await this.applyTargetedFiatAttemptFeature(key, value); + this._mayhemLastFeatureResult = result; + return result; + } + if (value.op === 'finalize_targeted_fiat_attempt') { + const result = await this.applyFinalizeTargetedFiatAttemptFeature(key, value); + this._mayhemLastFeatureResult = result; + return result; + } + if (value.op === 'settle_targeted_tap') { + const result = await this.applyTargetedTapSettlementFeature(key, value); + this._mayhemLastFeatureResult = result; + return result; + } + if (value.op === 'settle_targeted_tnk_output') { + const result = await this.applyTargetedTnkOutputFeature(key, value); + this._mayhemLastFeatureResult = result; + return result; + } + if (value.op === 'settle_targeted_fiat_output') { + const result = await this.applyTargetedFiatOutputFeature(key, value); + this._mayhemLastFeatureResult = result; + return result; + } + if (value.op === 'close_targeted_payout_epoch') { + const result = await this.applyCloseTargetedPayoutEpochFeature(key, value); + this._mayhemLastFeatureResult = result; + return result; + } + if (value.op === 'fiat_dust_sweep') { + const result = await this.applyFiatDustSweepFeature(key, value); + this._mayhemLastFeatureResult = result; + return result; + } + if (value.op === 'anchor_reputation') { + const result = await this.applyReputationAnchorFeature(key, value); + this._mayhemLastFeatureResult = result; + return result; + } + if (value.op === 'tier3_bless_measurement') { + const result = await this.applyTier3MeasurementFeature(key, value); + this._mayhemLastFeatureResult = result; + return result; + } + } + + async recordMayhemFeatureResult(result) { + const hash = String(this.op?.hash ?? '').toLowerCase(); + if (!/^[0-9a-f]+$/.test(hash)) return; + const error = result instanceof Error + ? result + : result === undefined + ? new Error('Feature returned no result.') + : result?.ok === false + ? new Error(String(result?.error?.message ?? result?.message ?? 'Feature rejected.')) + : null; + const ok = error === null; + await this.put(`fr/${hash}`, { + type: 'feature_result', + feature_key: this.op?.key ?? null, + hash, + address: this.address ?? null, + status: ok ? 'applied' : 'rejected', + ok, + result: ok ? serializableFeatureResult(result) : null, + error: ok + ? null + : { + name: error.name || 'FeatureRejected', + message: error.message || 'Feature rejected.', + }, + }); + } + + async applyAdminContractTxFeature(key, value) { + const shapeError = this.validateExactObjectKeys( + value, + ['op', 'tx', 'prepared_command', 'address', 'signature', 'nonce', 'sim', 'context'], + 'admin contract transaction feature' + ); + if (shapeError) return shapeError; + if ( + value.op !== 'admin_contract_tx' || + !this.isHexBytes(value.tx, 32) || + !this.isHexBytes(value.address, 32) || + !this.isHexBytes(value.signature, 64) || + !this.isHexBytes(value.nonce, 32) || + typeof value.sim !== 'boolean' || + !value.prepared_command || + typeof value.prepared_command !== 'object' || + Array.isArray(value.prepared_command) || + !value.context || + typeof value.context !== 'object' || + Array.isArray(value.context) + ) { + return new Error('Invalid admin contract transaction feature.'); + } + if (value.sim) { + return new Error('Simulated admin transactions must not be appended.'); + } + if (key !== `admin/contract-tx/${value.tx}`) { + return new Error('Admin contract transaction key does not match its digest.'); + } + const commandShapeError = this.validateExactObjectKeys( + value.prepared_command, + ['type', 'value'], + 'prepared admin command' + ); + if (commandShapeError) return commandShapeError; + const contextShapeError = this.validateExactObjectKeys( + value.context, + ['contract_version', 'msb_bootstrap', 'network_id', 'subnet_bootstrap'], + 'admin contract transaction context' + ); + if (contextShapeError) return contextShapeError; + const peer = this.protocol?.peer; + const configuredBootstrap = peer?.config?.bootstrap; + const subnetBootstrap = b4a.isBuffer(configuredBootstrap) + ? b4a.toString(configuredBootstrap, 'hex') + : typeof configuredBootstrap === 'string' && configuredBootstrap + ? configuredBootstrap.toLowerCase() + : b4a.isBuffer(peer?.base?.key) + ? b4a.toString(peer.base.key, 'hex') + : ''; + const runtimeContext = { + contract_version: CONTRACT_VERSION, + msb_bootstrap: String(peer?.msbClient?.bootstrapHex ?? '').toLowerCase(), + network_id: peer?.msbClient?.networkId, + subnet_bootstrap: subnetBootstrap, + }; + if ( + !Number.isSafeInteger(value.context.contract_version) || + !Number.isSafeInteger(value.context.network_id) || + !this.isHexBytes(value.context.msb_bootstrap, 32) || + !this.isHexBytes(value.context.subnet_bootstrap, 32) || + stableJson(value.context) !== stableJson(runtimeContext) + ) { + return new Error('Admin contract transaction context does not match this contract network.'); + } + const adminError = await this.requireAdmin(value.address); + if (adminError) return adminError; + const expectedTx = await adminContractTxDigest(value); + if (expectedTx !== value.tx) { + return new Error('Invalid admin contract transaction digest.'); + } + const verify = this.protocol?.peer?.wallet?.verify; + if ( + typeof verify !== 'function' || + verify.call( + this.protocol.peer.wallet, + value.signature, + b4a.from(value.tx, 'hex'), + value.address + ) !== true + ) { + return new Error('Invalid admin contract transaction signature.'); + } + + const existing = await this.get(key); + if (existing !== null) return existing; + + const type = value.prepared_command.type; + if ( + typeof type !== 'string' || + (!hasOwn(this.metadata.schemas, type) && !hasOwn(this.metadata.functions, type)) || + typeof this[type] !== 'function' + ) { + return new Error('Admin contract transaction type is not a registered Mayhem command.'); + } + if ( + hasOwn(this.metadata.schemas, type) && + this.check.validateSchema(type, value.prepared_command) !== true + ) { + return new Error('Invalid prepared admin command schema.'); + } + + const applyingRecord = { + ok: false, + op: 'admin_contract_tx', + status: 'applying', + tx: value.tx, + type, + result: null, + error: null, + }; + await this.put(key, applyingRecord); + + const context = { + address: this.address, + isFeature: this.is_feature, + op: this.op, + tx: this.tx, + value: this.value, + }; + let commandResult; + try { + this.address = value.address; + this.is_feature = false; + this.op = value.prepared_command; + this.tx = value.tx; + this.value = value.prepared_command.value; + commandResult = await this[type](); + } catch (error) { + commandResult = error instanceof Error ? error : new Error(String(error)); + } finally { + this.address = context.address; + this.is_feature = context.isFeature; + this.op = context.op; + this.tx = context.tx; + this.value = context.value; + } + const commandError = commandResult === undefined + ? new Error('Admin contract transaction returned no result.') + : commandResult instanceof Error + ? commandResult + : commandResult?.ok === false + ? new Error( + String( + commandResult?.error?.message ?? + commandResult?.message ?? + 'Admin command rejected.' + ) + ) + : null; + if (commandError) { + await this.put(key, { + ...applyingRecord, + status: 'rejected', + error: serializableFeatureResult(commandError), + }); + return commandError; + } + + const record = { + ok: true, + op: 'admin_contract_tx', + status: 'applied', + tx: value.tx, + type, + result: serializableFeatureResult(commandResult), + error: null, + }; + await this.put(key, record); + return record; + } + + async applyConsentFeature(key, value) { + const shapeError = this.validateExactObjectKeys( + value, + ['op', 'sender', 'ver', 'hash', 'sig'], + 'consent feature' + ); + if (shapeError) return; + if (!this.isHexBytes(value.sender, 32)) return; + if (!this.isHexBytes(value.sig, 64)) return; + const rules = await this.currentRules(); + if (!rules || value.ver !== rules.ver || value.hash !== rules.hash) return; + if (key !== `consent/${value.sender}/${value.ver}/${value.hash}`) return; + if (!this.verifyConsentSignature(value.sender, value.ver, value.hash, value.sig)) return; + + const record = { + ver: value.ver, + hash: value.hash, + at: key, + via: 'feature', + }; + await this.put(`consent/${value.sender}`, record); + console.log('mayhem consent feature', { address: value.sender, ...record }); + return { ok: true, op: 'consentFeature', address: value.sender, ...record }; + } + + async applyTapAccountBindingFeature(key, value) { + const normalized = this.normalizeTapAccountBinding(value); + if (normalized instanceof Error) return normalized; + const expectedKey = await this.tapAccountBindingFeatureKey(normalized); + if (expectedKey instanceof Error) return expectedKey; + if (key !== expectedKey) return new Error('Invalid TAP account binding key.'); + + const consentError = await this.requireConsent(normalized.user); + if (consentError) return consentError; + if (!this.verifyTapAccountUserSignature(normalized)) { + return new Error('Invalid TAP account user signature.'); + } + if (!this.verifyTapAccountEthereumSignature(normalized)) { + return new Error('Invalid TAP account Ethereum signature.'); + } + const poolError = await this.requireCanonicalTapPool( + normalized.chain_id, + normalized.pool_address + ); + if (poolError) return poolError; + + const bindingKey = this.tapAccountBindingKey( + normalized.user, + normalized.chain_id, + normalized.pool_address + ); + const addressKey = this.tapAccountAddressKey( + normalized.ethereum_address, + normalized.chain_id, + normalized.pool_address + ); + const existingBinding = await this.get(bindingKey); + if (existingBinding && existingBinding.ethereum_address !== normalized.ethereum_address) { + return new Error('Mayhem wallet is already bound to a different TAP account.'); + } + const existingAddress = await this.get(addressKey); + if (existingAddress && existingAddress.user !== normalized.user) { + return new Error('TAP account is already bound to a different Mayhem wallet.'); + } + + const source = await this.balanceRecord(normalized.ethereum_address, 'tap'); + if (source instanceof Error) return source; + const sourceError = this.guardianValidateBalanceRecord( + source, + normalized.ethereum_address, + 'tap' + ); + if (sourceError) return sourceError; + const target = await this.balanceRecord(normalized.user, 'tap'); + if (target instanceof Error) return target; + const targetError = this.guardianValidateBalanceRecord(target, normalized.user, 'tap'); + if (targetError) return targetError; + const nextAu = this.safeAddAu(target.au, source.au); + if (nextAu instanceof Error) return nextAu; + + const record = { + type: 'tap_account_binding', + user: normalized.user, + ethereum_address: normalized.ethereum_address, + chain_id: normalized.chain_id, + pool_address: normalized.pool_address, + user_sig: normalized.user_sig, + ethereum_sig: normalized.ethereum_sig, + status: 'active', + bound_at: key, + }; + await this.put(bindingKey, record); + await this.put(addressKey, record); + + await this.put(this.balanceKey(normalized.user, 'tap'), { + ...target, + user: normalized.user, + rail: 'tap', + au: nextAu, + updated_epoch: Math.max(target.updated_epoch, source.updated_epoch), + updated_at: key, + ...(!this.isZeroAu(source.au) ? { + last_tap_account_claim_au: source.au, + last_tap_account_claim_from: normalized.ethereum_address, + } : {}), + }); + await this.put(this.balanceKey(normalized.ethereum_address, 'tap'), { + ...source, + au: ZERO_AU, + updated_at: key, + tap_account_bound_to: normalized.user, + tap_account_binding_key: bindingKey, + }); + + return { + ok: true, + op: 'tapAccountBind', + user: normalized.user, + ethereum_address: normalized.ethereum_address, + chain_id: normalized.chain_id, + pool_address: normalized.pool_address, + claimed_au: source.au, + balance_au: nextAu, + idempotent: existingBinding !== null && existingAddress !== null && this.isZeroAu(source.au), + }; + } + + async applyProviderLifecycleFeature(key, value) { + const shapeError = this.validateExactObjectKeys( + value, + ['op', 'intent', 'sig'], + 'provider lifecycle feature' + ); + if (shapeError) return; + const intent = value.intent; + if (!intent || typeof intent !== 'object' || Array.isArray(intent)) return; + const intentError = this.validateProviderLifecycleIntent(intent); + if (intentError) return; + if (!this.isHexBytes(value.sig, 64)) return; + if (!(await this.providerLifecycleFeatureKeys(intent)).includes(key)) return; + if (!this.verifyProviderLifecycleSignature(intent.provider, intent, value.sig)) return; + + switch (intent.op) { + case 'register_provider': + return await this.applyRegisterProvider(intent.provider, key); + case 'join_enclave': + return await this.applyJoinEnclave( + intent.provider, + intent.enclave_id, + key, + intent.att_tier, + intent.attestation_head, + intent.hardware_fingerprint ?? null, + intent.device_key ?? null, + { + served_ctx: intent.served_ctx, + served_modalities: intent.served_modalities, + served_specialities: intent.served_specialities, + ctx_bracket: intent.ctx_bracket, + ctx_bracket_table_ver: intent.ctx_bracket_table_ver, + } + ); + case 'leave_enclave': + return await this.applyLeaveEnclave(intent.provider, intent.enclave_id, key); + case 'join_room': + return await this.applyJoinRoom(intent.provider, intent.room_id, intent.enclave_id, key); + case 'leave_room': + return await this.applyLeaveRoom(intent.provider, intent.room_id, intent.enclave_id, key); + case 'set_provider_rails': + return await this.applySetProviderRails(intent.provider, intent.rails, key); + default: + return; + } + } + + async applyProviderPayoutBindingFeature(key, value) { + const shapeError = this.validateExactObjectKeys( + value, + ['op', 'intent', 'provider_signature'], + 'provider payout binding feature' + ); + if (shapeError) return shapeError; + if (value.op !== 'bind_provider_payout') { + return new Error('Invalid provider payout binding feature op.'); + } + const intentError = await this.validateProviderPayoutBindingIntent( + value.intent, + { currentState: false } + ); + if (intentError) return intentError; + if (!this.isHexBytes(value.provider_signature, 64)) { + return new Error('Invalid provider payout binding signature.'); + } + + const revision = await this.providerPayoutBindingRevision(value.intent); + const expectedKey = this.providerPayoutBindingFeatureKey( + value.intent.rail, + value.intent.provider, + revision + ); + if (key !== expectedKey) return new Error('Invalid provider payout binding key.'); + if (!this.verifyProviderPayoutBindingSignature( + value.intent.provider, + value.intent, + value.provider_signature + )) { + return new Error('Invalid provider payout binding signature.'); + } + if (!this.verifyProviderPayoutTargetBindingSignature(value.intent)) { + return new Error('Invalid provider payout target ownership signature.'); + } + const adminError = await this.requireAdmin(this.address); + if (adminError) return adminError; + + const nonceKey = this.providerPayoutBindingNonceKey( + value.intent.provider, + value.intent.nonce + ); + const nonceRecord = await this.get(nonceKey); + if (nonceRecord) { + if (nonceRecord.revision !== revision) { + return new Error('Provider payout binding nonce already consumed.'); + } + const existing = await this.get(expectedKey); + if (!existing || + existing.type !== 'provider_payout_binding' || + existing.revision !== revision || + existing.provider !== value.intent.provider || + existing.rail !== value.intent.rail || + existing.nonce !== value.intent.nonce || + existing.provider_signature !== value.provider_signature || + existing.target_signature !== value.intent.target_signature) { + return new Error('Provider payout binding nonce record is inconsistent.'); + } + return { + ok: true, + op: 'bindProviderPayout', + provider: value.intent.provider, + rail: value.intent.rail, + revision, + activation_epoch: existing.activation_epoch, + idempotent: true, + }; + } + + const currentIntentError = await this.validateProviderPayoutBindingIntent(value.intent); + if (currentIntentError) return currentIntentError; + const provider = await this.get(`prov/${value.intent.provider}`); + if (!provider || provider.status !== 'active') { + return new Error('Active provider registration required.'); + } + if (!Array.isArray(provider.accepted_rails) || + !provider.accepted_rails.includes(value.intent.rail)) { + return new Error('Provider does not accept payout rail.'); + } + + const applyState = await this.epochApplyStateRecord(); + if ((applyState.pending_epoch ?? null) !== null) { + return new Error('Provider payout binding cannot rotate during a paged epoch apply.'); + } + if (value.intent.expires_after_epoch <= applyState.updated_epoch) { + return new Error('Provider payout binding intent expired.'); + } + const payoutParams = await this.activePayoutParamsAtEpoch(applyState.updated_epoch); + if (payoutParams instanceof Error) return payoutParams; + if (value.intent.expires_after_epoch - applyState.updated_epoch > + payoutParams.payout_intent_max_expiry_epochs) { + return new Error('Provider payout binding expiry is too far in the future.'); + } + + const context = await this.providerPayoutBindingContext(value.intent); + if (context instanceof Error) return context; + const currentContext = await this.get(PROVIDER_PAYOUT_BINDING_CONTEXT_CURRENT_KEY); + if (!currentContext) { + return new Error('Canonical provider payout context is not published.'); + } + const immutableContext = await this.get( + this.providerPayoutContextRecordKey( + value.intent.payment_config_version, + value.intent.context_revision + ) + ); + if (!immutableContext || immutableContext.revision !== value.intent.context_revision) { + return new Error('Referenced immutable provider payout context is not published.'); + } + if ( + currentContext.revision !== context.context_revision || + immutableContext.network !== context.network || + immutableContext.admin !== context.admin || + immutableContext.bootstrap !== context.bootstrap || + immutableContext.payment_config_version !== context.payment_config_version + ) { + return new Error('Provider payout binding canonical context mismatch.'); + } + + const pointerKey = this.providerPayoutBindingPointerKey( + value.intent.provider, + value.intent.rail + ); + const storedPointer = await this.get(pointerKey); + const activeBillingEpoch = applyState.updated_epoch + 1; + const pointer = storedPointer?.pending_revision !== null && + storedPointer?.pending_revision !== undefined && + storedPointer.pending_activation_epoch <= activeBillingEpoch + ? { + ...storedPointer, + current_revision: storedPointer.pending_revision, + pending_revision: null, + pending_activation_epoch: null, + } + : storedPointer; + const latestRevision = pointer?.latest_revision ?? null; + if (value.intent.previous_revision !== latestRevision) { + return new Error('Provider payout binding revision is stale.'); + } + if ((await this.get(expectedKey)) !== null) { + return new Error('Provider payout binding revision already exists.'); + } + + let stripeVerification = null; + if (value.intent.rail === 'fiat') { + stripeVerification = await this.providerStripePayoutVerificationForTarget( + value.intent.provider, + value.intent.target + ); + if (!stripeVerification || + stripeVerification.target !== value.intent.target || + stripeVerification.currency !== value.intent.currency || + stripeVerification.ready !== true) { + return new Error('Current ready provider-scoped Stripe verification required.'); + } + } + + const firstBinding = latestRevision === null; + const activationEpoch = applyState.updated_epoch + (firstBinding ? 1 : 2); + const binding = { + type: 'provider_payout_binding', + revision, + provider: value.intent.provider, + rail: value.intent.rail, + target: value.intent.target, + target_wallet: value.intent.target_wallet, + currency: value.intent.currency, + chain_id: value.intent.chain_id, + stripe_processor_revision: stripeVerification?.processor_revision ?? null, + stripe_verification_revision: stripeVerification?.revision ?? null, + network: value.intent.network, + admin: value.intent.admin, + bootstrap: value.intent.bootstrap, + context_revision: value.intent.context_revision, + payment_config_version: value.intent.payment_config_version, + previous_revision: value.intent.previous_revision, + nonce: value.intent.nonce, + expires_after_epoch: value.intent.expires_after_epoch, + activation_epoch: activationEpoch, + target_signature: value.intent.target_signature, + provider_signature: value.provider_signature, + verified: true, + bound_at: key, + bound_by: this.address, + bound_by_role: 'admin', + }; + const nextPointer = { + provider: value.intent.provider, + rail: value.intent.rail, + latest_revision: revision, + current_revision: firstBinding ? revision : pointer.current_revision, + pending_revision: firstBinding ? null : revision, + pending_activation_epoch: firstBinding ? null : activationEpoch, + updated_at: key, + }; + await this.put(expectedKey, binding); + await this.put(pointerKey, nextPointer); + await this.put(nonceKey, { + provider: value.intent.provider, + rail: value.intent.rail, + nonce: value.intent.nonce, + revision, + expires_after_epoch: value.intent.expires_after_epoch, + consumed_at: key, + }); + return { + ok: true, + op: 'bindProviderPayout', + provider: value.intent.provider, + rail: value.intent.rail, + revision, + activation_epoch: activationEpoch, + idempotent: false, + }; + } + + async applyPublishPayoutContextFeature(key, value) { + const shapeError = this.validateExactObjectKeys( + value, + [ + 'op', + 'network', + 'admin', + 'bootstrap', + 'payment_config_version', + 'payment_config_hash', + ], + 'provider payout context feature' + ); + if (shapeError) return shapeError; + if (value.op !== 'publish_payout_context') { + return new Error('Invalid provider payout context feature op.'); + } + if (!this.isSafeKeyPart(value.network) || + !this.isHexBytes(value.admin, 32) || + value.admin !== value.admin.toLowerCase() || + !this.isHexBytes(value.bootstrap, 32) || + value.bootstrap !== value.bootstrap.toLowerCase() || + !Number.isSafeInteger(value.payment_config_version) || + value.payment_config_version < 1 || + !this.isHexBytes(value.payment_config_hash, 32) || + value.payment_config_hash !== value.payment_config_hash.toLowerCase()) { + return new Error('Invalid provider payout context.'); + } + const admin = await this.get('admin'); + if (value.admin !== admin || this.address !== admin) { + return new Error('Provider payout context requires canonical admin authority.'); + } + const payments = await this.get('payments/current'); + if (!payments || payments.set_by !== admin || payments.set_by_role !== 'admin') { + return new Error('Canonical payment configuration required.'); + } + if (value.network !== payments.tnk?.network || + value.payment_config_version !== payments.ver) { + return new Error('Provider payout context does not match canonical payment configuration.'); + } + const paymentConfigHash = await this.providerPayoutPaymentConfigHash(payments); + if (value.payment_config_hash !== paymentConfigHash) { + return new Error('Provider payout context payment configuration hash mismatch.'); + } + const expectedKey = await this.providerPayoutContextFeatureKey(value); + if (key !== expectedKey) return new Error('Invalid provider payout context feature key.'); + + const record = { + type: 'provider_payout_context', + revision: await this.providerPayoutContextRevision(value), + network: value.network, + admin: value.admin, + bootstrap: value.bootstrap, + payment_config_version: value.payment_config_version, + payment_config_hash: value.payment_config_hash, + published_at: key, + published_by: this.address, + published_by_role: 'admin', + }; + const recordKey = this.providerPayoutContextRecordKey( + value.payment_config_version, + record.revision + ); + if (key !== recordKey) return new Error('Invalid provider payout context record key.'); + const existing = await this.get(recordKey); + if (existing) { + if (stableJson(existing) === stableJson(record)) { + const current = await this.get(PROVIDER_PAYOUT_BINDING_CONTEXT_CURRENT_KEY); + if (current?.revision !== record.revision) { + return new Error('Immutable provider payout context is not current.'); + } + return { + ok: true, + op: 'publishPayoutContext', + context: existing, + idempotent: true, + }; + } + return new Error('Immutable provider payout context record already exists.'); + } + const current = await this.get(PROVIDER_PAYOUT_BINDING_CONTEXT_CURRENT_KEY); + if (current && current.payment_config_version >= value.payment_config_version) { + return new Error('Provider payout context payment config version must increase.'); + } + await this.put(recordKey, record); + await this.put(PROVIDER_PAYOUT_BINDING_CONTEXT_CURRENT_KEY, { + type: 'provider_payout_context_pointer', + payment_config_version: value.payment_config_version, + revision: record.revision, + record_key: recordKey, + updated_at: key, + updated_by: this.address, + updated_by_role: 'admin', + }); + return { + ok: true, + op: 'publishPayoutContext', + context: record, + idempotent: false, + }; + } + + async applySchedulePayoutParameterFeature(key, value) { + const shapeError = this.validateExactObjectKeys( + value, + ['op', 'key', 'value', 'effective_epoch'], + 'payout parameter feature' + ); + if (shapeError) return shapeError; + if (value.op !== 'schedule_payout_parameter' || + !Object.hasOwn(PAYOUT_PARAM_DEFINITIONS, value.key)) { + return new Error('Invalid payout parameter feature.'); + } + const definition = PAYOUT_PARAM_DEFINITIONS[value.key]; + if (!Number.isSafeInteger(value.value) || + value.value < definition.min || + value.value > definition.max || + !Number.isSafeInteger(value.effective_epoch) || + value.effective_epoch < 1) { + return new Error('Invalid payout parameter value or activation epoch.'); + } + const adminError = await this.requireAdmin(this.address); + if (adminError) return adminError; + const applyState = await this.epochApplyStateRecord(); + if (value.effective_epoch <= applyState.updated_epoch) { + return new Error('Payout parameter activation epoch must be in the future.'); + } + const expectedKey = await this.payoutParameterFeatureKey(value); + if (key !== expectedKey) return new Error('Invalid payout parameter feature key.'); + + const recordKey = this.payoutParameterKey(value.key); + const schedule = await this.payoutParameterRecord(value.key); + const current = schedule.pending && + schedule.pending.effective_epoch <= applyState.updated_epoch + ? schedule.pending + : schedule.current; + const pending = schedule.pending && + schedule.pending.effective_epoch > applyState.updated_epoch + ? schedule.pending + : null; + const nextEntry = { + key: value.key, + value: value.value, + effective_epoch: value.effective_epoch, + scheduled_at: key, + scheduled_by: this.address, + scheduled_by_role: 'admin', + }; + if (pending) { + if (stableJson(pending) === stableJson(nextEntry)) { + return { + ok: true, + op: 'schedulePayoutParameter', + key: value.key, + value: value.value, + effective_epoch: value.effective_epoch, + idempotent: true, + }; + } + return new Error('A payout parameter update is already pending.'); + } + await this.put(recordKey, { key: value.key, current, pending: nextEntry }); + return { + ok: true, + op: 'schedulePayoutParameter', + key: value.key, + value: value.value, + effective_epoch: value.effective_epoch, + idempotent: false, + }; + } + + async applyVerifyStripePayoutFeature(key, value) { + const shapeError = this.validateExactObjectKeys( + value, + [ + 'op', + 'provider', + 'account_id', + 'account_type', + 'country', + 'currency', + 'mode', + 'verification_kind', + 'source_provider', + 'processor_revision', + 'previous_verification', + 'details_submitted', + 'payouts_enabled', + 'transfers_enabled', + 'network', + 'admin', + 'bootstrap', + 'context_revision', + 'payment_config_version', + 'request_nonce', + ], + 'Stripe payout verification feature' + ); + if (shapeError) return shapeError; + if (value.op !== 'verify_stripe_payout' || + !this.isHexBytes(value.provider, 32) || + value.provider !== value.provider.toLowerCase() || + typeof value.account_id !== 'string' || + !/^acct_[A-Za-z0-9._-]+$/.test(value.account_id) || + !['express', 'standard', 'custom'].includes(value.account_type) || + typeof value.country !== 'string' || + !/^[A-Z]{2}$/.test(value.country) || + !['adopt', 'onboard', 'status', 'relink'].includes(value.verification_kind) || + (value.verification_kind === 'adopt' && value.account_type !== 'standard') || + (value.source_provider !== null && + (!this.isHexBytes(value.source_provider, 32) || + value.source_provider !== value.source_provider.toLowerCase())) || + (value.verification_kind === 'relink' && + (value.source_provider === null || value.source_provider === value.provider)) || + (value.verification_kind !== 'relink' && value.source_provider !== null) || + !this.isHexBytes(value.processor_revision, 32) || + value.processor_revision !== value.processor_revision.toLowerCase() || + (value.previous_verification !== null && + (!this.isHexBytes(value.previous_verification, 32) || + value.previous_verification !== value.previous_verification.toLowerCase())) || + !['live', 'test'].includes(value.mode) || + typeof value.details_submitted !== 'boolean' || + typeof value.payouts_enabled !== 'boolean' || + typeof value.transfers_enabled !== 'boolean' || + !this.isHexBytes(value.admin, 32) || + value.admin !== value.admin.toLowerCase() || + !this.isHexBytes(value.bootstrap, 32) || + value.bootstrap !== value.bootstrap.toLowerCase() || + !this.isHexBytes(value.context_revision, 32) || + value.context_revision !== value.context_revision.toLowerCase() || + !Number.isSafeInteger(value.payment_config_version) || + value.payment_config_version < 1 || + !this.isHexBytes(value.request_nonce, 32) || + value.request_nonce !== value.request_nonce.toLowerCase()) { + return new Error('Invalid Stripe payout verification.'); + } + const processorRevision = await stripePayoutProcessorRevision(value); + if (value.processor_revision !== processorRevision) { + return new Error('Stripe payout processor evidence revision mismatch.'); + } + const adminError = await this.requireAdmin(this.address); + if (adminError) return adminError; + const admin = await this.get('admin'); + if (value.admin !== admin) { + return new Error('Stripe payout verification admin is not canonical.'); + } + const expectedKey = await stripePayoutVerificationFeatureKey(value); + if (key !== expectedKey) return new Error('Invalid Stripe payout verification key.'); + const record = { + type: 'stripe_payout_verification', + revision: key.split('/').at(-1), + provider: value.provider, + target: value.account_id, + account_type: value.account_type, + country: value.country, + currency: value.currency, + mode: value.mode, + verification_kind: value.verification_kind, + source_provider: value.source_provider, + processor_revision: value.processor_revision, + previous_verification: value.previous_verification, + details_submitted: value.details_submitted, + payouts_enabled: value.payouts_enabled, + transfers_enabled: value.transfers_enabled, + ready: value.details_submitted && + value.payouts_enabled && + value.transfers_enabled, + network: value.network, + admin: value.admin, + bootstrap: value.bootstrap, + context_revision: value.context_revision, + payment_config_version: value.payment_config_version, + request_nonce: value.request_nonce, + verified_at: key, + verified_by: this.address, + verified_by_role: 'admin', + }; + const existing = await this.get(key); + if (existing) { + if (stableJson(existing) !== stableJson(record)) { + return new Error('Stripe payout verification record already exists.'); + } + return { + ok: true, + op: 'verifyStripePayout', + provider: value.provider, + revision: record.revision, + idempotent: true, + }; + } + const nonceKey = `payout/stripe-verified/nonce/${value.provider}/${value.request_nonce}`; + const nonceRecord = await this.get(nonceKey); + if (nonceRecord) { + return new Error('Stripe payout verification request nonce already consumed.'); + } + const currentPointer = await this.get( + `payout/stripe-verified/current/${value.provider}` + ); + if ((currentPointer?.revision ?? null) !== value.previous_verification) { + return new Error('Stripe payout verification revision is stale.'); + } + + const contextPointer = await this.get(PROVIDER_PAYOUT_BINDING_CONTEXT_CURRENT_KEY); + const context = await this.get( + this.providerPayoutContextRecordKey( + value.payment_config_version, + value.context_revision + ) + ); + if (!contextPointer || + contextPointer.revision !== value.context_revision || + !context || + context.network !== value.network || + context.admin !== value.admin || + context.bootstrap !== value.bootstrap || + context.payment_config_version !== value.payment_config_version) { + return new Error('Stripe payout verification context is not current.'); + } + if ((value.network === 'mainnet' && value.mode !== 'live') || + (value.network !== 'mainnet' && value.mode !== 'test')) { + return new Error('Stripe payout verification mode does not match canonical network.'); + } + const payments = await this.get('payments/current'); + if (!payments || + payments.ver !== value.payment_config_version || + payments.set_by !== admin || + payments.set_by_role !== 'admin' || + payments.fiat?.processor !== 'stripe' || + !Array.isArray(payments.fiat.payout_currencies) || + !payments.fiat.payout_currencies.includes(value.currency)) { + return new Error('Stripe payout verification currency is not canonical.'); + } + const provider = await this.get(`prov/${value.provider}`); + if (!provider || provider.status !== 'active' || + !Array.isArray(provider.accepted_rails) || + !provider.accepted_rails.includes('fiat')) { + return new Error('Stripe payout verification requires an active fiat provider.'); + } + await this.put(key, record); + await this.put(nonceKey, { + provider: value.provider, + request_nonce: value.request_nonce, + processor_revision: value.processor_revision, + revision: record.revision, + record_key: key, + consumed_at: key, + }); + const verificationPointer = { + provider: value.provider, + revision: record.revision, + record_key: key, + target: value.account_id, + currency: value.currency, + processor_revision: value.processor_revision, + ready: record.ready, + details_submitted: value.details_submitted, + payouts_enabled: value.payouts_enabled, + transfers_enabled: value.transfers_enabled, + context_revision: value.context_revision, + payment_config_version: value.payment_config_version, + updated_at: key, + updated_by: this.address, + updated_by_role: 'admin', + }; + await this.put(`payout/stripe-verified/current/${value.provider}`, verificationPointer); + await this.put( + this.providerStripePayoutVerificationTargetKey(value.provider, value.account_id), + verificationPointer + ); + return { + ok: true, + op: 'verifyStripePayout', + provider: value.provider, + revision: record.revision, + idempotent: false, + }; + } + + async applySpendReserveFeature(key, value) { + const normalized = await this.normalizeSpendReserveValue(value); + if (normalized instanceof Error) return normalized; + normalized.voucher_hash = await this.opaqueHash('mayhem-spend-voucher-record-v1', normalized.voucher_body); + const expectedKey = await this.spendReservationFeatureKey(normalized); + if (expectedKey instanceof Error) return expectedKey; + if (key !== expectedKey) return; + if (!this.verifySpendVoucherSignature(normalized.user, normalized.voucher_body, normalized.voucher.user_sig)) { + return new Error('Invalid spend voucher signature.'); + } + if (!this.verifySpendReservationSignature(normalized.provider, normalized)) { + return new Error('Invalid spend reservation provider signature.'); + } + + const applyState = await this.epochApplyStateRecord(); + const activeEpoch = (applyState.pending_epoch ?? applyState.updated_epoch) + 1; + if (normalized.epoch !== activeEpoch) { + return new Error('Spend reservation epoch is not the active billing epoch.'); + } + + const provider = await this.get(`prov/${normalized.provider}`); + if (!provider || provider.status !== 'active') return new Error('Provider registration required.'); + if (!Array.isArray(provider.accepted_rails) || !provider.accepted_rails.includes(normalized.rail)) { + return new Error('Provider does not accept payment rail.'); + } + const enclave = await this.get(`enclave/${normalized.enclave_id}`); + if (!enclave || enclave.status !== 'active') return new Error('Admin enclave is not active.'); + const enclaveError = this.requireAdminCreatedEnclave(enclave); + if (enclaveError) return enclaveError; + const serve = await this.get(`serve/${normalized.provider}/${normalized.enclave_id}`); + if (!serve || serve.status !== 'active') { + return new Error('Provider is not actively serving this admin enclave.'); + } + const serveTermsError = this.validateCommittedServeTerms(serve, normalized); + if (serveTermsError) return serveTermsError; + const priceCtxBracket = normalized.ctx_bracket ?? null; + const priceError = await this.requireCurrentAdminPrice(normalized.enclave_id, priceCtxBracket); + if (priceError) return priceError; + const lockedPrice = await this.get( + this.priceRecordKey(normalized.enclave_id, normalized.price_ver, priceCtxBracket) + ); + if (!lockedPrice || lockedPrice.denom !== PRICE_DENOMINATION) { + return new Error('Spend reservation locked price version is not known.'); + } + if (lockedPrice.set_by_role !== 'admin') { + return new Error('Spend reservation locked price is not admin-set.'); + } + if ((lockedPrice.ctx_bracket ?? null) !== priceCtxBracket) { + return new Error('Spend reservation locked price context bracket mismatch.'); + } + if ((lockedPrice.ctx_bracket_table_ver ?? null) !== (normalized.ctx_bracket_table_ver ?? null)) { + return new Error('Spend reservation locked price context bracket table mismatch.'); + } + if (stableJson(normalized.locked_rate_map) !== stableJson(lockedPrice.rate_map)) { + return new Error('Spend reservation locked rate_map does not match price version.'); + } + if (this.compareAu(normalized.locked_per_req_au, lockedPrice.per_req_au ?? ZERO_AU) !== 0) { + return new Error('Spend reservation locked per_req_au does not match price version.'); + } + if (this.compareAu(normalized.locked_min_session_au, lockedPrice.min_session_au ?? ZERO_AU) !== 0) { + return new Error('Spend reservation locked min_session_au does not match price version.'); + } + const rules = await this.currentRules(); + if (!rules || rules.ver !== normalized.rules_ver) { + return new Error('Spend reservation rules version is not current.'); + } + + const balance = await this.balanceRecord(normalized.user, normalized.rail); + if (balance instanceof Error) return balance; + const balanceError = this.guardianValidateBalanceRecord(balance, normalized.user, normalized.rail); + if (balanceError) return balanceError; + + const holdKey = this.spendHoldKey(normalized.user, normalized.rail, normalized.epoch); + const hold = await this.normalizeSpendHoldRecord( + (await this.get(holdKey)) ?? null, + normalized.user, + normalized.rail, + normalized.epoch + ); + if (hold instanceof Error) return hold; + const existing = hold.sessions.find((session) => session.session_id === normalized.session_id); + if (existing) { + if ( + existing.provider !== normalized.provider || + existing.enclave_id !== normalized.enclave_id || + existing.price_ver !== normalized.price_ver || + stableJson(existing.locked_rate_map) !== stableJson(normalized.locked_rate_map) || + this.compareAu(existing.locked_per_req_au, normalized.locked_per_req_au) !== 0 || + this.compareAu(existing.locked_min_session_au, normalized.locked_min_session_au) !== 0 || + existing.served_ctx !== normalized.served_ctx || + stableJson(existing.required_modalities) !== stableJson(normalized.required_modalities) || + stableJson(existing.required_specialities) !== stableJson(normalized.required_specialities) || + stableJson(existing.workflow ?? null) !== stableJson(normalized.workflow ?? null) || + existing.ctx_bracket !== normalized.ctx_bracket || + existing.ctx_bracket_table_ver !== normalized.ctx_bracket_table_ver || + this.compareAu(existing.max_spend_au, normalized.max_spend_au) !== 0 || + existing.voucher_hash !== normalized.voucher_hash + ) { + return new Error('Spend reservation session already exists with different terms.'); + } + const availableAu = this.compareAu(hold.reserved_au, balance.au) >= 0 + ? ZERO_AU + : this.safeSubAu(balance.au, hold.reserved_au); + if (availableAu instanceof Error) return availableAu; + return { + ok: true, + op: 'spendReserve', + session_id: normalized.session_id, + epoch: normalized.epoch, + rail: normalized.rail, + user: normalized.user, + provider: normalized.provider, + reserved_au: hold.reserved_au, + available_au: availableAu, + idempotent: true, + }; + } + + const nextReservedAu = this.safeAddAu(hold.reserved_au, normalized.max_spend_au); + if (nextReservedAu instanceof Error) return nextReservedAu; + if (this.compareAu(nextReservedAu, balance.au) > 0) { + return new Error('Insufficient unreserved credit balance.'); + } + const session = { + session_id: normalized.session_id, + provider: normalized.provider, + enclave_id: normalized.enclave_id, + price_ver: normalized.price_ver, + locked_rate_map: normalized.locked_rate_map, + locked_per_req_au: normalized.locked_per_req_au, + locked_min_session_au: normalized.locked_min_session_au, + served_ctx: normalized.served_ctx, + required_modalities: normalized.required_modalities.slice(), + required_specialities: cloneValue(normalized.required_specialities), + ...(normalized.workflow ? { workflow: cloneValue(normalized.workflow) } : {}), + ctx_bracket: normalized.ctx_bracket, + ctx_bracket_table_ver: normalized.ctx_bracket_table_ver, + rules_ver: normalized.rules_ver, + max_spend_au: normalized.max_spend_au, + voucher_hash: normalized.voucher_hash, + feature_key: key, + reserved_at: normalized.at, + recorded_at: this.tx, + }; + const nextHold = { + ...hold, + balance_au_at_last_reserve: balance.au, + reserved_au: nextReservedAu, + sessions: [...hold.sessions, session].sort((a, b) => compareCodepoint(a.session_id, b.session_id)), + updated_at: this.tx, + }; + await this.put(holdKey, nextHold); + const availableAu = this.safeSubAu(balance.au, nextHold.reserved_au); + if (availableAu instanceof Error) return availableAu; + return { + ok: true, + op: 'spendReserve', + session_id: normalized.session_id, + epoch: normalized.epoch, + rail: normalized.rail, + user: normalized.user, + provider: normalized.provider, + reserved_au: nextHold.reserved_au, + available_au: availableAu, + idempotent: false, + }; + } + + async applyTargetedSpendReserveFeature(key, value) { + const normalized = await this.normalizeTargetedSpendReserveValue(value); + if (normalized instanceof Error) return normalized; + normalized.voucher_hash = await this.opaqueHash( + 'mayhem-spend-voucher-record-v1', + normalized.voucher_body + ); + const binding = await this.providerPayoutBindingForEpoch( + normalized.provider, + normalized.rail, + value.payout_revision, + normalized.epoch, + { requireCurrentReadiness: true } + ); + if (binding instanceof Error) return binding; + const expectedKey = await this.targetedSpendReservationFeatureKey(value); + if (expectedKey instanceof Error) return expectedKey; + if (key !== expectedKey) return new Error('Invalid targeted spend reservation key.'); + if (!this.verifySpendVoucherSignature( + normalized.user, + normalized.voucher_body, + normalized.voucher.user_sig + )) { + return new Error('Invalid spend voucher signature.'); + } + const verify = this.protocol?.peer?.wallet?.verify; + if (typeof verify !== 'function' || + verify.call( + this.protocol.peer.wallet, + normalized.provider_sig, + targetedSpendReservationMessage({ + ...normalized, + payout_revision: value.payout_revision, + }), + normalized.provider + ) !== true) { + return new Error('Invalid targeted spend reservation provider signature.'); + } + + const applyState = await this.epochApplyStateRecord(); + const activeEpoch = (applyState.pending_epoch ?? applyState.updated_epoch) + 1; + if (normalized.epoch !== activeEpoch) { + return new Error('Spend reservation epoch is not the active billing epoch.'); + } + + const provider = await this.get(`prov/${normalized.provider}`); + if (!provider || provider.status !== 'active') return new Error('Provider registration required.'); + if (!Array.isArray(provider.accepted_rails) || !provider.accepted_rails.includes(normalized.rail)) { + return new Error('Provider does not accept payment rail.'); + } + const enclave = await this.get(`enclave/${normalized.enclave_id}`); + if (!enclave || enclave.status !== 'active') return new Error('Admin enclave is not active.'); + const enclaveError = this.requireAdminCreatedEnclave(enclave); + if (enclaveError) return enclaveError; + if (enclave.model_id !== normalized.model_id) { + return new Error('Spend reservation model does not match admin enclave.'); + } + const serve = await this.get(`serve/${normalized.provider}/${normalized.enclave_id}`); + if (!serve || serve.status !== 'active') { + return new Error('Provider is not actively serving this admin enclave.'); + } + const serveTermsError = this.validateCommittedServeTerms(serve, normalized); + if (serveTermsError) return serveTermsError; + const priceCtxBracket = normalized.ctx_bracket ?? null; + const priceError = await this.requireCurrentAdminPrice(normalized.enclave_id, priceCtxBracket); + if (priceError) return priceError; + const lockedPrice = await this.get( + this.priceRecordKey(normalized.enclave_id, normalized.price_ver, priceCtxBracket) + ); + if (!lockedPrice || lockedPrice.denom !== PRICE_DENOMINATION) { + return new Error('Spend reservation locked price version is not known.'); + } + if (lockedPrice.set_by_role !== 'admin') { + return new Error('Spend reservation locked price is not admin-set.'); + } + if ((lockedPrice.ctx_bracket ?? null) !== priceCtxBracket) { + return new Error('Spend reservation locked price context bracket mismatch.'); + } + if ((lockedPrice.ctx_bracket_table_ver ?? null) !== (normalized.ctx_bracket_table_ver ?? null)) { + return new Error('Spend reservation locked price context bracket table mismatch.'); + } + if (stableJson(normalized.locked_rate_map) !== stableJson(lockedPrice.rate_map)) { + return new Error('Spend reservation locked rate_map does not match price version.'); + } + if (this.compareAu(normalized.locked_per_req_au, lockedPrice.per_req_au ?? ZERO_AU) !== 0) { + return new Error('Spend reservation locked per_req_au does not match price version.'); + } + if (this.compareAu(normalized.locked_min_session_au, lockedPrice.min_session_au ?? ZERO_AU) !== 0) { + return new Error('Spend reservation locked min_session_au does not match price version.'); + } + const rules = await this.currentRules(); + if (!rules || rules.ver !== normalized.rules_ver) { + return new Error('Spend reservation rules version is not current.'); + } + + const balance = await this.balanceRecord(normalized.user, normalized.rail); + if (balance instanceof Error) return balance; + const balanceError = this.guardianValidateBalanceRecord(balance, normalized.user, normalized.rail); + if (balanceError) return balanceError; + + const accounting = await this.targetedSpendAccountingState( + normalized.user, + normalized.rail + ); + if (accounting instanceof Error) return accounting; + const reservationState = await this.targetedSpendReservationState( + normalized.user, + normalized.rail, + normalized.reservation_id, + normalized.session_id + ); + if (reservationState instanceof Error) return reservationState; + const legacySessionById = accounting.hold.sessions.find( + (session) => session.session_id === normalized.session_id + ); + const sessionIndexKey = this.targetedSpendSessionIndexKey( + normalized.user, + normalized.rail, + normalized.session_id + ); + const sessionIndex = this.normalizeTargetedSpendReservationIndexRecord( + await this.get(sessionIndexKey), + { + user: normalized.user, + rail: normalized.rail, + sessionId: normalized.session_id, + } + ); + if (sessionIndex instanceof Error) return sessionIndex; + const billingAttemptKey = this.targetedSpendBillingAttemptKey( + normalized.user, + normalized.rail, + normalized.voucher_body.billing_id, + normalized.voucher_body.billing_attempt + ); + const billingAttemptIndex = this.normalizeTargetedSpendReservationIndexRecord( + await this.get(billingAttemptKey), + { + user: normalized.user, + rail: normalized.rail, + billingId: normalized.voucher_body.billing_id, + billingAttempt: normalized.voucher_body.billing_attempt, + } + ); + if (billingAttemptIndex instanceof Error) return billingAttemptIndex; + if (sessionIndex !== null && sessionIndex.reservation_id !== normalized.reservation_id) { + return new Error('Targeted spend reservation session already exists with different terms.'); + } + if (billingAttemptIndex !== null && + billingAttemptIndex.reservation_id !== normalized.reservation_id) { + return new Error('Billing attempt already has an active reservation.'); + } + const existing = reservationState.kind !== 'missing' + ? reservationState.session + : legacySessionById ?? null; + if (existing) { + if ( + existing.billing_id !== normalized.voucher_body.billing_id || + existing.billing_attempt !== normalized.voucher_body.billing_attempt || + existing.billing_epoch !== normalized.epoch || + existing.reservation_id !== normalized.voucher_body.reservation_id || + existing.reservation_expires_after_epoch !== normalized.reservation_expires_after_epoch || + existing.reservation_receipt_grace_epochs !== normalized.reservation_receipt_grace_epochs || + existing.user !== normalized.user || + existing.rail !== normalized.rail || + existing.provider !== normalized.provider || + existing.payout_revision !== value.payout_revision || + existing.enclave_id !== normalized.enclave_id || + existing.enclave_pubkey !== normalized.enclave_pubkey || + existing.model_id !== normalized.model_id || + existing.price_ver !== normalized.price_ver || + stableJson(existing.billing_prior_usage) !== + stableJson(normalized.voucher_body.billing_prior_usage) || + this.compareAu( + existing.billing_prior_au_owed_cum, + normalized.voucher_body.billing_prior_au_owed_cum + ) !== 0 || + stableJson(existing.locked_rate_map) !== stableJson(normalized.locked_rate_map) || + this.compareAu(existing.locked_per_req_au, normalized.locked_per_req_au) !== 0 || + this.compareAu(existing.locked_min_session_au, normalized.locked_min_session_au) !== 0 || + existing.served_ctx !== normalized.served_ctx || + stableJson(existing.required_modalities) !== stableJson(normalized.required_modalities) || + stableJson(existing.required_specialities) !== stableJson(normalized.required_specialities) || + stableJson(existing.workflow ?? null) !== stableJson(normalized.workflow ?? null) || + existing.ctx_bracket !== normalized.ctx_bracket || + existing.ctx_bracket_table_ver !== normalized.ctx_bracket_table_ver || + existing.rules_ver !== normalized.rules_ver || + this.compareAu(existing.max_spend_au, normalized.max_spend_au) !== 0 || + existing.voucher_hash !== normalized.voucher_hash || + existing.payout_revision !== value.payout_revision + ) { + return new Error('Targeted spend reservation session already exists with different terms.'); + } + const billingError = await this.validateExistingBillingReservation(normalized); + if (billingError) return billingError; + const availableAu = this.compareAu(accounting.total_reserved_au, balance.au) >= 0 + ? ZERO_AU + : this.safeSubAu(balance.au, accounting.total_reserved_au); + if (availableAu instanceof Error) return availableAu; + return { + ok: true, + op: 'spendReserveTargeted', + session_id: normalized.session_id, + epoch: normalized.epoch, + rail: normalized.rail, + user: normalized.user, + provider: normalized.provider, + payout_revision: value.payout_revision, + reserved_au: accounting.total_reserved_au, + available_au: availableAu, + idempotent: true, + }; + } + + const billingReservation = await this.prepareBillingReservation(normalized, key); + if (billingReservation instanceof Error) return billingReservation; + const nextSummaryReservedAu = this.safeAddAu( + accounting.summary.reserved_au, + normalized.max_spend_au + ); + if (nextSummaryReservedAu instanceof Error) return nextSummaryReservedAu; + const nextTotalReservedAu = this.safeAddAu( + accounting.legacy_reserved_au, + nextSummaryReservedAu + ); + if (nextTotalReservedAu instanceof Error) return nextTotalReservedAu; + if (this.compareAu(nextTotalReservedAu, balance.au) > 0) { + return new Error('Insufficient unreserved credit balance.'); + } + const session = { + session_id: normalized.session_id, + billing_id: normalized.voucher_body.billing_id, + billing_attempt: normalized.voucher_body.billing_attempt, + billing_prior_usage: cloneValue(normalized.voucher_body.billing_prior_usage), + billing_prior_au_owed_cum: normalized.voucher_body.billing_prior_au_owed_cum, + billing_epoch: normalized.epoch, + reservation_id: normalized.voucher_body.reservation_id, + reservation_expires_after_epoch: normalized.reservation_expires_after_epoch, + reservation_receipt_grace_epochs: normalized.reservation_receipt_grace_epochs, + user: normalized.user, + rail: normalized.rail, + provider: normalized.provider, + payout_revision: value.payout_revision, + enclave_id: normalized.enclave_id, + enclave_pubkey: normalized.enclave_pubkey, + model_id: normalized.model_id, + price_ver: normalized.price_ver, + locked_rate_map: normalized.locked_rate_map, + locked_per_req_au: normalized.locked_per_req_au, + locked_min_session_au: normalized.locked_min_session_au, + served_ctx: normalized.served_ctx, + required_modalities: normalized.required_modalities.slice(), + required_specialities: cloneValue(normalized.required_specialities), + ...(normalized.workflow ? { workflow: cloneValue(normalized.workflow) } : {}), + ctx_bracket: normalized.ctx_bracket, + ctx_bracket_table_ver: normalized.ctx_bracket_table_ver, + rules_ver: normalized.rules_ver, + max_spend_au: normalized.max_spend_au, + voucher_hash: normalized.voucher_hash, + feature_key: key, + reserved_at: normalized.at, + recorded_at: this.tx, + }; + const sessionKey = this.targetedSpendSessionKey( + normalized.user, + normalized.rail, + normalized.reservation_id + ); + const nextSummary = { + ...accounting.summary, + balance_au_at_last_reserve: balance.au, + reserved_au: nextSummaryReservedAu, + updated_at: this.tx, + }; + const indexRecord = this.targetedSpendReservationIndexRecord( + session, + sessionKey, + this.tx + ); + await this.put( + this.targetedSpendSummaryKey(normalized.user, normalized.rail), + nextSummary + ); + await this.put(sessionKey, this.targetedSpendSessionRecord(session, this.tx)); + await this.put(sessionIndexKey, indexRecord); + await this.put(billingAttemptKey, indexRecord); + await this.put(billingReservation.anchor_key, billingReservation.anchor); + await this.put(billingReservation.reservation_key, billingReservation.reservation); + const availableAu = this.safeSubAu(balance.au, nextTotalReservedAu); + if (availableAu instanceof Error) return availableAu; + return { + ok: true, + op: 'spendReserveTargeted', + session_id: normalized.session_id, + epoch: normalized.epoch, + rail: normalized.rail, + user: normalized.user, + provider: normalized.provider, + payout_revision: value.payout_revision, + reserved_au: nextTotalReservedAu, + available_au: availableAu, + idempotent: false, + }; + } + + receiptAttemptTerms(body) { + return { + schema_version: body.schema_version, + session_id: body.session_id, + billing_id: body.billing_id, + billing_attempt: body.billing_attempt, + billing_prior_usage: body.billing_prior_usage, + billing_prior_au_owed_cum: body.billing_prior_au_owed_cum, + billing_epoch: body.billing_epoch, + reservation_id: body.reservation_id, + reservation_expires_after_epoch: body.reservation_expires_after_epoch, + reservation_receipt_grace_epochs: body.reservation_receipt_grace_epochs, + payout_revision: body.payout_revision, + rail: body.rail, + user: body.user, + provider: body.provider, + enclave_id: body.enclave_id, + model_id: body.model_id, + price_ver: body.price_ver, + locked_rate_map: body.locked_rate_map, + locked_per_req_au: body.locked_per_req_au, + locked_min_session_au: body.locked_min_session_au, + served_ctx: body.served_ctx, + ctx_bracket: body.ctx_bracket, + ctx_bracket_table_ver: body.ctx_bracket_table_ver, + rules_ver: body.rules_ver, + workflow: body.workflow ?? null, + prompt_hash: body.prompt_hash, + }; + } + + receiptUsageIsMonotonic(previous, next) { + const units = new Set([...Object.keys(previous), ...Object.keys(next)]); + for (const unit of units) { + const before = previous[unit] ?? 0; + const after = next[unit] ?? 0; + if (!Number.isSafeInteger(before) || + !Number.isSafeInteger(after) || + after < before) { + return false; + } + } + return true; + } + + async normalizeRecordUsageReceiptValue(value) { + const shapeError = this.validateExactObjectKeys( + value, + ['op', 'contract_version', 'epoch', 'payout_revision', 'receipt', 'provider_sig'], + 'record usage receipt feature' + ); + if (shapeError) return shapeError; + if (value.op !== 'record_usage_receipt') { + return new Error('Invalid record usage receipt op.'); + } + if (value.contract_version !== CONTRACT_VERSION && + !RECOVERABLE_RECEIPT_CONTRACT_VERSIONS.has(value.contract_version)) { + return new Error('Invalid record usage receipt contract version.'); + } + if (!Number.isSafeInteger(value.epoch) || value.epoch < 1) { + return new Error('Invalid record usage receipt epoch.'); + } + if (!this.isHexBytes(value.payout_revision, 32) || + value.payout_revision !== value.payout_revision.toLowerCase()) { + return new Error('Invalid record usage receipt payout revision.'); + } + if (!this.isHexBytes(value.provider_sig, 64)) { + return new Error('Invalid record usage receipt provider signature.'); + } + const receiptShapeError = this.validateExactObjectKeys( + value.receipt, + ['body', 'enclave_sig', 'enclave_pubkey', 'user_sig'], + 'record usage receipt envelope' + ); + if (receiptShapeError) return receiptShapeError; + const receipt = await this.normalizeReceiptEnvelope(value.receipt, { + targetSchemaVersion: SESSION_RECEIPT_SCHEMA_VERSION, + }); + if (receipt instanceof Error) return receipt; + const canonicalReceipt = { + body: canonicalReceiptBody(receipt.body), + enclave_sig: receipt.enclave_sig.toLowerCase(), + enclave_pubkey: receipt.enclave_pubkey.toLowerCase(), + user_sig: receipt.user_sig.toLowerCase(), + }; + if (stableJson(value.receipt) !== stableJson(canonicalReceipt)) { + return new Error('Record usage receipt envelope must be canonical.'); + } + if (receipt.body.billing_epoch !== value.epoch) { + return new Error('Record usage receipt outer epoch does not match signed receipt.'); + } + if (receipt.body.payout_revision !== value.payout_revision) { + return new Error('Record usage receipt outer payout revision does not match signed receipt.'); + } + return { + op: 'record_usage_receipt', + // The outer version participates in the provider signature and feature key. + // Never rewrite retained v23 evidence while executing under a v24 dispatch. + contract_version: value.contract_version, + epoch: value.epoch, + payout_revision: value.payout_revision, + receipt: canonicalReceipt, + provider_sig: value.provider_sig.toLowerCase(), + }; + } + + async recordUsageReceiptFeatureKey(value) { + const normalized = await this.normalizeRecordUsageReceiptValue(value); + if (normalized instanceof Error) return normalized; + const digest = await blake3(b4a.from(stableJson({ + domain: 'mayhem-record-usage-receipt-feature-v1', + evidence: recordUsageReceiptEvidence(normalized), + }))); + const body = normalized.receipt.body; + return ( + `receipt/submit/${body.billing_epoch}/${body.billing_id}/` + + `${body.billing_attempt}/${body.seq}/${b4a.toString(digest, 'hex')}` + ); + } + + normalizeCloseUsageReservationValue(value, { expiry = false } = {}) { + const shapeError = this.validateExactObjectKeys( + value, + [ + 'op', + 'contract_version', + 'billing_epoch', + 'reservation_id', + 'reservation_expires_after_epoch', + 'reservation_receipt_grace_epochs', + 'billing_id', + 'billing_attempt', + 'session_id', + 'user', + 'rail', + 'provider', + 'payout_revision', + 'latest_receipt_seq', + 'latest_receipt_hash', + 'at', + 'reason', + 'actor', + 'actor_role', + 'actor_sig', + ], + 'close usage reservation feature' + ); + if (shapeError) return shapeError; + const rail = this.normalizeLedgerRail(value.rail, 'close usage reservation rail'); + if (rail instanceof Error) return rail; + const expectedOp = expiry ? 'expire_usage_reservation' : 'close_usage_reservation'; + const expectedRole = expiry ? 'user' : 'provider'; + if (value.op !== expectedOp || + value.contract_version !== CONTRACT_VERSION || + !Number.isSafeInteger(value.billing_epoch) || + value.billing_epoch < 1 || + !this.isHexBytes(value.reservation_id, 32) || + !Number.isSafeInteger(value.reservation_expires_after_epoch) || + value.reservation_expires_after_epoch <= value.billing_epoch || + !Number.isSafeInteger(value.reservation_receipt_grace_epochs) || + value.reservation_receipt_grace_epochs < 0 || + !this.isHexBytes(value.billing_id, 32) || + !Number.isSafeInteger(value.billing_attempt) || + value.billing_attempt < 0 || + !this.isHexBytes(value.session_id, 32) || + !this.isHexBytes(value.user, 32) || + !this.isHexBytes(value.provider, 32) || + !this.isHexBytes(value.payout_revision, 32) || + !Number.isSafeInteger(value.at) || + value.at < 0 || + !this.isSafeKeyPart(value.reason) || + !this.isHexBytes(value.actor, 32) || + value.actor_role !== expectedRole || + !this.isHexBytes(value.actor_sig, 64)) { + return new Error('Invalid close usage reservation feature.'); + } + const expectedActor = expiry ? value.user : value.provider; + if (value.actor !== expectedActor) { + return new Error('Close usage reservation actor does not match its role.'); + } + const hasReceipt = value.latest_receipt_seq !== null || + value.latest_receipt_hash !== null; + if ( + hasReceipt + ? (!Number.isSafeInteger(value.latest_receipt_seq) || + value.latest_receipt_seq < 0 || + !this.isHexBytes(value.latest_receipt_hash, 32)) + : value.latest_receipt_seq !== null || value.latest_receipt_hash !== null + ) { + return new Error('Invalid close usage reservation receipt head.'); + } + const normalized = { + op: expectedOp, + contract_version: CONTRACT_VERSION, + billing_epoch: value.billing_epoch, + reservation_id: value.reservation_id.toLowerCase(), + reservation_expires_after_epoch: value.reservation_expires_after_epoch, + reservation_receipt_grace_epochs: value.reservation_receipt_grace_epochs, + billing_id: value.billing_id.toLowerCase(), + billing_attempt: value.billing_attempt, + session_id: value.session_id.toLowerCase(), + user: value.user.toLowerCase(), + rail, + provider: value.provider.toLowerCase(), + payout_revision: value.payout_revision.toLowerCase(), + latest_receipt_seq: value.latest_receipt_seq, + latest_receipt_hash: value.latest_receipt_hash?.toLowerCase() ?? null, + at: value.at, + reason: value.reason, + actor: value.actor.toLowerCase(), + actor_role: value.actor_role, + actor_sig: value.actor_sig.toLowerCase(), + }; + if (stableJson(normalized) !== stableJson(value)) { + return new Error('Close usage reservation feature must be canonical.'); + } + return normalized; + } + + async closeUsageReservationFeatureKey(value, { expiry = false } = {}) { + const normalized = this.normalizeCloseUsageReservationValue(value, { expiry }); + if (normalized instanceof Error) return normalized; + const digest = await blake3(b4a.from(stableJson({ + domain: expiry + ? 'mayhem-expire-usage-reservation-feature-v1' + : 'mayhem-close-usage-reservation-feature-v1', + evidence: closeUsageReservationEvidence(normalized), + actor_sig: normalized.actor_sig, + }))); + return ( + `receipt/${expiry ? 'expire' : 'close'}/${normalized.billing_epoch}/` + + `${normalized.reservation_id}/` + + `${b4a.toString(digest, 'hex')}` + ); + } + + async nextReceiptEpochIndex(epoch, billingId, billingAttempt) { + if (await this.get(`epoch/freeze/${epoch}`)) { + return new Error('Cannot append receipts to a frozen settlement epoch.'); + } + const indexKey = this.receiptEpochIndexKey(epoch); + const existingIndex = await this.get(indexKey); + const index = existingIndex ?? { + type: 'canonical_receipt_epoch_index', + epoch, + count: 0, + page_size: RECEIPT_EPOCH_INDEX_PAGE_SIZE, + page_count: 0, + revision: 0, + updated_at: null, + }; + const normalizedIndex = this.normalizeReceiptEpochIndexMetadata(index, epoch, { + allowEmpty: true, + }); + if (normalizedIndex instanceof Error) return normalizedIndex; + if (index.count >= Number.MAX_SAFE_INTEGER) { + return new Error('Canonical receipt epoch count overflow.'); + } + const page = Math.floor(index.count / RECEIPT_EPOCH_INDEX_PAGE_SIZE); + const pageKey = this.receiptEpochPageKey(epoch, page); + const existingPage = await this.get(pageKey); + const pageRecord = existingPage ?? { + type: 'canonical_receipt_epoch_page', + epoch, + page, + identities: [], + }; + if (pageRecord.type !== 'canonical_receipt_epoch_page' || + pageRecord.epoch !== epoch || + pageRecord.page !== page || + !Array.isArray(pageRecord.identities) || + Object.keys(pageRecord).sort().join(',') !== 'epoch,identities,page,type' || + pageRecord.identities.length !== index.count % RECEIPT_EPOCH_INDEX_PAGE_SIZE || + pageRecord.identities.length >= RECEIPT_EPOCH_INDEX_PAGE_SIZE) { + return new Error('Canonical receipt epoch page is invalid or full.'); + } + const identities = pageRecord.identities; + if ( + identities.length >= RECEIPT_EPOCH_INDEX_PAGE_SIZE) { + return new Error('Canonical receipt epoch page is invalid or full.'); + } + const identity = { billing_id: billingId, billing_attempt: billingAttempt }; + if (identities.some((entry) => + entry?.billing_id === billingId && entry?.billing_attempt === billingAttempt + )) { + return new Error('Canonical receipt billing attempt is already indexed.'); + } + return { + index_key: indexKey, + index: { + ...index, + count: index.count + 1, + page_count: Math.max(index.page_count, page + 1), + }, + page_key: pageKey, + page: { + ...pageRecord, + identities: [...identities, identity], + }, + position: index.count, + }; + } + + normalizeReceiptEpochIndexMetadata(value, epoch, { allowEmpty = false } = {}) { + const shapeError = this.validateExactObjectKeys( + value, + ['type', 'epoch', 'count', 'page_size', 'page_count', 'revision', 'updated_at'], + 'canonical receipt epoch metadata' + ); + if (shapeError) return shapeError; + if (value.type !== 'canonical_receipt_epoch_index' || value.epoch !== epoch) { + return new Error('Canonical receipt epoch metadata identity is invalid.'); + } + if (!Number.isSafeInteger(value.count) || value.count < 0 || + value.page_size !== RECEIPT_EPOCH_INDEX_PAGE_SIZE || + !Number.isSafeInteger(value.page_count) || value.page_count < 0 || + !Number.isSafeInteger(value.revision) || value.revision < value.count) { + return new Error('Canonical receipt epoch metadata counters are invalid.'); + } + const expectedPageCount = value.count === 0 + ? 0 + : Math.ceil(value.count / RECEIPT_EPOCH_INDEX_PAGE_SIZE); + if (value.page_count !== expectedPageCount) { + return new Error('Canonical receipt epoch metadata page count is invalid.'); + } + if (value.count === 0) { + if (!allowEmpty || value.revision !== 0 || value.updated_at !== null) { + return new Error('Canonical receipt epoch metadata cannot be empty.'); + } + } else if (typeof value.updated_at !== 'string' || value.updated_at.length === 0) { + return new Error('Canonical receipt epoch metadata updated_at is invalid.'); + } + return { + type: value.type, + epoch: value.epoch, + count: value.count, + page_size: value.page_size, + page_count: value.page_count, + revision: value.revision, + updated_at: value.updated_at, + }; + } + + async receiptSettlementEpoch(applyState) { + const base = applyState.pending_epoch ?? applyState.updated_epoch; + if (!Number.isSafeInteger(base) || base < 0 || base >= Number.MAX_SAFE_INTEGER) { + return new Error('Receipt settlement epoch overflow.'); + } + const cursor = await this.get('receipt/ingress'); + if (cursor === null) return base + 1; + if (cursor.type !== 'receipt_ingress' || + !Number.isSafeInteger(cursor.next_epoch) || cursor.next_epoch < 1 || + !Number.isSafeInteger(cursor.activated_epoch) || cursor.activated_epoch < 1 || + cursor.activated_epoch >= cursor.next_epoch || + cursor.next_epoch > base + 2) { + return new Error('Canonical receipt ingress cursor is invalid.'); + } + return Math.max(base + 1, cursor.next_epoch); + } + + async prepareStateCheckpoint() { + const adminError = await this.requireAdmin(); + if (adminError) return adminError; + const shapeError = this.validateExactCommandValue( + ['op', 'slot', 'observed_at', 'contract_code_sha256'], 'prepare_state_checkpoint' + ); + if (shapeError) return shapeError; + const { slot, observed_at: observedAt, contract_code_sha256: codeHash } = this.value; + if (!Number.isSafeInteger(slot) || slot < 1 || !Number.isSafeInteger(slot * 30) || + !Number.isSafeInteger(observedAt) || observedAt < slot * 30 || + !this.isHexBytes(codeHash, 32) || codeHash !== codeHash.toLowerCase()) { + return new Error('Invalid checkpoint slot, observation time or release hash.'); + } + const key = `checkpoint/prepared/${slot}`; + const existing = await this.get(key); + if (existing !== null) { + return { ok: true, op: 'prepareStateCheckpoint', idempotent: true, snapshot: existing }; + } + const previous = await this.get('checkpoint/current'); + const preparing = await this.get('checkpoint/preparing'); + if (preparing && preparing.slot !== slot && preparing.slot > (previous?.slot ?? 0)) { + return new Error('An earlier checkpoint preparation is still awaiting its paid transaction.'); + } + if (previous && (slot !== previous.slot + 1 || observedAt < previous.observed_at)) { + return new Error('Checkpoint preparation must follow the last paid slot and observation time.'); + } + const applyState = await this.epochApplyStateRecord(); + const ingress = await this.receiptSettlementEpoch(applyState); + if (ingress instanceof Error) return ingress; + const completed = applyState.updated_epoch > 0 + ? await this.epochApplyAnchor(applyState.updated_epoch) : null; + if (completed instanceof Error) return completed; + if (applyState.updated_epoch > 0 && completed === null) { + return new Error('Completed settlement anchor is unavailable for checkpoint.'); + } + const catalog = await this.get('catalog/current'); + const state = { + completed_settlement: completed, + pending_apply: applyState.pending_epoch == null ? null : { + epoch: applyState.pending_epoch, next_page: applyState.pending_next_page, + apply_hash: applyState.last_apply_hash, + }, + receipt_ingress_epoch: ingress, + open_receipt_index: await this.get(this.receiptEpochIndexKey(ingress)), + frozen_receipts: await this.get(`epoch/freeze/${applyState.updated_epoch + 1}`), + catalog_hash: catalog?.catalog_hash ?? null, + catalog_version: catalog?.ver ?? catalog?.version ?? null, + contract_version: CONTRACT_VERSION, + // The writer supplies its startup-verified release manifest digest. This + // attests the publisher's code identity; canonical state is read here. + contract_code_sha256: codeHash, + }; + const stateHash = await this.opaqueHash('mayhem-checkpoint-state-v1', state); + const body = { + type: 'state_checkpoint_snapshot', schema_version: 1, slot, + scheduled_at: slot * 30, observed_at: observedAt, + previous_tx: previous?.tx ?? null, + state, state_hash: stateHash, + no_change: previous?.state_hash === stateHash, + prepared_by: this.address, + }; + const snapshot = { + ...body, snapshot_hash: await this.opaqueHash('mayhem-checkpoint-snapshot-v1', body), + }; + await this.put(key, snapshot); + await this.put('checkpoint/preparing', { slot, snapshot_hash: snapshot.snapshot_hash }); + return { ok: true, op: 'prepareStateCheckpoint', idempotent: false, snapshot }; + } + + async stateCheckpoint() { + if (this._mayhemExecutionType !== 'tx' || this.isFeature() || !this.isHexBytes(this.tx, 32)) { + return new Error('State checkpoints require a paid MSB transaction; free admin Features are forbidden.'); + } + const adminError = await this.requireAdmin(); + if (adminError) return adminError; + const shapeError = this.validateExactCommandValue( + ['op', 'slot', 'snapshot_hash'], 'state_checkpoint' + ); + if (shapeError) return shapeError; + const { slot, snapshot_hash: snapshotHash } = this.value; + if (!Number.isSafeInteger(slot) || slot < 1 || !this.isHexBytes(snapshotHash, 32)) { + return new Error('Invalid paid checkpoint identity.'); + } + const key = `checkpoint/slot/${slot}`; + const existing = await this.get(key); + if (existing !== null) { + return existing.tx === this.tx && existing.snapshot_hash === snapshotHash + ? { ok: true, op: 'stateCheckpoint', idempotent: true, checkpoint: existing } + : new Error('Checkpoint slot already has a paid transaction.'); + } + const snapshot = await this.get(`checkpoint/prepared/${slot}`); + if (!snapshot || snapshot.type !== 'state_checkpoint_snapshot' || + snapshot.slot !== slot || snapshot.snapshot_hash !== snapshotHash || + snapshot.prepared_by !== this.address) { + return new Error('Matching canonical checkpoint snapshot required.'); + } + const previous = await this.get('checkpoint/current'); + if (snapshot.previous_tx !== (previous?.tx ?? null) || + (previous && slot !== previous.slot + 1)) { + return new Error('Paid checkpoint predecessor does not match canonical history.'); + } + const checkpoint = { + type: 'paid_state_checkpoint', schema_version: 1, slot, + scheduled_at: snapshot.scheduled_at, observed_at: snapshot.observed_at, + snapshot_hash: snapshotHash, state_hash: snapshot.state_hash, + no_change: snapshot.no_change, previous_tx: snapshot.previous_tx, + tx: this.tx, paid_by: this.address, + }; + await this.put(key, checkpoint); + await this.put('checkpoint/current', checkpoint); + return { ok: true, op: 'stateCheckpoint', idempotent: false, checkpoint }; + } + + async epochFreeze() { + const adminError = await this.requireAdmin(); + if (adminError) return adminError; + const shapeError = this.validateExactCommandValue(['op', 'epoch', 'at'], 'epoch_freeze'); + if (shapeError) return shapeError; + const { epoch, at } = this.value; + if (!Number.isSafeInteger(epoch) || epoch < 1 || epoch >= Number.MAX_SAFE_INTEGER || + !Number.isSafeInteger(at) || at < 0) { + return new Error('Invalid epoch freeze identity or timestamp.'); + } + const key = `epoch/freeze/${epoch}`; + const existing = await this.get(key); + if (existing !== null) { + return { ok: true, op: 'epochFreeze', idempotent: true, freeze: existing }; + } + const applyState = await this.epochApplyStateRecord(); + const orderError = this.validateEpochApplyPageOrder(applyState, epoch, 0); + if (orderError) return orderError; + const params = await this.activeParamsAt(at, ['epoch_seconds']); + const cadenceError = await this.validateEpochCadenceTime( + applyState, epoch, 0, at, params.epoch_seconds + ); + if (cadenceError) return cadenceError; + if (at < epoch * params.epoch_seconds) { + return new Error('Epoch freeze is not active until the epoch window ends.'); + } + const ingress = await this.receiptSettlementEpoch(applyState); + if (ingress instanceof Error) return ingress; + if (ingress !== epoch) return new Error('Epoch freeze must close the open receipt batch.'); + const metadata = this.normalizeReceiptEpochIndexMetadata( + (await this.get(this.receiptEpochIndexKey(epoch))) ?? { + type: 'canonical_receipt_epoch_index', epoch, count: 0, + page_size: RECEIPT_EPOCH_INDEX_PAGE_SIZE, page_count: 0, + revision: 0, updated_at: null, + }, epoch, { allowEmpty: true } + ); + if (metadata instanceof Error) return metadata; + const body = { + type: 'epoch_receipt_freeze', epoch, at, epoch_seconds: params.epoch_seconds, + previous_apply_hash: applyState.last_apply_hash ?? null, + receipt_index: metadata, frozen_by: this.address, + }; + const freeze = { + ...body, freeze_hash: await this.opaqueHash('mayhem-epoch-receipt-freeze-v1', body), + frozen_at: this.tx, + }; + const cursor = await this.get('receipt/ingress'); + // Both writes are in the same consensus apply. No external observation or + // receipt append can interleave with this cutoff. + await this.put(key, freeze); + await this.put('receipt/ingress', { + type: 'receipt_ingress', next_epoch: epoch + 1, + activated_epoch: cursor?.activated_epoch ?? epoch, + freeze_hash: freeze.freeze_hash, updated_at: this.tx, + }); + return { ok: true, op: 'epochFreeze', idempotent: false, freeze }; + } + + async validateFrozenEpoch(epoch, at, receiptIndex) { + const cursor = await this.get('receipt/ingress'); + const freeze = await this.get(`epoch/freeze/${epoch}`); + // Legacy already-open/partially-applied epochs remain recoverable. Once + // ingress is activated, every subsequent epoch requires a canonical cutoff. + if (freeze === null) { + return cursor && epoch >= cursor.activated_epoch + ? new Error('Canonical epoch freeze required before settlement.') : null; + } + if (freeze.type !== 'epoch_receipt_freeze' || freeze.epoch !== epoch || + freeze.at !== at || + stableJson(freeze.receipt_index) !== stableJson(receiptIndex)) { + return new Error('Settlement does not match its canonical epoch freeze.'); + } + return null; + } + + async normalizeTargetedSpendSummaryRecord(record, user, rail) { + if (!record) { + return { + type: 'targeted_spend_summary', + user, + rail, + denom: PRICE_DENOMINATION, + reserved_au: ZERO_AU, + balance_au_at_last_reserve: null, + updated_at: null, + }; + } + if (!record || typeof record !== 'object' || Array.isArray(record) || + record.type !== 'targeted_spend_summary' || + record.user !== user || + record.rail !== rail || + record.denom !== PRICE_DENOMINATION) { + return new Error('Targeted spend summary key mismatch.'); + } + const reservedAu = this.normalizeAu(record.reserved_au, 'targeted spend summary reserved amount'); + if (reservedAu instanceof Error) { + return new Error('Invalid targeted spend summary reserved amount.'); + } + let balanceAu = null; + if (record.balance_au_at_last_reserve !== null) { + balanceAu = this.normalizeAu( + record.balance_au_at_last_reserve, + 'targeted spend summary balance amount' + ); + if (balanceAu instanceof Error) { + return new Error('Invalid targeted spend summary balance amount.'); + } + } + if (record.updated_at !== null && + (typeof record.updated_at !== 'string' || record.updated_at.length === 0)) { + return new Error('Invalid targeted spend summary update pointer.'); + } + return { + type: 'targeted_spend_summary', + user, + rail, + denom: PRICE_DENOMINATION, + reserved_au: reservedAu, + balance_au_at_last_reserve: balanceAu, + updated_at: record.updated_at, + }; + } + + async normalizeTargetedSpendLegacyReleaseSummaryRecord(record, user, rail) { + if (!record) { + return { + type: 'targeted_spend_legacy_release_summary', + user, + rail, + denom: PRICE_DENOMINATION, + released_au: ZERO_AU, + updated_at: null, + }; + } + if (!record || typeof record !== 'object' || Array.isArray(record) || + record.type !== 'targeted_spend_legacy_release_summary' || + record.user !== user || + record.rail !== rail || + record.denom !== PRICE_DENOMINATION) { + return new Error('Targeted spend legacy release summary key mismatch.'); + } + const releasedAu = this.normalizeAu( + record.released_au, + 'targeted spend legacy release amount' + ); + if (releasedAu instanceof Error) { + return new Error('Invalid targeted spend legacy release amount.'); + } + if (record.updated_at !== null && + record.updated_at !== undefined && + (typeof record.updated_at !== 'string' || record.updated_at.length === 0)) { + return new Error('Invalid targeted spend legacy release update pointer.'); + } + return { + type: 'targeted_spend_legacy_release_summary', + user, + rail, + denom: PRICE_DENOMINATION, + released_au: releasedAu, + updated_at: record.updated_at ?? null, + }; + } + + async normalizeTargetedSpendSessionRecord(record, user, rail, reservationId = null) { + if (!record) return null; + if (!record || typeof record !== 'object' || Array.isArray(record) || + record.type !== 'targeted_spend_session') { + return new Error('Targeted spend session record must be a session object.'); + } + const session = { ...record }; + delete session.type; + delete session.updated_at; + const hold = await this.normalizeTargetedSpendHoldRecord({ + type: 'targeted_spend_hold', + user, + rail, + denom: PRICE_DENOMINATION, + reserved_au: session.max_spend_au, + balance_au_at_last_reserve: null, + sessions: [session], + updated_at: record.updated_at ?? null, + }, user, rail); + if (hold instanceof Error) return hold; + const normalized = hold.sessions[0]; + if (normalized.user !== user || + normalized.rail !== rail || + (reservationId !== null && normalized.reservation_id !== reservationId)) { + return new Error('Targeted spend session key mismatch.'); + } + if (record.updated_at !== null && + record.updated_at !== undefined && + (typeof record.updated_at !== 'string' || record.updated_at.length === 0)) { + return new Error('Invalid targeted spend session update pointer.'); + } + return { + type: 'targeted_spend_session', + ...normalized, + updated_at: record.updated_at ?? null, + }; + } + + targetedSpendSessionRecord(session, updatedAt) { + return { + type: 'targeted_spend_session', + ...session, + updated_at: updatedAt, + }; + } + + normalizeTargetedSpendReservationIndexRecord( + record, + { + user, + rail, + sessionId = null, + billingId = null, + billingAttempt = null, + } + ) { + if (!record) return null; + if (!record || typeof record !== 'object' || Array.isArray(record) || + record.type !== 'targeted_spend_reservation_index' || + record.user !== user || + record.rail !== rail || + (sessionId !== null && record.session_id !== sessionId) || + (billingId !== null && record.billing_id !== billingId) || + (billingAttempt !== null && record.billing_attempt !== billingAttempt) || + !this.isHexBytes(record.session_id, 32) || + !this.isHexBytes(record.billing_id, 32) || + !Number.isSafeInteger(record.billing_attempt) || + record.billing_attempt < 0 || + !this.isHexBytes(record.reservation_id, 32) || + typeof record.session_key !== 'string' || + record.session_key !== + this.targetedSpendSessionKey(user, rail, record.reservation_id) || + (record.updated_at !== null && + record.updated_at !== undefined && + (typeof record.updated_at !== 'string' || record.updated_at.length === 0))) { + return new Error('Targeted spend reservation index is invalid.'); + } + return { + type: 'targeted_spend_reservation_index', + user, + rail, + session_id: record.session_id, + billing_id: record.billing_id, + billing_attempt: record.billing_attempt, + reservation_id: record.reservation_id, + session_key: record.session_key, + updated_at: record.updated_at, + }; + } + + targetedSpendReservationIndexRecord(session, sessionKey, updatedAt) { + return { + type: 'targeted_spend_reservation_index', + user: session.user, + rail: session.rail, + session_id: session.session_id, + billing_id: session.billing_id, + billing_attempt: session.billing_attempt, + reservation_id: session.reservation_id, + session_key: sessionKey, + updated_at: updatedAt, + }; + } + + async targetedSpendAccountingState(user, rail) { + // MAYHEM PATCH: combine legacy aggregate holds with sharded reservation + // state so existing reservations survive the targeted-hold rollout. + const hold = await this.normalizeTargetedSpendHoldRecord( + (await this.get(this.targetedSpendHoldKey(user, rail))) ?? null, + user, + rail + ); + if (hold instanceof Error) return hold; + const summary = await this.normalizeTargetedSpendSummaryRecord( + await this.get(this.targetedSpendSummaryKey(user, rail)), + user, + rail + ); + if (summary instanceof Error) return summary; + const legacyRelease = await this.normalizeTargetedSpendLegacyReleaseSummaryRecord( + await this.get(this.targetedSpendLegacyReleaseSummaryKey(user, rail)), + user, + rail + ); + if (legacyRelease instanceof Error) return legacyRelease; + const legacyReservedAu = this.safeSubAu(hold.reserved_au, legacyRelease.released_au); + if (legacyReservedAu instanceof Error) { + return new Error('Targeted spend legacy release exceeds outstanding holds.'); + } + const totalReservedAu = this.safeAddAu(legacyReservedAu, summary.reserved_au); + if (totalReservedAu instanceof Error) return totalReservedAu; + return { + hold, + summary, + legacy_release: legacyRelease, + legacy_reserved_au: legacyReservedAu, + total_reserved_au: totalReservedAu, + }; + } + + async targetedSpendReservationState(user, rail, reservationId, sessionId) { + // MAYHEM PATCH: prefer sharded targeted reservation records and retain a + // legacy overlay path for reservations opened before the sharded layout. + const sessionKey = this.targetedSpendSessionKey(user, rail, reservationId); + const sessionRecord = await this.normalizeTargetedSpendSessionRecord( + await this.get(sessionKey), + user, + rail, + reservationId + ); + if (sessionRecord instanceof Error) return sessionRecord; + if (sessionRecord !== null) { + if (sessionRecord.session_id !== sessionId) { + return new Error('Targeted spend session id does not match reservation key.'); + } + const summary = await this.normalizeTargetedSpendSummaryRecord( + await this.get(this.targetedSpendSummaryKey(user, rail)), + user, + rail + ); + if (summary instanceof Error) return summary; + return { + kind: 'sharded', + sessionKey, + sessionIndexKey: this.targetedSpendSessionIndexKey(user, rail, sessionId), + billingAttemptKey: this.targetedSpendBillingAttemptKey( + user, + rail, + sessionRecord.billing_id, + sessionRecord.billing_attempt + ), + summary, + session: sessionRecord, + }; + } + const holdKey = this.targetedSpendHoldKey(user, rail); + const hold = await this.normalizeTargetedSpendHoldRecord( + (await this.get(holdKey)) ?? null, + user, + rail + ); + if (hold instanceof Error) return hold; + const session = hold.sessions.find((entry) => + entry.session_id === sessionId && + entry.reservation_id === reservationId + ); + if (!session) return { kind: 'missing', hold }; + const legacySessionKey = this.targetedSpendLegacySessionKey(user, rail, reservationId); + const legacySessionRecord = await this.normalizeTargetedSpendSessionRecord( + await this.get(legacySessionKey), + user, + rail, + reservationId + ); + if (legacySessionRecord instanceof Error) return legacySessionRecord; + const legacyRelease = await this.normalizeTargetedSpendLegacyReleaseSummaryRecord( + await this.get(this.targetedSpendLegacyReleaseSummaryKey(user, rail)), + user, + rail + ); + if (legacyRelease instanceof Error) return legacyRelease; + if (legacySessionRecord !== null) { + if (legacySessionRecord.session_id !== sessionId) { + return new Error('Targeted spend legacy session id does not match reservation key.'); + } + return { + kind: 'legacy_overlay', + holdKey, + hold, + legacySessionKey, + legacyRelease, + session: legacySessionRecord, + }; + } + return { kind: 'legacy', holdKey, hold, legacySessionKey, legacyRelease, session }; + } + + prepareTargetedReservationClosure({ + hold, + session, + reservation, + head, + closeRecordKey, + closedBy, + closedByRole, + at, + reason, + }) { + if (reservation.status !== 'active' || + reservation.closed_at !== null || + reservation.close_record_key !== null) { + return new Error('Targeted reservation is already closed.'); + } + const retainedAu = head?.incremental_au ?? ZERO_AU; + const releasedAu = this.safeSubAu(session.max_spend_au, retainedAu); + if (releasedAu instanceof Error) { + return new Error('Targeted reservation close exceeds its hold.'); + } + const reservedAu = this.safeSubAu(hold.reserved_au, releasedAu); + if (reservedAu instanceof Error) { + return new Error('Targeted reservation close exceeds outstanding holds.'); + } + const sessions = head + ? hold.sessions.map((entry) => ( + entry.reservation_id === session.reservation_id + ? { + ...entry, + max_spend_au: retainedAu, + settlement_ready: true, + closed_at: closeRecordKey, + } + : entry + )) + : hold.sessions.filter((entry) => entry.reservation_id !== session.reservation_id); + const closeRecord = { + type: 'targeted_reservation_close', + reservation_id: session.reservation_id, + billing_id: session.billing_id, + billing_attempt: session.billing_attempt, + billing_epoch: session.billing_epoch, + reservation_expires_after_epoch: session.reservation_expires_after_epoch, + reservation_receipt_grace_epochs: session.reservation_receipt_grace_epochs, + session_id: session.session_id, + user: session.user, + rail: session.rail, + provider: session.provider, + payout_revision: session.payout_revision, + latest_receipt_seq: head?.receipt_seq ?? null, + latest_receipt_hash: head?.receipt_hash ?? null, + retained_au: retainedAu, + released_au: releasedAu, + reason, + closed_by: closedBy, + closed_by_role: closedByRole, + at, + recorded_at: closeRecordKey, + }; + return { + hold: { + ...hold, + reserved_au: reservedAu, + sessions, + updated_at: closeRecordKey, + }, + reservation: { + ...reservation, + status: 'closed', + closed_at: closeRecordKey, + close_record_key: closeRecordKey, + }, + close_record: closeRecord, + }; + } + + prepareShardedTargetedReservationClosure({ + summary, + session, + reservation, + head, + closeRecordKey, + closedBy, + closedByRole, + at, + reason, + }) { + const closure = this.prepareTargetedReservationClosure({ + hold: { + type: 'targeted_spend_hold', + user: session.user, + rail: session.rail, + denom: PRICE_DENOMINATION, + reserved_au: session.max_spend_au, + balance_au_at_last_reserve: summary.balance_au_at_last_reserve, + sessions: [session], + updated_at: summary.updated_at, + }, + session, + reservation, + head, + closeRecordKey, + closedBy, + closedByRole, + at, + reason, + }); + if (closure instanceof Error) return closure; + const nextReservedAu = this.safeSubAu( + summary.reserved_au, + closure.close_record.released_au + ); + if (nextReservedAu instanceof Error) { + return new Error('Targeted reservation close exceeds outstanding sharded holds.'); + } + return { + summary: { + ...summary, + reserved_au: nextReservedAu, + updated_at: closeRecordKey, + }, + session: head ? this.targetedSpendSessionRecord(closure.hold.sessions[0], closeRecordKey) : null, + reservation: closure.reservation, + close_record: closure.close_record, + }; + } + + prepareLegacyTargetedReservationClosure({ + legacyRelease, + hold, + session, + reservation, + head, + closeRecordKey, + closedBy, + closedByRole, + at, + reason, + }) { + // MAYHEM PATCH: close or retain legacy targeted holds deterministically + // while payout epochs consume the new sharded reservation records. + const closure = this.prepareTargetedReservationClosure({ + hold, + session, + reservation, + head, + closeRecordKey, + closedBy, + closedByRole, + at, + reason, + }); + if (closure instanceof Error) return closure; + const nextReleasedAu = this.safeAddAu( + legacyRelease.released_au, + closure.close_record.released_au + ); + if (nextReleasedAu instanceof Error || + this.compareAu(nextReleasedAu, hold.reserved_au) > 0) { + return new Error('Targeted reservation close exceeds legacy outstanding holds.'); + } + const overlaySession = head + ? closure.hold.sessions.find( + (entry) => entry.reservation_id === session.reservation_id + ) + : null; + if (head && !overlaySession) { + return new Error('Targeted reservation close lost its retained legacy session.'); + } + return { + legacy_release: { + ...legacyRelease, + released_au: nextReleasedAu, + updated_at: closeRecordKey, + }, + session: overlaySession + ? this.targetedSpendSessionRecord(overlaySession, closeRecordKey) + : null, + reservation: closure.reservation, + close_record: closure.close_record, + }; + } + + async applyRecordUsageReceiptFeature(key, value) { + const normalized = await this.normalizeRecordUsageReceiptValue(value); + if (normalized instanceof Error) return normalized; + const expectedKey = await this.recordUsageReceiptFeatureKey(normalized); + if (expectedKey instanceof Error) return expectedKey; + if (key !== expectedKey) return new Error('Invalid record usage receipt feature key.'); + const body = normalized.receipt.body; + if (!verifyEd25519Hex( + normalized.provider_sig, + recordUsageReceiptMessage(normalized), + body.provider + )) { + return new Error('Invalid record usage receipt provider signature.'); + } + if (!this.verifyReceiptEnvelope(normalized.receipt)) { + return new Error('Invalid record usage receipt user or enclave signature.'); + } + + const receiptHash = await this.opaqueHash( + 'mayhem-canonical-receipt-v1', + normalized.receipt + ); + const headKey = this.receiptHeadKey(body.billing_id, body.billing_attempt); + const existingHead = await this.get(headKey); + if (existingHead) { + if (existingHead.type !== 'canonical_receipt_head') { + return new Error('Canonical receipt head is invalid.'); + } + if (existingHead.receipt_hash === receiptHash && + stableJson(existingHead.receipt) === stableJson(normalized.receipt)) { + return { + ok: true, + op: 'recordUsageReceipt', + epoch: existingHead.settlement_epoch ?? null, + billing_epoch: body.billing_epoch, + billing_id: body.billing_id, + billing_attempt: body.billing_attempt, + receipt_seq: body.seq, + receipt_hash: receiptHash, + idempotent: true, + }; + } + if ((await this.get(this.receiptConsumedKey(body.billing_id, body.billing_attempt))) !== null) { + return new Error('A consumed canonical receipt head cannot advance.'); + } + if (body.seq <= existingHead.receipt_seq) { + return new Error('Receipt sequence conflicts with the canonical high-water head.'); + } + if (existingHead.receipt.body.final === true || + existingHead.settlement_ready === true) { + return new Error('A finalized canonical receipt head cannot advance.'); + } + if (stableJson(this.receiptAttemptTerms(existingHead.receipt.body)) !== + stableJson(this.receiptAttemptTerms(body))) { + return new Error('Higher receipt sequence changed immutable attempt terms.'); + } + if (!this.receiptUsageIsMonotonic(existingHead.receipt.body.usage, body.usage) || + this.compareAu(body.au_owed_cum, existingHead.receipt.body.au_owed_cum) < 0) { + return new Error('Higher receipt sequence is not monotonic.'); + } + } + + const reservationState = await this.targetedSpendReservationState( + body.user, + body.rail, + body.reservation_id, + body.session_id + ); + if (reservationState instanceof Error) return reservationState; + if (reservationState.kind === 'missing') { + return new Error('Receipt does not match an exact targeted spend hold session.'); + } + const session = reservationState.session; + const sessionTermsMatch = + session.billing_id === body.billing_id && + session.billing_attempt === body.billing_attempt && + session.billing_epoch === body.billing_epoch && + session.reservation_id === body.reservation_id && + session.reservation_expires_after_epoch === body.reservation_expires_after_epoch && + session.reservation_receipt_grace_epochs === body.reservation_receipt_grace_epochs && + session.user === body.user && + session.rail === body.rail && + session.provider === body.provider && + session.payout_revision === body.payout_revision && + session.enclave_id === body.enclave_id && + session.enclave_pubkey === normalized.receipt.enclave_pubkey && + session.model_id === body.model_id && + session.price_ver === body.price_ver && + stableJson(session.billing_prior_usage) === stableJson(body.billing_prior_usage) && + this.compareAu( + session.billing_prior_au_owed_cum, + body.billing_prior_au_owed_cum + ) === 0 && + stableJson(session.locked_rate_map) === stableJson(body.locked_rate_map) && + this.compareAu(session.locked_per_req_au, body.locked_per_req_au) === 0 && + this.compareAu(session.locked_min_session_au, body.locked_min_session_au) === 0 && + session.served_ctx === body.served_ctx && + session.ctx_bracket === body.ctx_bracket && + session.ctx_bracket_table_ver === body.ctx_bracket_table_ver && + session.rules_ver === body.rules_ver && + stableJson(session.workflow ?? null) === stableJson(body.workflow ?? null); + if (!sessionTermsMatch) { + return new Error('Receipt terms do not match the targeted spend hold session.'); + } + + const incrementalAu = this.safeSubAu( + body.au_owed_cum, + body.billing_prior_au_owed_cum + ); + if (incrementalAu instanceof Error || + this.isZeroAu(incrementalAu) || + this.compareAu(incrementalAu, session.max_spend_au) > 0) { + return new Error('Receipt incremental amount is not positive or exceeds its session reservation.'); + } + + const billingAnchor = await this.get(this.receiptBillingKey(body.billing_id)); + if (!billingAnchor || + billingAnchor.type !== 'receipt_billing_anchor' || + billingAnchor.user !== body.user || + billingAnchor.rail !== body.rail || + billingAnchor.epoch !== body.billing_epoch || + billingAnchor.latest_attempt < body.billing_attempt) { + return new Error('Receipt billing anchor is missing or inconsistent.'); + } + if (billingAnchor.latest_attempt > body.billing_attempt) { + return new Error('An older billing attempt cannot advance after a higher attempt exists.'); + } + const reservationKey = this.receiptReservationKey(body.reservation_id); + const reservation = await this.get(reservationKey); + if (!reservation || + reservation.type !== 'receipt_reservation_identity' || + reservation.reservation_id !== body.reservation_id || + reservation.billing_id !== body.billing_id || + reservation.billing_attempt !== body.billing_attempt || + reservation.billing_epoch !== body.billing_epoch || + reservation.reservation_expires_after_epoch !== body.reservation_expires_after_epoch || + reservation.reservation_receipt_grace_epochs !== body.reservation_receipt_grace_epochs || + reservation.session_id !== body.session_id || + reservation.user !== body.user || + reservation.rail !== body.rail || + reservation.provider !== body.provider || + reservation.payout_revision !== body.payout_revision) { + return new Error('Receipt reservation identity is missing or inconsistent.'); + } + if (reservation.status !== 'active' || + reservation.closed_at !== null || + reservation.close_record_key !== null) { + return new Error('A closed targeted reservation cannot advance.'); + } + const applyState = await this.epochApplyStateRecord(); + const settlementEpoch = await this.receiptSettlementEpoch(applyState); + if (settlementEpoch instanceof Error) return settlementEpoch; + if (body.billing_epoch > settlementEpoch) { + return new Error('Receipt billing epoch is in the future.'); + } + const isFinal = body.final === true; + let epochIndex = null; + if (isFinal) { + if (existingHead?.index_position !== null && + existingHead?.index_position !== undefined) { + return new Error('Non-final canonical receipt head was unexpectedly indexed.'); + } + epochIndex = await this.nextReceiptEpochIndex( + settlementEpoch, + body.billing_id, + body.billing_attempt + ); + if (epochIndex instanceof Error) return epochIndex; + } + const head = { + type: 'canonical_receipt_head', + billing_id: body.billing_id, + billing_attempt: body.billing_attempt, + epoch: isFinal ? settlementEpoch : null, + billing_epoch: body.billing_epoch, + settlement_epoch: isFinal ? settlementEpoch : null, + index_position: isFinal ? epochIndex.position : null, + settlement_ready: isFinal, + user: body.user, + rail: body.rail, + provider: body.provider, + payout_revision: body.payout_revision, + session_id: body.session_id, + reservation_id: body.reservation_id, + receipt_seq: body.seq, + receipt_hash: receiptHash, + incremental_au: incrementalAu, + receipt: cloneValue(normalized.receipt), + feature_key: key, + updated_at: key, + }; + let closure = null; + let nextMetadata = null; + if (isFinal) { + const closeRecordKey = this.receiptReservationCloseKey(body.reservation_id); + if ((await this.get(closeRecordKey)) !== null) { + return new Error('Targeted reservation close record already exists.'); + } + closure = reservationState.kind === 'legacy' + ? this.prepareLegacyTargetedReservationClosure({ + legacyRelease: reservationState.legacyRelease, + hold: reservationState.hold, + session, + reservation, + head, + closeRecordKey, + closedBy: body.provider, + closedByRole: 'provider', + at: body.ts, + reason: 'final_receipt', + }) + : this.prepareShardedTargetedReservationClosure({ + summary: reservationState.summary, + session, + reservation, + head, + closeRecordKey, + closedBy: body.provider, + closedByRole: 'provider', + at: body.ts, + reason: 'final_receipt', + }); + if (closure instanceof Error) return closure; + if (epochIndex.index.revision >= Number.MAX_SAFE_INTEGER) { + return new Error('Canonical receipt epoch revision overflow.'); + } + nextMetadata = { + ...epochIndex.index, + revision: epochIndex.index.revision + 1, + updated_at: key, + }; + } + await this.put(headKey, head); + if (isFinal) { + await this.put(epochIndex.page_key, epochIndex.page); + await this.put(epochIndex.index_key, nextMetadata); + if (reservationState.kind === 'legacy') { + await this.put( + this.targetedSpendLegacyReleaseSummaryKey(body.user, body.rail), + closure.legacy_release + ); + await this.put(reservationState.legacySessionKey, closure.session); + } else { + await this.put( + this.targetedSpendSummaryKey(body.user, body.rail), + closure.summary + ); + await this.put(reservationState.sessionKey, closure.session); + } + await this.put(reservationKey, closure.reservation); + await this.put(this.receiptReservationCloseKey(body.reservation_id), closure.close_record); + } + return { + ok: true, + op: 'recordUsageReceipt', + epoch: isFinal ? settlementEpoch : null, + billing_epoch: body.billing_epoch, + billing_id: body.billing_id, + billing_attempt: body.billing_attempt, + receipt_seq: body.seq, + receipt_hash: receiptHash, + idempotent: false, + }; + } + + async applyCloseUsageReservationFeature(key, value, { expiry = false } = {}) { + const normalized = this.normalizeCloseUsageReservationValue(value, { expiry }); + if (normalized instanceof Error) return normalized; + const expectedKey = await this.closeUsageReservationFeatureKey(normalized, { expiry }); + if (expectedKey instanceof Error) return expectedKey; + if (key !== expectedKey) return new Error('Invalid close usage reservation feature key.'); + const verify = this.protocol?.peer?.wallet?.verify; + if (typeof verify !== 'function' || + verify.call( + this.protocol.peer.wallet, + normalized.actor_sig, + expiry + ? expireUsageReservationMessage(normalized) + : closeUsageReservationMessage(normalized), + normalized.actor + ) !== true) { + return new Error('Invalid close usage reservation signature.'); + } + + const closeRecordKey = this.receiptReservationCloseKey(normalized.reservation_id); + const existingClose = await this.get(closeRecordKey); + if (existingClose !== null) { + if (existingClose.feature_key === key) { + return { + ok: true, + op: expiry ? 'expireUsageReservation' : 'closeUsageReservation', + billing_epoch: normalized.billing_epoch, + reservation_id: normalized.reservation_id, + settlement_epoch: existingClose.settlement_epoch, + idempotent: true, + }; + } + return new Error('Targeted reservation close conflicts with its canonical close.'); + } + + const reservationKey = this.receiptReservationKey(normalized.reservation_id); + const reservation = await this.get(reservationKey); + if (!reservation || + reservation.type !== 'receipt_reservation_identity' || + reservation.status !== 'active' || + reservation.closed_at !== null || + reservation.close_record_key !== null || + reservation.reservation_id !== normalized.reservation_id || + reservation.billing_id !== normalized.billing_id || + reservation.billing_attempt !== normalized.billing_attempt || + reservation.billing_epoch !== normalized.billing_epoch || + reservation.reservation_expires_after_epoch !== + normalized.reservation_expires_after_epoch || + reservation.reservation_receipt_grace_epochs !== + normalized.reservation_receipt_grace_epochs || + reservation.session_id !== normalized.session_id || + reservation.user !== normalized.user || + reservation.rail !== normalized.rail || + reservation.provider !== normalized.provider || + reservation.payout_revision !== normalized.payout_revision) { + return new Error('Close usage reservation does not match an active reservation.'); + } + const reservationState = await this.targetedSpendReservationState( + normalized.user, + normalized.rail, + normalized.reservation_id, + normalized.session_id + ); + if (reservationState instanceof Error) return reservationState; + const session = reservationState.kind === 'missing' + ? null + : reservationState.session; + if (!session || + session.billing_id !== normalized.billing_id || + session.billing_attempt !== normalized.billing_attempt || + session.billing_epoch !== normalized.billing_epoch || + session.reservation_expires_after_epoch !== + normalized.reservation_expires_after_epoch || + session.reservation_receipt_grace_epochs !== + normalized.reservation_receipt_grace_epochs || + session.provider !== normalized.provider || + session.payout_revision !== normalized.payout_revision) { + return new Error('Close usage reservation does not match its outstanding hold.'); + } + + const headKey = this.receiptHeadKey( + normalized.billing_id, + normalized.billing_attempt + ); + const existingHead = await this.get(headKey); + if (existingHead === null) { + if (normalized.latest_receipt_seq !== null || + normalized.latest_receipt_hash !== null) { + return new Error('Close usage reservation receipt head does not exist.'); + } + } else { + if (existingHead.type !== 'canonical_receipt_head' || + existingHead.billing_epoch !== normalized.billing_epoch || + existingHead.reservation_id !== normalized.reservation_id || + existingHead.session_id !== normalized.session_id || + existingHead.user !== normalized.user || + existingHead.rail !== normalized.rail || + existingHead.provider !== normalized.provider || + existingHead.payout_revision !== normalized.payout_revision || + existingHead.receipt_seq !== normalized.latest_receipt_seq || + existingHead.receipt_hash !== normalized.latest_receipt_hash) { + return new Error('Close usage reservation does not match the canonical receipt head.'); + } + if (existingHead.settlement_ready === true || + existingHead.receipt?.body?.final === true || + (await this.get( + this.receiptConsumedKey(normalized.billing_id, normalized.billing_attempt) + )) !== null) { + return new Error('Finalized or consumed receipt evidence cannot be closed again.'); + } + } + + const applyState = await this.epochApplyStateRecord(); + const settlementEpoch = await this.receiptSettlementEpoch(applyState); + if (settlementEpoch instanceof Error) return settlementEpoch; + if (normalized.billing_epoch > settlementEpoch) { + return new Error('Close usage reservation billing epoch is in the future.'); + } + if (expiry) { + if ( + normalized.reservation_expires_after_epoch > + Number.MAX_SAFE_INTEGER - normalized.reservation_receipt_grace_epochs || + applyState.updated_epoch < + normalized.reservation_expires_after_epoch + + normalized.reservation_receipt_grace_epochs + ) { + return new Error( + 'Buyer reservation close is not yet past canonical expiry and receipt grace.' + ); + } + } + let head = null; + let epochIndex = null; + let nextMetadata = null; + if (existingHead !== null) { + epochIndex = await this.nextReceiptEpochIndex( + settlementEpoch, + normalized.billing_id, + normalized.billing_attempt + ); + if (epochIndex instanceof Error) return epochIndex; + if (epochIndex.index.revision >= Number.MAX_SAFE_INTEGER) { + return new Error('Canonical receipt epoch revision overflow.'); + } + head = { + ...existingHead, + epoch: settlementEpoch, + settlement_epoch: settlementEpoch, + index_position: epochIndex.position, + settlement_ready: true, + updated_at: key, + }; + nextMetadata = { + ...epochIndex.index, + revision: epochIndex.index.revision + 1, + updated_at: key, + }; + } + const closure = reservationState.kind === 'legacy' + ? this.prepareLegacyTargetedReservationClosure({ + legacyRelease: reservationState.legacyRelease, + hold: reservationState.hold, + session, + reservation, + head, + closeRecordKey, + closedBy: normalized.actor, + closedByRole: normalized.actor_role, + at: normalized.at, + reason: normalized.reason, + }) + : this.prepareShardedTargetedReservationClosure({ + summary: reservationState.summary, + session, + reservation, + head, + closeRecordKey, + closedBy: normalized.actor, + closedByRole: normalized.actor_role, + at: normalized.at, + reason: normalized.reason, + }); + if (closure instanceof Error) return closure; + const closeRecord = { + ...closure.close_record, + settlement_epoch: head?.settlement_epoch ?? null, + actor_sig: normalized.actor_sig, + feature_key: key, + signed_evidence: closeUsageReservationEvidence(normalized), + }; + + if (head !== null) { + await this.put(headKey, head); + await this.put(epochIndex.page_key, epochIndex.page); + await this.put(epochIndex.index_key, nextMetadata); + } + if (reservationState.kind === 'legacy') { + await this.put( + this.targetedSpendLegacyReleaseSummaryKey(normalized.user, normalized.rail), + closure.legacy_release + ); + if (closure.session) { + await this.put(reservationState.legacySessionKey, closure.session); + } else { + await this.del(reservationState.legacySessionKey); + } + } else { + await this.put( + this.targetedSpendSummaryKey(normalized.user, normalized.rail), + closure.summary + ); + if (closure.session) { + await this.put(reservationState.sessionKey, closure.session); + } else { + await this.del(reservationState.sessionKey); + await this.del(reservationState.sessionIndexKey); + await this.del(reservationState.billingAttemptKey); + } + } + await this.put(reservationKey, closure.reservation); + await this.put(closeRecordKey, closeRecord); + return { + ok: true, + op: expiry ? 'expireUsageReservation' : 'closeUsageReservation', + billing_epoch: normalized.billing_epoch, + reservation_id: normalized.reservation_id, + settlement_epoch: head?.settlement_epoch ?? null, + retained_au: closeRecord.retained_au, + released_au: closeRecord.released_au, + idempotent: false, + }; + } + + async applyEpochApplyFeature(key, value) { + const expectedKey = await this.epochApplyFeatureKey(value); + if (expectedKey instanceof Error) return expectedKey; + if (key !== expectedKey) return; + + const previousTx = this.tx; + this.tx = key; + try { + return await this.epochApply(); + } finally { + this.tx = previousTx; + } + } + + async applyTargetedEpochFeature(key, value, options = {}) { + const normalized = options.normalized ?? await this.normalizeTargetedEpochFeatureValue(value); + if (normalized instanceof Error) return normalized; + const expectedKey = options.expectedKey ?? await this.targetedEpochFeatureKey(value); + if (expectedKey instanceof Error) return expectedKey; + if (key !== expectedKey) return new Error('Invalid targeted epoch feature key.'); + + const applyState = await this.epochApplyStateRecord(); + const page = this.epochApplyPage(value); + if (page instanceof Error) return page; + const lastPage = this.epochApplyLastPage(value); + if (lastPage instanceof Error) return lastPage; + const replayPosition = this.isEpochApplyPagePositionReplay( + applyState, + value.epoch, + page, + lastPage + ); + const params = await this.activeParamsAt(value.at, [ + 'fee_bps', + 'probation_successful_sessions', + 'new_provider_holdback_epochs', + 'holdback_epochs', + 'challenge_epochs', + 'canary_probe_holdback_bps', + 'canary_probe_release_min_passes', + ]); + const reservationBindings = await this.validateTargetedEpochReservationBindings( + value, + normalized.targeted_earnings, + key + ); + if (reservationBindings instanceof Error) return reservationBindings; + const allocationUpdates = normalized.allocations.map((allocation) => ({ + key: `payout/allocation/${value.epoch}/${allocation.session_id}`, + value: { + type: 'provider_payout_session_allocation', + epoch: value.epoch, + page, + ...allocation, + feature_key: key, + }, + })); + const consumptionUpdates = normalized.allocations.map((allocation) => ({ + key: this.receiptConsumedKey(allocation.billing_id, allocation.billing_attempt), + value: this.receiptConsumptionRecord(value.epoch, allocation, key), + })); + let hasPreexistingFeatureArtifact = false; + for (const update of allocationUpdates) { + const existing = await this.get(update.key); + if (existing !== null && stableJson(existing) !== stableJson(update.value)) { + return new Error('Targeted epoch session allocation already exists.'); + } + hasPreexistingFeatureArtifact ||= existing !== null; + } + for (const update of consumptionUpdates) { + const existing = await this.get(update.key); + if (existing !== null && stableJson(existing) !== stableJson(update.value)) { + return new Error('Canonical receipt billing attempt is already consumed.'); + } + hasPreexistingFeatureArtifact ||= existing !== null; + } + const epochCommit = options.commitTransition?.record ?? + await this.get(`epoch/commit/${value.epoch}`); + const boundedReceiptSettlement = + epochCommit?.apply_mode === 'targeted_receipt_pages_v1'; + const providerSettlementDeltas = new Map(); + const liabilityUpdates = []; + for (const earning of normalized.targeted_earnings) { + const binding = await this.providerPayoutBindingForEpoch( + earning.provider, + earning.rail, + earning.payout_revision, + value.epoch + ); + if (binding instanceof Error) return binding; + const provider = await this.get(`prov/${earning.provider}`); + if (!provider || provider.status !== 'active') { + return new Error('Targeted epoch provider is not active.'); + } + let priorGrossAu = ZERO_AU; + if (boundedReceiptSettlement) { + const marker = await this.get( + `epoch/earning-provider/${value.epoch}/${earning.rail}/${earning.provider}` + ); + if (marker !== null) { + if (marker.type !== 'epoch_provider_earning' || + marker.epoch !== value.epoch || + marker.rail !== earning.rail || + marker.provider !== earning.provider || + !Number.isSafeInteger(marker.last_page) || + (replayPosition ? marker.last_page !== page : marker.last_page >= page)) { + return new Error('Bounded receipt provider earning marker is inconsistent.'); + } + priorGrossAu = replayPosition + ? this.safeSubAu(marker.gross_au, earning.gross_au) + : this.normalizeAu( + marker.gross_au, + 'bounded receipt prior provider gross earning', + { allowZero: true } + ); + if (priorGrossAu instanceof Error) return priorGrossAu; + } else if (replayPosition) { + return new Error('Bounded receipt provider earning marker is missing on replay.'); + } + } + const settlementDelta = this.providerSettlementPageDelta({ + grossAu: earning.gross_au, + priorGrossAu, + rail: earning.rail, + feeBps: params.fee_bps, + }); + if (settlementDelta instanceof Error) return settlementDelta; + const { + fee_au: feeAu, + burn_au: burnAu, + provider_au: providerAu, + } = settlementDelta; + providerSettlementDeltas.set( + stableJson([earning.rail, earning.provider]), + { + gross_au: earning.gross_au, + ...settlementDelta, + } + ); + + const liabilityKey = this.providerPayoutLiabilityKey( + earning.provider, + earning.rail, + earning.payout_revision + ); + const existing = (await this.get(liabilityKey)) ?? { + type: 'provider_payout_liability', + provider: earning.provider, + rail: earning.rail, + revision: earning.payout_revision, + target: binding.target, + currency: binding.currency, + chain_id: binding.chain_id, + total_au: ZERO_AU, + held_au: ZERO_AU, + paid_cum_au: ZERO_AU, + holdbacks: [], + updated_epoch: 0, + updated_at: null, + }; + if ( + existing.provider !== earning.provider || + existing.rail !== earning.rail || + existing.revision !== earning.payout_revision || + existing.target !== binding.target || + (existing.currency ?? null) !== binding.currency || + (existing.chain_id ?? null) !== binding.chain_id + ) { + return new Error('Provider payout liability binding mismatch.'); + } + const existingLiabilityError = this.guardianValidatePayoutLiabilityRecord( + existing, + earning.provider, + earning.rail, + earning.payout_revision + ); + if (existingLiabilityError) return existingLiabilityError; + const aggregate = await this.earningRecord(earning.provider, earning.rail); + if (aggregate instanceof Error) return aggregate; + const probeGate = await this.probeGateForEarning(earning.provider, aggregate, params); + if (probeGate instanceof Error) return probeGate; + const lockedEarningEpochs = this.providerLockedEarningEpochs(provider, params); + if (lockedEarningEpochs instanceof Error) return lockedEarningEpochs; + const disputeGate = await this.providerHasOpenDispute(earning.provider); + if (disputeGate instanceof Error) return disputeGate; + const refreshed = this.refreshEarningHoldback( + existing, + value.epoch, + lockedEarningEpochs, + probeGate, + disputeGate + ); + if (refreshed instanceof Error) return refreshed; + const totalAu = this.safeAddAu(refreshed.total_au, providerAu); + if (totalAu instanceof Error) return totalAu; + const heldAu = this.safeAddAu(refreshed.held_au, providerAu); + if (heldAu instanceof Error) return heldAu; + const holdbacks = this.appendHoldbackBucket( + refreshed.holdbacks, + value.epoch, + providerAu, + lockedEarningEpochs + ); + if (holdbacks instanceof Error) return holdbacks; + const nextLiability = { + ...refreshed, + total_au: totalAu, + held_au: heldAu, + holdbacks, + updated_epoch: value.epoch, + updated_at: key, + }; + const nextLiabilityError = this.guardianValidatePayoutLiabilityRecord( + nextLiability, + earning.provider, + earning.rail, + earning.payout_revision + ); + if (nextLiabilityError) return nextLiabilityError; + liabilityUpdates.push({ + key: liabilityKey, + value: nextLiability, + snapshot_key: this.providerPayoutEpochSnapshotKey( + value.epoch, + page, + earning.provider, + earning.rail + ), + snapshot: { + type: 'provider_payout_epoch_binding', + epoch: value.epoch, + page, + provider: earning.provider, + rail: earning.rail, + revision: earning.payout_revision, + target: binding.target, + currency: binding.currency, + chain_id: binding.chain_id, + earned_au: providerAu, + feature_key: key, + }, + }); + } + const liabilityIndexUpdates = await this.nextProviderPayoutLiabilityIndexes( + liabilityUpdates, + value.epoch, + key + ); + if (liabilityIndexUpdates instanceof Error) return liabilityIndexUpdates; + for (const update of liabilityUpdates) { + const existing = await this.get(update.snapshot_key); + if (existing !== null && stableJson(existing) !== stableJson(update.snapshot)) { + return new Error('Targeted epoch payout snapshot already exists.'); + } + hasPreexistingFeatureArtifact ||= existing !== null; + } + for (const update of liabilityIndexUpdates) { + const existing = await this.get(update.key); + hasPreexistingFeatureArtifact ||= + existing !== null && stableJson(existing) === stableJson(update.value); + } + if (hasPreexistingFeatureArtifact && !replayPosition) { + return new Error('Targeted epoch feature artifacts require an idempotent page replay.'); + } + + const previousTx = this.tx; + this.tx = key; + let result; + try { + result = await this.targetedEpochApply( + normalized.ledger_value, + normalized.revision_bindings, + normalized.allocations, + { + commitTransition: options.commitTransition ?? null, + providerSettlementDeltas, + canonicalMarketUsage: reservationBindings.market_usage, + } + ); + } finally { + this.tx = previousTx; + } + if (!result || result instanceof Error || result.ok !== true) return result; + if (result.idempotent === true) { + for (const update of allocationUpdates) { + const allocation = await this.get(update.key); + if (!allocation || stableJson(allocation) !== stableJson(update.value)) { + return new Error('Targeted epoch session allocation is missing.'); + } + } + for (const update of liabilityUpdates) { + const snapshot = await this.get(update.snapshot_key); + if (!snapshot || stableJson(snapshot) !== stableJson(update.snapshot)) { + return new Error('Targeted epoch liability snapshot is missing.'); + } + } + for (const update of liabilityIndexUpdates) { + const index = await this.get(update.key); + if (!index || stableJson(index) !== stableJson(update.value)) { + return new Error('Targeted epoch liability index is missing.'); + } + } + for (const update of consumptionUpdates) { + const consumption = await this.get(update.key); + if (!consumption || stableJson(consumption) !== stableJson(update.value)) { + return new Error('Canonical receipt consumption is missing.'); + } + } + return { + ...result, + op: options.commitTransition ? 'commitApplyTargetedEpochPage0' : 'applyTargetedEpoch', + ...(options.commitTransition ? { + commit_hash: options.commitTransition.record.commit_hash, + replaced_commit: false, + } : {}), + }; + } + + for (const update of reservationBindings.hold_updates) { + await this.put(update.key, update.value); + } + for (const update of reservationBindings.summary_updates) { + await this.put(update.key, update.value); + } + for (const update of reservationBindings.legacy_release_updates) { + await this.put(update.key, update.value); + } + for (const deleteKey of reservationBindings.session_deletes) { + await this.del(deleteKey); + } + for (const update of allocationUpdates) { + await this.put(update.key, update.value); + } + for (const update of consumptionUpdates) { + await this.put(update.key, update.value); + } + for (const update of liabilityUpdates) { + await this.put(update.key, update.value); + await this.put(update.snapshot_key, update.snapshot); + const pointerKey = this.providerPayoutBindingPointerKey( + update.value.provider, + update.value.rail + ); + const pointer = await this.get(pointerKey); + if (pointer?.pending_revision === update.value.revision && + pointer.pending_activation_epoch <= value.epoch) { + await this.put(pointerKey, { + ...pointer, + current_revision: update.value.revision, + pending_revision: null, + pending_activation_epoch: null, + updated_at: key, + }); + } + } + for (const update of liabilityIndexUpdates) { + await this.put(update.key, update.value); + } + return { + ...result, + op: options.commitTransition ? 'commitApplyTargetedEpochPage0' : 'applyTargetedEpoch', + ...(options.commitTransition ? { + commit_hash: options.commitTransition.record.commit_hash, + replaced_commit: options.commitTransition.archive !== null, + } : {}), + }; + } + + async applyCommitTargetedEpochPageZeroFeature(key, value) { + const expectedKey = await this.commitTargetedEpochPageZeroFeatureKey(value); + if (expectedKey instanceof Error) return expectedKey; + if (key !== expectedKey) return new Error('Invalid commit-plus-page-zero feature key.'); + const prepared = await this.normalizeCommitTargetedEpochPageZeroFeatureValue(value); + if (prepared instanceof Error) return prepared; + const applyState = await this.epochApplyStateRecord(); + const commitTransition = await this.prepareTargetedEpochCommitTransition( + prepared, + applyState, + key + ); + if (commitTransition instanceof Error) return commitTransition; + return await this.applyTargetedEpochFeature( + key, + prepared.targeted_value, + { + normalized: prepared.normalized, + expectedKey: key, + commitTransition, + } + ); + } + + async applyDepositTnkFeature(key, value) { + const shapeError = this.validateExactObjectKeys( + value, + ['op', 'sender', 'intent', 'sig'], + 'deposit TNK feature' + ); + if (shapeError) return shapeError; + if (value.op !== 'deposit_tnk') return new Error('Invalid deposit TNK feature op.'); + if (!this.isHexBytes(value.sender, 32)) return new Error('Invalid deposit TNK sender.'); + if (!this.isHexBytes(value.sig, 64)) return new Error('Invalid deposit TNK signature.'); + const intentError = this.validateDepositTnkIntent(value.intent); + if (intentError) return intentError; + if (!this.verifyDepositTnkSignature(value.sender, value.intent, value.sig)) { + return new Error('Invalid deposit TNK signature.'); + } + const expectedKey = await this.depositFeatureKey(value); + if (expectedKey instanceof Error) return expectedKey; + if (key !== expectedKey) return; + + const previousAddress = this.address; + const previousTx = this.tx; + const previousValue = this.value; + this.address = value.sender; + this.tx = key; + this.value = value.intent; + try { + return await this.depositTnk(); + } finally { + this.address = previousAddress; + this.tx = previousTx; + this.value = previousValue; + } + } + + async applyDepositCreditFeature(key, value) { + const expectedKey = await this.depositFeatureKey(value); + if (expectedKey instanceof Error) return expectedKey; + if (key !== expectedKey) return; + + const previousTx = this.tx; + this.tx = key; + try { + if (value.op === 'tnk_deposit') return await this.tnkDeposit(); + if (value.op === 'tap_deposit') return await this.tapDeposit(); + if (value.op === 'tap_deposit_reversal') return await this.tapDepositReversal(); + if (value.op === 'fiat_deposit') return await this.fiatDeposit(); + if (value.op === 'fiat_chargeback') return await this.fiatChargeback(); + return; + } finally { + this.tx = previousTx; + } + } + + async applyRateOracleFeature(key, value) { + this._mayhemApplyStage = 'rate:key'; + const expectedKey = await this.rateFeatureKey(value); + if (expectedKey instanceof Error) return expectedKey; + if (key !== expectedKey) return; + + const previousTx = this.tx; + this.tx = key; + try { + this._mayhemApplyStage = 'rate:dispatch'; + if (value.op === 'rate_oracle') return await this.rateOracle(); + if (value.op === 'tap_rate_oracle') return await this.tapRateOracle(); + return; + } finally { + this.tx = previousTx; + } + } + + async normalizeTargetedPayoutPreparationValue(value) { + const shapeError = this.validateExactObjectKeys( + value, + [ + 'op', + 'contract_version', + 'economic_op_id', + 'rail', + 'epoch', + 'epoch_apply_hash', + 'prepared_at', + 'kind', + 'output_index', + 'payload_hash', + 'payload', + 'liability', + 'external_effect_ids', + 'admin', + 'admin_sig', + ], + 'targeted payout preparation' + ); + if (shapeError) return shapeError; + const rail = this.normalizeLedgerRail(value.rail, 'targeted payout preparation rail'); + if (rail instanceof Error) return rail; + if (value.op !== 'prepare_targeted_payout' || + value.contract_version !== CONTRACT_VERSION || + !this.isHexBytes(value.economic_op_id, 32) || + value.economic_op_id !== value.economic_op_id.toLowerCase() || + !Number.isSafeInteger(value.epoch) || + value.epoch < 1 || + !this.isHexBytes(value.epoch_apply_hash, 32) || + value.epoch_apply_hash !== value.epoch_apply_hash.toLowerCase() || + !Number.isSafeInteger(value.prepared_at) || + value.prepared_at < 0 || + !['liability', 'fee', 'tap_root'].includes(value.kind) || + !Number.isSafeInteger(value.output_index) || + value.output_index < 0 || + !this.isHexBytes(value.payload_hash, 32) || + value.payload_hash !== value.payload_hash.toLowerCase() || + !value.payload || + typeof value.payload !== 'object' || + Array.isArray(value.payload) || + !Array.isArray(value.external_effect_ids) || + value.external_effect_ids.length > 2 || + !this.isHexBytes(value.admin, 32) || + value.admin !== value.admin.toLowerCase() || + !this.isHexBytes(value.admin_sig, 64)) { + return new Error('Invalid targeted payout preparation.'); + } + if (b4a.byteLength(stableJson(value.payload)) > 16_384) { + return new Error('Targeted payout preparation payload exceeds 16384 bytes.'); + } + const externalEffectIds = value.external_effect_ids.map((effectId) => + String(effectId).toLowerCase() + ); + if (externalEffectIds.some((effectId) => !this.isHexBytes(effectId, 32)) || + new Set(externalEffectIds).size !== externalEffectIds.length) { + return new Error('Invalid targeted payout preparation external effect ids.'); + } + if ((value.kind === 'tap_root' && rail !== 'tap') || + (value.kind === 'tap_root' && externalEffectIds.length !== 2) || + (value.kind === 'fee' && rail === 'tap') || + (value.kind === 'fee' && rail === 'tnk' && + externalEffectIds.length !== 1) || + (value.kind === 'fee' && rail === 'fiat' && + externalEffectIds.length !== 0) || + (value.kind === 'liability' && rail === 'tnk' && + externalEffectIds.length !== 1) || + (value.kind === 'liability' && ['tap', 'fiat'].includes(rail) && + externalEffectIds.length !== 0)) { + return new Error('Targeted payout preparation kind does not match rail effects.'); + } + let liability = null; + if (value.kind === 'liability') { + const liabilityShapeError = this.validateExactObjectKeys( + value.liability, + [ + 'provider', + 'payout_revision', + 'target', + 'currency', + 'chain_id', + 'paid_cum_au_before', + 'aggregate_paid_cum_au_before', + 'liability_au', + 'paid_au', + ], + 'targeted payout preparation liability' + ); + if (liabilityShapeError) return liabilityShapeError; + if (!this.isHexBytes(value.liability.provider, 32) || + value.liability.provider !== value.liability.provider.toLowerCase() || + !this.isHexBytes(value.liability.payout_revision, 32) || + value.liability.payout_revision !== value.liability.payout_revision.toLowerCase() || + !this.isSafeKeyPart(value.liability.target) || + (value.liability.currency !== null && + this.normalizeFiatCurrency(value.liability.currency) !== value.liability.currency) || + (value.liability.chain_id !== null && + (!Number.isSafeInteger(value.liability.chain_id) || + value.liability.chain_id < 1))) { + return new Error('Invalid targeted payout preparation liability identity.'); + } + const paidCumAuBefore = this.normalizeAu( + value.liability.paid_cum_au_before, + 'targeted payout preparation liability watermark', + { allowZero: true } + ); + const aggregatePaidCumAuBefore = this.normalizeAu( + value.liability.aggregate_paid_cum_au_before, + 'targeted payout preparation aggregate watermark', + { allowZero: true } + ); + const liabilityAu = this.normalizeAu( + value.liability.liability_au, + 'targeted payout preparation liability amount', + { allowZero: false } + ); + const paidAu = this.normalizeAu( + value.liability.paid_au, + 'targeted payout preparation paid amount', + { allowZero: false } + ); + if ([paidCumAuBefore, aggregatePaidCumAuBefore, liabilityAu, paidAu] + .some((entry) => entry instanceof Error) || + this.compareAu(paidAu, liabilityAu) > 0) { + return new Error('Invalid targeted payout preparation liability amount.'); + } + liability = { + provider: value.liability.provider, + payout_revision: value.liability.payout_revision, + target: value.liability.target, + currency: value.liability.currency, + chain_id: value.liability.chain_id, + paid_cum_au_before: paidCumAuBefore, + aggregate_paid_cum_au_before: aggregatePaidCumAuBefore, + liability_au: liabilityAu, + paid_au: paidAu, + }; + } else if (value.liability !== null) { + return new Error('Non-liability payout preparation cannot bind a liability.'); + } + let payload = stableValue(value.payload); + if (rail === 'fiat') { + payload = this.normalizeTargetedFiatPreparationPayload( + value, + payload, + liability + ); + if (payload instanceof Error) return payload; + } else if (rail === 'tnk') { + payload = await this.normalizeTargetedTnkPreparationPayload( + value, + payload, + liability, + externalEffectIds + ); + if (payload instanceof Error) return payload; + } + if (stableJson(payload) !== stableJson(value.payload)) { + return new Error('Targeted payout preparation payload must be canonical.'); + } + const payloadHash = await this.opaqueHash( + 'mayhem-targeted-payout-preparation-payload-v1', + { + economic_op_id: value.economic_op_id, + rail, + epoch: value.epoch, + epoch_apply_hash: value.epoch_apply_hash, + kind: value.kind, + output_index: value.output_index, + payload, + } + ); + if (payloadHash !== value.payload_hash) { + return new Error('Targeted payout preparation payload hash mismatch.'); + } + const normalized = { + op: 'prepare_targeted_payout', + contract_version: CONTRACT_VERSION, + economic_op_id: value.economic_op_id, + rail, + epoch: value.epoch, + epoch_apply_hash: value.epoch_apply_hash, + prepared_at: value.prepared_at, + kind: value.kind, + output_index: value.output_index, + payload_hash: value.payload_hash, + payload, + liability, + external_effect_ids: externalEffectIds, + admin: value.admin, + admin_sig: value.admin_sig.toLowerCase(), + }; + if (stableJson(normalized) !== stableJson(value)) { + return new Error('Targeted payout preparation must be canonical.'); + } + return normalized; + } + + payoutPreparationRecordKey(rail, economicOpId) { + return `payout/preparation/${rail}/${economicOpId}`; + } + + payoutPreparationLiabilityLockKey(rail, liability) { + return ( + `payout/preparation-lock/${rail}/${liability.provider}/` + + `${liability.payout_revision}/${liability.paid_cum_au_before}` + ); + } + + payoutPreparationAggregateTailKey(rail, provider) { + return `payout/preparation-tail/${rail}/${provider}`; + } + + payoutPreparationEffectLockKey(rail, effectId) { + return `payout/preparation-effect/${rail}/${effectId}`; + } + + async validatePayoutPreparationLiability(value) { + const liability = value.liability; + const binding = await this.get( + this.providerPayoutBindingFeatureKey( + value.rail, + liability.provider, + liability.payout_revision + ) + ); + if (!binding || + binding.verified !== true || + binding.provider !== liability.provider || + binding.rail !== value.rail || + binding.revision !== liability.payout_revision || + binding.activation_epoch > value.epoch || + binding.target !== liability.target || + (binding.currency ?? null) !== liability.currency || + (binding.chain_id ?? null) !== liability.chain_id) { + return new Error('Targeted payout preparation requires its immutable payout binding.'); + } + const liabilityKey = this.providerPayoutLiabilityKey( + liability.provider, + value.rail, + liability.payout_revision + ); + const current = await this.get(liabilityKey); + if (!current || + current.provider !== liability.provider || + current.rail !== value.rail || + current.revision !== liability.payout_revision || + current.target !== liability.target || + (current.currency ?? null) !== liability.currency || + (current.chain_id ?? null) !== liability.chain_id || + current.paid_cum_au !== liability.paid_cum_au_before) { + return new Error('Targeted payout preparation liability watermark mismatch.'); + } + const provider = await this.get(`prov/${liability.provider}`); + if (!provider || + (provider.status !== 'active' && provider.status !== 'banned')) { + return new Error('Targeted payout preparation provider status is not payable.'); + } + const params = await this.activeParamsAt(value.prepared_at, [ + 'holdback_epochs', + 'challenge_epochs', + 'canary_probe_holdback_bps', + 'canary_probe_release_min_passes', + ]); + if (params instanceof Error) return params; + const probeGate = await this.probeGateForEarning( + liability.provider, + current, + params + ); + if (probeGate instanceof Error) return probeGate; + const lockedEpochs = this.providerLockedEarningEpochs(provider, params); + if (lockedEpochs instanceof Error) return lockedEpochs; + const disputeGate = await this.providerHasOpenDispute(liability.provider); + if (disputeGate instanceof Error) return disputeGate; + const refreshed = this.refreshEarningHoldback( + current, + value.epoch, + lockedEpochs, + probeGate, + disputeGate + ); + if (refreshed instanceof Error) return refreshed; + const payable = this.safeSubAu( + this.safeSubAu(refreshed.total_au, refreshed.held_au), + refreshed.paid_cum_au + ); + if (payable instanceof Error) return payable; + if (this.compareAu(liability.liability_au, payable) > 0) { + return new Error('Targeted payout preparation exceeds revision liability.'); + } + const earning = await this.earningRecord(liability.provider, value.rail); + if (earning instanceof Error) return earning; + const tailKey = this.payoutPreparationAggregateTailKey(value.rail, liability.provider); + const tail = await this.get(tailKey); + if (tail && + typeof tail.consumed !== 'boolean') { + return new Error('Targeted payout preparation aggregate tail is invalid.'); + } + if (tail?.consumed === true && + tail.paid_cum_au_after !== earning.paid_cum_au) { + return new Error('Consumed payout preparation tail does not match aggregate earnings.'); + } + const expectedAggregateBefore = tail?.consumed === false + ? tail.paid_cum_au_after + : earning.paid_cum_au; + if (liability.aggregate_paid_cum_au_before !== expectedAggregateBefore) { + return new Error('Targeted payout preparation aggregate watermark mismatch.'); + } + const paidCumAuAfter = this.safeAddAu( + liability.aggregate_paid_cum_au_before, + liability.paid_au + ); + if (paidCumAuAfter instanceof Error) return paidCumAuAfter; + return { + liability_key: liabilityKey, + tail_key: tailKey, + paid_cum_au_after: paidCumAuAfter, + }; + } + + async applyTargetedPayoutPreparationFeature(key, value) { + const normalized = await this.normalizeTargetedPayoutPreparationValue(value); + if (normalized instanceof Error) return normalized; + const expectedKey = await this.targetedPayoutPreparationFeatureKey(normalized); + if (expectedKey instanceof Error) return expectedKey; + if (key !== expectedKey) return new Error('Invalid targeted payout preparation key.'); + const admin = await this.get('admin'); + if (normalized.admin !== admin || this.address !== admin) { + return new Error('Targeted payout preparation requires canonical admin authority.'); + } + const verify = this.protocol?.peer?.wallet?.verify; + if (typeof verify !== 'function' || + verify.call( + this.protocol.peer.wallet, + normalized.admin_sig, + payoutPreparationMessage(normalized), + admin + ) !== true) { + return new Error('Invalid targeted payout preparation admin signature.'); + } + const applyAnchor = await this.requireEpochApplyAnchor( + normalized.epoch, + normalized.epoch_apply_hash, + 'Targeted payout preparation' + ); + if (applyAnchor instanceof Error) return applyAnchor; + if (['fiat', 'tnk'].includes(normalized.rail)) { + const plan = await this.get( + this.targetedPayoutEpochPlanRecordKey(normalized.rail, normalized.epoch) + ); + const output = plan?.value?.outputs?.[normalized.output_index]; + if (!plan || + plan.plan_root !== normalized.payload.plan_root || + plan.value.epoch_apply_hash !== normalized.epoch_apply_hash || + output?.economic_op_id !== normalized.economic_op_id || + stableJson(output) !== stableJson(normalized.payload.output)) { + return new Error( + 'Targeted payout preparation does not match its canonical epoch plan.' + ); + } + } + if (normalized.rail === 'tnk') { + const payment = await this.canonicalTnkPaymentConfig(); + if (payment instanceof Error) return payment; + if (normalized.payload.network !== payment.network || + normalized.payload.treasury_from !== payment.treasury_address) { + return new Error( + 'Targeted TNK preparation source does not match payment config.' + ); + } + } + if (normalized.rail === 'tap') { + const params = await this.activeParamsAt(normalized.prepared_at, ['fee_bps']); + const split = this.targetedTapPayoutSplit(params.fee_bps); + if (split instanceof Error) return split; + if (normalized.payload.fee_bps !== split.fee_bps || + normalized.payload.tap_burn_bps !== split.tap_burn_bps || + normalized.payload.provider_share_bps !== split.provider_share_bps) { + return new Error( + 'Targeted TAP preparation does not match the fixed on-chain split.' + ); + } + } + const recordKey = this.payoutPreparationRecordKey( + normalized.rail, + normalized.economic_op_id + ); + const record = { + type: 'targeted_payout_preparation', + ...normalized, + consumed: false, + consumed_by: null, + prepared_at_tx: this.tx, + }; + const existing = await this.get(recordKey); + if (existing !== null) { + if (stableJson(existing) === stableJson(record)) { + return { + ok: true, + op: 'targetedPayoutPreparation', + rail: normalized.rail, + economic_op_id: normalized.economic_op_id, + idempotent: true, + }; + } + return new Error('Targeted payout preparation economic operation already exists.'); + } + let liabilityState = null; + if (normalized.liability !== null) { + liabilityState = await this.validatePayoutPreparationLiability(normalized); + if (liabilityState instanceof Error) return liabilityState; + const lockKey = this.payoutPreparationLiabilityLockKey( + normalized.rail, + normalized.liability + ); + if ((await this.get(lockKey)) !== null) { + return new Error('Targeted payout liability watermark already has a preparation.'); + } + } + for (const effectId of normalized.external_effect_ids) { + if ((await this.get( + this.payoutPreparationEffectLockKey(normalized.rail, effectId) + )) !== null) { + return new Error('Targeted payout external effect id already has a preparation.'); + } + } + if (normalized.liability !== null) { + await this.put( + this.payoutPreparationLiabilityLockKey(normalized.rail, normalized.liability), + { + economic_op_id: normalized.economic_op_id, + rail: normalized.rail, + provider: normalized.liability.provider, + payout_revision: normalized.liability.payout_revision, + paid_cum_au_before: normalized.liability.paid_cum_au_before, + prepared_at: this.tx, + } + ); + await this.put(liabilityState.tail_key, { + economic_op_id: normalized.economic_op_id, + paid_cum_au_before: normalized.liability.aggregate_paid_cum_au_before, + paid_cum_au_after: liabilityState.paid_cum_au_after, + consumed: false, + updated_at: this.tx, + }); + } + for (const effectId of normalized.external_effect_ids) { + await this.put(this.payoutPreparationEffectLockKey(normalized.rail, effectId), { + economic_op_id: normalized.economic_op_id, + effect_id: effectId, + consumed: false, + updated_at: this.tx, + }); + } + await this.put(recordKey, record); + return { + ok: true, + op: 'targetedPayoutPreparation', + rail: normalized.rail, + economic_op_id: normalized.economic_op_id, + idempotent: false, + }; + } + + async requireTargetedPayoutAdminSignature(value, label) { + const admin = await this.get('admin'); + if (value.admin !== admin || this.address !== admin) { + return new Error(`${label} requires canonical admin authority.`); + } + const verify = this.protocol?.peer?.wallet?.verify; + if (typeof verify !== 'function' || + verify.call( + this.protocol.peer.wallet, + value.admin_sig, + targetedPayoutControlMessage(value), + admin + ) !== true) { + return new Error(`Invalid ${label.toLowerCase()} admin signature.`); + } + return null; + } + + targetedPayoutEpochPlanRecordKey(rail, epoch) { + return `payout/epoch-plan/${rail}/${epoch}`; + } + + targetedPayoutOutputRecordKey(rail, epoch, economicOpId) { + return `settle/targeted/${rail}/${epoch}/output/${economicOpId}`; + } + + targetedPayoutEpochCloseRecordKey(rail, epoch) { + return `settle/targeted/${rail}/${epoch}`; + } + + targetedFiatAttemptRecordKey(economicOpId, attemptId) { + return `payout/attempt/fiat/${economicOpId}/${attemptId}`; + } + + targetedFiatAttemptTailKey(economicOpId) { + return `payout/attempt/fiat/${economicOpId}/latest`; + } + + targetedFiatAttemptEffectKey(effectId) { + return `payout/attempt-effect/fiat/${effectId}`; + } + + normalizeTargetedPayoutPlanOutput(rail, output, expectedIndex) { + if (!output || typeof output !== 'object' || Array.isArray(output) || + !this.isHexBytes(output.economic_op_id, 32) || + output.economic_op_id !== output.economic_op_id.toLowerCase() || + output.output_index !== expectedIndex) { + return new Error('Invalid targeted payout epoch output identity.'); + } + const { + economic_op_id: economicOpId, + output_index: outputIndex, + ...economicOutput + } = output; + let normalized; + if (rail === 'tnk') { + const allowed = economicOutput.role === 'provider' + ? [ + 'role', + 'provider', + 'payout_revision', + 'to', + 'paid_cum_au_before', + 'aggregate_paid_cum_au_before', + 'au', + 'tnk_e18', + ] + : ['role', 'to', 'au', 'tnk_e18']; + const shapeError = this.validateExactObjectKeys( + economicOutput, + allowed, + 'targeted TNK epoch output' + ); + if (shapeError) return shapeError; + if (economicOutput.role === 'provider') { + if (!this.isHexBytes(economicOutput.provider, 32) || + economicOutput.provider !== economicOutput.provider.toLowerCase() || + !this.isHexBytes(economicOutput.payout_revision, 32) || + economicOutput.payout_revision !== economicOutput.payout_revision.toLowerCase()) { + return new Error('Invalid targeted TNK epoch provider identity.'); + } + const paidCumAuBefore = this.normalizeAu( + economicOutput.paid_cum_au_before, + 'targeted TNK epoch liability watermark', + { allowZero: true } + ); + const aggregatePaidCumAuBefore = this.normalizeAu( + economicOutput.aggregate_paid_cum_au_before, + 'targeted TNK epoch aggregate watermark', + { allowZero: true } + ); + if (paidCumAuBefore instanceof Error || + aggregatePaidCumAuBefore instanceof Error) { + return new Error('Invalid targeted TNK epoch output watermark.'); + } + normalized = this.normalizeTargetedTnkSettlementOutput(economicOutput); + if (normalized instanceof Error) return normalized; + normalized = { + ...normalized, + payout_revision: economicOutput.payout_revision, + paid_cum_au_before: paidCumAuBefore, + aggregate_paid_cum_au_before: aggregatePaidCumAuBefore, + }; + } else if (economicOutput.role === 'operator_fee') { + normalized = this.normalizeTargetedTnkSettlementOutput(economicOutput); + if (normalized instanceof Error) return normalized; + } else { + return new Error('Invalid targeted TNK epoch output role.'); + } + } else if (rail === 'fiat') { + normalized = this.normalizeTargetedFiatPreparationOutput(economicOutput); + if (normalized instanceof Error) return normalized; + } else { + return new Error('Targeted payout epoch plans support only fiat and TNK.'); + } + const result = { + economic_op_id: economicOpId, + output_index: outputIndex, + ...normalized, + }; + return stableJson(result) === stableJson(output) + ? result + : new Error('Targeted payout epoch output must be canonical.'); + } + + normalizeTargetedPayoutCarry(value) { + const shapeError = this.validateExactObjectKeys( + value, + [ + 'provider', + 'payout_revision', + 'liability_au', + 'held_au', + 'payable_au', + 'payout_min_au', + 'reason', + ], + 'targeted payout epoch carry' + ); + if (shapeError) return shapeError; + if (!this.isHexBytes(value.provider, 32) || + value.provider !== value.provider.toLowerCase() || + !this.isHexBytes(value.payout_revision, 32) || + value.payout_revision !== value.payout_revision.toLowerCase() || + !['held', 'below_payout_minimum'].includes(value.reason)) { + return new Error('Invalid targeted payout epoch carry identity.'); + } + const liabilityAu = this.normalizeAu( + value.liability_au, + 'targeted payout carry liability', + { allowZero: false } + ); + const heldAu = this.normalizeAu( + value.held_au, + 'targeted payout carry held amount', + { allowZero: true } + ); + const payableAu = this.normalizeAu( + value.payable_au, + 'targeted payout carry payable amount', + { allowZero: true } + ); + const payoutMinAu = this.normalizeAu( + value.payout_min_au, + 'targeted payout carry minimum', + { allowZero: true } + ); + if ([liabilityAu, heldAu, payableAu, payoutMinAu] + .some((entry) => entry instanceof Error)) { + return new Error('Invalid targeted payout epoch carry amount.'); + } + const classified = this.safeAddAu(heldAu, payableAu); + if (classified instanceof Error || + classified !== liabilityAu || + (value.reason === 'held' && + (this.isZeroAu(heldAu) || !this.isZeroAu(payableAu))) || + (value.reason === 'below_payout_minimum' && + (this.isZeroAu(payableAu) || + this.compareAu(payableAu, payoutMinAu) >= 0))) { + return new Error('Targeted payout epoch carry classification is invalid.'); + } + return { + provider: value.provider, + payout_revision: value.payout_revision, + liability_au: liabilityAu, + held_au: heldAu, + payable_au: payableAu, + payout_min_au: payoutMinAu, + reason: value.reason, + }; + } + + async normalizeTargetedPayoutEpochValue(value) { + const shapeError = this.validateExactObjectKeys( + value, + [ + 'op', + 'contract_version', + 'rail', + 'epoch', + 'at', + 'epoch_apply_hash', + 'snapshot_signed_length', + 'outcome', + 'outputs', + 'carry', + 'outputs_root', + 'carry_root', + 'plan_root', + 'admin', + 'admin_sig', + ], + 'targeted payout epoch plan' + ); + if (shapeError) return shapeError; + if (value.op !== 'prepare_targeted_payout_epoch' || + value.contract_version !== CONTRACT_VERSION || + !['fiat', 'tnk'].includes(value.rail) || + !Number.isSafeInteger(value.epoch) || + value.epoch < 1 || + !Number.isSafeInteger(value.at) || + value.at < 0 || + !this.isHexBytes(value.epoch_apply_hash, 32) || + value.epoch_apply_hash !== value.epoch_apply_hash.toLowerCase() || + !Number.isSafeInteger(value.snapshot_signed_length) || + value.snapshot_signed_length < 1 || + !['payouts', 'carry', 'no_work'].includes(value.outcome) || + !Array.isArray(value.outputs) || + !Array.isArray(value.carry) || + !this.isHexBytes(value.outputs_root, 32) || + value.outputs_root !== value.outputs_root.toLowerCase() || + !this.isHexBytes(value.carry_root, 32) || + value.carry_root !== value.carry_root.toLowerCase() || + !this.isHexBytes(value.plan_root, 32) || + value.plan_root !== value.plan_root.toLowerCase() || + !this.isHexBytes(value.admin, 32) || + value.admin !== value.admin.toLowerCase() || + !this.isHexBytes(value.admin_sig, 64)) { + return new Error('Invalid targeted payout epoch plan.'); + } + const outputs = value.outputs.map((output, index) => + this.normalizeTargetedPayoutPlanOutput(value.rail, output, index) + ); + const outputError = outputs.find((output) => output instanceof Error); + if (outputError) return outputError; + const economicIds = new Set(outputs.map((output) => output.economic_op_id)); + if (economicIds.size !== outputs.length) { + return new Error('Targeted payout epoch output identities must be unique.'); + } + if (outputs.filter((output) => output.role === 'operator_fee').length > 1) { + return new Error('Targeted payout epoch may contain only one operator output.'); + } + for (let index = 1; index < outputs.length; index += 1) { + const left = outputs[index - 1]; + const right = outputs[index]; + const order = left.role === right.role + ? left.role === 'provider' + ? compareCodepoint(left.provider, right.provider) || + compareCodepoint(left.payout_revision, right.payout_revision) + : compareCodepoint(left.economic_op_id, right.economic_op_id) + : left.role === 'provider' ? -1 : 1; + if (order >= 0) { + return new Error('Targeted payout epoch outputs are not canonically ordered.'); + } + } + const carry = value.carry.map((entry) => this.normalizeTargetedPayoutCarry(entry)); + const carryError = carry.find((entry) => entry instanceof Error); + if (carryError) return carryError; + carry.sort((left, right) => + compareCodepoint(left.provider, right.provider) || + compareCodepoint(left.payout_revision, right.payout_revision) + ); + if (stableJson(carry) !== stableJson(value.carry) || + new Set(carry.map((entry) => + `${entry.provider}/${entry.payout_revision}` + )).size !== carry.length) { + return new Error('Targeted payout epoch carries must be canonical and unique.'); + } + const plannedLiabilities = new Set( + outputs + .filter((output) => output.role === 'provider') + .map((output) => `${output.provider}/${output.payout_revision}`) + ); + if (carry.some((entry) => + plannedLiabilities.has(`${entry.provider}/${entry.payout_revision}`) + )) { + return new Error('Targeted payout liability cannot be both payable and carried.'); + } + const expectedOutcome = outputs.length > 0 + ? 'payouts' + : carry.length > 0 ? 'carry' : 'no_work'; + if (value.outcome !== expectedOutcome) { + return new Error('Targeted payout epoch outcome does not match its work.'); + } + const outputsRoot = await this.opaqueHash( + 'mayhem-targeted-payout-epoch-outputs-v1', + { rail: value.rail, epoch: value.epoch, outputs } + ); + const carryRoot = await this.opaqueHash( + 'mayhem-targeted-payout-epoch-carry-v1', + { rail: value.rail, epoch: value.epoch, carry } + ); + const planRoot = await this.opaqueHash( + 'mayhem-targeted-payout-epoch-plan-v1', + { + rail: value.rail, + epoch: value.epoch, + at: value.at, + epoch_apply_hash: value.epoch_apply_hash, + snapshot_signed_length: value.snapshot_signed_length, + outcome: value.outcome, + outputs_root: outputsRoot, + carry_root: carryRoot, + } + ); + if (value.outputs_root !== outputsRoot || + value.carry_root !== carryRoot || + value.plan_root !== planRoot) { + return new Error('Targeted payout epoch root mismatch.'); + } + const normalized = { + op: 'prepare_targeted_payout_epoch', + contract_version: CONTRACT_VERSION, + rail: value.rail, + epoch: value.epoch, + at: value.at, + epoch_apply_hash: value.epoch_apply_hash, + snapshot_signed_length: value.snapshot_signed_length, + outcome: value.outcome, + outputs, + carry, + outputs_root: outputsRoot, + carry_root: carryRoot, + plan_root: planRoot, + admin: value.admin, + admin_sig: value.admin_sig.toLowerCase(), + }; + return stableJson(normalized) === stableJson(value) + ? normalized + : new Error('Targeted payout epoch plan must be canonical.'); + } + + async validateTargetedPayoutEpochCompleteness(value, params) { + const index = await this.providerPayoutLiabilityIndex(value.rail); + if (index instanceof Error) return index; + const providerOutputs = new Map( + value.outputs + .filter((output) => output.role === 'provider') + .map((output) => [ + `${output.provider}/${output.payout_revision}`, + output, + ]) + ); + const carries = new Map(value.carry.map((entry) => [ + `${entry.provider}/${entry.payout_revision}`, + entry, + ])); + const classified = new Set(); + const aggregateCursors = new Map(); + + for (const entry of index.entries) { + const identity = `${entry.provider}/${entry.payout_revision}`; + const liability = await this.get( + this.providerPayoutLiabilityKey( + entry.provider, + value.rail, + entry.payout_revision + ) + ); + if (!liability || + liability.type !== 'provider_payout_liability' || + liability.provider !== entry.provider || + liability.rail !== value.rail || + liability.revision !== entry.payout_revision || + liability.updated_epoch > value.epoch) { + return new Error('Targeted payout liability index does not match canonical state.'); + } + const liabilityError = this.guardianValidatePayoutLiabilityRecord( + liability, + entry.provider, + value.rail, + entry.payout_revision + ); + if (liabilityError) return liabilityError; + const provider = await this.get(`prov/${entry.provider}`); + if (!provider || + (provider.status !== 'active' && provider.status !== 'banned')) { + return new Error('Targeted payout indexed provider status is not payable.'); + } + const binding = await this.get( + this.providerPayoutBindingFeatureKey( + value.rail, + entry.provider, + entry.payout_revision + ) + ); + if (!binding || + binding.verified !== true || + binding.provider !== entry.provider || + binding.rail !== value.rail || + binding.revision !== entry.payout_revision || + binding.activation_epoch > value.epoch || + binding.target !== liability.target || + (binding.currency ?? null) !== (liability.currency ?? null) || + (binding.chain_id ?? null) !== (liability.chain_id ?? null)) { + return new Error('Targeted payout indexed liability binding mismatch.'); + } + const probeGate = await this.probeGateForEarning( + entry.provider, + liability, + params + ); + if (probeGate instanceof Error) return probeGate; + const lockedEpochs = this.providerLockedEarningEpochs(provider, params); + if (lockedEpochs instanceof Error) return lockedEpochs; + const disputeGate = await this.providerHasOpenDispute(entry.provider); + if (disputeGate instanceof Error) return disputeGate; + const refreshed = this.refreshEarningHoldback( + liability, + value.epoch, + lockedEpochs, + probeGate, + disputeGate + ); + if (refreshed instanceof Error) return refreshed; + const outstanding = this.safeSubAu( + refreshed.total_au, + refreshed.paid_cum_au + ); + const payable = this.safeSubAu(outstanding, refreshed.held_au); + if (outstanding instanceof Error || payable instanceof Error) { + return new Error('Targeted payout canonical liability is invalid.'); + } + const output = providerOutputs.get(identity) ?? null; + const carry = carries.get(identity) ?? null; + if (this.isZeroAu(outstanding)) { + if (output !== null || carry !== null) { + return new Error('Settled targeted payout liability must be omitted.'); + } + continue; + } + + const isPayable = + !this.isZeroAu(payable) && + this.compareAu(payable, params.payout_min_au) >= 0; + if (!isPayable) { + const expectedCarry = { + provider: entry.provider, + payout_revision: entry.payout_revision, + liability_au: outstanding, + held_au: refreshed.held_au, + payable_au: payable, + payout_min_au: params.payout_min_au, + reason: this.isZeroAu(payable) ? 'held' : 'below_payout_minimum', + }; + if (output !== null || + carry === null || + stableJson(carry) !== stableJson(expectedCarry)) { + return new Error( + 'Targeted payout plan must explicitly carry every held or below-minimum liability.' + ); + } + classified.add(identity); + continue; + } + + if (output === null || carry !== null || + output.to !== binding.target || + output.paid_cum_au_before !== refreshed.paid_cum_au || + (value.rail === 'tnk' && output.au !== payable) || + (value.rail === 'fiat' && + (output.liability_au !== payable || + output.destination_currency !== binding.currency))) { + return new Error( + 'Targeted payout plan must include every payable canonical liability exactly.' + ); + } + let aggregate = aggregateCursors.get(entry.provider); + if (!aggregate) { + const earning = await this.earningRecord(entry.provider, value.rail); + if (earning instanceof Error) return earning; + const earningError = this.guardianValidateEarningRecord( + earning, + entry.provider, + value.rail + ); + if (earningError) return earningError; + const aggregateProbeGate = await this.probeGateForEarning( + entry.provider, + earning, + params + ); + if (aggregateProbeGate instanceof Error) return aggregateProbeGate; + const refreshedEarning = this.refreshEarningHoldback( + earning, + value.epoch, + lockedEpochs, + aggregateProbeGate, + disputeGate + ); + if (refreshedEarning instanceof Error) return refreshedEarning; + aggregate = { paid_cum_au: refreshedEarning.paid_cum_au }; + } + if (output.aggregate_paid_cum_au_before !== aggregate.paid_cum_au) { + return new Error('Targeted payout aggregate paid watermark mismatch.'); + } + const settledAu = value.rail === 'fiat' ? output.paid_au : output.au; + const nextPaid = this.safeAddAu(aggregate.paid_cum_au, settledAu); + if (nextPaid instanceof Error) return nextPaid; + aggregateCursors.set(entry.provider, { paid_cum_au: nextPaid }); + classified.add(identity); + } + + if (classified.size !== carries.size + providerOutputs.size) { + return new Error( + 'Targeted payout plan contains a liability absent from the canonical index.' + ); + } + + const operatorOutputs = value.outputs.filter( + (output) => output.role === 'operator_fee' + ); + const fee = await this.feeCumRecord(value.rail); + if (fee instanceof Error) return fee; + const feeError = this.guardianValidateFeeRecord(fee, value.rail); + if (feeError) return feeError; + const payableFee = this.safeSubAu(fee.cum_au, fee.swept_cum_au); + if (payableFee instanceof Error) return payableFee; + if (this.isZeroAu(payableFee)) { + if (operatorOutputs.length !== 0) { + return new Error('Targeted payout plan includes a nonexistent operator fee.'); + } + } else if ( + operatorOutputs.length !== 1 || + (value.rail === 'tnk' && operatorOutputs[0].au !== payableFee) || + (value.rail === 'fiat' && + operatorOutputs[0].liability_au !== payableFee) + ) { + return new Error( + 'Targeted payout plan must include the complete canonical operator fee.' + ); + } + return null; + } + + async applyTargetedPayoutEpochFeature(key, value) { + const normalized = await this.normalizeTargetedPayoutEpochValue(value); + if (normalized instanceof Error) return normalized; + const expectedKey = await this.targetedPayoutEpochFeatureKey(normalized); + if (expectedKey instanceof Error) return expectedKey; + if (key !== expectedKey) return new Error('Invalid targeted payout epoch feature key.'); + const adminError = await this.requireTargetedPayoutAdminSignature( + normalized, + 'Targeted payout epoch plan' + ); + if (adminError) return adminError; + const applyAnchor = await this.requireEpochApplyAnchor( + normalized.epoch, + normalized.epoch_apply_hash, + 'Targeted payout epoch plan' + ); + if (applyAnchor instanceof Error) return applyAnchor; + const recordKey = this.targetedPayoutEpochPlanRecordKey( + normalized.rail, + normalized.epoch + ); + const existing = await this.get(recordKey); + if (existing !== null) { + if (existing.plan_root === normalized.plan_root && + stableJson(existing.value) === stableJson(normalized)) { + return { + ok: true, + op: 'targetedPayoutEpochPrepared', + rail: normalized.rail, + epoch: normalized.epoch, + plan_root: normalized.plan_root, + idempotent: true, + }; + } + return new Error('Targeted payout epoch plan already exists.'); + } + const applyState = await this.epochApplyStateRecord(); + if (applyState.updated_epoch !== normalized.epoch || + applyState.last_apply_hash !== normalized.epoch_apply_hash || + (applyState.pending_epoch ?? null) !== null) { + return new Error( + 'Targeted payout epoch plan must freeze the latest completed canonical apply.' + ); + } + const params = await this.activeParamsAt( + normalized.at, + [ + normalized.rail === 'tnk' + ? 'max_tnk_settlement_outputs' + : 'max_fiat_settlement_outputs', + 'payout_min_au', + 'holdback_epochs', + 'challenge_epochs', + 'canary_probe_holdback_bps', + 'canary_probe_release_min_passes', + ] + ); + const maxOutputs = normalized.rail === 'tnk' + ? params.max_tnk_settlement_outputs + : params.max_fiat_settlement_outputs; + if (normalized.outputs.length > maxOutputs) { + return new Error('Targeted payout epoch output count exceeds limit.'); + } + const completenessError = await this.validateTargetedPayoutEpochCompleteness( + normalized, + params + ); + if (completenessError) return completenessError; + await this.put(recordKey, { + type: 'targeted_payout_epoch_plan', + rail: normalized.rail, + epoch: normalized.epoch, + plan_root: normalized.plan_root, + value: normalized, + prepared_at: this.tx, + }); + return { + ok: true, + op: 'targetedPayoutEpochPrepared', + rail: normalized.rail, + epoch: normalized.epoch, + plan_root: normalized.plan_root, + idempotent: false, + }; + } + + normalizeTargetedFiatAttemptRequest(request) { + const shapeError = this.validateExactObjectKeys( + request, + [ + 'processor', + 'kind', + 'destination', + 'source_currency', + 'source_amount_minor', + 'destination_currency', + 'destination_amount_min_minor', + 'destination_amount_max_minor', + 'fx_quote_id', + 'fx_quote_hash', + 'transfer_group', + 'metadata_hash', + ], + 'targeted fiat attempt request' + ); + if (shapeError) return shapeError; + const sourceCurrency = this.normalizeFiatCurrency(request.source_currency); + const sourceAmountMinor = this.normalizeFiatMinor(request.source_amount_minor); + if (request.processor !== 'stripe' || + !['stripe_transfer', 'platform_balance'].includes(request.kind) || + !this.isSafeKeyPart(request.destination) || + sourceCurrency instanceof Error || + sourceCurrency !== request.source_currency || + sourceAmountMinor instanceof Error || + sourceAmountMinor !== request.source_amount_minor || + !this.isHexBytes(request.metadata_hash, 32) || + request.metadata_hash !== request.metadata_hash.toLowerCase()) { + return new Error('Invalid targeted fiat attempt request.'); + } + if (request.kind === 'platform_balance') { + if (request.destination_currency !== null || + request.destination_amount_min_minor !== null || + request.destination_amount_max_minor !== null || + request.fx_quote_id !== null || + request.fx_quote_hash !== null || + request.transfer_group !== null) { + return new Error('Platform-balance fiat attempt must not contain Stripe transfer terms.'); + } + return { + processor: 'stripe', + kind: 'platform_balance', + destination: request.destination, + source_currency: sourceCurrency, + source_amount_minor: sourceAmountMinor, + destination_currency: null, + destination_amount_min_minor: null, + destination_amount_max_minor: null, + fx_quote_id: null, + fx_quote_hash: null, + transfer_group: null, + metadata_hash: request.metadata_hash, + }; + } + const destinationCurrency = this.normalizeFiatCurrency(request.destination_currency); + const destinationMin = this.normalizeFiatMinor(request.destination_amount_min_minor); + const destinationMax = this.normalizeFiatMinor(request.destination_amount_max_minor); + const expectedGroup = /^mayhem_fiat_epoch_[1-9][0-9]*_[0-9a-f]{16}$/; + if (destinationCurrency instanceof Error || + destinationCurrency !== request.destination_currency || + destinationMin instanceof Error || + destinationMax instanceof Error || + BigInt(destinationMax) < BigInt(destinationMin) || + !expectedGroup.test(String(request.transfer_group || ''))) { + return new Error('Invalid targeted fiat attempt destination terms.'); + } + const requiresQuote = sourceCurrency !== 'usd' || destinationCurrency !== 'usd'; + if (requiresQuote) { + if (!/^fxq_[A-Za-z0-9._-]+$/.test(String(request.fx_quote_id || '')) || + !this.isHexBytes(request.fx_quote_hash, 32) || + request.fx_quote_hash !== request.fx_quote_hash.toLowerCase()) { + return new Error('Targeted fiat attempt requires a canonical FX quote.'); + } + } else if (request.fx_quote_id !== null || request.fx_quote_hash !== null) { + return new Error('Direct USD fiat attempt must not contain an FX quote.'); + } + return { + processor: 'stripe', + kind: 'stripe_transfer', + destination: request.destination, + source_currency: sourceCurrency, + source_amount_minor: sourceAmountMinor, + destination_currency: destinationCurrency, + destination_amount_min_minor: destinationMin, + destination_amount_max_minor: destinationMax, + fx_quote_id: request.fx_quote_id, + fx_quote_hash: request.fx_quote_hash, + transfer_group: request.transfer_group, + metadata_hash: request.metadata_hash, + }; + } + + async targetedFiatAttemptId(economicOpId, attemptNo, request) { + return await this.opaqueHash( + 'mayhem-targeted-fiat-attempt-id-v1', + { + economic_op_id: economicOpId, + attempt_no: attemptNo, + request, + } + ); + } + + async targetedFiatAttemptIdempotencyKeyHash(attemptId) { + const digest = await blake3(b4a.from(stableJson({ + domain: 'mayhem-targeted-fiat-idempotency-key-v1', + key: `mayhem:fiat:attempt:v1:${attemptId}`, + }))); + return b4a.toString(digest, 'hex'); + } + + async normalizePrepareTargetedFiatAttemptValue(value) { + const shapeError = this.validateExactObjectKeys( + value, + [ + 'op', + 'contract_version', + 'rail', + 'epoch', + 'epoch_apply_hash', + 'plan_root', + 'economic_op_id', + 'output_index', + 'attempt_id', + 'attempt_no', + 'prepared_at', + 'quote_expires_at', + 'idempotency_key_hash', + 'request_hash', + 'request', + 'admin', + 'admin_sig', + ], + 'targeted fiat attempt preparation' + ); + if (shapeError) return shapeError; + if (value.op !== 'prepare_targeted_fiat_attempt' || + value.contract_version !== CONTRACT_VERSION || + value.rail !== 'fiat' || + !Number.isSafeInteger(value.epoch) || + value.epoch < 1 || + !this.isHexBytes(value.epoch_apply_hash, 32) || + value.epoch_apply_hash !== value.epoch_apply_hash.toLowerCase() || + !this.isHexBytes(value.plan_root, 32) || + value.plan_root !== value.plan_root.toLowerCase() || + !this.isHexBytes(value.economic_op_id, 32) || + value.economic_op_id !== value.economic_op_id.toLowerCase() || + !Number.isSafeInteger(value.output_index) || + value.output_index < 0 || + !this.isHexBytes(value.attempt_id, 32) || + value.attempt_id !== value.attempt_id.toLowerCase() || + !Number.isSafeInteger(value.attempt_no) || + value.attempt_no < 1 || + !Number.isSafeInteger(value.prepared_at) || + value.prepared_at < 0 || + !this.isHexBytes(value.idempotency_key_hash, 32) || + value.idempotency_key_hash !== value.idempotency_key_hash.toLowerCase() || + !this.isHexBytes(value.request_hash, 32) || + value.request_hash !== value.request_hash.toLowerCase() || + !this.isHexBytes(value.admin, 32) || + value.admin !== value.admin.toLowerCase() || + !this.isHexBytes(value.admin_sig, 64)) { + return new Error('Invalid targeted fiat attempt preparation.'); + } + const request = this.normalizeTargetedFiatAttemptRequest(value.request); + if (request instanceof Error) return request; + const expectedAttemptId = await this.targetedFiatAttemptId( + value.economic_op_id, + value.attempt_no, + request + ); + if (value.attempt_id !== expectedAttemptId) { + return new Error('Targeted fiat attempt id does not match its canonical request.'); + } + const expectedIdempotencyKeyHash = + await this.targetedFiatAttemptIdempotencyKeyHash(expectedAttemptId); + if (value.idempotency_key_hash !== expectedIdempotencyKeyHash) { + return new Error( + 'Targeted fiat attempt idempotency key hash does not match its canonical attempt.' + ); + } + if ((request.kind === 'stripe_transfer' && + (!Number.isSafeInteger(value.quote_expires_at) || + value.quote_expires_at <= value.prepared_at)) || + (request.kind === 'platform_balance' && value.quote_expires_at !== null)) { + return new Error('Invalid targeted fiat attempt quote expiry.'); + } + const requestHash = await this.opaqueHash( + 'mayhem-targeted-fiat-attempt-request-v1', + { + economic_op_id: value.economic_op_id, + attempt_id: value.attempt_id, + attempt_no: value.attempt_no, + request, + } + ); + if (requestHash !== value.request_hash) { + return new Error('Targeted fiat attempt request hash mismatch.'); + } + const normalized = { + op: 'prepare_targeted_fiat_attempt', + contract_version: CONTRACT_VERSION, + rail: 'fiat', + epoch: value.epoch, + epoch_apply_hash: value.epoch_apply_hash, + plan_root: value.plan_root, + economic_op_id: value.economic_op_id, + output_index: value.output_index, + attempt_id: value.attempt_id, + attempt_no: value.attempt_no, + prepared_at: value.prepared_at, + quote_expires_at: value.quote_expires_at, + idempotency_key_hash: value.idempotency_key_hash.toLowerCase(), + request_hash: requestHash, + request, + admin: value.admin, + admin_sig: value.admin_sig.toLowerCase(), + }; + return stableJson(normalized) === stableJson(value) + ? normalized + : new Error('Targeted fiat attempt preparation must be canonical.'); + } + + async applyTargetedFiatAttemptFeature(key, value) { + const normalized = await this.normalizePrepareTargetedFiatAttemptValue(value); + if (normalized instanceof Error) return normalized; + const expectedKey = await this.targetedFiatAttemptFeatureKey(normalized); + if (expectedKey instanceof Error) return expectedKey; + if (key !== expectedKey) return new Error('Invalid targeted fiat attempt feature key.'); + const adminError = await this.requireTargetedPayoutAdminSignature( + normalized, + 'Targeted fiat attempt preparation' + ); + if (adminError) return adminError; + const plan = await this.get( + this.targetedPayoutEpochPlanRecordKey('fiat', normalized.epoch) + ); + const output = plan?.value?.outputs?.[normalized.output_index]; + if (!plan || + plan.plan_root !== normalized.plan_root || + plan.value.epoch_apply_hash !== normalized.epoch_apply_hash || + output?.economic_op_id !== normalized.economic_op_id) { + return new Error('Targeted fiat attempt does not match the canonical epoch plan.'); + } + const preparation = await this.get( + this.payoutPreparationRecordKey('fiat', normalized.economic_op_id) + ); + if (!preparation || + preparation.consumed !== false || + preparation.payload?.plan_root !== normalized.plan_root || + preparation.payload?.output_index !== normalized.output_index || + stableJson(preparation.payload?.output) !== stableJson(output)) { + return new Error('Targeted fiat attempt requires its unconsumed economic preparation.'); + } + const expectedKind = output.role === 'provider' + ? 'stripe_transfer' + : 'platform_balance'; + if (normalized.request.kind !== expectedKind || + normalized.request.destination !== output.to || + normalized.request.source_currency !== output.source_currency || + normalized.request.source_amount_minor !== output.source_amount_minor || + (output.role === 'provider' && + (normalized.request.destination_currency !== output.destination_currency || + normalized.request.destination_amount_min_minor !== + output.destination_amount_min_minor || + normalized.request.destination_amount_max_minor !== + output.destination_amount_max_minor || + normalized.request.transfer_group !== + `mayhem_fiat_epoch_${normalized.epoch}_${normalized.epoch_apply_hash.slice(0, 16)}`))) { + return new Error('Targeted fiat attempt request does not match its planned output.'); + } + const recordKey = this.targetedFiatAttemptRecordKey( + normalized.economic_op_id, + normalized.attempt_id + ); + const existing = await this.get(recordKey); + if (existing !== null) { + if (stableJson(existing.preparation) === stableJson(normalized)) { + return { + ok: true, + op: 'targetedFiatAttemptPrepared', + economic_op_id: normalized.economic_op_id, + attempt_id: normalized.attempt_id, + status: existing.status, + idempotent: true, + }; + } + return new Error('Targeted fiat attempt identity already exists.'); + } + const tailKey = this.targetedFiatAttemptTailKey(normalized.economic_op_id); + const tail = await this.get(tailKey); + if ((!tail && normalized.attempt_no !== 1) || + (tail && + (tail.status !== 'expired_pre_effect' || + normalized.attempt_no !== tail.attempt_no + 1))) { + return new Error( + 'Targeted fiat attempt may renew only after definitive pre-effect expiry.' + ); + } + if ((await this.get( + `payout/attempt-idempotency/fiat/${normalized.idempotency_key_hash}` + )) !== null) { + return new Error('Targeted fiat attempt idempotency key was already used.'); + } + if (normalized.request.fx_quote_id !== null && + (await this.get( + `payout/attempt-quote/fiat/${normalized.request.fx_quote_id}` + )) !== null) { + return new Error('Targeted fiat attempt FX quote was already used.'); + } + const record = { + type: 'targeted_fiat_attempt', + economic_op_id: normalized.economic_op_id, + attempt_id: normalized.attempt_id, + attempt_no: normalized.attempt_no, + status: 'prepared', + preparation: normalized, + result: null, + consumed: false, + consumed_by: null, + updated_at: this.tx, + }; + await this.put(recordKey, record); + await this.put(tailKey, { + economic_op_id: normalized.economic_op_id, + attempt_id: normalized.attempt_id, + attempt_no: normalized.attempt_no, + status: 'prepared', + updated_at: this.tx, + }); + await this.put( + `payout/attempt-idempotency/fiat/${normalized.idempotency_key_hash}`, + { + economic_op_id: normalized.economic_op_id, + attempt_id: normalized.attempt_id, + updated_at: this.tx, + } + ); + if (normalized.request.fx_quote_id !== null) { + await this.put( + `payout/attempt-quote/fiat/${normalized.request.fx_quote_id}`, + { + economic_op_id: normalized.economic_op_id, + attempt_id: normalized.attempt_id, + quote_hash: normalized.request.fx_quote_hash, + updated_at: this.tx, + } + ); + } + return { + ok: true, + op: 'targetedFiatAttemptPrepared', + economic_op_id: normalized.economic_op_id, + attempt_id: normalized.attempt_id, + status: 'prepared', + idempotent: false, + }; + } + + normalizeFinalizeTargetedFiatAttemptValue(value) { + const shapeError = this.validateExactObjectKeys( + value, + [ + 'op', + 'contract_version', + 'rail', + 'epoch', + 'economic_op_id', + 'attempt_id', + 'status', + 'at', + 'evidence', + 'admin', + 'admin_sig', + ], + 'targeted fiat attempt finalization' + ); + if (shapeError) return shapeError; + if (value.op !== 'finalize_targeted_fiat_attempt' || + value.contract_version !== CONTRACT_VERSION || + value.rail !== 'fiat' || + !Number.isSafeInteger(value.epoch) || + value.epoch < 1 || + !this.isHexBytes(value.economic_op_id, 32) || + value.economic_op_id !== value.economic_op_id.toLowerCase() || + !this.isHexBytes(value.attempt_id, 32) || + value.attempt_id !== value.attempt_id.toLowerCase() || + !['succeeded', 'expired_pre_effect'].includes(value.status) || + !Number.isSafeInteger(value.at) || + value.at < 0 || + !this.isHexBytes(value.admin, 32) || + value.admin !== value.admin.toLowerCase() || + !this.isHexBytes(value.admin_sig, 64)) { + return new Error('Invalid targeted fiat attempt finalization.'); + } + let evidence; + if (value.status === 'succeeded') { + evidence = this.normalizeStripeTransferEvidence( + value.evidence, + 'targeted fiat attempt success evidence', + { expectedAttemptId: value.attempt_id } + ); + if (evidence instanceof Error) return evidence; + } else { + const expiryShape = this.validateExactObjectKeys( + value.evidence, + [ + 'fx_quote_id', + 'fx_quote_hash', + 'quote_expires_at', + 'error_code', + 'external_effect_absent', + ], + 'targeted fiat attempt expiry evidence' + ); + if (expiryShape) return expiryShape; + if (!/^fxq_[A-Za-z0-9._-]+$/.test(String(value.evidence.fx_quote_id || '')) || + !this.isHexBytes(value.evidence.fx_quote_hash, 32) || + !Number.isSafeInteger(value.evidence.quote_expires_at) || + value.evidence.quote_expires_at < 0 || + value.evidence.error_code !== 'fx_quote_expired' || + value.evidence.external_effect_absent !== true) { + return new Error('Invalid targeted fiat attempt expiry evidence.'); + } + evidence = stableValue(value.evidence); + } + const normalized = { + op: 'finalize_targeted_fiat_attempt', + contract_version: CONTRACT_VERSION, + rail: 'fiat', + epoch: value.epoch, + economic_op_id: value.economic_op_id, + attempt_id: value.attempt_id, + status: value.status, + at: value.at, + evidence, + admin: value.admin, + admin_sig: value.admin_sig.toLowerCase(), + }; + return stableJson(normalized) === stableJson(value) + ? normalized + : new Error('Targeted fiat attempt finalization must be canonical.'); + } + + async applyFinalizeTargetedFiatAttemptFeature(key, value) { + const normalized = this.normalizeFinalizeTargetedFiatAttemptValue(value); + if (normalized instanceof Error) return normalized; + const expectedKey = await this.finalizeTargetedFiatAttemptFeatureKey(normalized); + if (expectedKey instanceof Error) return expectedKey; + if (key !== expectedKey) { + return new Error('Invalid targeted fiat attempt finalization key.'); + } + const adminError = await this.requireTargetedPayoutAdminSignature( + normalized, + 'Targeted fiat attempt finalization' + ); + if (adminError) return adminError; + const recordKey = this.targetedFiatAttemptRecordKey( + normalized.economic_op_id, + normalized.attempt_id + ); + const record = await this.get(recordKey); + if (!record || + record.type !== 'targeted_fiat_attempt' || + record.preparation.epoch !== normalized.epoch) { + return new Error('Targeted fiat attempt preparation not found.'); + } + if (record.status !== 'prepared') { + if (record.status === normalized.status && + stableJson(record.result) === stableJson(normalized)) { + return { + ok: true, + op: 'targetedFiatAttemptFinalized', + economic_op_id: normalized.economic_op_id, + attempt_id: normalized.attempt_id, + status: normalized.status, + idempotent: true, + }; + } + return new Error('Targeted fiat attempt is already terminal.'); + } + const request = record.preparation.request; + if (normalized.status === 'expired_pre_effect') { + if (request.kind !== 'stripe_transfer' || + normalized.at < record.preparation.quote_expires_at || + normalized.evidence.fx_quote_id !== request.fx_quote_id || + normalized.evidence.fx_quote_hash !== request.fx_quote_hash || + normalized.evidence.quote_expires_at !== + record.preparation.quote_expires_at) { + return new Error('Targeted fiat attempt expiry does not match its prepared quote.'); + } + } else { + const transfer = normalized.evidence; + const expectedKind = request.kind; + if (transfer.kind !== expectedKind || + transfer.destination !== request.destination || + transfer.source_currency !== request.source_currency || + transfer.source_amount_minor !== request.source_amount_minor || + (expectedKind === 'stripe_transfer' && + (transfer.destination_currency !== request.destination_currency || + BigInt(transfer.destination_amount_minor) < + BigInt(request.destination_amount_min_minor) || + BigInt(transfer.destination_amount_minor) > + BigInt(request.destination_amount_max_minor) || + transfer.fx_quote_id !== request.fx_quote_id || + transfer.fx_quote_hash !== request.fx_quote_hash || + transfer.transfer_group !== request.transfer_group))) { + return new Error('Targeted fiat attempt result does not match its prepared request.'); + } + const effectKey = this.targetedFiatAttemptEffectKey(transfer.ref); + const priorEffect = await this.get(effectKey); + if (priorEffect !== null) { + return new Error('Targeted fiat external effect was already finalized.'); + } + await this.put(effectKey, { + economic_op_id: normalized.economic_op_id, + attempt_id: normalized.attempt_id, + effect_id: transfer.ref, + consumed: false, + updated_at: this.tx, + }); + } + const terminal = { + ...record, + status: normalized.status, + result: normalized, + updated_at: this.tx, + }; + await this.put(recordKey, terminal); + await this.put(this.targetedFiatAttemptTailKey(normalized.economic_op_id), { + economic_op_id: normalized.economic_op_id, + attempt_id: normalized.attempt_id, + attempt_no: record.attempt_no, + status: normalized.status, + updated_at: this.tx, + }); + return { + ok: true, + op: 'targetedFiatAttemptFinalized', + economic_op_id: normalized.economic_op_id, + attempt_id: normalized.attempt_id, + status: normalized.status, + idempotent: false, + }; + } + + normalizeTargetedTnkOutputSettlementValue(value) { + const shapeError = this.validateExactObjectKeys( + value, + [ + 'op', + 'contract_version', + 'rail', + 'epoch', + 'at', + 'epoch_apply_hash', + 'plan_root', + 'economic_op_id', + 'output_index', + 'preparation_id', + 'external_effect_id', + 'msb_transfer', + 'admin', + 'admin_sig', + ], + 'targeted TNK output settlement' + ); + if (shapeError) return shapeError; + const transfer = this.normalizeMsbTransferEvidence( + value.msb_transfer, + 'targeted TNK output transfer' + ); + if (transfer instanceof Error) return transfer; + if (value.op !== 'settle_targeted_tnk_output' || + value.contract_version !== CONTRACT_VERSION || + value.rail !== 'tnk' || + !Number.isSafeInteger(value.epoch) || + value.epoch < 1 || + !Number.isSafeInteger(value.at) || + value.at < 0 || + !this.isHexBytes(value.epoch_apply_hash, 32) || + value.epoch_apply_hash !== value.epoch_apply_hash.toLowerCase() || + !this.isHexBytes(value.plan_root, 32) || + value.plan_root !== value.plan_root.toLowerCase() || + !this.isHexBytes(value.economic_op_id, 32) || + value.economic_op_id !== value.economic_op_id.toLowerCase() || + !Number.isSafeInteger(value.output_index) || + value.output_index < 0 || + value.preparation_id !== value.economic_op_id || + !this.isHexBytes(value.external_effect_id, 32) || + value.external_effect_id !== transfer.tx_hash || + !this.isHexBytes(value.admin, 32) || + value.admin !== value.admin.toLowerCase() || + !this.isHexBytes(value.admin_sig, 64)) { + return new Error('Invalid targeted TNK output settlement.'); + } + const normalized = { + op: 'settle_targeted_tnk_output', + contract_version: CONTRACT_VERSION, + rail: 'tnk', + epoch: value.epoch, + at: value.at, + epoch_apply_hash: value.epoch_apply_hash, + plan_root: value.plan_root, + economic_op_id: value.economic_op_id, + output_index: value.output_index, + preparation_id: value.preparation_id, + external_effect_id: value.external_effect_id, + msb_transfer: transfer, + admin: value.admin, + admin_sig: value.admin_sig.toLowerCase(), + }; + return stableJson(normalized) === stableJson(value) + ? normalized + : new Error('Targeted TNK output settlement must be canonical.'); + } + + normalizeTargetedFiatOutputSettlementValue(value) { + const shapeError = this.validateExactObjectKeys( + value, + [ + 'op', + 'contract_version', + 'rail', + 'epoch', + 'at', + 'epoch_apply_hash', + 'plan_root', + 'economic_op_id', + 'output_index', + 'preparation_id', + 'attempt_id', + 'stripe_transfer', + 'admin', + 'admin_sig', + ], + 'targeted fiat output settlement' + ); + if (shapeError) return shapeError; + const transfer = this.normalizeStripeTransferEvidence( + value.stripe_transfer, + 'targeted fiat output transfer', + { expectedAttemptId: value.attempt_id } + ); + if (transfer instanceof Error) return transfer; + if (value.op !== 'settle_targeted_fiat_output' || + value.contract_version !== CONTRACT_VERSION || + value.rail !== 'fiat' || + !Number.isSafeInteger(value.epoch) || + value.epoch < 1 || + !Number.isSafeInteger(value.at) || + value.at < 0 || + !this.isHexBytes(value.epoch_apply_hash, 32) || + value.epoch_apply_hash !== value.epoch_apply_hash.toLowerCase() || + !this.isHexBytes(value.plan_root, 32) || + value.plan_root !== value.plan_root.toLowerCase() || + !this.isHexBytes(value.economic_op_id, 32) || + value.economic_op_id !== value.economic_op_id.toLowerCase() || + !Number.isSafeInteger(value.output_index) || + value.output_index < 0 || + value.preparation_id !== value.economic_op_id || + !this.isHexBytes(value.attempt_id, 32) || + value.attempt_id !== value.attempt_id.toLowerCase() || + !this.isHexBytes(value.admin, 32) || + value.admin !== value.admin.toLowerCase() || + !this.isHexBytes(value.admin_sig, 64)) { + return new Error('Invalid targeted fiat output settlement.'); + } + const normalized = { + op: 'settle_targeted_fiat_output', + contract_version: CONTRACT_VERSION, + rail: 'fiat', + epoch: value.epoch, + at: value.at, + epoch_apply_hash: value.epoch_apply_hash, + plan_root: value.plan_root, + economic_op_id: value.economic_op_id, + output_index: value.output_index, + preparation_id: value.preparation_id, + attempt_id: value.attempt_id, + stripe_transfer: transfer, + admin: value.admin, + admin_sig: value.admin_sig.toLowerCase(), + }; + return stableJson(normalized) === stableJson(value) + ? normalized + : new Error('Targeted fiat output settlement must be canonical.'); + } + + async targetedPayoutPlannedOutput(value, rail) { + const plan = await this.get( + this.targetedPayoutEpochPlanRecordKey(rail, value.epoch) + ); + const output = plan?.value?.outputs?.[value.output_index]; + if (!plan || + plan.plan_root !== value.plan_root || + plan.value.epoch_apply_hash !== value.epoch_apply_hash || + output?.economic_op_id !== value.economic_op_id) { + return new Error('Targeted output settlement does not match its epoch plan.'); + } + return { plan, output }; + } + + async targetedPayoutPreparationForOutput(value, output, rail) { + const preparation = await this.get( + this.payoutPreparationRecordKey(rail, value.preparation_id) + ); + if (!preparation || + preparation.type !== 'targeted_payout_preparation' || + preparation.consumed !== false || + preparation.economic_op_id !== value.economic_op_id || + preparation.epoch !== value.epoch || + preparation.epoch_apply_hash !== value.epoch_apply_hash || + preparation.output_index !== value.output_index || + preparation.payload?.plan_root !== value.plan_root || + stableJson(preparation.payload?.output) !== stableJson(output)) { + return new Error('Targeted output settlement requires its unconsumed preparation.'); + } + return preparation; + } + + async applyTargetedTnkOutputFeature(key, value) { + const normalized = this.normalizeTargetedTnkOutputSettlementValue(value); + if (normalized instanceof Error) return normalized; + const expectedKey = await this.targetedTnkOutputFeatureKey(normalized); + if (expectedKey instanceof Error) return expectedKey; + if (key !== expectedKey) return new Error('Invalid targeted TNK output feature key.'); + const adminError = await this.requireTargetedPayoutAdminSignature( + normalized, + 'Targeted TNK output settlement' + ); + if (adminError) return adminError; + const recordKey = this.targetedPayoutOutputRecordKey( + 'tnk', + normalized.epoch, + normalized.economic_op_id + ); + const existing = await this.get(recordKey); + if (existing !== null) { + if (stableJson(existing.value) === stableJson(normalized)) { + return { + ok: true, + op: 'targetedTnkOutputSettled', + epoch: normalized.epoch, + economic_op_id: normalized.economic_op_id, + idempotent: true, + }; + } + return new Error('Targeted TNK output is already settled.'); + } + const planned = await this.targetedPayoutPlannedOutput(normalized, 'tnk'); + if (planned instanceof Error) return planned; + const preparation = await this.targetedPayoutPreparationForOutput( + normalized, + planned.output, + 'tnk' + ); + if (preparation instanceof Error) return preparation; + const transfer = normalized.msb_transfer; + const payload = preparation.payload; + if (preparation.external_effect_ids.length !== 1 || + preparation.external_effect_ids[0] !== normalized.external_effect_id || + payload.msb_tx_hash !== normalized.external_effect_id || + payload.msb_payload?.tro?.tx !== normalized.external_effect_id || + transfer.network !== payload.network || + transfer.from !== payload.treasury_from || + transfer.to !== planned.output.to || + transfer.amount_e18 !== planned.output.tnk_e18) { + return new Error('Targeted TNK output evidence does not match its signed preparation.'); + } + const rate = await this.guardianRequireHistoricalTnkRate(payload, normalized.at); + if (rate instanceof Error) return rate; + const expectedTnkE18 = this.auToTnkE18Ceil( + planned.output.au, + payload.rate_tnk_usd_au + ); + if (expectedTnkE18 instanceof Error || + expectedTnkE18.toString() !== planned.output.tnk_e18) { + return new Error('Targeted TNK output does not match its oracle rate.'); + } + if ((await this.get(this.msbTransferSeenKey(transfer))) !== null) { + return new Error('Targeted TNK transfer was already consumed.'); + } + const preparationReady = await this.validatePayoutPreparationConsumption( + [preparation] + ); + if (preparationReady instanceof Error) return preparationReady; + if (planned.output.role === 'provider') { + const updates = await this.targetedPayoutSettlementUpdates( + [planned.output], + 'tnk', + normalized.epoch, + normalized.at, + [transfer.tx_hash] + ); + if (updates instanceof Error) return updates; + for (const update of updates.liabilityUpdates) { + await this.put(update.key, update.value); + } + for (const earning of updates.earningUpdates) { + await this.put(this.earningKey(earning.provider, 'tnk'), earning); + } + } else { + const fee = await this.feeCumRecord('tnk'); + if (fee instanceof Error) return fee; + const payable = this.safeSubAu(fee.cum_au, fee.swept_cum_au); + if (payable instanceof Error || this.compareAu(payable, planned.output.au) < 0) { + return new Error('Targeted TNK fee output does not match canonical fee state.'); + } + const swept = this.safeAddAu(fee.swept_cum_au, planned.output.au); + if (swept instanceof Error) return swept; + const nextFee = { + ...fee, + swept_cum_au: swept, + updated_epoch: Math.max(fee.updated_epoch, normalized.epoch), + last_targeted_settlement_epoch: normalized.epoch, + last_targeted_settlement_transfer: transfer.tx_hash, + }; + const nextFeeError = this.guardianValidateFeeRecord(nextFee, 'tnk'); + if (nextFeeError) return nextFeeError; + await this.put(this.feeCumKey('tnk'), nextFee); + } + await this.put(this.msbTransferSeenKey(transfer), { + rail: 'tnk', + purpose: 'targeted_output_settlement', + epoch: normalized.epoch, + economic_op_id: normalized.economic_op_id, + consumed_at: this.tx, + }); + const preparationError = await this.consumePayoutPreparations( + [preparation], + recordKey + ); + if (preparationError) return preparationError; + await this.put(recordKey, { + type: 'targeted_tnk_output_settlement', + rail: 'tnk', + epoch: normalized.epoch, + economic_op_id: normalized.economic_op_id, + value: normalized, + settled_at: this.tx, + }); + return { + ok: true, + op: 'targetedTnkOutputSettled', + epoch: normalized.epoch, + economic_op_id: normalized.economic_op_id, + external_effect_id: normalized.external_effect_id, + idempotent: false, + }; + } + + async applyTargetedFiatOutputFeature(key, value) { + const normalized = this.normalizeTargetedFiatOutputSettlementValue(value); + if (normalized instanceof Error) return normalized; + const expectedKey = await this.targetedFiatOutputFeatureKey(normalized); + if (expectedKey instanceof Error) return expectedKey; + if (key !== expectedKey) return new Error('Invalid targeted fiat output feature key.'); + const adminError = await this.requireTargetedPayoutAdminSignature( + normalized, + 'Targeted fiat output settlement' + ); + if (adminError) return adminError; + const recordKey = this.targetedPayoutOutputRecordKey( + 'fiat', + normalized.epoch, + normalized.economic_op_id + ); + const existing = await this.get(recordKey); + if (existing !== null) { + if (stableJson(existing.value) === stableJson(normalized)) { + return { + ok: true, + op: 'targetedFiatOutputSettled', + epoch: normalized.epoch, + economic_op_id: normalized.economic_op_id, + idempotent: true, + }; + } + return new Error('Targeted fiat output is already settled.'); + } + const planned = await this.targetedPayoutPlannedOutput(normalized, 'fiat'); + if (planned instanceof Error) return planned; + const preparation = await this.targetedPayoutPreparationForOutput( + normalized, + planned.output, + 'fiat' + ); + if (preparation instanceof Error) return preparation; + const attemptKey = this.targetedFiatAttemptRecordKey( + normalized.economic_op_id, + normalized.attempt_id + ); + const attempt = await this.get(attemptKey); + const transfer = normalized.stripe_transfer; + if (!attempt || + attempt.status !== 'succeeded' || + attempt.consumed !== false || + stableJson(attempt.result?.evidence) !== stableJson(transfer)) { + return new Error('Targeted fiat output requires a succeeded canonical attempt.'); + } + const attemptEffectKey = this.targetedFiatAttemptEffectKey(transfer.ref); + const attemptEffect = await this.get(attemptEffectKey); + if (!attemptEffect || + attemptEffect.economic_op_id !== normalized.economic_op_id || + attemptEffect.attempt_id !== normalized.attempt_id || + attemptEffect.consumed !== false) { + return new Error('Targeted fiat attempt effect lock mismatch.'); + } + for (const seenKey of [ + this.stripeTransferSeenKey(transfer), + this.stripeFxQuoteSeenKey(transfer), + this.stripeDestinationPaymentSeenKey(transfer), + ]) { + if (seenKey && (await this.get(seenKey)) !== null) { + return new Error('Targeted Stripe transfer evidence was already consumed.'); + } + } + const preparationReady = await this.validatePayoutPreparationConsumption( + [preparation] + ); + if (preparationReady instanceof Error) return preparationReady; + if (planned.output.role === 'provider') { + const updates = await this.targetedPayoutSettlementUpdates( + [planned.output], + 'fiat', + normalized.epoch, + normalized.at, + [transfer.ref] + ); + if (updates instanceof Error) return updates; + for (const update of updates.liabilityUpdates) { + await this.put(update.key, update.value); + } + for (const earning of updates.earningUpdates) { + await this.put(this.earningKey(earning.provider, 'fiat'), earning); + } + } else { + const fee = await this.feeCumRecord('fiat'); + if (fee instanceof Error) return fee; + const payable = this.safeSubAu(fee.cum_au, fee.swept_cum_au); + if (payable instanceof Error || + this.compareAu(payable, planned.output.liability_au) < 0 || + planned.output.paid_au !== planned.output.liability_au) { + return new Error('Targeted fiat fee output does not match canonical fee state.'); + } + const swept = this.safeAddAu(fee.swept_cum_au, planned.output.paid_au); + if (swept instanceof Error) return swept; + const nextFee = { + ...fee, + swept_cum_au: swept, + updated_epoch: Math.max(fee.updated_epoch, normalized.epoch), + last_targeted_settlement_epoch: normalized.epoch, + last_targeted_settlement_transfer: transfer.ref, + }; + const nextFeeError = this.guardianValidateFeeRecord(nextFee, 'fiat'); + if (nextFeeError) return nextFeeError; + await this.put(this.feeCumKey('fiat'), nextFee); + } + for (const seenKey of [ + this.stripeTransferSeenKey(transfer), + this.stripeFxQuoteSeenKey(transfer), + this.stripeDestinationPaymentSeenKey(transfer), + ]) { + if (!seenKey) continue; + await this.put(seenKey, { + rail: 'fiat', + purpose: 'targeted_output_settlement', + epoch: normalized.epoch, + economic_op_id: normalized.economic_op_id, + consumed_at: this.tx, + }); + } + const preparationError = await this.consumePayoutPreparations( + [preparation], + recordKey + ); + if (preparationError) return preparationError; + await this.put(attemptKey, { + ...attempt, + consumed: true, + consumed_by: recordKey, + updated_at: this.tx, + }); + await this.put(attemptEffectKey, { + ...attemptEffect, + consumed: true, + consumed_by: recordKey, + updated_at: this.tx, + }); + await this.put(recordKey, { + type: 'targeted_fiat_output_settlement', + rail: 'fiat', + epoch: normalized.epoch, + economic_op_id: normalized.economic_op_id, + value: normalized, + settled_at: this.tx, + }); + return { + ok: true, + op: 'targetedFiatOutputSettled', + epoch: normalized.epoch, + economic_op_id: normalized.economic_op_id, + external_effect_id: transfer.ref, + idempotent: false, + }; + } + + normalizeCloseTargetedPayoutEpochValue(value) { + const shapeError = this.validateExactObjectKeys( + value, + [ + 'op', + 'contract_version', + 'rail', + 'epoch', + 'at', + 'epoch_apply_hash', + 'plan_root', + 'admin', + 'admin_sig', + ], + 'targeted payout epoch close' + ); + if (shapeError) return shapeError; + if (value.op !== 'close_targeted_payout_epoch' || + value.contract_version !== CONTRACT_VERSION || + !['fiat', 'tnk'].includes(value.rail) || + !Number.isSafeInteger(value.epoch) || + value.epoch < 1 || + !Number.isSafeInteger(value.at) || + value.at < 0 || + !this.isHexBytes(value.epoch_apply_hash, 32) || + value.epoch_apply_hash !== value.epoch_apply_hash.toLowerCase() || + !this.isHexBytes(value.plan_root, 32) || + value.plan_root !== value.plan_root.toLowerCase() || + !this.isHexBytes(value.admin, 32) || + value.admin !== value.admin.toLowerCase() || + !this.isHexBytes(value.admin_sig, 64)) { + return new Error('Invalid targeted payout epoch close.'); + } + const normalized = { + op: 'close_targeted_payout_epoch', + contract_version: CONTRACT_VERSION, + rail: value.rail, + epoch: value.epoch, + at: value.at, + epoch_apply_hash: value.epoch_apply_hash, + plan_root: value.plan_root, + admin: value.admin, + admin_sig: value.admin_sig.toLowerCase(), + }; + return stableJson(normalized) === stableJson(value) + ? normalized + : new Error('Targeted payout epoch close must be canonical.'); + } + + async applyCloseTargetedPayoutEpochFeature(key, value) { + const normalized = this.normalizeCloseTargetedPayoutEpochValue(value); + if (normalized instanceof Error) return normalized; + const expectedKey = await this.closeTargetedPayoutEpochFeatureKey(normalized); + if (expectedKey instanceof Error) return expectedKey; + if (key !== expectedKey) return new Error('Invalid targeted payout epoch close key.'); + const adminError = await this.requireTargetedPayoutAdminSignature( + normalized, + 'Targeted payout epoch close' + ); + if (adminError) return adminError; + const recordKey = this.targetedPayoutEpochCloseRecordKey( + normalized.rail, + normalized.epoch + ); + const existing = await this.get(recordKey); + if (existing !== null) { + if (stableJson(existing.value) === stableJson(normalized)) { + return { + ok: true, + op: 'targetedPayoutEpochClosed', + rail: normalized.rail, + epoch: normalized.epoch, + idempotent: true, + }; + } + return new Error('Targeted payout epoch is already closed.'); + } + const plan = await this.get( + this.targetedPayoutEpochPlanRecordKey(normalized.rail, normalized.epoch) + ); + if (!plan || + plan.plan_root !== normalized.plan_root || + plan.value.epoch_apply_hash !== normalized.epoch_apply_hash) { + return new Error('Targeted payout epoch close does not match its plan.'); + } + for (const output of plan.value.outputs) { + const settled = await this.get( + this.targetedPayoutOutputRecordKey( + normalized.rail, + normalized.epoch, + output.economic_op_id + ) + ); + if (!settled || + settled.economic_op_id !== output.economic_op_id || + settled.value.plan_root !== normalized.plan_root) { + return new Error('Targeted payout epoch has unsettled planned outputs.'); + } + } + await this.put(recordKey, { + type: 'targeted_payout_epoch_close', + rail: normalized.rail, + epoch: normalized.epoch, + plan_root: normalized.plan_root, + outcome: plan.value.outcome, + output_count: plan.value.outputs.length, + carry_count: plan.value.carry.length, + outputs_root: plan.value.outputs_root, + carry_root: plan.value.carry_root, + value: normalized, + closed_at: this.tx, + }); + return { + ok: true, + op: 'targetedPayoutEpochClosed', + rail: normalized.rail, + epoch: normalized.epoch, + outcome: plan.value.outcome, + output_count: plan.value.outputs.length, + carry_count: plan.value.carry.length, + idempotent: false, + }; + } + + async applyTargetedTnkSettlementFeature(key, value) { + const expectedKey = await this.targetedTnkSettlementFeatureKey(value); + if (expectedKey instanceof Error) return expectedKey; + if (key !== expectedKey) return new Error('Invalid targeted TNK settlement key.'); + const previousTx = this.tx; + this.tx = key; + try { + return await this.targetedTnkSettlement(value); + } finally { + this.tx = previousTx; + } + } + + async applyTargetedTapSettlementFeature(key, value) { + const expectedKey = await this.targetedTapSettlementFeatureKey(value); + if (expectedKey instanceof Error) return expectedKey; + if (key !== expectedKey) return new Error('Invalid targeted TAP settlement key.'); + const previousTx = this.tx; + this.tx = key; + try { + return await this.targetedTapSettlement(value); + } finally { + this.tx = previousTx; + } + } + + async applyTargetedFiatSettlementFeature(key, value) { + const expectedKey = await this.targetedFiatSettlementFeatureKey(value); + if (expectedKey instanceof Error) return expectedKey; + if (key !== expectedKey) return new Error('Invalid targeted fiat settlement key.'); + const previousTx = this.tx; + this.tx = key; + try { + return await this.targetedFiatSettlement(value); + } finally { + this.tx = previousTx; + } + } + + async applyFiatDustSweepFeature(key, value) { + const expectedKey = await this.fiatDustSweepFeatureKey(value); + if (expectedKey instanceof Error) return expectedKey; + if (key !== expectedKey) return; + + const previousTx = this.tx; + this.tx = key; + try { + return await this.fiatDustSweep(); + } finally { + this.tx = previousTx; + } + } + + async applyReputationAnchorFeature(key, value) { + const expectedKey = await this.reputationAnchorFeatureKey(value); + if (expectedKey instanceof Error) return expectedKey; + if (key !== expectedKey) return; + + const previousTx = this.tx; + const previousValue = this.value; + this.tx = key; + this.value = value; + try { + return await this.anchorReputation(); + } finally { + this.tx = previousTx; + this.value = previousValue; + } + } + + async applyTier3MeasurementFeature(key, value) { + const expectedKey = await this.tier3MeasurementFeatureKey(value); + if (expectedKey instanceof Error) return expectedKey; + if (key !== expectedKey) return; + + const previousTx = this.tx; + const previousValue = this.value; + this.tx = key; + this.value = value; + try { + return await this.tier3BlessMeasurement(); + } finally { + this.tx = previousTx; + this.value = previousValue; + } + } + + async tier3BlessMeasurement() { + const adminError = await this.requireAdmin(); + if (adminError) return adminError; + const validationError = this.validateTier3MeasurementBlessValue(this.value); + if (validationError) return validationError; + + const platform = this.value.platform; + const layer = this.value.layer; + const measurementName = this.value.measurement_name; + const measurement = this.value.measurement.toLowerCase(); + const key = `tier3/measurement/${platform}`; + const current = await this.get(key); + const record = current + ? cloneValue(current) + : { + schema_version: 1, + platform, + entries: [], + measurements: {}, + created_at: this.tx, + created_by: this.address, + }; + if (record.platform !== platform) return new Error('Tier-3 measurement platform mismatch.'); + if (!Array.isArray(record.entries)) record.entries = []; + if (!record.measurements || typeof record.measurements !== 'object' || Array.isArray(record.measurements)) { + record.measurements = {}; + } + if (!record.measurements[layer] || typeof record.measurements[layer] !== 'object' || Array.isArray(record.measurements[layer])) { + record.measurements[layer] = {}; + } + + const already = record.entries.find((entry) => + entry.layer === layer && entry.measurement_name === measurementName && entry.measurement === measurement + ); + if (already) { + return { + ok: true, + op: 'tier3BlessMeasurement', + platform, + layer, + measurement_name: measurementName, + measurement, + status: 'already_blessed', + }; + } + + const entry = { + layer, + measurement_name: measurementName, + measurement, + effective_epoch: this.value.effective_epoch, + region: this.value.region ?? null, + derivation_hash: this.value.derivation_hash ?? null, + source: this.value.source ?? 'admin-derive', + blessed_at: this.tx, + blessed_by: this.address, + }; + record.entries.push(entry); + const values = Array.isArray(record.measurements[layer][measurementName]) + ? record.measurements[layer][measurementName] + : []; + if (!values.includes(measurement)) values.push(measurement); + values.sort(); + record.measurements[layer][measurementName] = values; + record.updated_at = this.tx; + record.updated_by = this.address; + await this.put(key, record); + await this.put(`tier3/measurement/${platform}/${layer}/${measurementName}/${measurement}`, entry); + console.log('mayhem tier3BlessMeasurement', entry); + return { + ok: true, + op: 'tier3BlessMeasurement', + platform, + layer, + measurement_name: measurementName, + measurement, + status: 'blessed', + }; + } + + async setRules() { + const adminError = await this.requireAdmin(); + if (adminError) return adminError; + + const current = await this.currentRules(); + if (current && this.value.ver <= current.ver) { + return new Error('Rules version must increase.'); + } + + const rules = { + ver: this.value.ver, + hash: this.value.hash, + set_by: this.address, + set_by_role: 'admin', + activated_at: this.tx, + }; + await this.put(`rules/${rules.ver}`, rules); + await this.put(CURRENT_RULES_KEY, rules); + if ((await this.get('epoch/apply/state')) === null) { + await this.put('epoch/apply/state', { + updated_epoch: 0, + updated_at: null, + last_apply_hash: null, + }); + } + console.log('mayhem setRules', rules); + return { ok: true, op: 'setRules', rules }; + } + + async setParams() { + const adminError = await this.requireAdmin(); + if (adminError) return adminError; + + const governanceParams = await this.activeParamsAt(this.value.submitted_at, [ + 'param_activation_delay_seconds', + ]); + if ( + this.value.effective_at - this.value.submitted_at < + governanceParams.param_activation_delay_seconds + ) { + return new Error('Parameter changes require at least the active param_activation_delay_seconds.'); + } + + const valuesError = this.validateParamValues(this.value.values); + if (valuesError) return valuesError; + const normalizedValues = this.normalizeParamValues(this.value.values); + if (normalizedValues instanceof Error) return normalizedValues; + + const existingAtEffective = await this.activeParamsAt(this.value.effective_at); + const mergedAtEffective = { ...existingAtEffective, ...normalizedValues }; + const boundsError = this.validateParamBounds(mergedAtEffective); + if (boundsError) return boundsError; + + const meta = await this.get('params/current'); + const ver = meta ? meta.ver + 1 : 1; + const keys = Object.keys(normalizedValues).sort(); + const update = { + ver, + values: cloneValue(normalizedValues), + submitted_at: this.value.submitted_at, + effective_at: this.value.effective_at, + set_by: this.address, + set_by_role: 'admin', + tx: this.tx, + }; + + for (const key of keys) { + const record = await this.paramRecord(key); + if (record.pending && record.pending.effective_at > this.value.submitted_at) { + return new Error(`Pending parameter change already scheduled for ${key}.`); + } + + const current = this.paramActiveEntry(record, this.value.submitted_at); + const updated = { + key, + current, + pending: { + value: normalizedValues[key], + ver, + submitted_at: this.value.submitted_at, + effective_at: this.value.effective_at, + set_by: this.address, + set_by_role: 'admin', + set_at: this.tx, + }, + }; + await this.put(`params/${key}`, updated); + } + + await this.put(`params/update/${ver}`, update); + await this.put('params/current', { + ver, + keys, + set_by: this.address, + set_by_role: 'admin', + updated_at: this.tx, + effective_at: this.value.effective_at, + }); + console.log('mayhem setParams', update); + return { ok: true, op: 'setParams', ver, effective_at: this.value.effective_at, keys }; + } + + async setPayments() { + const adminError = await this.requireAdmin(); + if (adminError) return adminError; + + const normalized = this.normalizePaymentConfig(this.value); + if (normalized instanceof Error) return normalized; + const current = await this.get('payments/current'); + if (current && this.value.ver <= current.ver) { + return new Error('Payment config version must increase.'); + } + const record = { + denom: PRICE_DENOMINATION, + rails: PROVIDER_ACCEPTED_RAIL_ORDER.slice(), + ...normalized, + ver: this.value.ver, + updated_at: this.tx, + set_by: this.address, + set_by_role: 'admin', + }; + await this.put('payments/current', record); + console.log('mayhem setPayments', record); + return { ok: true, op: 'setPayments', ver: record.ver }; + } + + async readParams() { + const keys = this.value.keys ?? Object.keys(PARAM_DEFINITIONS); + const keyError = this.validateParamKeys(keys); + if (keyError) return keyError; + + const params = await this.activeParamsAt(this.value.at, keys); + console.log('mayhem readParams', { at: this.value.at, params }); + return { ok: true, op: 'readParams', at: this.value.at, params }; + } + + async setCtxBrackets() { + const adminError = await this.requireAdmin(); + if (adminError) return adminError; + + const governanceParams = await this.activeParamsAt(this.value.submitted_at, [ + 'param_activation_delay_seconds', + ]); + if ( + this.value.effective_at - this.value.submitted_at < + governanceParams.param_activation_delay_seconds + ) { + return new Error('Context bracket changes require at least the active param_activation_delay_seconds.'); + } + + const brackets = this.normalizeCtxBracketTable(this.value.brackets); + if (brackets instanceof Error) return brackets; + + const schedule = await this.ctxBracketSchedule(); + if (schedule.pending && schedule.pending.effective_at > this.value.submitted_at) { + return new Error('Pending context bracket table already scheduled.'); + } + const latest = this.ctxBracketLatestEntry(schedule); + const record = { + ver: latest.ver + 1, + brackets, + submitted_at: this.value.submitted_at, + effective_at: this.value.effective_at, + effective_from: this.tx, + updated_at: this.tx, + set_by: this.address, + set_by_role: 'admin', + }; + const updated = { + current: schedule.current, + pending: schedule.pending, + }; + if (updated.pending && updated.pending.effective_at <= this.value.effective_at) { + updated.current = updated.pending; + updated.pending = null; + } + if (updated.pending) return new Error('Pending context bracket table already scheduled.'); + updated.pending = record; + + await this.put('ctx_brackets', updated); + await this.put(`ctx_brackets/v/${record.ver}`, record); + console.log('mayhem setCtxBrackets', record); + return { ok: true, op: 'setCtxBrackets', ver: record.ver, effective_at: record.effective_at }; + } + + async readCtxBrackets() { + if (hasOwn(this.value, 'ver') && hasOwn(this.value, 'at')) { + return new Error('Read context brackets by either ver or at, not both.'); + } + const table = hasOwn(this.value, 'ver') + ? await this.ctxBracketTableByVersion(this.value.ver) + : await this.ctxBracketTableAt(this.value.at ?? 0); + if (table instanceof Error) return table; + console.log('mayhem readCtxBrackets', table); + return { ok: true, op: 'readCtxBrackets', table }; + } + + async consent() { + const rules = await this.currentRules(); + if (!rules) return new Error('Rules are not set.'); + if (this.value.ver !== rules.ver || this.value.hash !== rules.hash) { + return new Error('Consent must match the current rules.'); + } + if (!this.verifyConsentSignature(this.address, this.value.ver, this.value.hash, this.value.sig)) { + return new Error('Invalid consent signature.'); + } + + const record = { + ver: this.value.ver, + hash: this.value.hash, + at: this.tx, + }; + await this.put(`consent/${this.address}`, record); + console.log('mayhem consent', { address: this.address, ...record }); + return { ok: true, op: 'consent', address: this.address, ...record }; + } + + async registerProvider() { + const shapeError = this.validateExactCommandValue(['op'], 'register_provider'); + if (shapeError) return shapeError; + if (this.value.op !== 'register_provider') return new Error('Invalid provider registration op.'); + + return this.applyRegisterProvider(this.address, this.tx); + } + + async applyRegisterProvider(providerId, stamp) { + const consentError = await this.requireConsent(providerId); + if (consentError) return consentError; + + const auditor = await this.get(`auditor/${providerId}`); + if (auditor?.status === 'active') { + return new Error('Auditor keys cannot register as providers.'); + } + + const key = `prov/${providerId}`; + if ((await this.get(key)) !== null) return new Error('Provider already registered.'); + + const record = { + provider: providerId, + accepted_rails: ['fiat'], + accepted_rails_schema_version: PROVIDER_RAIL_SCHEMA_VERSION, + accepted_rails_set_by: providerId, + accepted_rails_set_at: stamp, + status: 'active', + enclaves: [], + probation: { + since: stamp, + since_seconds: 0, + successful_sessions: 0, + }, + registered_at: stamp, + updated_at: stamp, + }; + await this.put(key, record); + console.log('mayhem registerProvider', record); + return { ok: true, op: 'registerProvider', provider: providerId }; + } + + normalizeProviderAcceptedRails(rails) { + if (!Array.isArray(rails) || rails.length === 0) { + return new Error('Provider accepted rails cannot be empty.'); + } + const accepted = []; + for (const rawRail of rails) { + if (typeof rawRail !== 'string') return new Error('Invalid provider rail.'); + const rail = rawRail.toLowerCase(); + if (!PROVIDER_ACCEPTED_RAILS.has(rail)) { + return new Error('Unsupported provider payment rail.'); + } + if (accepted.includes(rail)) return new Error('Duplicate provider payment rail.'); + accepted.push(rail); + } + if (accepted.length === 0) return new Error('Provider accepted rails cannot be empty.'); + accepted.sort( + (left, right) => + PROVIDER_ACCEPTED_RAIL_ORDER.indexOf(left) - PROVIDER_ACCEPTED_RAIL_ORDER.indexOf(right) + ); + return accepted; + } + + normalizeLedgerRail(value, label = 'ledger rail') { + if (typeof value !== 'string') return new Error(`Invalid ${label}.`); + const rail = value.toLowerCase(); + if (!PROVIDER_ACCEPTED_RAILS.has(rail)) return new Error(`Unsupported ${label}.`); + return rail; + } + + balanceKey(user, rail) { + return `bal/${user}/${rail}`; + } + + spendHoldKey(user, rail, epoch) { + return `hold/${rail}/${user}/${epoch}`; + } + + targetedSpendHoldKey(user, rail) { + return `hold/targeted-outstanding/${rail}/${user}`; + } + + targetedSpendSummaryKey(user, rail) { + return `hold/targeted-summary/${rail}/${user}`; + } + + targetedSpendLegacyReleaseSummaryKey(user, rail) { + return `hold/targeted-legacy-release/${rail}/${user}`; + } + + targetedSpendSessionKey(user, rail, reservationId) { + return `hold/targeted-session/${rail}/${user}/${reservationId}`; + } + + targetedSpendLegacySessionKey(user, rail, reservationId) { + return `hold/targeted-legacy-session/${rail}/${user}/${reservationId}`; + } + + targetedSpendSessionIndexKey(user, rail, sessionId) { + return `hold/targeted-session-index/${rail}/${user}/${sessionId}`; + } + + targetedSpendBillingAttemptKey(user, rail, billingId, billingAttempt) { + return `hold/targeted-billing/${rail}/${user}/${billingId}/${billingAttempt}`; + } + + receiptBillingKey(billingId) { + return `receipt/billing/${billingId}`; + } + + receiptReservationKey(reservationId) { + return `receipt/reservation/${reservationId}`; + } + + receiptReservationCloseKey(reservationId) { + return `receipt/reservation-close/${reservationId}`; + } + + receiptHeadKey(billingId, billingAttempt) { + return `receipt/head/${billingId}/${billingAttempt}`; + } + + receiptConsumedKey(billingId, billingAttempt) { + return `receipt/consumed/${billingId}/${billingAttempt}`; + } + + receiptEpochIndexKey(epoch) { + return `receipt/epoch/${epoch}/index`; + } + + receiptEpochPageKey(epoch, page) { + return `receipt/epoch/${epoch}/page/${page}`; + } + + disputeOpenCountKey(opener) { + return `disp/open/${opener}`; + } + + providerOpenDisputeCountKey(provider) { + return `disp/provider-open/${provider}`; + } + + async disputeOpenCount(key) { + const record = await this.get(key); + const count = record?.count ?? 0; + if (!Number.isSafeInteger(count) || count < 0) { + return new Error('Invalid open dispute count.'); + } + return count; + } + + async providerHasOpenDispute(provider) { + const count = await this.disputeOpenCount(this.providerOpenDisputeCountKey(provider)); + if (count instanceof Error) return count; + return count > 0; + } + + async closeDisputeCounts(dispute) { + const openerKey = this.disputeOpenCountKey(dispute.opened_by); + const providerKey = this.providerOpenDisputeCountKey(dispute.provider); + const openerCount = await this.disputeOpenCount(openerKey); + if (openerCount instanceof Error) return openerCount; + const providerCount = await this.disputeOpenCount(providerKey); + if (providerCount instanceof Error) return providerCount; + if (openerCount < 1 || providerCount < 1) { + return new Error('Open dispute count underflow.'); + } + await this.put(openerKey, { + opener: dispute.opened_by, + count: openerCount - 1, + updated_at: this.tx, + }); + await this.put(providerKey, { + provider: dispute.provider, + count: providerCount - 1, + updated_at: this.tx, + }); + return null; + } + + earningKey(provider, rail) { + return `earn/${rail}/${provider}`; + } + + feeCumKey(rail) { + return `fee/${rail}/cum`; + } + + burnCumKey(rail) { + return `burn/${rail}/cum`; + } + + async setProviderRails() { + const shapeError = this.validateExactCommandValue(['op', 'rails'], 'set_provider_rails'); + if (shapeError) return shapeError; + return await this.applySetProviderRails(this.address, this.value.rails, this.tx); + } + + async applySetProviderRails(providerId, acceptedRails, stamp) { + const consentError = await this.requireConsent(providerId); + if (consentError) return consentError; + const provider = await this.get(`prov/${providerId}`); + if (!provider || provider.status !== 'active') return new Error('Provider registration required.'); + const rails = this.normalizeProviderAcceptedRails(acceptedRails); + if (rails instanceof Error) return rails; + + const updated = { + ...provider, + accepted_rails: rails, + accepted_rails_schema_version: PROVIDER_RAIL_SCHEMA_VERSION, + accepted_rails_set_by: providerId, + accepted_rails_set_at: stamp, + updated_at: stamp, + }; + await this.put(`prov/${providerId}`, updated); + console.log('mayhem setProviderRails', updated); + return { ok: true, op: 'setProviderRails', provider: providerId, rails }; + } + + async setProviderKyb() { + const adminError = await this.requireAdmin(); + if (adminError) return adminError; + const shapeError = this.validateExactCommandValue( + [ + 'op', + 'provider', + 'legal_name', + 'jurisdiction', + 'proof_hash', + 'kyb_ref', + 'verified_at', + 'admin_sig', + ], + 'set_provider_kyb', + ['schema_version'] + ); + if (shapeError) return shapeError; + + const normalized = this.normalizeProviderKybValue(this.value); + if (normalized instanceof Error) return normalized; + if (!(await this.verifyProviderKybSignature(normalized))) { + return new Error('Invalid provider KYB admin signature.'); + } + + const key = `prov/${normalized.provider}`; + const provider = await this.get(key); + if (!provider) return new Error('Provider not found.'); + if (provider.status === 'banned') return new Error('Provider is banned.'); + const kybBanError = await this.rejectBannedProviderKyb(normalized); + if (kybBanError) return kybBanError; + + const record = { + status: 'verified', + provider: normalized.provider, + legal_name: normalized.legal_name, + jurisdiction: normalized.jurisdiction, + proof_hash: normalized.proof_hash, + kyb_ref: normalized.kyb_ref, + verified_at: normalized.verified_at, + verified_by: this.address, + verified_by_role: 'admin', + admin_sig: normalized.admin_sig, + schema_version: normalized.schema_version, + updated_at: this.tx, + }; + const providerSummary = { + status: 'verified', + legal_name: record.legal_name, + jurisdiction: record.jurisdiction, + proof_hash: record.proof_hash, + kyb_ref: record.kyb_ref, + verified_at: record.verified_at, + verified_by: record.verified_by, + verified_by_role: 'admin', + schema_version: record.schema_version, + set_at: this.tx, + }; + const updatedProvider = { + ...provider, + kyb: providerSummary, + updated_at: this.tx, + }; + + await this.put(`kyb/${normalized.provider}`, record); + await this.put(key, updatedProvider); + console.log('mayhem setProviderKyb', record); + return { + ok: true, + op: 'setProviderKyb', + provider: normalized.provider, + att_tier: 4, + }; + } + + async revokeProviderKyb() { + const adminError = await this.requireAdmin(); + if (adminError) return adminError; + const shapeError = this.validateExactCommandValue( + ['op', 'provider'], + 'revoke_provider_kyb', + ['reason_hash'] + ); + if (shapeError) return shapeError; + if (!this.isHexBytes(this.value.provider, 32)) return new Error('Invalid provider id.'); + if ( + this.value.reason_hash !== undefined && + !this.isHexBytes(this.value.reason_hash, 32) + ) { + return new Error('Invalid provider KYB revoke reason hash.'); + } + + const key = `prov/${this.value.provider}`; + const provider = await this.get(key); + if (!provider) return new Error('Provider not found.'); + const current = await this.get(`kyb/${this.value.provider}`); + if (!current || current.status !== 'verified') return new Error('Active provider KYB not found.'); + + const revoked = { + ...current, + status: 'revoked', + revoked_at: this.tx, + revoked_by: this.address, + revoked_by_role: 'admin', + revoke_reason_hash: this.value.reason_hash ?? null, + updated_at: this.tx, + }; + const updatedProvider = { + ...provider, + kyb: { + ...(provider.kyb ?? {}), + status: 'revoked', + revoked_at: this.tx, + revoked_by: this.address, + revoked_by_role: 'admin', + revoke_reason_hash: this.value.reason_hash ?? null, + }, + updated_at: this.tx, + }; + + await this.put(`kyb/${this.value.provider}`, revoked); + await this.put(key, updatedProvider); + const kybBanIndexError = await this.writeProviderKybBanIndexes(revoked, { + status: 'revoked', + source: 'revoke_provider_kyb', + provider: this.value.provider, + reason_hash: this.value.reason_hash ?? null, + recorded_at: this.tx, + recorded_by: this.address, + recorded_by_role: 'admin', + }); + if (kybBanIndexError instanceof Error) return kybBanIndexError; + console.log('mayhem revokeProviderKyb', revoked); + return { + ok: true, + op: 'revokeProviderKyb', + provider: this.value.provider, + }; + } + + async banProvider() { + const adminError = await this.requireAdmin(); + if (adminError) return adminError; + if (!this.isSafeKeyPart(this.value.provider)) return new Error('Invalid provider id.'); + if (this.value.device_key !== undefined && !this.isHexBytes(this.value.device_key, 32)) { + return new Error('Invalid provider device key.'); + } + if (this.value.hardware_fingerprint !== undefined && !this.isHexBytes(this.value.hardware_fingerprint, 32)) { + return new Error('Invalid provider hardware fingerprint.'); + } + + const key = `prov/${this.value.provider}`; + const record = await this.get(key); + if (!record) return new Error('Provider not found.'); + if (record.status === 'banned') return new Error('Provider already banned.'); + + const tombstones = await this.tombstoneProviderEnclaves( + this.value.provider, + this.providerActiveEnclaves(record), + this.value.reason_hash ?? null + ); + if (tombstones instanceof Error) return tombstones; + + const updated = { + ...record, + status: 'banned', + enclaves: [], + tombstoned_enclaves: tombstones.map((tombstone) => tombstone.enclave_id), + banned_at: this.tx, + banned_by: this.address, + banned_by_role: 'admin', + ban_reason_hash: this.value.reason_hash ?? null, + updated_at: this.tx, + }; + const providerBan = { + target_type: 'provider', + target: this.value.provider, + provider: this.value.provider, + status: 'banned', + reason_hash: this.value.reason_hash ?? null, + banned_at: this.tx, + banned_by: this.address, + banned_by_role: 'admin', + reversible: true, + }; + const deviceKey = this.value.device_key ?? record.device_key ?? null; + const fingerprint = this.value.hardware_fingerprint ?? record.hardware_fingerprint ?? null; + await this.put(key, updated); + await this.put(`ban/provider/${this.value.provider}`, providerBan); + if (deviceKey) { + await this.put(`ban/device/${deviceKey}`, { + target_type: 'device', + target: deviceKey, + provider: this.value.provider, + status: 'banned', + reason_hash: this.value.reason_hash ?? null, + banned_at: this.tx, + banned_by: this.address, + banned_by_role: 'admin', + reversible: true, + }); + } + if (fingerprint) { + const fpKey = `ban/fingerprint/${fingerprint}`; + const current = await this.get(fpKey); + const wallets = { + ...(current?.wallets ?? {}), + [this.value.provider]: { + provider: this.value.provider, + reason_hash: this.value.reason_hash ?? null, + banned_at: this.tx, + }, + }; + await this.put(fpKey, { + target_type: 'fingerprint', + target: fingerprint, + status: 'banned', + wallets, + reason_hash: this.value.reason_hash ?? null, + banned_at: current?.banned_at ?? this.tx, + updated_at: this.tx, + banned_by: this.address, + banned_by_role: 'admin', + reversible: true, + auto_reject: true, + }); + } + if (record.kyb?.status === 'verified') { + const kybBanIndexError = await this.writeProviderKybBanIndexes(record.kyb, { + status: 'banned', + source: 'ban_provider', + provider: this.value.provider, + reason_hash: this.value.reason_hash ?? null, + recorded_at: this.tx, + recorded_by: this.address, + recorded_by_role: 'admin', + }); + if (kybBanIndexError instanceof Error) return kybBanIndexError; + } + console.log('mayhem banProvider', updated); + return { + ok: true, + op: 'banProvider', + provider: this.value.provider, + tombstoned_enclaves: updated.tombstoned_enclaves, + device_key: deviceKey, + hardware_fingerprint: fingerprint, + }; + } + + async unban() { + const adminError = await this.requireAdmin(); + if (adminError) return adminError; + const shapeError = this.validateExactCommandValue( + ['op', 'target_type', 'target', 'reason_hash'], + 'unban' + ); + if (shapeError) return shapeError; + const targetType = String(this.value.target_type).toLowerCase(); + if (!BAN_TARGET_TYPES.has(targetType)) return new Error('Unsupported ban target type.'); + if (!this.isHexBytes(this.value.target, 32)) return new Error('Invalid ban target.'); + if (!this.isHexBytes(this.value.reason_hash, 32)) return new Error('Invalid unban reason hash.'); + + if (targetType === 'provider') { + const provider = await this.get(`prov/${this.value.target}`); + if (!provider) return new Error('Provider not found.'); + if (provider.status === 'banned') { + await this.put(`prov/${this.value.target}`, { + ...provider, + status: 'active', + unbanned_at: this.tx, + unbanned_by: this.address, + unbanned_by_role: 'admin', + unban_reason_hash: this.value.reason_hash, + updated_at: this.tx, + }); + } + } + + const key = this.banRecordKey(targetType, this.value.target); + const current = await this.get(key); + const record = { + ...(current ?? {}), + target_type: targetType, + target: this.value.target, + status: 'unbanned', + unbanned_at: this.tx, + unbanned_by: this.address, + unbanned_by_role: 'admin', + unban_reason_hash: this.value.reason_hash, + reversible: true, + }; + await this.put(key, record); + console.log('mayhem unban', record); + return { + ok: true, + op: 'unban', + target_type: targetType, + target: this.value.target, + }; + } + + async deviceRebind() { + const adminError = await this.requireAdmin(); + if (adminError) return adminError; + const shapeError = this.validateExactCommandValue( + ['op', 'device_key', 'provider', 'reason_hash'], + 'device_rebind' + ); + if (shapeError) return shapeError; + if (!this.isHexBytes(this.value.device_key, 32)) return new Error('Invalid device key.'); + if (!this.isHexBytes(this.value.provider, 32)) return new Error('Invalid provider id.'); + if (!this.isHexBytes(this.value.reason_hash, 32)) return new Error('Invalid rebind reason hash.'); + const provider = await this.get(`prov/${this.value.provider}`); + if (!provider || provider.status !== 'active') return new Error('Active provider not found.'); + const deviceBan = await this.get(`ban/device/${this.value.device_key}`); + if (deviceBan?.status === 'banned') return new Error('Device key is banned.'); + const current = await this.get(`device/${this.value.device_key}`); + const record = { + ...(current ?? {}), + device_key: this.value.device_key, + provider: this.value.provider, + status: 'active', + rebound_at: this.tx, + rebound_by: this.address, + rebound_by_role: 'admin', + rebind_reason_hash: this.value.reason_hash, + previous_provider: current?.provider ?? null, + }; + await this.put(`device/${this.value.device_key}`, record); + await this.put(`prov/${this.value.provider}`, { + ...provider, + device_key: this.value.device_key, + device_key_bound_at: this.tx, + updated_at: this.tx, + }); + console.log('mayhem deviceRebind', record); + return { + ok: true, + op: 'deviceRebind', + device_key: this.value.device_key, + provider: this.value.provider, + }; + } + + async setModelRef() { + const adminError = await this.requireAdmin(); + if (adminError) return adminError; + + const validationError = this.validateModelRef(this.value); + if (validationError) return validationError; + + const key = `modelref/${this.value.model_id}`; + const current = await this.get(key); + const calibration = Object.hasOwn(this.value, 'activity_calibration') + ? this.value.activity_calibration : current?.activity_calibration; + if (calibration) { + const error = this.validateActivityCalibration(calibration, this.modelClassFor(this.value), this.value.rate_map); + if (error) return error; + } + const record = { + model_id: this.value.model_id, + model_class: this.modelClassFor(this.value), + denom: PRICE_DENOMINATION, + rate_map: this.normalizeRateMap(this.value.rate_map), + ver: (current?.ver ?? 0) + 1, + source_hash: this.value.source_hash ?? null, + ...(calibration ? { + activity_calibration: cloneValue(calibration), + } : {}), + updated_at: this.tx, + set_by: this.address, + set_by_role: 'admin', + }; + await this.put(key, record); + console.log('mayhem setModelRef', record); + return { ok: true, op: 'setModelRef', model_id: record.model_id, ver: record.ver }; + } + + async publishCatalog() { + const adminError = await this.requireAdmin(); + if (adminError) return adminError; + + const validationError = this.validateCatalogRelease(this.value); + if (validationError) return validationError; + + const current = await this.get('catalog/current'); + const record = { + catalog_id: this.value.catalog_id, + source_kind: this.value.source_kind, + catalog_url: this.value.catalog_url, + signature_url: this.value.signature_url, + catalog_hash: this.value.catalog_hash, + signature_hash: this.value.signature_hash, + key_id: this.value.key_id, + public_key: this.value.public_key, + model_count: this.value.model_count, + artifact_count: this.value.artifact_count, + canaries: cloneValue(this.value.canaries), + ver: (current?.ver ?? 0) + 1, + supersedes: current?.catalog_hash ?? null, + status: 'active', + published_at: this.tx, + published_by: this.address, + published_by_role: 'admin', + }; + if (hasOwn(this.value, 'parts_anchor')) { + record.parts_anchor = cloneValue(this.value.parts_anchor); + } + if (hasOwn(this.value, 'blessed_runtimes')) { + record.blessed_runtimes = cloneValue(this.value.blessed_runtimes); + } + if (hasOwn(this.value, 'outcome_classes')) { + record.outcome_classes = cloneValue(this.value.outcome_classes); + } + await this.put(`catalog/release/${record.catalog_hash}`, record); + await this.put('catalog/current', record); + console.log('mayhem publishCatalog', record); + return { + ok: true, + op: 'publishCatalog', + catalog_hash: record.catalog_hash, + ver: record.ver, + }; + } + + async registerEnclave() { + const adminError = await this.requireAdmin(); + if (adminError) return adminError; + if (!this.isSafeKeyPart(this.value.enclave_id)) return new Error('Invalid enclave id.'); + if (!this.isSafeModelId(this.value.model_id)) return new Error('Invalid model id.'); + + const key = `enclave/${this.value.enclave_id}`; + if ((await this.get(key)) !== null) return new Error('Enclave already registered.'); + const modelClass = this.modelClassFor(this.value); + const classError = this.validateModelClass(modelClass, 'Enclave model_class'); + if (classError) return classError; + const tierError = this.validateLaunchEnclaveAttestationTier(this.value.att_tier); + if (tierError) return tierError; + const capsError = this.validateEnclaveCaps(this.value.caps, modelClass); + if (capsError) return capsError; + const artifactError = this.validateEnclaveArtifactBinding(this.value); + if (artifactError) return artifactError; + if (hasOwn(this.value, 'approved_binary_hashes') && !Array.isArray(this.value.approved_binary_hashes)) { + return new Error('Enclave approved_binary_hashes must be an array.'); + } + const approvedBinaryHashes = this.normalizeApprovedBinaryHashes( + this.value.binary_hash, + this.value.approved_binary_hashes + ); + const binaryHashesError = this.validateApprovedBinaryHashes( + this.value.binary_hash, + approvedBinaryHashes + ); + if (binaryHashesError) return binaryHashesError; + const launchMeasurements = this.normalizeEnclaveLaunchMeasurements(this.value.launch_measurements); + const measurementsError = this.validateEnclaveLaunchMeasurements(launchMeasurements, this.value.att_tier); + if (measurementsError) return measurementsError; + const quant = this.normalizeEnclaveQuant(this.value.quant ?? 'unknown'); + const quantError = this.validateEnclaveQuant(quant); + if (quantError) return quantError; + + const record = { + enclave_id: this.value.enclave_id, + model_id: this.value.model_id, + model_class: modelClass, + backend: this.value.backend, + artifact_root: this.value.artifact_root, + artifact_root_kind: this.value.artifact_root_kind, + artifact_source: cloneValue(this.value.artifact_source), + artifact_sidecars: cloneValue(this.value.artifact_sidecars ?? {}), + source_sha256: this.value.source_sha256 ?? null, + manifest_hash: this.value.manifest_hash, + att_tier: this.value.att_tier, + min_att_tier: this.value.att_tier, + pending_min_att_tier: null, + quant, + binary_hash: this.value.binary_hash, + approved_binary_hashes: approvedBinaryHashes, + launch_measurements: launchMeasurements, + caps: cloneValue(this.value.caps), + status: 'active', + providers: [], + created_by: this.address, + created_by_role: 'admin', + registered_at: this.tx, + updated_at: this.tx, + retired_at: null, + }; + await this.put(key, record); + console.log('mayhem registerEnclave', record); + return { ok: true, op: 'registerEnclave', enclave_id: record.enclave_id }; + } + + async updateEnclave() { + const adminError = await this.requireAdmin(); + if (adminError) return adminError; + if (!this.isSafeKeyPart(this.value.enclave_id)) return new Error('Invalid enclave id.'); + const allowedFields = new Set(['op', 'enclave_id', ...ENCLAVE_UPDATE_FIELDS]); + const unknownFields = Object.keys(this.value).filter((field) => !allowedFields.has(field)).sort(); + if (unknownFields.length > 0) { + return new Error(`update_enclave does not accept immutable fields: ${unknownFields.join(', ')}.`); + } + + const key = `enclave/${this.value.enclave_id}`; + const record = await this.get(key); + if (!record) return new Error('Enclave not found.'); + if (record.status === 'retired') return new Error('Enclave is retired.'); + + let changed = false; + const updated = cloneValue(record); + for (const field of ENCLAVE_UPDATE_FIELDS) { + if (!hasOwn(this.value, field)) continue; + updated[field] = cloneValue(this.value[field]); + changed = true; + } + if (!changed) return new Error('No enclave fields to update.'); + if (hasOwn(this.value, 'quant')) { + updated.quant = this.normalizeEnclaveQuant(updated.quant); + } + const modelClass = this.modelClassFor(updated); + const classError = this.validateModelClass(modelClass, 'Enclave model_class'); + if (classError) return classError; + const tierError = this.validateLaunchEnclaveAttestationTier(updated.att_tier); + if (tierError) return tierError; + const capsError = this.validateEnclaveCaps(updated.caps, modelClass); + if (capsError) return capsError; + const artifactError = this.validateEnclaveArtifactBinding(updated); + if (artifactError) return artifactError; + if (hasOwn(this.value, 'approved_binary_hashes')) { + if (!Array.isArray(this.value.approved_binary_hashes)) { + return new Error('Enclave approved_binary_hashes must be an array.'); + } + updated.approved_binary_hashes = this.normalizeApprovedBinaryHashes( + updated.binary_hash, + this.value.approved_binary_hashes + ); + } else { + updated.approved_binary_hashes = this.normalizeApprovedBinaryHashes( + updated.binary_hash, + [record.binary_hash, ...(record.approved_binary_hashes ?? [])] + ); + } + const binaryHashesError = this.validateApprovedBinaryHashes( + updated.binary_hash, + updated.approved_binary_hashes + ); + if (binaryHashesError) return binaryHashesError; + updated.launch_measurements = this.normalizeEnclaveLaunchMeasurements(updated.launch_measurements); + const measurementsError = this.validateEnclaveLaunchMeasurements(updated.launch_measurements, updated.att_tier); + if (measurementsError) return measurementsError; + const quantError = this.validateEnclaveQuant(updated.quant ?? 'unknown'); + if (quantError) return quantError; + updated.quant = this.normalizeEnclaveQuant(updated.quant ?? 'unknown'); + + updated.updated_by = this.address; + updated.updated_by_role = 'admin'; + updated.updated_at = this.tx; + await this.put(key, updated); + console.log('mayhem updateEnclave', updated); + return { ok: true, op: 'updateEnclave', enclave_id: updated.enclave_id }; + } + + async setEnclaveMinTier() { + const adminError = await this.requireAdmin(); + if (adminError) return adminError; + const shapeError = this.validateExactCommandValue( + [ + 'op', + 'enclave_id', + 'min_att_tier', + 'submitted_epoch', + 'effective_epoch', + 'submitted_at', + 'reason_hash', + ], + 'set_enclave_min_tier' + ); + if (shapeError) return shapeError; + if (!this.isSafeKeyPart(this.value.enclave_id)) return new Error('Invalid enclave id.'); + if (!this.isHexBytes(this.value.reason_hash, 32)) return new Error('Invalid min-tier reason hash.'); + + const tierError = this.validateLaunchEnclaveAttestationTier(this.value.min_att_tier); + if (tierError) return tierError; + if (this.value.effective_epoch <= this.value.submitted_epoch) { + return new Error('Enclave min-tier effective_epoch must be after submitted_epoch.'); + } + + const enclaveKey = `enclave/${this.value.enclave_id}`; + const enclave = await this.get(enclaveKey); + if (!enclave) return new Error('Enclave not found.'); + if (enclave.status !== 'active') return new Error('Enclave is not active.'); + const enclaveRoleError = this.requireAdminCreatedEnclave(enclave); + if (enclaveRoleError) return enclaveRoleError; + + const params = await this.activeParamsAt(this.value.submitted_at, ['min_tier_notice_epochs']); + if ( + this.value.effective_epoch - this.value.submitted_epoch < + params.min_tier_notice_epochs + ) { + return new Error('Enclave min-tier changes require at least min_tier_notice_epochs notice.'); + } + + const currentEpoch = await this.currentAppliedEpoch(); + const activePolicy = await this.enclaveMinTierPolicy(enclave, currentEpoch); + const pending = { + min_att_tier: this.value.min_att_tier, + previous_min_att_tier: activePolicy.min_att_tier, + submitted_epoch: this.value.submitted_epoch, + effective_epoch: this.value.effective_epoch, + submitted_at: this.value.submitted_at, + reason_hash: this.value.reason_hash, + scheduled_at: this.tx, + scheduled_by: this.address, + scheduled_by_role: 'admin', + }; + const record = { + enclave_id: this.value.enclave_id, + current_min_att_tier: activePolicy.min_att_tier, + current_epoch: currentEpoch, + pending, + updated_at: this.tx, + updated_by: this.address, + updated_by_role: 'admin', + }; + await this.put(`tierpolicy/enclave/${this.value.enclave_id}`, record); + await this.put(enclaveKey, { + ...enclave, + min_att_tier: activePolicy.min_att_tier, + pending_min_att_tier: pending, + updated_at: this.tx, + updated_by: this.address, + updated_by_role: 'admin', + }); + console.log('mayhem setEnclaveMinTier', record); + return { + ok: true, + op: 'setEnclaveMinTier', + enclave_id: this.value.enclave_id, + min_att_tier: this.value.min_att_tier, + effective_epoch: this.value.effective_epoch, + }; + } + + async retireEnclave() { + const adminError = await this.requireAdmin(); + if (adminError) return adminError; + if (!this.isSafeKeyPart(this.value.enclave_id)) return new Error('Invalid enclave id.'); + + const key = `enclave/${this.value.enclave_id}`; + const record = await this.get(key); + if (!record) return new Error('Enclave not found.'); + if (record.status === 'retired') return new Error('Enclave already retired.'); + + const activeProviders = this.enclaveActiveProviders(record); + const tombstones = await this.tombstoneEnclaveProviders( + this.value.enclave_id, + activeProviders, + null + ); + if (tombstones instanceof Error) return tombstones; + + const updated = { + ...record, + status: 'retired', + providers: [], + tombstoned_providers: tombstones + .filter((tombstone) => tombstone.serve_tombstoned) + .map((tombstone) => tombstone.provider), + retired_at: this.tx, + retired_by: this.address, + retired_by_role: 'admin', + updated_by: this.address, + updated_by_role: 'admin', + updated_at: this.tx, + }; + await this.put(key, updated); + console.log('mayhem retireEnclave', updated); + return { + ok: true, + op: 'retireEnclave', + enclave_id: updated.enclave_id, + tombstoned_providers: updated.tombstoned_providers, + }; + } + + async joinEnclave() { + const shapeError = this.validateExactCommandValue( + [ + 'op', + 'enclave_id', + 'att_tier', + 'attestation_head', + 'served_ctx', + 'served_modalities', + 'served_specialities', + 'ctx_bracket', + 'ctx_bracket_table_ver', + ], + 'join_enclave', + ['hardware_fingerprint', 'device_key'] + ); + if (shapeError) return shapeError; + if (!this.isSafeKeyPart(this.value.enclave_id)) return new Error('Invalid enclave id.'); + + return this.applyJoinEnclave( + this.address, + this.value.enclave_id, + this.tx, + this.value.att_tier, + this.value.attestation_head, + this.value.hardware_fingerprint ?? null, + this.value.device_key ?? null, + { + served_ctx: this.value.served_ctx, + served_modalities: this.value.served_modalities, + served_specialities: this.value.served_specialities, + ctx_bracket: this.value.ctx_bracket, + ctx_bracket_table_ver: this.value.ctx_bracket_table_ver, + } + ); + } + + async applyJoinEnclave( + providerId, + enclaveId, + stamp, + attTier, + attestationHead, + hardwareFingerprint = null, + deviceKey = null, + serveTerms = null + ) { + const consentError = await this.requireConsent(providerId); + if (consentError) return consentError; + const providerError = await this.requireProvider(providerId); + if (providerError) return providerError; + if (!Number.isSafeInteger(attTier) || attTier < 1 || attTier > MAX_LAUNCH_ENCLAVE_ATTESTATION_TIER) { + return new Error('Invalid provider attestation tier.'); + } + if (!this.isHexBytes(attestationHead, 32)) { + return new Error('Invalid provider attestation head.'); + } + if (hardwareFingerprint !== null && !this.isHexBytes(hardwareFingerprint, 32)) { + return new Error('Invalid provider hardware fingerprint.'); + } + if (deviceKey !== null && !this.isHexBytes(deviceKey, 32)) { + return new Error('Invalid provider device key.'); + } + + const enclave = await this.get(`enclave/${enclaveId}`); + if (!enclave) return new Error('Enclave not found.'); + if (enclave.status !== 'active') return new Error('Enclave is not active.'); + const enclaveRoleError = this.requireAdminCreatedEnclave(enclave); + if (enclaveRoleError) return enclaveRoleError; + const normalizedServeTerms = await this.normalizeProviderServeTerms( + enclaveId, + serveTerms, + 'provider serve' + ); + if (normalizedServeTerms instanceof Error) return normalizedServeTerms; + const priceError = await this.requireCurrentAdminPrice( + enclaveId, + normalizedServeTerms?.ctx_bracket ?? null + ); + if (priceError) return priceError; + const minTierPolicy = await this.enclaveMinTierPolicy(enclave); + if (attTier !== enclave.att_tier) { + return new Error( + `Provider attestation tier ${attTier} does not match enclave tier ${enclave.att_tier}.` + ); + } + const provider = await this.get(`prov/${providerId}`); + if (!provider || provider.status !== 'active') return new Error('Provider registration required.'); + const effectiveTier = provider.kyb?.status === 'verified' ? 4 : attTier; + if (effectiveTier < minTierPolicy.min_att_tier) { + return new Error( + `Enclave now requires minimum attestation tier ${minTierPolicy.min_att_tier}; provider proved tier ${effectiveTier}.` + ); + } + + const key = `serve/${providerId}/${enclaveId}`; + const existing = await this.get(key); + if (existing && existing.status === 'active') return new Error('Provider already serving enclave.'); + if (deviceKey !== null) { + const deviceBan = await this.get(`ban/device/${deviceKey}`); + if (deviceBan?.status === 'banned') return new Error('Provider device key is banned.'); + const binding = await this.get(`device/${deviceKey}`); + if (binding?.provider && binding.provider !== providerId) { + return new Error('Provider device key is bound to a different wallet; admin rebind required.'); + } + } + + if (hardwareFingerprint !== null) { + const fingerprintBan = await this.get(`ban/fingerprint/${hardwareFingerprint}`); + if (fingerprintBan?.status === 'banned') { + return new Error('Provider hardware fingerprint is banned.'); + } + } + + const record = { + provider: providerId, + enclave_id: enclaveId, + model_id: enclave.model_id, + status: 'active', + att_tier: attTier, + effective_att_tier: effectiveTier, + attestation_head: attestationHead.toLowerCase(), + ...(normalizedServeTerms ?? {}), + ...(hardwareFingerprint !== null ? { hardware_fingerprint: hardwareFingerprint } : {}), + ...(deviceKey !== null ? { device_key: deviceKey } : {}), + joined_at: existing?.joined_at ?? stamp, + updated_at: stamp, + left_at: null, + rooms: Array.isArray(existing?.rooms) ? existing.rooms.slice() : [], + }; + await this.put(key, record); + await this.put(`enclave/${enclaveId}`, { + ...enclave, + providers: this.enclaveProvidersWith(enclave, providerId), + updated_at: stamp, + }); + if (deviceKey !== null) { + const currentDevice = await this.get(`device/${deviceKey}`); + await this.put(`device/${deviceKey}`, { + ...(currentDevice ?? {}), + device_key: deviceKey, + provider: providerId, + status: 'active', + bound_at: stamp, + updated_at: stamp, + }); + } + await this.put(`prov/${providerId}`, { + ...provider, + enclaves: this.providerEnclavesWith(provider, enclaveId), + ...(hardwareFingerprint !== null ? { hardware_fingerprint: hardwareFingerprint } : {}), + ...(deviceKey !== null ? { device_key: deviceKey, device_key_bound_at: stamp } : {}), + updated_at: stamp, + }); + console.log('mayhem joinEnclave', record); + return { ok: true, op: 'joinEnclave', provider: providerId, enclave_id: enclaveId }; + } + + async leaveEnclave() { + const shapeError = this.validateExactCommandValue(['op', 'enclave_id'], 'leave_enclave'); + if (shapeError) return shapeError; + if (!this.isSafeKeyPart(this.value.enclave_id)) return new Error('Invalid enclave id.'); + + return this.applyLeaveEnclave(this.address, this.value.enclave_id, this.tx); + } + + async applyLeaveEnclave(providerId, enclaveId, stamp) { + const key = `serve/${providerId}/${enclaveId}`; + const record = await this.get(key); + if (!record || record.status !== 'active') return new Error('Provider is not serving enclave.'); + if (Array.isArray(record.rooms) && record.rooms.length > 0) { + return new Error('Provider must leave rooms before leaving enclave.'); + } + + const updated = { + ...record, + status: 'inactive', + updated_at: stamp, + left_at: stamp, + }; + await this.put(key, updated); + const provider = await this.get(`prov/${providerId}`); + if (provider) { + await this.put(`prov/${providerId}`, { + ...provider, + enclaves: this.providerEnclavesWithout(provider, enclaveId), + updated_at: stamp, + }); + } + const enclave = await this.get(`enclave/${enclaveId}`); + if (enclave) { + await this.put(`enclave/${enclaveId}`, { + ...enclave, + providers: this.enclaveProvidersWithout(enclave, providerId), + updated_at: stamp, + }); + } + console.log('mayhem leaveEnclave', updated); + return { ok: true, op: 'leaveEnclave', provider: providerId, enclave_id: enclaveId }; + } + + async joinRoom() { + const shapeError = this.validateExactCommandValue( + ['op', 'room_id', 'enclave_id'], + 'join_room' + ); + if (shapeError) return shapeError; + if (!this.isSafeKeyPart(this.value.room_id)) return new Error('Invalid room id.'); + if (!this.isSafeKeyPart(this.value.enclave_id)) return new Error('Invalid enclave id.'); + + return this.applyJoinRoom(this.address, this.value.room_id, this.value.enclave_id, this.tx); + } + + async applyJoinRoom(providerId, roomId, enclaveId, stamp) { + const consentError = await this.requireConsent(providerId); + if (consentError) return consentError; + const providerError = await this.requireProvider(providerId); + if (providerError) return providerError; + + const room = await this.get(`room/${roomId}`); + if (!room) return new Error('Room not found.'); + if (room.status !== 'open') return new Error('Room is not open.'); + + const serving = await this.get(`serve/${providerId}/${enclaveId}`); + if (!serving || serving.status !== 'active') return new Error('Provider is not serving enclave.'); + const enclave = await this.get(`enclave/${enclaveId}`); + if (!enclave || enclave.status !== 'active') return new Error('Enclave is not active.'); + const enclaveRoleError = this.requireAdminCreatedEnclave(enclave); + if (enclaveRoleError) return enclaveRoleError; + const roomRoleError = this.requireAdminCreatedRoom(room); + if (roomRoleError) return roomRoleError; + const priceError = await this.requireCurrentAdminPrice(enclaveId, serving.ctx_bracket ?? null); + if (priceError) return priceError; + if (room.enclave_id !== enclaveId) { + return new Error('Room enclave does not match served enclave.'); + } + if (serving.model_id !== enclave.model_id || enclave.model_id !== room.model_id) { + return new Error('Enclave model does not match room model.'); + } + + const key = `roomserve/${roomId}/${providerId}/${enclaveId}`; + const existing = await this.get(key); + if (existing && existing.status === 'active') return new Error('Provider already joined room with enclave.'); + + const rooms = Array.isArray(serving.rooms) ? serving.rooms.slice() : []; + if (!rooms.includes(roomId)) rooms.push(roomId); + rooms.sort(); + const record = { + room_id: roomId, + sidechannel: room.sidechannel, + provider: providerId, + enclave_id: enclaveId, + model_id: enclave.model_id, + status: 'active', + joined_at: existing?.joined_at ?? stamp, + updated_at: stamp, + left_at: null, + }; + await this.put(key, record); + await this.put(`serve/${providerId}/${enclaveId}`, { + ...serving, + rooms, + updated_at: stamp, + }); + await this.put(`room/${roomId}`, { + ...room, + serves: this.roomServesWith(room, providerId, enclaveId), + serves_updated_at: stamp, + }); + console.log('mayhem joinRoom', record); + return { + ok: true, + op: 'joinRoom', + room_id: roomId, + provider: providerId, + enclave_id: enclaveId, + sidechannel: room.sidechannel, + }; + } + + async leaveRoom() { + const shapeError = this.validateExactCommandValue( + ['op', 'room_id', 'enclave_id'], + 'leave_room' + ); + if (shapeError) return shapeError; + if (!this.isSafeKeyPart(this.value.room_id)) return new Error('Invalid room id.'); + if (!this.isSafeKeyPart(this.value.enclave_id)) return new Error('Invalid enclave id.'); + + return this.applyLeaveRoom(this.address, this.value.room_id, this.value.enclave_id, this.tx); + } + + async applyLeaveRoom(providerId, roomId, enclaveId, stamp) { + const key = `roomserve/${roomId}/${providerId}/${enclaveId}`; + const record = await this.get(key); + if (!record) return new Error('Provider has not joined room with enclave.'); + if (record.status !== 'active') { + return { + ok: true, + op: 'leaveRoom', + room_id: roomId, + provider: providerId, + enclave_id: enclaveId, + sidechannel: record.sidechannel, + status: record.status, + idempotent: true, + }; + } + + const servingKey = `serve/${providerId}/${enclaveId}`; + const serving = await this.get(servingKey); + const rooms = Array.isArray(serving?.rooms) + ? serving.rooms.filter((servingRoomId) => servingRoomId !== roomId) + : []; + const roomKey = `room/${roomId}`; + const room = await this.get(roomKey); + const updated = { + ...record, + status: 'inactive', + updated_at: stamp, + left_at: stamp, + }; + await this.put(key, updated); + if (serving) { + await this.put(servingKey, { + ...serving, + rooms, + updated_at: stamp, + }); + } + if (room) { + await this.put(roomKey, { + ...room, + serves: this.roomServesWithout(room, providerId, enclaveId), + serves_updated_at: stamp, + }); + } + console.log('mayhem leaveRoom', updated); + return { + ok: true, + op: 'leaveRoom', + room_id: roomId, + provider: providerId, + enclave_id: enclaveId, + sidechannel: updated.sidechannel, + }; + } + + async openRoom() { + const adminError = await this.requireAdmin(); + if (adminError) return adminError; + if (!this.isSafeKeyPart(this.value.enclave_id)) return new Error('Invalid enclave id.'); + if (this.value.model_id && !this.isSafeModelId(this.value.model_id)) { + return new Error('Invalid model id.'); + } + + const policyError = this.validateRoomPolicy(this.value.policy); + if (policyError) return policyError; + + let recordModelId = this.value.model_id; + const enclave = await this.get(`enclave/${this.value.enclave_id}`); + if (!enclave) return new Error('Enclave not found.'); + if (enclave.status !== 'active') return new Error('Enclave is not active.'); + const enclaveRoleError = this.requireAdminCreatedEnclave(enclave); + if (enclaveRoleError) return enclaveRoleError; + if (this.value.model_id && this.value.model_id !== enclave.model_id) { + return new Error('Room model does not match enclave model.'); + } + recordModelId = enclave.model_id; + + const roomId = await deriveRoomId(this.value.enclave_id, this.address, this.value.nonce); + const key = `room/${roomId}`; + const existing = await this.get(key); + if (existing && existing.status !== 'closed') return new Error('Room already open.'); + + const record = { + room_id: roomId, + sidechannel: roomSidechannelName(roomId), + enclave_id: this.value.enclave_id, + model_id: recordModelId, + label: this.value.label, + creator: this.address, + creator_role: 'admin', + policy: cloneValue(this.value.policy), + serves: [], + serves_updated_at: null, + created_at: this.tx, + updated_at: this.tx, + closed_at: null, + status: 'open', + }; + await this.put(key, record); + console.log('mayhem openRoom', record); + return { ok: true, op: 'openRoom', room_id: roomId, sidechannel: record.sidechannel }; + } + + async closeRoom() { + const adminError = await this.requireAdmin(); + if (adminError) return adminError; + if (!this.isSafeKeyPart(this.value.room_id)) return new Error('Invalid room id.'); + + const key = `room/${this.value.room_id}`; + const record = await this.get(key); + if (!record) return new Error('Room not found.'); + if (record.status === 'closed') return new Error('Room already closed.'); + + const tombstones = await this.tombstoneRoomServes( + this.value.room_id, + this.roomServingEntries(record), + null + ); + if (tombstones instanceof Error) return tombstones; + const current = (await this.get(key)) ?? record; + const updated = { + ...current, + status: 'closed', + serves: [], + serves_updated_at: this.tx, + tombstoned_serves: tombstones + .filter((tombstone) => tombstone.roomserve_tombstoned) + .map(({ provider, enclave_id }) => ({ provider, enclave_id })), + updated_at: this.tx, + closed_at: this.tx, + closed_by: this.address, + closed_by_role: 'admin', + }; + await this.put(key, updated); + console.log('mayhem closeRoom', updated); + return { + ok: true, + op: 'closeRoom', + room_id: updated.room_id, + sidechannel: updated.sidechannel, + tombstoned_serves: updated.tombstoned_serves, + }; + } + + async setPrice() { + const adminError = await this.requireAdmin(); + if (adminError) return adminError; + if (!this.isSafeKeyPart(this.value.enclave_id)) return new Error('Invalid enclave id.'); + + const enclave = await this.get(`enclave/${this.value.enclave_id}`); + if (!enclave) return new Error('Enclave not found.'); + if (enclave.status === 'retired') return new Error('Enclave is retired.'); + + const modelRef = await this.get(`modelref/${enclave.model_id}`); + if (!modelRef) return new Error('Model reference not found.'); + const enclaveClass = this.modelClassFor(enclave); + const enclaveClassError = this.validateModelClass(enclaveClass, 'Enclave model_class'); + if (enclaveClassError) return enclaveClassError; + const modelRefClass = this.modelClassFor(modelRef); + const modelRefClassError = this.validateModelClass(modelRefClass, 'Model reference model_class'); + if (modelRefClassError) return modelRefClassError; + if (modelRefClass !== enclaveClass) { + return new Error('Model reference model_class must match enclave model_class.'); + } + + const rateError = this.validateRateMap(this.value.rate_map, enclaveClass, 'Enclave price rate_map', { + allowZeroPrice: true, + }); + if (rateError) return rateError; + const priceRateMap = this.normalizeRateMap(this.value.rate_map); + const modalityRateError = this.validateEnclaveModalityRateMap(enclave, priceRateMap); + if (modalityRateError) return modalityRateError; + const perReqAu = this.normalizeAu(this.value.per_req_au, 'Enclave price per_req_au'); + if (perReqAu instanceof Error) return perReqAu; + const minSessionAu = this.normalizeAu(this.value.min_session_au, 'Enclave price min_session_au'); + if (minSessionAu instanceof Error) return minSessionAu; + const params = await this.activeParamsAt(this.value.effective_at, [ + 'price_min_bps', + 'price_max_bps', + 'price_rate_limit_seconds', + ]); + const boundsError = this.validateRateMapBounds(priceRateMap, modelRef.rate_map, params); + if (boundsError) return boundsError; + + const ctxMeta = await this.priceCtxMetaForEnclave(enclave, this.value.ctx_bracket, this.value.effective_at, 'Enclave price'); + if (ctxMeta instanceof Error) return ctxMeta; + const key = this.priceScheduleKey(this.value.enclave_id, ctxMeta?.ctx_bracket ?? null); + const schedule = await this.priceSchedule(key, enclave, ctxMeta); + const latest = this.priceLatestEntry(schedule); + const latestSeed = this.priceLatestSeedEntry(schedule); + if ( + latestSeed && + this.value.effective_at - latestSeed.effective_at < params.price_rate_limit_seconds + ) { + return new Error('Price seed changes are limited by price_rate_limit_seconds.'); + } + + const record = { + enclave_id: this.value.enclave_id, + model_id: enclave.model_id, + denom: PRICE_DENOMINATION, + ver: latest ? latest.ver + 1 : 1, + rate_map: priceRateMap, + per_req_au: perReqAu, + min_session_au: minSessionAu, + effective_at: this.value.effective_at, + effective_from: this.tx, + updated_at: this.tx, + set_by: this.address, + set_by_role: 'admin', + ...(ctxMeta ? { + ctx_bracket: ctxMeta.ctx_bracket, + ctx_bracket_table_ver: ctxMeta.ctx_bracket_table_ver, + } : {}), + }; + + const updated = { + enclave_id: this.value.enclave_id, + model_id: enclave.model_id, + denom: PRICE_DENOMINATION, + ...(ctxMeta ? { + ctx_bracket: ctxMeta.ctx_bracket, + ctx_bracket_table_ver: ctxMeta.ctx_bracket_table_ver, + } : {}), + current: schedule.current, + pending: schedule.pending, + }; + if (!updated.current) { + updated.current = record; + } else { + if (updated.pending && updated.pending.effective_at <= this.value.effective_at) { + updated.current = updated.pending; + updated.pending = null; + } + if (updated.pending) return new Error('Pending price change already scheduled.'); + updated.pending = record; + } + + await this.put(key, updated); + await this.put(this.priceRecordKey(this.value.enclave_id, record.ver, ctxMeta?.ctx_bracket ?? null), record); + console.log('mayhem setPrice', { schedule: updated, record }); + return { + ok: true, + op: 'setPrice', + enclave_id: record.enclave_id, + ver: record.ver, + }; + } + + async readPrice() { + if (!this.isSafeKeyPart(this.value.enclave_id)) return new Error('Invalid enclave id.'); + const enclave = await this.get(`enclave/${this.value.enclave_id}`); + if (!enclave) return new Error('Enclave not found.'); + const ctxMeta = await this.priceCtxMetaForEnclave(enclave, this.value.ctx_bracket, this.value.at, 'Enclave price'); + if (ctxMeta instanceof Error) return ctxMeta; + const key = this.priceScheduleKey(this.value.enclave_id, ctxMeta?.ctx_bracket ?? null); + const schedule = await this.get(key); + if (!schedule) { + return { + ok: true, + op: 'readPrice', + enclave_id: this.value.enclave_id, + at: this.value.at, + ...(ctxMeta ? { + ctx_bracket: ctxMeta.ctx_bracket, + ctx_bracket_table_ver: ctxMeta.ctx_bracket_table_ver, + } : {}), + price: null, + }; + } + + const price = this.priceActiveEntry(schedule, this.value.at); + console.log('mayhem readPrice', { enclave_id: this.value.enclave_id, at: this.value.at, price }); + return { + ok: true, + op: 'readPrice', + enclave_id: this.value.enclave_id, + at: this.value.at, + ...(ctxMeta ? { + ctx_bracket: ctxMeta.ctx_bracket, + ctx_bracket_table_ver: ctxMeta.ctx_bracket_table_ver, + } : {}), + price, + }; + } + + async recordReputationEvent() { + const adminError = await this.requireAdmin(); + if (adminError) return adminError; + + const validationError = this.validateReputationEvent(this.value); + if (validationError) return validationError; + + const provider = await this.get(`prov/${this.value.provider}`); + if (!provider) return new Error('Provider not found.'); + + const record = await this.appendReputationEvent({ + provider: this.value.provider, + event_id: this.value.event_id, + kind: this.value.kind, + epoch: this.value.epoch, + at: this.value.at, + paid_au: this.value.paid_au !== undefined + ? this.normalizeAu(this.value.paid_au, 'reputation paid amount') + : null, + max_spend_au: this.value.max_spend_au !== undefined + ? this.normalizeAu(this.value.max_spend_au, 'reputation max spend') + : null, + evidence_hash: this.value.evidence_hash ?? null, + }); + if (record instanceof Error) return record; + + let slash = null; + if (this.value.kind === 'dispute_lost') { + const params = await this.activeParamsAt(this.value.at, ['dispute_lost_slash_bps']); + slash = await this.applyProviderSlash({ + providerId: this.value.provider, + source: 'dispute', + reason: 'dispute_lost', + evidenceHash: this.value.evidence_hash ?? null, + epoch: this.value.epoch, + at: this.value.at, + slashBps: params.dispute_lost_slash_bps, + beneficiary: this.value.beneficiary ?? null, + enclaveId: this.value.enclave_id ?? null, + banProvider: false, + tombstoneEnclave: false, + }); + if (slash instanceof Error) return slash; + } + + console.log('mayhem recordReputationEvent', record); + return { + ok: true, + op: 'recordReputationEvent', + provider: this.value.provider, + event_id: this.value.event_id, + head: record.head, + slash, + }; + } + + async anchorReputation() { + const adminError = await this.requireAdmin(); + if (adminError) return adminError; + + const validationError = this.validateReputationAnchor(this.value); + if (validationError) return validationError; + + const providerKey = `prov/${this.value.provider}`; + const provider = await this.get(providerKey); + if (!provider) return new Error('Provider not found.'); + + const head = await this.get(`ev/rep/head/${this.value.provider}`); + if (!head || head.head !== this.value.events_head) { + return new Error('Reputation events head mismatch.'); + } + const fold = await this.get(`ev/rep/fold/${this.value.provider}`); + if ( + !fold || + fold.events_head !== head.head || + fold.event_count !== head.count + ) { + return new Error('Reputation fold state mismatch.'); + } + if (this.value.epoch < fold.max_epoch) { + return new Error('Reputation anchor epoch precedes an event.'); + } + const expected = this.reputationFoldAt(fold, this.value.folded_at); + if (expected instanceof Error) return expected; + if ( + this.value.r_bps !== expected.r_bps || + this.value.raw_milli !== expected.raw_milli || + this.value.successful_sessions !== expected.successful_sessions || + (this.value.provenance_violation === true) !== expected.provenance_violation + ) { + return new Error('Reputation anchor does not match the contract fold.'); + } + + const params = await this.activeParamsAt(this.value.folded_at, [ + 'probation_successful_sessions', + 'probation_seconds', + 'probation_max_concurrent_sessions_per_user', + 'probation_price_max_bps', + 'probation_weight_bps', + ]); + const sinceSeconds = provider.probation?.since_seconds ?? 0; + const probationActive = ( + this.value.successful_sessions < params.probation_successful_sessions || + this.value.folded_at - sinceSeconds < params.probation_seconds + ); + const probation = { + active: probationActive, + since: provider.probation?.since ?? provider.registered_at, + since_seconds: sinceSeconds, + successful_sessions: this.value.successful_sessions, + required_successful_sessions: params.probation_successful_sessions, + required_seconds: params.probation_seconds, + caps: { + max_concurrent_sessions_per_user: params.probation_max_concurrent_sessions_per_user, + price_max_bps: params.probation_price_max_bps, + weight_bps: params.probation_weight_bps, + }, + }; + const snapshot = { + provider: this.value.provider, + r: this.value.r_bps / 10_000, + r_bps: this.value.r_bps, + raw: this.value.raw_milli / 1_000, + raw_milli: this.value.raw_milli, + events_head: this.value.events_head, + epoch: this.value.epoch, + folded_at: this.value.folded_at, + updated_at: this.tx, + probation, + provenance_violation: this.value.provenance_violation === true, + }; + const updatedProvider = { + ...provider, + probation: { + ...(provider.probation ?? {}), + successful_sessions: this.value.successful_sessions, + since_seconds: sinceSeconds, + }, + updated_at: this.tx, + }; + + await this.put(`rep/${this.value.provider}`, snapshot); + await this.put(providerKey, updatedProvider); + console.log('mayhem anchorReputation', snapshot); + return { + ok: true, + op: 'anchorReputation', + provider: this.value.provider, + epoch: this.value.epoch, + events_head: this.value.events_head, + }; + } + + async auditorRegister() { + const target = this.value.auditor ?? this.address; + if (!this.isSafeKeyPart(target)) return new Error('Invalid auditor id.'); + + const consentError = await this.requireConsent(target); + if (consentError) return consentError; + + const provider = await this.get(`prov/${target}`); + if (provider) return new Error('Provider keys cannot register as auditors.'); + + const adminRegistersOther = target !== this.address; + if (adminRegistersOther) { + const adminError = await this.requireAdmin(); + if (adminError) return adminError; + } else { + const eligibilityError = await this.requireAuditorEligibility( + target, + this.value.registered_at_seconds ?? 0 + ); + if (eligibilityError) return eligibilityError; + } + + const key = `auditor/${target}`; + const existing = await this.get(key); + if (existing?.status === 'active') return new Error('Auditor already registered.'); + if (existing?.status === 'slashed') return new Error('Auditor is slashed.'); + + const record = { + auditor: target, + status: 'active', + registered_at: this.tx, + registered_at_seconds: this.value.registered_at_seconds ?? 0, + accredited_by: adminRegistersOther ? this.address : null, + successful_probes: existing?.successful_probes ?? 0, + submitted_probes: existing?.submitted_probes ?? 0, + false_reports: existing?.false_reports ?? 0, + updated_at: this.tx, + }; + await this.put(key, record); + console.log('mayhem auditorRegister', record); + return { ok: true, op: 'auditorRegister', auditor: target }; + } + + async auditorSlash() { + const adminError = await this.requireAdmin(); + if (adminError) return adminError; + if (this.value.op !== 'auditor_slash') return new Error('Invalid auditor slash op.'); + if (!this.isHexBytes(this.value.auditor, 32)) return new Error('Invalid auditor slash target.'); + if (!this.isHexBytes(this.value.provider, 32)) return new Error('Invalid auditor slash provider.'); + if (!this.isSafeKeyPart(this.value.probe_id)) return new Error('Invalid auditor slash probe id.'); + if (!AUDITOR_SLASH_REASONS.has(this.value.reason)) return new Error('Unsupported auditor slash reason.'); + if (!this.isHexBytes(this.value.evidence_hash, 32)) return new Error('Invalid auditor slash evidence hash.'); + + const auditorKey = `auditor/${this.value.auditor}`; + const auditor = await this.get(auditorKey); + if (!auditor) return new Error('Auditor not found.'); + if (auditor.status === 'slashed') return new Error('Auditor is already slashed.'); + if (auditor.status !== 'active') return new Error('Auditor is not active.'); + const probeKey = `ev/probe/${this.value.probe_id}`; + const probe = await this.get(probeKey); + if (!probe || probe.probe_kind !== 'canary') return new Error('Canary probe not found.'); + if ( + probe.auditor !== this.value.auditor || + probe.provider !== this.value.provider || + probe.epoch !== this.value.epoch || + probe.pass !== true + ) { + return new Error('Auditor slash evidence does not match the passing probe.'); + } + if (probe.status === 'slashed') return new Error('Canary probe is already slashed.'); + const slashKey = `ev/auditor-slash/${this.value.auditor}/${this.value.probe_id}`; + if ((await this.get(slashKey)) !== null) return new Error('Auditor slash already recorded.'); + + const passKey = `probe/pass/${this.value.provider}/${this.value.epoch}`; + const passRecord = await this.get(passKey); + if (!passRecord || !Array.isArray(passRecord.probes)) { + return new Error('Canary pass record not found.'); + } + const remaining = passRecord.probes.filter((entry) => !( + entry.auditor === this.value.auditor && entry.probe_id === this.value.probe_id + )); + if (remaining.length === passRecord.probes.length) { + return new Error('Canary pass record does not contain the slashed probe.'); + } + const falseReports = this.safeAddCount(auditor.false_reports ?? 0, 1, 'auditor false report count'); + if (falseReports instanceof Error) return falseReports; + const slash = { + auditor: this.value.auditor, + provider: this.value.provider, + probe_id: this.value.probe_id, + epoch: this.value.epoch, + reason: this.value.reason, + evidence_hash: this.value.evidence_hash.toLowerCase(), + reward_forfeited_au: probe.probe_reward_au ?? ZERO_AU, + slashed_at_seconds: this.value.at, + slashed_at: this.tx, + slashed_by: this.address, + slashed_by_role: 'admin', + }; + await this.put(passKey, { + ...passRecord, + pass_count: remaining.length, + auditors: remaining.map((entry) => entry.auditor), + probes: remaining, + last_slash_evidence_hash: slash.evidence_hash, + updated_at: this.tx, + }); + await this.put(probeKey, { + ...probe, + status: 'slashed', + collusion_evidence_hash: slash.evidence_hash, + slashed_at: this.tx, + }); + await this.put(auditorKey, { + ...auditor, + status: 'slashed', + false_reports: falseReports, + slash_reason: this.value.reason, + slash_evidence_hash: slash.evidence_hash, + slashed_at: this.tx, + slashed_by: this.address, + updated_at: this.tx, + }); + await this.put(slashKey, slash); + console.log('mayhem auditorSlash', slash); + return { + ok: true, + op: 'auditorSlash', + auditor: this.value.auditor, + probe_id: this.value.probe_id, + evidence_hash: slash.evidence_hash, + }; + } + + async probeResult() { + const auditor = await this.get(`auditor/${this.address}`); + if (!auditor || auditor.status !== 'active') return new Error('Auditor registration required.'); + if ((await this.get(`prov/${this.address}`)) !== null) { + return new Error('Provider keys cannot submit auditor probes.'); + } + + const validationError = this.validateProbeResult(this.value); + if (validationError) return validationError; + if ((await this.get(`ev/probe/${this.value.probe_id}`)) !== null) { + return new Error('Probe result already recorded.'); + } + + const providerKey = `prov/${this.value.provider}`; + const provider = await this.get(providerKey); + if (!provider) return new Error('Provider not found.'); + + if (this.value.probe_kind === 'canary') { + const canaryBindingError = await this.requireBoundCanaryProbe(this.value, this.address); + if (canaryBindingError) return canaryBindingError; + } + + const params = await this.activeParamsAt(this.value.at, [ + 'canary_match_min_bps', + 'probe_reward_au', + 'uptime_tick_seconds', + 'fraud_slash_bps', + ]); + const pass = this.probePass(this.value, params); + if (this.value.pass !== undefined && this.value.pass !== pass) { + return new Error('Probe pass flag does not match contract threshold.'); + } + if (this.value.probe_kind === 'canary' && pass) { + const passRecord = await this.get(`probe/pass/${this.value.provider}/${this.value.epoch}`); + if (passRecord?.auditors?.includes(this.address)) { + return new Error('Auditor already supplied a canary pass for this provider epoch.'); + } + } + + const reputationEvent = await this.appendReputationEvent({ + provider: this.value.provider, + event_id: `probe-${this.value.probe_id}`, + kind: this.value.probe_kind === 'uptime_tick' + ? 'uptime_tick' + : pass + ? 'probe_ok' + : 'probe_fail', + epoch: this.value.epoch, + at: this.value.at, + paid_au: null, + max_spend_au: null, + evidence_hash: this.value.evidence_hash ?? null, + }); + if (reputationEvent instanceof Error) return reputationEvent; + + let provenanceViolation = false; + let slash = null; + if (this.value.probe_kind === 'canary' && !pass) { + provenanceViolation = true; + const violationEvent = await this.appendReputationEvent({ + provider: this.value.provider, + event_id: `probe-${this.value.probe_id}-violation`, + kind: 'provenance_violation', + epoch: this.value.epoch, + at: this.value.at, + paid_au: null, + max_spend_au: null, + evidence_hash: this.value.evidence_hash ?? null, + }); + if (violationEvent instanceof Error) return violationEvent; + + slash = await this.applyProviderSlash({ + providerId: this.value.provider, + source: 'probe', + reason: 'canary_mismatch', + evidenceHash: this.value.evidence_hash ?? null, + epoch: this.value.epoch, + at: this.value.at, + slashBps: params.fraud_slash_bps, + beneficiary: this.address, + enclaveId: this.value.enclave_id ?? null, + probeId: this.value.probe_id, + banProvider: true, + tombstoneEnclave: true, + }); + if (slash instanceof Error) return slash; + } + + const record = { + probe_id: this.value.probe_id, + probe_kind: this.value.probe_kind, + auditor: this.address, + provider: this.value.provider, + enclave_id: this.value.enclave_id ?? null, + epoch: this.value.epoch, + at: this.value.at, + canary_set: this.value.canary_set ?? null, + canary_prompt_id: this.value.canary_prompt_id ?? null, + challenge_epoch: this.value.challenge_epoch ?? null, + challenge_apply_hash: this.value.challenge_apply_hash ?? null, + challenge_seed: this.value.challenge_seed ?? null, + verification_method: this.value.verification_method ?? null, + binary_hash: this.value.binary_hash ?? null, + match_bps: this.value.match_bps ?? null, + pass, + session_receipt_hash: this.value.session_receipt_hash ?? null, + evidence_hash: this.value.evidence_hash ?? null, + auditor_sig: this.value.auditor_sig ?? null, + reputation_head: (await this.get(`ev/rep/head/${this.value.provider}`))?.head ?? null, + provenance_violation: provenanceViolation, + probe_reward_au: params.probe_reward_au, + slash, + recorded_at: this.tx, + }; + await this.put(`ev/probe/${this.value.probe_id}`, record); + let probePassRecord = null; + if (this.value.probe_kind === 'canary' && pass) { + probePassRecord = await this.recordCanaryProbePass(this.value, this.address); + if (probePassRecord instanceof Error) return probePassRecord; + } + await this.put(`auditor/${this.address}`, { + ...auditor, + submitted_probes: (auditor.submitted_probes ?? 0) + 1, + successful_probes: (auditor.successful_probes ?? 0) + (pass ? 1 : 0), + updated_at: this.tx, + }); + console.log('mayhem probeResult', record); + return { + ok: true, + op: 'probeResult', + probe_id: this.value.probe_id, + provider: this.value.provider, + pass, + ...(probePassRecord ? { probe_pass_record: probePassRecord } : {}), + provenance_violation: provenanceViolation, + }; + } + + async epochApply() { + if (CONTRACT_VERSION >= 17) { + return new Error('Aggregate epoch_apply is disabled; use receipt-bound apply_targeted_epoch.'); + } + const adminError = await this.requireAdmin(); + if (adminError) return adminError; + + const shapeError = this.validateEpochApplyShape(this.value); + if (shapeError) return shapeError; + const roots = this.value.roots === undefined ? null : this.normalizeEpochRoots(this.value.roots); + if (roots instanceof Error) return roots; + const totals = this.value.totals === undefined ? null : this.normalizeEpochTotals(this.value.totals); + if (totals instanceof Error) return totals; + if ((roots && !totals) || (!roots && totals)) { + return new Error('Epoch apply roots and totals must be provided together.'); + } + const page = this.epochApplyPage(this.value); + if (page instanceof Error) return page; + const lastPage = this.epochApplyLastPage(this.value); + if (lastPage instanceof Error) return lastPage; + const pagedApply = hasOwn(this.value, 'page'); + if (pagedApply && (roots || totals)) { + return new Error('Paged epochApply pages must omit aggregate roots and totals.'); + } + + const params = await this.activeParamsAt(this.value.at, [ + 'epoch_seconds', + 'fee_bps', + 'max_apply_batch', + 'max_market_usage_entries', + 'probation_successful_sessions', + 'new_provider_holdback_epochs', + 'holdback_epochs', + 'challenge_epochs', + 'probation_successful_sessions', + 'new_provider_holdback_epochs', + 'canary_probe_holdback_bps', + 'canary_probe_release_min_passes', + ]); + if (this.value.debits.length + this.value.earnings.length > params.max_apply_batch) { + return new Error('Epoch apply batch exceeds max_apply_batch.'); + } + if ((this.value.market_usage?.length ?? 0) > params.max_market_usage_entries) { + return new Error('Epoch market usage batch exceeds max_market_usage_entries.'); + } + + const debitMap = this.aggregateRailLedgerEntries(this.value.debits, 'user', 'au', 'debit'); + if (debitMap instanceof Error) return debitMap; + const grossEarningMap = this.aggregateRailLedgerEntries( + this.value.earnings, + 'provider', + 'gross_au', + 'earning' + ); + if (grossEarningMap instanceof Error) return grossEarningMap; + + const debitTotal = this.sumRailAu(debitMap, 'au'); + if (debitTotal instanceof Error) return debitTotal; + const grossTotal = this.sumRailAu(grossEarningMap, 'gross_au'); + if (grossTotal instanceof Error) return grossTotal; + const debitRailTotals = this.railTotals(debitMap, 'au'); + if (debitRailTotals instanceof Error) return debitRailTotals; + const grossRailTotals = this.railTotals(grossEarningMap, 'gross_au'); + if (grossRailTotals instanceof Error) return grossRailTotals; + const railTotalError = this.assertMatchingRailTotals(debitRailTotals, grossRailTotals); + if (railTotalError) return railTotalError; + const marketUsageProvided = hasOwn(this.value, 'market_usage'); + const marketUsageMap = marketUsageProvided + ? this.aggregateMarketUsageEntries(this.value.market_usage) + : new Map(); + if (marketUsageMap instanceof Error) return marketUsageMap; + const marketUsageTotal = this.sumMarketDemandAu(marketUsageMap); + if (marketUsageTotal instanceof Error) return marketUsageTotal; + const applyState = await this.epochApplyStateRecord(); + const previousApplyHash = page === 0 ? null : applyState.last_apply_hash; + const normalized = { + epoch: this.value.epoch, + page, + last_page: lastPage, + at: this.value.at, + epoch_seconds: params.epoch_seconds, + fee_bps: params.fee_bps, + tap_burn_bps: TAP_BURN_BPS, + debits: this.mapRailEntriesForHash(debitMap, 'user', 'au'), + earnings: this.mapRailEntriesForHash(grossEarningMap, 'provider', 'gross_au'), + roots, + totals, + }; + if (previousApplyHash !== null) { + normalized.previous_apply_hash = previousApplyHash; + } + if (marketUsageProvided) { + normalized.market_usage = this.mapMarketUsageEntriesForHash(marketUsageMap); + } + const applyHash = await this.epochApplyHash(normalized); + if (this.isIdempotentEpochApplyPage(applyState, this.value.epoch, page, lastPage, applyHash)) { + const result = { + ok: true, + op: 'epochApply', + epoch: this.value.epoch, + idempotent: true, + debited_au: ZERO_AU, + earned_au: ZERO_AU, + fee_au: ZERO_AU, + burn_au: ZERO_AU, + }; + if (pagedApply) { + result.page = page; + result.last_page = lastPage; + } + return result; + } + const pageOrderError = this.validateEpochApplyPageOrder(applyState, this.value.epoch, page); + if (pageOrderError) return pageOrderError; + const reservationDebitTotals = this.nextReservationDebitTotals(applyState, this.value.epoch, page, debitMap); + if (reservationDebitTotals instanceof Error) return reservationDebitTotals; + const reservationError = await this.validateEpochDebitReservations(this.value.epoch, reservationDebitTotals); + if (reservationError) return reservationError; + + const balances = new Map(); + for (const debit of debitMap.values()) { + const { rail, user, au: debitAu } = debit; + const balance = await this.balanceRecord(user, rail); + if (balance instanceof Error) return balance; + const balanceError = this.guardianValidateBalanceRecord(balance, user, rail); + if (balanceError) return balanceError; + if (this.compareAu(balance.au, debitAu) < 0) return new Error('Insufficient credit balance.'); + const nextBalanceAu = this.safeSubAu(balance.au, debitAu); + if (nextBalanceAu instanceof Error) return nextBalanceAu; + balances.set(stableJson([rail, user]), { + ...balance, + rail, + au: nextBalanceAu, + updated_epoch: this.value.epoch, + updated_at: this.tx, + }); + } + + const earningDeltas = new Map(); + const feeDeltaByRail = new Map(PROVIDER_ACCEPTED_RAIL_ORDER.map((rail) => [rail, ZERO_AU])); + const burnDeltaByRail = new Map(PROVIDER_ACCEPTED_RAIL_ORDER.map((rail) => [rail, ZERO_AU])); + let feeDeltaTotalAu = ZERO_AU; + let burnDeltaTotalAu = ZERO_AU; + for (const earning of grossEarningMap.values()) { + const { rail, provider, gross_au: grossAu } = earning; + const providerRecord = await this.get(`prov/${provider}`); + if (!providerRecord) return new Error('Provider not found.'); + if (!Array.isArray(providerRecord.accepted_rails)) { + return new Error('Provider accepted rails are not set.'); + } + if (!providerRecord.accepted_rails.includes(rail)) { + return new Error('Provider does not accept payment rail.'); + } + + const feeAu = this.safeMulDivAu(grossAu, params.fee_bps, 10_000); + if (feeAu instanceof Error) return feeAu; + const burnAu = rail === 'tap' + ? this.safeMulDivAu(grossAu, TAP_BURN_BPS, 10_000) + : ZERO_AU; + if (burnAu instanceof Error) return burnAu; + const afterFeeAu = this.safeSubAu(grossAu, feeAu); + if (afterFeeAu instanceof Error) return afterFeeAu; + const providerAu = this.safeSubAu(afterFeeAu, burnAu); + if (providerAu instanceof Error) return providerAu; + feeDeltaTotalAu = this.safeAddAu(feeDeltaTotalAu, feeAu); + if (feeDeltaTotalAu instanceof Error) return feeDeltaTotalAu; + const nextRailFee = this.safeAddAu(feeDeltaByRail.get(rail) ?? ZERO_AU, feeAu); + if (nextRailFee instanceof Error) return nextRailFee; + feeDeltaByRail.set(rail, nextRailFee); + burnDeltaTotalAu = this.safeAddAu(burnDeltaTotalAu, burnAu); + if (burnDeltaTotalAu instanceof Error) return burnDeltaTotalAu; + const nextRailBurn = this.safeAddAu(burnDeltaByRail.get(rail) ?? ZERO_AU, burnAu); + if (nextRailBurn instanceof Error) return nextRailBurn; + burnDeltaByRail.set(rail, nextRailBurn); + const key = stableJson([rail, provider]); + const current = earningDeltas.get(key) ?? { rail, provider, provider_record: providerRecord, au: ZERO_AU }; + const next = this.safeAddAu(current.au, providerAu); + if (next instanceof Error) return next; + earningDeltas.set(key, { ...current, au: next }); + } + + const earnings = new Map(); + let earnCumTotal = ZERO_AU; + for (const delta of earningDeltas.values()) { + const { rail, provider, provider_record: providerRecord, au: deltaAu } = delta; + const current = await this.earningRecord(provider, rail); + if (current instanceof Error) return current; + const currentError = this.guardianValidateEarningRecord(current, provider, rail); + if (currentError) return currentError; + const probeGate = await this.probeGateForEarning(provider, current, params); + if (probeGate instanceof Error) return probeGate; + const lockedEarningEpochs = this.providerLockedEarningEpochs(providerRecord, params); + if (lockedEarningEpochs instanceof Error) return lockedEarningEpochs; + const disputeGate = await this.providerHasOpenDispute(provider); + if (disputeGate instanceof Error) return disputeGate; + const refreshed = this.refreshEarningHoldback( + current, + this.value.epoch, + lockedEarningEpochs, + probeGate, + disputeGate + ); + if (refreshed instanceof Error) return refreshed; + const totalAu = this.safeAddAu(refreshed.total_au, deltaAu); + if (totalAu instanceof Error) return totalAu; + const heldAu = this.safeAddAu(refreshed.held_au, deltaAu); + if (heldAu instanceof Error) return heldAu; + const holdbacks = this.appendHoldbackBucket( + refreshed.holdbacks, + this.value.epoch, + deltaAu, + lockedEarningEpochs + ); + if (holdbacks instanceof Error) return holdbacks; + earnings.set(stableJson([rail, provider]), { + ...refreshed, + rail, + total_au: totalAu, + held_au: heldAu, + holdbacks, + updated_epoch: this.value.epoch, + updated_at: this.tx, + }); + earnCumTotal = this.safeAddAu(earnCumTotal, totalAu); + if (earnCumTotal instanceof Error) return earnCumTotal; + } + + const feeRecords = new Map(); + const nextFeeRecords = new Map(); + const burnRecords = new Map(); + const nextBurnRecords = new Map(); + let nextFeeCumTotal = ZERO_AU; + let nextBurnCumTotal = ZERO_AU; + const touchedRails = new Set([ + ...Array.from(debitRailTotals.entries()).filter(([, au]) => this.compareAu(au, ZERO_AU) > 0).map(([rail]) => rail), + ...Array.from(grossRailTotals.entries()).filter(([, au]) => this.compareAu(au, ZERO_AU) > 0).map(([rail]) => rail), + ]); + for (const rail of PROVIDER_ACCEPTED_RAIL_ORDER) { + const fee = await this.feeCumRecord(rail); + if (fee instanceof Error) return fee; + const feeError = this.guardianValidateFeeRecord(fee, rail); + if (feeError) return feeError; + feeRecords.set(rail, fee); + const feeDeltaAu = feeDeltaByRail.get(rail) ?? ZERO_AU; + const nextFeeCum = this.safeAddAu(fee.cum_au, feeDeltaAu); + if (nextFeeCum instanceof Error) return nextFeeCum; + // settled_cum_au must advance with this epoch's settled rail debits in + // the SAME record that carries the new cum_au, otherwise + // guardianValidateFeeRecord rejects every fee-bearing epoch apply + // (settled < cum). This mirrors next_settled_cum_by_rail in + // guardianValidateEpochTotals, which is also what gets persisted. + const nextFeeSettledCum = this.safeAddAu( + fee.settled_cum_au ?? fee.cum_au, + debitRailTotals.get(rail) ?? ZERO_AU + ); + if (nextFeeSettledCum instanceof Error) return nextFeeSettledCum; + const nextFee = touchedRails.has(rail) + ? { + ...fee, + cum_au: nextFeeCum, + settled_cum_au: nextFeeSettledCum, + updated_epoch: this.value.epoch, + updated_at: this.tx, + last_apply_hash: applyHash, + last_fee_bps: params.fee_bps, + } + : fee; + nextFeeRecords.set(rail, nextFee); + nextFeeCumTotal = this.safeAddAu(nextFeeCumTotal, nextFee.cum_au); + if (nextFeeCumTotal instanceof Error) return nextFeeCumTotal; + + const burn = await this.burnCumRecord(rail); + if (burn instanceof Error) return burn; + const burnError = this.guardianValidateBurnRecord(burn, rail); + if (burnError) return burnError; + burnRecords.set(rail, burn); + const burnDeltaAu = burnDeltaByRail.get(rail) ?? ZERO_AU; + const nextBurnCum = this.safeAddAu(burn.cum_au, burnDeltaAu); + if (nextBurnCum instanceof Error) return nextBurnCum; + const nextBurn = touchedRails.has(rail) + ? { + ...burn, + cum_au: nextBurnCum, + updated_epoch: this.value.epoch, + updated_at: this.tx, + last_apply_hash: applyHash, + burn_bps: rail === 'tap' ? TAP_BURN_BPS : 0, + } + : burn; + nextBurnRecords.set(rail, nextBurn); + nextBurnCumTotal = this.safeAddAu(nextBurnCumTotal, nextBurn.cum_au); + if (nextBurnCumTotal instanceof Error) return nextBurnCumTotal; + } + + const grossAfterFees = this.safeSubAu(grossTotal, feeDeltaTotalAu); + if (grossAfterFees instanceof Error) return grossAfterFees; + const providerDeltaTotal = this.safeSubAu(grossAfterFees, burnDeltaTotalAu); + if (providerDeltaTotal instanceof Error) return providerDeltaTotal; + const guardian = this.guardianCheckEpochApply({ + epoch: this.value.epoch, + page, + applyState, + feeRecords, + burnRecords, + debitTotal, + debitRailTotals, + feeDeltaByRail, + burnDeltaByRail, + providerDeltaTotal, + nextFeeRecords, + nextBurnRecords, + balances, + earnings, + }); + if (guardian instanceof Error) return guardian; + + let marketPriceUpdates = []; + if (marketUsageProvided) { + marketPriceUpdates = await this.computeMarketPriceUpdates(marketUsageMap, { + epoch: this.value.epoch, + at: this.value.at, + epochSeconds: params.epoch_seconds, + }); + if (marketPriceUpdates instanceof Error) return marketPriceUpdates; + } + const priceDerivations = await this.priceDerivationsFromMarketUpdates(marketPriceUpdates, { + epoch: this.value.epoch, + at: this.value.at, + epochSeconds: params.epoch_seconds, + usageRoot: roots?.use ?? null, + }); + if (priceDerivations instanceof Error) return priceDerivations; + + if (totals) { + const totalsError = await this.validateEpochApplyTotals({ + epoch: this.value.epoch, + roots, + totals, + debitTotal, + feeDeltaAu: feeDeltaTotalAu, + nextFeeCum: nextFeeCumTotal, + burnDeltaAu: burnDeltaTotalAu, + nextBurnCum: nextBurnCumTotal, + providerCount: grossEarningMap.size, + earnCumTotal, + epochSeconds: params.epoch_seconds, + priceDerivations, + }); + if (totalsError) return totalsError; + } + + for (const balance of this.sortedRailRecords(balances, 'user')) { + await this.put(this.balanceKey(balance.user, balance.rail), balance); + } + for (const earning of this.sortedRailRecords(earnings, 'provider')) { + await this.put(this.earningKey(earning.provider, earning.rail), earning); + } + for (const rail of PROVIDER_ACCEPTED_RAIL_ORDER.filter((rail) => touchedRails.has(rail))) { + const feeRecord = { + ...nextFeeRecords.get(rail), + settled_cum_au: guardian.next_settled_cum_by_rail.get(rail), + }; + await this.put(this.feeCumKey(rail), feeRecord); + await this.put(this.burnCumKey(rail), nextBurnRecords.get(rail)); + } + const nextApplyState = this.nextEpochApplyState({ + applyState, + epoch: this.value.epoch, + page, + lastPage, + applyHash, + epochSeconds: params.epoch_seconds, + settledAt: this.value.at, + reservationDebitTotals, + }); + const previousChallengeError = await this.rememberCanaryChallengeAnchor(applyState); + if (previousChallengeError) return previousChallengeError; + await this.put('epoch/apply/state', nextApplyState); + if (lastPage) { + const anchorError = await this.rememberEpochApplyAnchor(nextApplyState); + if (anchorError) return anchorError; + const challengeError = await this.rememberCanaryChallengeAnchor(nextApplyState); + if (challengeError) return challengeError; + } + if (totals) { + await this.writeEpochEvidenceRoots({ + epoch: this.value.epoch, + at: this.value.at, + epoch_seconds: params.epoch_seconds, + roots, + totals, + feeDeltaAu: feeDeltaTotalAu, + feeCumAu: nextFeeCumTotal, + burnDeltaAu: burnDeltaTotalAu, + burnCumAu: nextBurnCumTotal, + priceDerivations, + }); + } else if (priceDerivations.length > 0) { + await this.writePriceDerivationEvidence({ + epoch: this.value.epoch, + at: this.value.at, + epoch_seconds: params.epoch_seconds, + root: await this.priceDerivationRoot(priceDerivations), + count: priceDerivations.length, + derivations: priceDerivations, + }); + } + const activityIndexError = await this.writeActivityMarketIndex(marketPriceUpdates); + if (activityIndexError instanceof Error) return activityIndexError; + for (const update of marketPriceUpdates) { + await this.put(update.schedule_key, update.schedule); + await this.put(update.record_key, update.record); + } + + const result = { + ok: true, + op: 'epochApply', + epoch: this.value.epoch, + idempotent: false, + debited_au: debitTotal, + earned_au: providerDeltaTotal, + fee_au: feeDeltaTotalAu, + burn_au: burnDeltaTotalAu, + rails: Array.from(touchedRails).sort( + (left, right) => PROVIDER_ACCEPTED_RAIL_ORDER.indexOf(left) - PROVIDER_ACCEPTED_RAIL_ORDER.indexOf(right) + ), + }; + if (pagedApply) { + result.page = page; + result.last_page = lastPage; + } + if (marketPriceUpdates.length > 0) { + result.market_prices = marketPriceUpdates.map((update) => ({ + enclave_id: update.enclave_id, + ...(update.ctx_bracket ? { + ctx_bracket: update.ctx_bracket, + ctx_bracket_table_ver: update.ctx_bracket_table_ver, + } : {}), + ver: update.ver, + momentum_bps: update.momentum_bps, + activity_rate: update.activity_rate, + ema_activity_rate: update.ema_activity_rate, + active_supply: update.active_supply, + active_demand_au: update.active_demand_au, + frozen: update.frozen, + derivation_hash: update.derivation_hash, + })); + result.price_root = await this.priceDerivationRoot(priceDerivations); + } + console.log('mayhem epochApply', result); + return result; + } + + async targetedEpochApply(value, revisionBindings, allocations, options = {}) { + const adminError = await this.requireAdmin(); + if (adminError) return adminError; + + const shapeError = this.validateEpochApplyShape(value); + if (shapeError) return shapeError; + const roots = value.roots === undefined ? null : this.normalizeEpochRoots(value.roots); + if (roots instanceof Error) return roots; + const totals = value.totals === undefined ? null : this.normalizeEpochTotals(value.totals); + if (totals instanceof Error) return totals; + if ((roots && !totals) || (!roots && totals)) { + return new Error('Epoch apply roots and totals must be provided together.'); + } + const page = this.epochApplyPage(value); + if (page instanceof Error) return page; + const lastPage = this.epochApplyLastPage(value); + if (lastPage instanceof Error) return lastPage; + const pagedApply = hasOwn(value, 'page'); + if (pagedApply && (roots || totals)) { + return new Error('Paged epochApply pages must omit aggregate roots and totals.'); + } + + const params = await this.activeParamsAt(value.at, [ + 'epoch_seconds', + 'fee_bps', + 'max_apply_batch', + 'max_market_usage_entries', + 'probation_successful_sessions', + 'new_provider_holdback_epochs', + 'holdback_epochs', + 'challenge_epochs', + 'probation_successful_sessions', + 'new_provider_holdback_epochs', + 'canary_probe_holdback_bps', + 'canary_probe_release_min_passes', + ]); + if (value.debits.length + value.earnings.length + allocations.length > params.max_apply_batch) { + return new Error('Epoch apply batch exceeds max_apply_batch.'); + } + if ((value.market_usage?.length ?? 0) > params.max_market_usage_entries) { + return new Error('Epoch market usage batch exceeds max_market_usage_entries.'); + } + + const debitMap = this.aggregateRailLedgerEntries(value.debits, 'user', 'au', 'debit'); + if (debitMap instanceof Error) return debitMap; + const grossEarningMap = this.aggregateRailLedgerEntries( + value.earnings, + 'provider', + 'gross_au', + 'earning' + ); + if (grossEarningMap instanceof Error) return grossEarningMap; + + const debitTotal = this.sumRailAu(debitMap, 'au'); + if (debitTotal instanceof Error) return debitTotal; + const grossTotal = this.sumRailAu(grossEarningMap, 'gross_au'); + if (grossTotal instanceof Error) return grossTotal; + const debitRailTotals = this.railTotals(debitMap, 'au'); + if (debitRailTotals instanceof Error) return debitRailTotals; + const grossRailTotals = this.railTotals(grossEarningMap, 'gross_au'); + if (grossRailTotals instanceof Error) return grossRailTotals; + const railTotalError = this.assertMatchingRailTotals(debitRailTotals, grossRailTotals); + if (railTotalError) return railTotalError; + const marketUsageProvided = hasOwn(value, 'market_usage'); + const marketUsageMap = marketUsageProvided + ? this.aggregateMarketUsageEntries(value.market_usage) + : new Map(); + if (marketUsageMap instanceof Error) return marketUsageMap; + const marketUsageTotal = this.sumMarketDemandAu(marketUsageMap); + if (marketUsageTotal instanceof Error) return marketUsageTotal; + const earningFinals = value.earning_finals ?? []; + + const receiptIndex = this.normalizeReceiptEpochIndexMetadata( + value.receipt_index, + value.epoch + ); + if (receiptIndex instanceof Error) return receiptIndex; + const currentReceiptIndex = this.normalizeReceiptEpochIndexMetadata( + await this.get(this.receiptEpochIndexKey(value.epoch)), + value.epoch + ); + if (currentReceiptIndex instanceof Error) return currentReceiptIndex; + if (stableJson(currentReceiptIndex) !== stableJson(receiptIndex)) { + return new Error('Canonical receipt epoch index changed after the apply snapshot.'); + } + const freezeError = await this.validateFrozenEpoch(value.epoch, value.at, receiptIndex); + if (freezeError) return freezeError; + const epochCommit = options.commitTransition?.record ?? + await this.get(`epoch/commit/${value.epoch}`); + if (!epochCommit || + epochCommit.type !== 'epoch_commit' || + epochCommit.epoch !== value.epoch || + epochCommit.status !== 'provisional' || + epochCommit.commit_hash !== value.epoch_commit_hash) { + return new Error('Matching provisional epoch commit required for targeted apply.'); + } + const boundedReceiptSettlement = + epochCommit.apply_mode === 'targeted_receipt_pages_v1'; + const canonicalPageMarketUsageMap = new Map(); + if (boundedReceiptSettlement) { + if (!Array.isArray(options.canonicalMarketUsage) || + options.canonicalMarketUsage.length === 0) { + return new Error('Bounded receipt canonical market usage is missing.'); + } + let canonicalPageDemandAu = ZERO_AU; + let canonicalPageSessionCount = 0; + for (const usage of options.canonicalMarketUsage) { + const ctxBracket = usage?.ctx_bracket ?? null; + const marketKey = this.priceMarketKey(usage?.enclave_id, ctxBracket); + const demandAu = this.normalizeAu( + usage?.demand_au, + 'bounded receipt canonical market demand', + { allowZero: false } + ); + const providers = Array.isArray(usage?.providers) + ? usage.providers.slice().sort(compareCodepoint) + : []; + if (!usage || + !this.isSafeKeyPart(usage.enclave_id) || + (ctxBracket !== null && !this.isSafeKeyPart(ctxBracket)) || + (usage.ctx_bracket_table_ver !== undefined && + (!Number.isSafeInteger(usage.ctx_bracket_table_ver) || + usage.ctx_bracket_table_ver < 1)) || + demandAu instanceof Error || + !Number.isSafeInteger(usage.session_count) || + usage.session_count < 1 || + providers.length < 1 || + providers.some((provider) => !this.isSafeKeyPart(provider)) || + new Set(providers).size !== providers.length || + stableJson(providers) !== stableJson(usage.providers) || + canonicalPageMarketUsageMap.has(marketKey)) { + return new Error('Bounded receipt canonical market usage is invalid.'); + } + canonicalPageDemandAu = this.safeAddAu(canonicalPageDemandAu, demandAu); + canonicalPageSessionCount = this.safeAddCount( + canonicalPageSessionCount, + usage.session_count, + 'bounded receipt canonical market sessions' + ); + if (canonicalPageDemandAu instanceof Error || + canonicalPageSessionCount instanceof Error) { + return new Error('Bounded receipt canonical market usage overflow.'); + } + canonicalPageMarketUsageMap.set(marketKey, { + enclave_id: usage.enclave_id, + ...(ctxBracket ? { ctx_bracket: ctxBracket } : {}), + ...(usage.ctx_bracket_table_ver ? { + ctx_bracket_table_ver: usage.ctx_bracket_table_ver, + } : {}), + demand_au: demandAu, + settled_usage: usage.settled_usage, + session_count: usage.session_count, + providers, + }); + } + if (this.compareAu(canonicalPageDemandAu, grossTotal) !== 0 || + canonicalPageSessionCount !== allocations.length) { + return new Error('Bounded receipt canonical market usage does not match page allocations.'); + } + } + if (boundedReceiptSettlement && epochCommit.at !== value.at) { + return new Error('Bounded receipt settlement timestamp must match its epoch commit.'); + } + if (epochCommit.totals?.use_count !== receiptIndex.count) { + return new Error('Epoch commit receipt count does not match the canonical receipt index.'); + } + if (boundedReceiptSettlement) { + if (lastPage !== hasOwn(value, 'earning_finals') || + lastPage !== marketUsageProvided) { + return new Error( + 'Bounded receipt settlement requires earning_finals and market_usage on its final page only.' + ); + } + if (lastPage && this.compareAu(marketUsageTotal, epochCommit.totals.use_au) !== 0) { + return new Error('Final epoch market usage demand must equal committed usage.'); + } + } else if (marketUsageProvided && this.compareAu(marketUsageTotal, grossTotal) !== 0) { + return new Error('Epoch market usage demand must equal gross provider earnings.'); + } + + const applyState = await this.epochApplyStateRecord(); + const replayPosition = this.isEpochApplyPagePositionReplay( + applyState, + value.epoch, + page, + lastPage + ); + const previousApplyHash = replayPosition + ? (applyState.last_apply_previous_hash ?? null) + : page === 0 + ? null + : applyState.last_apply_hash; + const expectedStatePrefix = replayPosition ? 'last_receipt' : 'pending_receipt'; + if (replayPosition || page > 0) { + if ( + applyState[`${expectedStatePrefix}_index_count`] !== receiptIndex.count || + applyState[`${expectedStatePrefix}_index_revision`] !== receiptIndex.revision || + applyState[`${expectedStatePrefix}_index_page_count`] !== receiptIndex.page_count || + applyState[`${expectedStatePrefix}_index_updated_at`] !== receiptIndex.updated_at || + applyState[`${expectedStatePrefix}_commit_hash`] !== value.epoch_commit_hash + ) { + return new Error('Paged targeted apply receipt snapshot changed between pages.'); + } + } + let cumulativeAllocationCount; + if (replayPosition) { + cumulativeAllocationCount = applyState.last_receipt_allocation_count; + } else { + const priorAllocationCount = page === 0 + ? 0 + : applyState.pending_receipt_allocation_count; + if (!Number.isSafeInteger(priorAllocationCount) || priorAllocationCount < 0) { + return new Error('Paged targeted apply allocation count is missing.'); + } + cumulativeAllocationCount = priorAllocationCount + allocations.length; + } + if (!Number.isSafeInteger(cumulativeAllocationCount) || + cumulativeAllocationCount < 1 || + cumulativeAllocationCount > receiptIndex.count) { + return new Error('Paged targeted apply allocation count exceeds the canonical receipt index.'); + } + if (lastPage && cumulativeAllocationCount !== receiptIndex.count) { + return new Error('Last targeted apply page does not consume the complete canonical receipt index.'); + } + if (!lastPage && cumulativeAllocationCount >= receiptIndex.count) { + return new Error('Non-final targeted apply page already consumes the complete canonical receipt index.'); + } + const normalized = { + epoch: value.epoch, + page, + last_page: lastPage, + at: value.at, + epoch_seconds: params.epoch_seconds, + fee_bps: params.fee_bps, + tap_burn_bps: TAP_BURN_BPS, + epoch_commit_hash: value.epoch_commit_hash, + receipt_index: receiptIndex, + debits: this.mapRailEntriesForHash(debitMap, 'user', 'au'), + earnings: this.mapRailEntriesForHash(grossEarningMap, 'provider', 'gross_au'), + roots, + totals, + }; + if (previousApplyHash !== null) { + normalized.previous_apply_hash = previousApplyHash; + } + if (marketUsageProvided) { + normalized.market_usage = this.mapMarketUsageEntriesForHash(marketUsageMap); + } + if (hasOwn(value, 'earning_finals')) { + normalized.earning_finals = earningFinals; + } + const applyHash = await this.opaqueHash( + 'mayhem-targeted-epoch-apply-v1', + { + value: normalized, + payout_revisions: revisionBindings, + allocations, + } + ); + if (this.isIdempotentEpochApplyPage(applyState, value.epoch, page, lastPage, applyHash)) { + const result = { + ok: true, + op: 'epochApply', + epoch: value.epoch, + idempotent: true, + debited_au: ZERO_AU, + earned_au: ZERO_AU, + fee_au: ZERO_AU, + burn_au: ZERO_AU, + }; + if (pagedApply) { + result.page = page; + result.last_page = lastPage; + } + return result; + } + const cadenceError = await this.validateEpochCadenceTime( + applyState, + value.epoch, + page, + value.at, + params.epoch_seconds + ); + if (cadenceError) return cadenceError; + const pageOrderError = this.validateEpochApplyPageOrder(applyState, value.epoch, page); + if (pageOrderError) return pageOrderError; + const reservationDebitTotals = this.nextReservationDebitTotals(applyState, value.epoch, page, debitMap); + if (reservationDebitTotals instanceof Error) return reservationDebitTotals; + if (lastPage) { + const cumulativeDebitAu = this.sumRailAu(reservationDebitTotals, 'au'); + if (cumulativeDebitAu instanceof Error) return cumulativeDebitAu; + if (this.compareAu(cumulativeDebitAu, epochCommit.totals.use_au) !== 0) { + return new Error('Targeted apply cumulative debit does not match the epoch commit.'); + } + } + + const balances = new Map(); + for (const debit of debitMap.values()) { + const { rail, user, au: debitAu } = debit; + const balance = await this.balanceRecord(user, rail); + if (balance instanceof Error) return balance; + const balanceError = this.guardianValidateBalanceRecord(balance, user, rail); + if (balanceError) return balanceError; + if (this.compareAu(balance.au, debitAu) < 0) return new Error('Insufficient credit balance.'); + const nextBalanceAu = this.safeSubAu(balance.au, debitAu); + if (nextBalanceAu instanceof Error) return nextBalanceAu; + balances.set(stableJson([rail, user]), { + ...balance, + rail, + au: nextBalanceAu, + updated_epoch: value.epoch, + updated_at: this.tx, + }); + } + + const earningDeltas = new Map(); + const feeDeltaByRail = new Map(PROVIDER_ACCEPTED_RAIL_ORDER.map((rail) => [rail, ZERO_AU])); + const burnDeltaByRail = new Map(PROVIDER_ACCEPTED_RAIL_ORDER.map((rail) => [rail, ZERO_AU])); + let feeDeltaTotalAu = ZERO_AU; + let burnDeltaTotalAu = ZERO_AU; + for (const earning of grossEarningMap.values()) { + const { rail, provider, gross_au: grossAu } = earning; + const providerRecord = await this.get(`prov/${provider}`); + if (!providerRecord) return new Error('Provider not found.'); + if (!Array.isArray(providerRecord.accepted_rails)) { + return new Error('Provider accepted rails are not set.'); + } + if (!providerRecord.accepted_rails.includes(rail)) { + return new Error('Provider does not accept payment rail.'); + } + + const settlementDelta = boundedReceiptSettlement + ? options.providerSettlementDeltas?.get(stableJson([rail, provider])) + : null; + if (boundedReceiptSettlement && + (!settlementDelta || settlementDelta.gross_au !== grossAu)) { + return new Error('Bounded receipt provider settlement delta is missing.'); + } + const feeAu = settlementDelta?.fee_au ?? + this.safeMulDivAu(grossAu, params.fee_bps, 10_000); + const burnAu = settlementDelta?.burn_au ?? (rail === 'tap' + ? this.safeMulDivAu(grossAu, TAP_BURN_BPS, 10_000) + : ZERO_AU); + const providerAu = settlementDelta?.provider_au ?? this.safeSubAu( + this.safeSubAu(grossAu, feeAu), + burnAu + ); + if (feeAu instanceof Error || burnAu instanceof Error || providerAu instanceof Error) { + return new Error('Invalid provider settlement delta.'); + } + feeDeltaTotalAu = this.safeAddAu(feeDeltaTotalAu, feeAu); + if (feeDeltaTotalAu instanceof Error) return feeDeltaTotalAu; + const nextRailFee = this.safeAddAu(feeDeltaByRail.get(rail) ?? ZERO_AU, feeAu); + if (nextRailFee instanceof Error) return nextRailFee; + feeDeltaByRail.set(rail, nextRailFee); + burnDeltaTotalAu = this.safeAddAu(burnDeltaTotalAu, burnAu); + if (burnDeltaTotalAu instanceof Error) return burnDeltaTotalAu; + const nextRailBurn = this.safeAddAu(burnDeltaByRail.get(rail) ?? ZERO_AU, burnAu); + if (nextRailBurn instanceof Error) return nextRailBurn; + burnDeltaByRail.set(rail, nextRailBurn); + const key = stableJson([rail, provider]); + const current = earningDeltas.get(key) ?? { rail, provider, provider_record: providerRecord, au: ZERO_AU }; + const next = this.safeAddAu(current.au, providerAu); + if (next instanceof Error) return next; + earningDeltas.set(key, { ...current, au: next }); + } + + const earnings = new Map(); + let earnCumTotal = ZERO_AU; + for (const delta of earningDeltas.values()) { + const { rail, provider, provider_record: providerRecord, au: deltaAu } = delta; + const current = await this.earningRecord(provider, rail); + if (current instanceof Error) return current; + const currentError = this.guardianValidateEarningRecord(current, provider, rail); + if (currentError) return currentError; + const probeGate = await this.probeGateForEarning(provider, current, params); + if (probeGate instanceof Error) return probeGate; + const lockedEarningEpochs = this.providerLockedEarningEpochs(providerRecord, params); + if (lockedEarningEpochs instanceof Error) return lockedEarningEpochs; + const disputeGate = await this.providerHasOpenDispute(provider); + if (disputeGate instanceof Error) return disputeGate; + const refreshed = this.refreshEarningHoldback( + current, + value.epoch, + lockedEarningEpochs, + probeGate, + disputeGate + ); + if (refreshed instanceof Error) return refreshed; + const totalAu = this.safeAddAu(refreshed.total_au, deltaAu); + if (totalAu instanceof Error) return totalAu; + const heldAu = this.safeAddAu(refreshed.held_au, deltaAu); + if (heldAu instanceof Error) return heldAu; + const holdbacks = this.appendHoldbackBucket( + refreshed.holdbacks, + value.epoch, + deltaAu, + lockedEarningEpochs + ); + if (holdbacks instanceof Error) return holdbacks; + earnings.set(stableJson([rail, provider]), { + ...refreshed, + rail, + total_au: totalAu, + held_au: heldAu, + holdbacks, + updated_epoch: value.epoch, + updated_at: this.tx, + }); + earnCumTotal = this.safeAddAu(earnCumTotal, totalAu); + if (earnCumTotal instanceof Error) return earnCumTotal; + } + + const epochEarningUpdates = []; + const epochMarketUsageUpdates = []; + const epochMarketProviderUpdates = []; + let epochProviderCount = 0; + let epochMarketCount = 0; + let epochEarnCumAu = ZERO_AU; + let epochFeeAu = feeDeltaTotalAu; + let epochBurnAu = burnDeltaTotalAu; + if (boundedReceiptSettlement) { + const priorProviderCount = page === 0 + ? 0 + : applyState.pending_receipt_provider_count; + const priorMarketCount = page === 0 + ? 0 + : applyState.pending_receipt_market_count; + const priorEarnCumAu = page === 0 + ? ZERO_AU + : this.normalizeAu( + applyState.pending_receipt_earn_cum_au, + 'pending receipt cumulative earning', + { allowZero: true } + ); + const priorFeeAu = page === 0 + ? ZERO_AU + : this.normalizeAu( + applyState.pending_receipt_fee_au, + 'pending receipt epoch fee', + { allowZero: true } + ); + const priorBurnAu = page === 0 + ? ZERO_AU + : this.normalizeAu( + applyState.pending_receipt_burn_au, + 'pending receipt epoch burn', + { allowZero: true } + ); + if (!Number.isSafeInteger(priorProviderCount) || priorProviderCount < 0 || + !Number.isSafeInteger(priorMarketCount) || priorMarketCount < 0 || + priorEarnCumAu instanceof Error || priorFeeAu instanceof Error || + priorBurnAu instanceof Error) { + return new Error('Bounded receipt settlement cumulative state is invalid.'); + } + let newProviderCount = 0; + for (const earning of this.sortedRailRecords(grossEarningMap, 'provider')) { + const identity = stableJson([earning.rail, earning.provider]); + const delta = earningDeltas.get(identity); + const nextEarning = earnings.get(identity); + if (!delta || !nextEarning) { + return new Error('Bounded receipt provider earning update is missing.'); + } + const key = `epoch/earning-provider/${value.epoch}/${earning.rail}/${earning.provider}`; + const existing = await this.get(key); + const priorTotalAu = this.safeSubAu(nextEarning.total_au, delta.au); + if (priorTotalAu instanceof Error) return priorTotalAu; + let marker = existing; + if (marker === null) { + newProviderCount += 1; + marker = { + type: 'epoch_provider_earning', + epoch: value.epoch, + rail: earning.rail, + provider: earning.provider, + prior_cumulative_au: priorTotalAu, + gross_au: ZERO_AU, + net_au: ZERO_AU, + cumulative_au: priorTotalAu, + first_page: page, + last_page: page - 1, + updated_at: null, + }; + } else if ( + marker.type !== 'epoch_provider_earning' || + marker.epoch !== value.epoch || + marker.rail !== earning.rail || + marker.provider !== earning.provider || + marker.cumulative_au !== priorTotalAu || + !Number.isSafeInteger(marker.first_page) || + !Number.isSafeInteger(marker.last_page) || + marker.first_page < 0 || + marker.last_page >= page + ) { + return new Error('Bounded receipt provider earning marker is inconsistent.'); + } + const grossAu = this.safeAddAu(marker.gross_au, earning.gross_au); + const netAu = this.safeAddAu(marker.net_au, delta.au); + if (grossAu instanceof Error || netAu instanceof Error) { + return new Error('Bounded receipt provider earning marker overflow.'); + } + epochEarningUpdates.push({ + key, + value: { + ...marker, + gross_au: grossAu, + net_au: netAu, + cumulative_au: nextEarning.total_au, + last_page: page, + updated_at: this.tx, + }, + }); + } + let newMarketCount = 0; + const updatedMarketMarkers = new Map(); + for (const usage of canonicalPageMarketUsageMap.values()) { + const ctxKey = usage.ctx_bracket ? `ctx/${usage.ctx_bracket}` : 'base'; + const markerKey = + `epoch/market-usage/${value.epoch}/${usage.enclave_id}/${ctxKey}`; + const existing = await this.get(markerKey); + let marker = existing; + if (marker === null) { + newMarketCount += 1; + marker = { + type: 'epoch_market_usage', + epoch: value.epoch, + enclave_id: usage.enclave_id, + ...(usage.ctx_bracket ? { ctx_bracket: usage.ctx_bracket } : {}), + ...(usage.ctx_bracket_table_ver ? { + ctx_bracket_table_ver: usage.ctx_bracket_table_ver, + } : {}), + demand_au: ZERO_AU, + settled_usage: {}, + session_count: 0, + provider_count: 0, + first_page: page, + last_page: page - 1, + updated_at: null, + }; + } else if ( + marker.type !== 'epoch_market_usage' || + marker.epoch !== value.epoch || + marker.enclave_id !== usage.enclave_id || + (marker.ctx_bracket ?? null) !== (usage.ctx_bracket ?? null) || + (marker.ctx_bracket_table_ver ?? null) !== + (usage.ctx_bracket_table_ver ?? null) || + !Number.isSafeInteger(marker.first_page) || + !Number.isSafeInteger(marker.last_page) || + marker.first_page < 0 || + marker.last_page >= page || + !Number.isSafeInteger(marker.session_count) || + marker.session_count < 1 || + !Number.isSafeInteger(marker.provider_count) || + marker.provider_count < 1 + ) { + return new Error('Bounded receipt market usage marker is inconsistent.'); + } + let newProviderCount = 0; + for (const provider of usage.providers) { + const providerKey = + `epoch/market-provider/${value.epoch}/${usage.enclave_id}/${ctxKey}/${provider}`; + const providerMarker = await this.get(providerKey); + if (providerMarker === null) { + newProviderCount += 1; + epochMarketProviderUpdates.push({ + key: providerKey, + value: { + type: 'epoch_market_provider', + epoch: value.epoch, + enclave_id: usage.enclave_id, + ...(usage.ctx_bracket ? { ctx_bracket: usage.ctx_bracket } : {}), + provider, + first_page: page, + updated_at: this.tx, + }, + }); + } else if ( + providerMarker.type !== 'epoch_market_provider' || + providerMarker.epoch !== value.epoch || + providerMarker.enclave_id !== usage.enclave_id || + (providerMarker.ctx_bracket ?? null) !== (usage.ctx_bracket ?? null) || + providerMarker.provider !== provider || + !Number.isSafeInteger(providerMarker.first_page) || + providerMarker.first_page < 0 || + providerMarker.first_page >= page + ) { + return new Error('Bounded receipt market provider marker is inconsistent.'); + } + } + const demandAu = this.safeAddAu(marker.demand_au, usage.demand_au); + const sessionCount = this.safeAddCount( + marker.session_count, + usage.session_count, + 'bounded receipt market sessions' + ); + const providerCount = this.safeAddCount( + marker.provider_count, + newProviderCount, + 'bounded receipt market providers' + ); + if (demandAu instanceof Error || sessionCount instanceof Error || + providerCount instanceof Error) { + return new Error('Bounded receipt market usage marker overflow.'); + } + const settledUsage = this.addSettledUsage(marker.settled_usage, usage.settled_usage); + if (settledUsage instanceof Error) return settledUsage; + const nextMarker = { + ...marker, + settled_usage: settledUsage, + demand_au: demandAu, + session_count: sessionCount, + provider_count: providerCount, + last_page: page, + updated_at: this.tx, + }; + epochMarketUsageUpdates.push({ key: markerKey, value: nextMarker }); + updatedMarketMarkers.set( + this.priceMarketKey(usage.enclave_id, usage.ctx_bracket ?? null), + nextMarker + ); + } + epochProviderCount = this.safeAddCount( + priorProviderCount, + newProviderCount, + 'bounded receipt provider count' + ); + if (epochProviderCount instanceof Error) return epochProviderCount; + epochMarketCount = this.safeAddCount( + priorMarketCount, + newMarketCount, + 'bounded receipt market count' + ); + if (epochMarketCount instanceof Error) return epochMarketCount; + epochFeeAu = this.safeAddAu(priorFeeAu, feeDeltaTotalAu); + epochBurnAu = this.safeAddAu(priorBurnAu, burnDeltaTotalAu); + if (epochFeeAu instanceof Error || epochBurnAu instanceof Error) { + return new Error('Bounded receipt epoch fee or burn overflow.'); + } + if (!lastPage) { + epochEarnCumAu = priorEarnCumAu; + } else { + if (marketUsageMap.size !== epochMarketCount) { + return new Error('Final market usage count does not match bounded settlement state.'); + } + for (const usage of marketUsageMap.values()) { + const marketKey = this.priceMarketKey( + usage.enclave_id, + usage.ctx_bracket ?? null + ); + const ctxKey = usage.ctx_bracket ? `ctx/${usage.ctx_bracket}` : 'base'; + const marker = updatedMarketMarkers.get(marketKey) ?? await this.get( + `epoch/market-usage/${value.epoch}/${usage.enclave_id}/${ctxKey}` + ); + if (!marker || stableJson({ + enclave_id: marker.enclave_id, + ...(marker.ctx_bracket ? { ctx_bracket: marker.ctx_bracket } : {}), + ...(marker.ctx_bracket_table_ver ? { + ctx_bracket_table_ver: marker.ctx_bracket_table_ver, + } : {}), + demand_au: marker.demand_au, + session_count: marker.session_count, + provider_count: marker.provider_count, + }) !== stableJson(usage)) { + return new Error('Final market usage does not match canonical receipt settlement state.'); + } + } + const updatedMarkers = new Map( + epochEarningUpdates.map((update) => [ + stableJson([update.value.rail, update.value.provider]), + update.value, + ]) + ); + if (earningFinals.length !== epochProviderCount) { + return new Error('Final provider earning count does not match bounded settlement state.'); + } + const leaves = []; + for (const final of earningFinals) { + const identity = stableJson([final.rail, final.provider]); + const marker = updatedMarkers.get(identity) ?? + await this.get(`epoch/earning-provider/${value.epoch}/${final.rail}/${final.provider}`); + if (!marker || stableJson({ + rail: marker.rail, + provider: marker.provider, + gross_au: marker.gross_au, + net_au: marker.net_au, + cumulative_au: marker.cumulative_au, + }) !== stableJson(final)) { + return new Error('Final provider earning evidence does not match applied settlement state.'); + } + epochEarnCumAu = this.safeAddAu(epochEarnCumAu, final.cumulative_au); + if (epochEarnCumAu instanceof Error) return epochEarnCumAu; + leaves.push(await this.opaqueHash('mayhem-earn-leaf-v1', final)); + } + if (epochProviderCount !== epochCommit.totals.provider_count || + this.compareAu(epochEarnCumAu, epochCommit.totals.earn_au) !== 0 || + this.compareAu(epochFeeAu, epochCommit.totals.fee_au) !== 0 || + this.compareAu(epochBurnAu, epochCommit.totals.burn_au) !== 0) { + return new Error('Final bounded settlement totals do not match the epoch commit.'); + } + const earnRoot = await this.merkleRoot('earn', leaves); + if (earnRoot !== epochCommit.roots.earn) { + return new Error('Final provider earning root does not match the epoch commit.'); + } + const feeRoot = await this.opaqueHash('mayhem-fee-root-v1', { + epoch: value.epoch, + fee_au: epochFeeAu, + fee_cum_au: epochCommit.totals.fee_cum_au, + burn_au: epochBurnAu, + burn_cum_au: epochCommit.totals.burn_cum_au, + tap_burn_bps: TAP_BURN_BPS, + }); + if (feeRoot !== epochCommit.roots.fee) { + return new Error('Final fee root does not match the epoch commit.'); + } + } + } + + const feeRecords = new Map(); + const nextFeeRecords = new Map(); + const burnRecords = new Map(); + const nextBurnRecords = new Map(); + let nextFeeCumTotal = ZERO_AU; + let nextBurnCumTotal = ZERO_AU; + const touchedRails = new Set([ + ...Array.from(debitRailTotals.entries()).filter(([, au]) => this.compareAu(au, ZERO_AU) > 0).map(([rail]) => rail), + ...Array.from(grossRailTotals.entries()).filter(([, au]) => this.compareAu(au, ZERO_AU) > 0).map(([rail]) => rail), + ]); + for (const rail of PROVIDER_ACCEPTED_RAIL_ORDER) { + const fee = await this.feeCumRecord(rail); + if (fee instanceof Error) return fee; + const feeError = this.guardianValidateFeeRecord(fee, rail); + if (feeError) return feeError; + feeRecords.set(rail, fee); + const feeDeltaAu = feeDeltaByRail.get(rail) ?? ZERO_AU; + const nextFeeCum = this.safeAddAu(fee.cum_au, feeDeltaAu); + if (nextFeeCum instanceof Error) return nextFeeCum; + // settled_cum_au must advance with this epoch's settled rail debits in + // the SAME record that carries the new cum_au, otherwise + // guardianValidateFeeRecord rejects every fee-bearing epoch apply + // (settled < cum). This mirrors next_settled_cum_by_rail in + // guardianValidateEpochTotals, which is also what gets persisted. + const nextFeeSettledCum = this.safeAddAu( + fee.settled_cum_au ?? fee.cum_au, + debitRailTotals.get(rail) ?? ZERO_AU + ); + if (nextFeeSettledCum instanceof Error) return nextFeeSettledCum; + const nextFee = touchedRails.has(rail) + ? { + ...fee, + cum_au: nextFeeCum, + settled_cum_au: nextFeeSettledCum, + updated_epoch: value.epoch, + updated_at: this.tx, + last_apply_hash: applyHash, + last_fee_bps: params.fee_bps, + } + : fee; + nextFeeRecords.set(rail, nextFee); + nextFeeCumTotal = this.safeAddAu(nextFeeCumTotal, nextFee.cum_au); + if (nextFeeCumTotal instanceof Error) return nextFeeCumTotal; + + const burn = await this.burnCumRecord(rail); + if (burn instanceof Error) return burn; + const burnError = this.guardianValidateBurnRecord(burn, rail); + if (burnError) return burnError; + burnRecords.set(rail, burn); + const burnDeltaAu = burnDeltaByRail.get(rail) ?? ZERO_AU; + const nextBurnCum = this.safeAddAu(burn.cum_au, burnDeltaAu); + if (nextBurnCum instanceof Error) return nextBurnCum; + const nextBurn = touchedRails.has(rail) + ? { + ...burn, + cum_au: nextBurnCum, + updated_epoch: value.epoch, + updated_at: this.tx, + last_apply_hash: applyHash, + burn_bps: rail === 'tap' ? TAP_BURN_BPS : 0, + } + : burn; + nextBurnRecords.set(rail, nextBurn); + nextBurnCumTotal = this.safeAddAu(nextBurnCumTotal, nextBurn.cum_au); + if (nextBurnCumTotal instanceof Error) return nextBurnCumTotal; + } + + const grossAfterFees = this.safeSubAu(grossTotal, feeDeltaTotalAu); + if (grossAfterFees instanceof Error) return grossAfterFees; + const providerDeltaTotal = this.safeSubAu(grossAfterFees, burnDeltaTotalAu); + if (providerDeltaTotal instanceof Error) return providerDeltaTotal; + const guardian = this.guardianCheckEpochApply({ + epoch: value.epoch, + page, + applyState, + feeRecords, + burnRecords, + debitTotal, + debitRailTotals, + feeDeltaByRail, + burnDeltaByRail, + providerDeltaTotal, + nextFeeRecords, + nextBurnRecords, + balances, + earnings, + }); + if (guardian instanceof Error) return guardian; + + const canonicalActivity = new Map(); + if (Array.isArray(options.canonicalMarketUsage)) { + for (const usage of options.canonicalMarketUsage) canonicalActivity.set( + this.priceMarketKey(usage.enclave_id, usage.ctx_bracket ?? null), usage); + } + if (boundedReceiptSettlement && lastPage) { + for (const usage of marketUsageMap.values()) { + const ctxKey = usage.ctx_bracket ? `ctx/${usage.ctx_bracket}` : 'base'; + const markerKey = `epoch/market-usage/${value.epoch}/${usage.enclave_id}/${ctxKey}`; + const marker = epochMarketUsageUpdates.find((u) => u.key === markerKey)?.value ?? await this.get(markerKey); + canonicalActivity.set(this.priceMarketKey(usage.enclave_id, usage.ctx_bracket ?? null), marker); + } + } + let marketPriceUpdates = []; + if (marketUsageProvided) { + marketPriceUpdates = await this.computeMarketPriceUpdates(marketUsageMap, { + epoch: value.epoch, + at: value.at, + epochSeconds: params.epoch_seconds, + canonicalActivity, + includeDormant: boundedReceiptSettlement && lastPage, + }); + if (marketPriceUpdates instanceof Error) return marketPriceUpdates; + } + const priceDerivations = await this.priceDerivationsFromMarketUpdates(marketPriceUpdates, { + epoch: value.epoch, + at: value.at, + epochSeconds: params.epoch_seconds, + usageRoot: boundedReceiptSettlement ? epochCommit.roots.use : (roots?.use ?? null), + }); + if (priceDerivations instanceof Error) return priceDerivations; + + if (totals) { + const totalsError = await this.validateEpochApplyTotals({ + epoch: value.epoch, + roots, + totals, + debitTotal, + feeDeltaAu: feeDeltaTotalAu, + nextFeeCum: nextFeeCumTotal, + burnDeltaAu: burnDeltaTotalAu, + nextBurnCum: nextBurnCumTotal, + providerCount: grossEarningMap.size, + earnCumTotal, + epochSeconds: params.epoch_seconds, + priceDerivations, + }); + if (totalsError) return totalsError; + } + + if (lastPage && boundedReceiptSettlement) { + const finalEvidenceError = await this.validatePagedEpochCommitEvidence({ + commit: epochCommit, + feeCumAu: nextFeeCumTotal, + burnCumAu: nextBurnCumTotal, + priceDerivations, + }); + if (finalEvidenceError) return finalEvidenceError; + } + + const nextApplyState = this.nextEpochApplyState({ + applyState, + epoch: value.epoch, + page, + lastPage, + applyHash, + epochSeconds: params.epoch_seconds, + settledAt: value.at, + reservationDebitTotals, + receiptApply: { + index_count: receiptIndex.count, + index_revision: receiptIndex.revision, + index_page_count: receiptIndex.page_count, + index_updated_at: receiptIndex.updated_at, + commit_hash: value.epoch_commit_hash, + allocation_count: cumulativeAllocationCount, + provider_count: epochProviderCount, + market_count: epochMarketCount, + earn_cum_au: epochEarnCumAu, + fee_au: epochFeeAu, + burn_au: epochBurnAu, + }, + }); + const previousChallengeAnchor = await this.prepareCanaryChallengeAnchor(applyState); + if (previousChallengeAnchor instanceof Error) return previousChallengeAnchor; + const epochApplyAnchor = lastPage + ? await this.prepareEpochApplyAnchor(nextApplyState) + : null; + if (epochApplyAnchor instanceof Error) return epochApplyAnchor; + const challengeAnchor = lastPage + ? await this.prepareCanaryChallengeAnchor(nextApplyState) + : null; + if (challengeAnchor instanceof Error) return challengeAnchor; + + if (options.commitTransition?.write) { + if (options.commitTransition.archive) { + await this.put( + options.commitTransition.archive.key, + options.commitTransition.archive.value + ); + } + await this.put(options.commitTransition.key, options.commitTransition.record); + } + + for (const balance of this.sortedRailRecords(balances, 'user')) { + await this.put(this.balanceKey(balance.user, balance.rail), balance); + } + for (const earning of this.sortedRailRecords(earnings, 'provider')) { + await this.put(this.earningKey(earning.provider, earning.rail), earning); + } + for (const update of epochEarningUpdates) { + await this.put(update.key, update.value); + } + for (const update of epochMarketProviderUpdates) { + await this.put(update.key, update.value); + } + for (const update of epochMarketUsageUpdates) { + await this.put(update.key, update.value); + } + for (const rail of PROVIDER_ACCEPTED_RAIL_ORDER.filter((rail) => touchedRails.has(rail))) { + const feeRecord = { + ...nextFeeRecords.get(rail), + settled_cum_au: guardian.next_settled_cum_by_rail.get(rail), + }; + await this.put(this.feeCumKey(rail), feeRecord); + await this.put(this.burnCumKey(rail), nextBurnRecords.get(rail)); + } + await this.writePreparedAnchor(previousChallengeAnchor); + await this.put('epoch/apply/state', nextApplyState); + await this.writePreparedAnchor(epochApplyAnchor); + await this.writePreparedAnchor(challengeAnchor); + if (lastPage && boundedReceiptSettlement) { + await this.writeEpochEvidenceRoots({ + epoch: value.epoch, + at: value.at, + epoch_seconds: params.epoch_seconds, + roots: epochCommit.roots, + totals: epochCommit.totals, + feeDeltaAu: epochCommit.totals.fee_au, + feeCumAu: nextFeeCumTotal, + burnDeltaAu: epochCommit.totals.burn_au, + burnCumAu: nextBurnCumTotal, + priceDerivations: [], + }); + await this.writeBoundedMarketPriceEvidence({ + epoch: value.epoch, + at: value.at, + epochSeconds: params.epoch_seconds, + usageRoot: epochCommit.roots.use, + derivations: priceDerivations, + }); + } else if (totals) { + await this.writeEpochEvidenceRoots({ + epoch: value.epoch, + at: value.at, + epoch_seconds: params.epoch_seconds, + roots, + totals, + feeDeltaAu: feeDeltaTotalAu, + feeCumAu: nextFeeCumTotal, + burnDeltaAu: burnDeltaTotalAu, + burnCumAu: nextBurnCumTotal, + priceDerivations, + }); + } else if (priceDerivations.length > 0) { + await this.writePriceDerivationEvidence({ + epoch: value.epoch, + at: value.at, + epoch_seconds: params.epoch_seconds, + root: await this.priceDerivationRoot(priceDerivations), + count: priceDerivations.length, + derivations: priceDerivations, + }); + } + const activityIndexError = await this.writeActivityMarketIndex(marketPriceUpdates); + if (activityIndexError instanceof Error) return activityIndexError; + for (const update of marketPriceUpdates) { + await this.put(update.schedule_key, update.schedule); + await this.put(update.record_key, update.record); + } + + const result = { + ok: true, + op: 'epochApply', + epoch: value.epoch, + idempotent: false, + debited_au: debitTotal, + earned_au: providerDeltaTotal, + fee_au: feeDeltaTotalAu, + burn_au: burnDeltaTotalAu, + rails: Array.from(touchedRails).sort( + (left, right) => PROVIDER_ACCEPTED_RAIL_ORDER.indexOf(left) - PROVIDER_ACCEPTED_RAIL_ORDER.indexOf(right) + ), + }; + if (pagedApply) { + result.page = page; + result.last_page = lastPage; + } + if (marketPriceUpdates.length > 0) { + result.market_prices = marketPriceUpdates.map((update) => ({ + enclave_id: update.enclave_id, + ...(update.ctx_bracket ? { + ctx_bracket: update.ctx_bracket, + ctx_bracket_table_ver: update.ctx_bracket_table_ver, + } : {}), + ver: update.ver, + momentum_bps: update.momentum_bps, + activity_rate: update.activity_rate, + ema_activity_rate: update.ema_activity_rate, + active_supply: update.active_supply, + active_demand_au: update.active_demand_au, + frozen: update.frozen, + derivation_hash: update.derivation_hash, + })); + result.price_root = await this.priceDerivationRoot(priceDerivations); + } + console.log('mayhem epochApply', result); + return result; + } + + + async epochSealEmpty() { + const adminError = await this.requireAdmin(); + if (adminError) return adminError; + + const shapeError = this.validateExactCommandValue( + ['op', 'epoch', 'at', 'reason_hash'], + 'epoch_seal_empty' + ); + if (shapeError) return shapeError; + if (!Number.isSafeInteger(this.value.epoch) || this.value.epoch < 1) { + return new Error('Invalid empty epoch seal epoch.'); + } + if (!Number.isSafeInteger(this.value.at) || this.value.at < 0) { + return new Error('Invalid empty epoch seal timestamp.'); + } + if (!this.isHexBytes(this.value.reason_hash, 32)) { + return new Error('Invalid empty epoch seal reason hash.'); + } + + const params = await this.activeParamsAt(this.value.at, ['epoch_seconds']); + const applyState = await this.epochApplyStateRecord(); + const reasonHash = this.value.reason_hash.toLowerCase(); + const key = `epoch/seal/${this.value.epoch}`; + const existing = await this.get(key); + if (existing) { + const existingHash = await this.epochEmptySealHash(this.epochEmptySealHashValue(existing)); + if ( + existing.seal_hash === existingHash && + existing.reason_hash === reasonHash && + existing.at === this.value.at && + existing.sealed_by === this.address && + applyState.updated_epoch === this.value.epoch && + applyState.last_apply_hash === existing.seal_hash && + (applyState.pending_epoch ?? null) === null + ) { + return { + ok: true, + op: 'epochSealEmpty', + epoch: this.value.epoch, + idempotent: true, + seal_hash: existing.seal_hash, + }; + } + return new Error('Empty epoch seal already exists.'); + } + + const receiptIndex = await this.get(this.receiptEpochIndexKey(this.value.epoch)); + if (receiptIndex !== null) { + if (receiptIndex.type !== 'canonical_receipt_epoch_index' || + receiptIndex.epoch !== this.value.epoch || + !Number.isSafeInteger(receiptIndex.count) || + receiptIndex.count < 0 || + !Number.isSafeInteger(receiptIndex.revision) || + receiptIndex.revision < 0) { + return new Error('Canonical receipt epoch index is invalid.'); + } + if (receiptIndex.count !== 0 || receiptIndex.revision !== 0) { + return new Error('Cannot seal an epoch empty while canonical receipts exist.'); + } + } + + const freezeError = await this.validateFrozenEpoch(this.value.epoch, this.value.at, + receiptIndex ?? { + type: 'canonical_receipt_epoch_index', epoch: this.value.epoch, count: 0, + page_size: RECEIPT_EPOCH_INDEX_PAGE_SIZE, page_count: 0, revision: 0, updated_at: null, + }); + if (freezeError) return freezeError; + const notYetActiveAt = this.value.epoch * params.epoch_seconds; + if (this.value.at < notYetActiveAt) { + return new Error('Empty epoch seal is not active until the epoch window ends.'); + } + const cadenceError = await this.validateEpochCadenceTime( + applyState, + this.value.epoch, + 0, + this.value.at, + params.epoch_seconds + ); + if (cadenceError) return cadenceError; + const pageOrderError = this.validateEpochApplyPageOrder(applyState, this.value.epoch, 0); + if (pageOrderError) return pageOrderError; + + const activityUpdates = await this.computeMarketPriceUpdates(new Map(), { + epoch: this.value.epoch, at: this.value.at, epochSeconds: params.epoch_seconds, + canonicalActivity: new Map(), includeDormant: true, + }); + if (activityUpdates instanceof Error) return activityUpdates; + const usageRoot = await this.merkleRoot('use', []); + const activityDerivations = await this.priceDerivationsFromMarketUpdates(activityUpdates, { + epoch: this.value.epoch, at: this.value.at, epochSeconds: params.epoch_seconds, usageRoot, + }); + if (activityDerivations instanceof Error) return activityDerivations; + if ((await this.get(`market/price/${this.value.epoch}`)) !== null) { + return new Error('Empty epoch market price evidence already exists.'); + } + const activityRoot = await this.priceDerivationRoot(activityDerivations); + const sealValue = { + type: 'epoch_empty_seal', + market_price_root: activityRoot, + market_price_count: activityDerivations.length, + epoch: this.value.epoch, + at: this.value.at, + epoch_seconds: params.epoch_seconds, + previous_apply_hash: applyState.last_apply_hash ?? null, + reason_hash: reasonHash, + sealed_by: this.address, + sealed_by_role: 'admin', + totals: { + debited_au: ZERO_AU, + earned_au: ZERO_AU, + fee_au: ZERO_AU, + burn_au: ZERO_AU, + }, + }; + const sealHash = await this.epochEmptySealHash(sealValue); + const record = { + ...sealValue, + seal_hash: sealHash, + sealed_at: this.tx, + }; + const nextApplyState = this.nextEpochApplyState({ + applyState, + epoch: this.value.epoch, + page: 0, + lastPage: true, + applyHash: sealHash, + epochSeconds: params.epoch_seconds, + settledAt: this.value.at, + }); + const previousChallengeAnchor = await this.prepareCanaryChallengeAnchor(applyState); + if (previousChallengeAnchor instanceof Error) return previousChallengeAnchor; + const epochApplyAnchor = await this.prepareEpochApplyAnchor(nextApplyState); + if (epochApplyAnchor instanceof Error) return epochApplyAnchor; + const challengeAnchor = await this.prepareCanaryChallengeAnchor(nextApplyState); + if (challengeAnchor instanceof Error) return challengeAnchor; + + await this.writeBoundedMarketPriceEvidence({ + epoch: this.value.epoch, at: this.value.at, epochSeconds: params.epoch_seconds, + usageRoot, derivations: activityDerivations, + }); + const activityIndexError = await this.writeActivityMarketIndex(activityUpdates); + if (activityIndexError instanceof Error) return activityIndexError; + for (const update of activityUpdates) { + await this.put(update.schedule_key, update.schedule); + await this.put(update.record_key, update.record); + } + await this.put(key, record); + await this.writePreparedAnchor(previousChallengeAnchor); + await this.put('epoch/apply/state', nextApplyState); + await this.writePreparedAnchor(epochApplyAnchor); + await this.writePreparedAnchor(challengeAnchor); + const result = { + ok: true, + op: 'epochSealEmpty', + epoch: this.value.epoch, + idempotent: false, + seal_hash: sealHash, + }; + console.log('mayhem epochSealEmpty', result); + return result; + } + + async epochCommit() { + const banned = await this.get(`committer/ban/${this.address}`); + if (banned?.status === 'banned') return new Error('Epoch committer is banned.'); + + const roots = this.normalizeEpochRoots(this.value.roots); + if (roots instanceof Error) return roots; + const totals = this.normalizeEpochTotals(this.value.totals); + if (totals instanceof Error) return totals; + const params = await this.activeParamsAt(this.value.at, ['challenge_epochs', 'epoch_seconds']); + const normalized = { + epoch: this.value.epoch, + epoch_seconds: params.epoch_seconds, + roots, + totals, + }; + const commitHash = await this.epochCommitHash(normalized); + const key = `epoch/commit/${this.value.epoch}`; + const existing = await this.get(key); + if (existing) { + if (existing.commit_hash === commitHash) { + return { + ok: true, + op: 'epochCommit', + epoch: this.value.epoch, + idempotent: true, + commit_hash: commitHash, + }; + } + return new Error('Epoch commit already exists.'); + } + + const activityEvidence = await this.prepareCommittedActivityEvidence({ + epoch: this.value.epoch, at: this.value.at, epochSeconds: params.epoch_seconds, roots, totals, + }); + if (activityEvidence instanceof Error) return activityEvidence; + const record = { + type: 'epoch_commit', + pricing_schema_version: 2, + ...(activityEvidence ? { expected_activity_evidence: activityEvidence } : {}), + epoch: this.value.epoch, + epoch_seconds: params.epoch_seconds, + roots, + totals, + status: 'provisional', + challenge_epochs: params.challenge_epochs, + provisional_until_epoch: this.value.epoch + params.challenge_epochs, + commit_hash: commitHash, + submitted_by: this.address, + submitted_at: this.tx, + at: this.value.at, + }; + await this.put(key, record); + console.log('mayhem epochCommit', record); + return { + ok: true, + op: 'epochCommit', + epoch: this.value.epoch, + idempotent: false, + commit_hash: commitHash, + }; + } + + async fraudProof() { + if (!FRAUD_PROOF_REASONS.has(this.value.reason)) { + return new Error('Unsupported fraud proof reason.'); + } + if (b4a.from(stableJson(this.value)).byteLength > FRAUD_PROOF_MAX_BYTES) { + return new Error('Fraud proof exceeds 4096 bytes.'); + } + + const commitKey = `epoch/commit/${this.value.epoch}`; + const commit = await this.get(commitKey); + if (!commit) return new Error('Epoch commit not found.'); + + let proofHashPayload; + let proof; + let proofHash = null; + let slashReason = 'receipt_forgery'; + let slashEnclaveId = null; + + if (this.value.reason === 'over_credit') { + if (!hasOwn(this.value, 'receipt')) return new Error('Over-credit fraud proof requires a receipt.'); + if (!hasOwn(this.value, 'claimed_au_owed_cum')) { + return new Error('Over-credit fraud proof requires claimed_au_owed_cum.'); + } + const receipt = await this.normalizeReceiptEnvelope(this.value.receipt); + if (receipt instanceof Error) return receipt; + if (!this.verifyReceiptEnvelope(receipt)) { + return new Error('Invalid receipt signature.'); + } + proofHashPayload = { + epoch: this.value.epoch, + proof_epoch: this.value.proof_epoch, + reason: this.value.reason, + receipt, + claimed_au_owed_cum: this.value.claimed_au_owed_cum, + previous_au_owed_cum: this.value.previous_au_owed_cum ?? ZERO_AU, + }; + proofHash = await this.fraudProofHash(proofHashPayload); + const existingProof = await this.get(`ev/fraud/${this.value.epoch}/${proofHash}`); + if (existingProof) { + return { + ok: true, + op: 'fraudProof', + epoch: this.value.epoch, + idempotent: true, + proof_hash: proofHash, + voided_commit: commit.commit_hash, + banned_submitter: commit.submitted_by, + }; + } + proof = await this.validateOverCreditFraudProof(commit, receipt); + if (proof instanceof Error) return proof; + slashEnclaveId = receipt.body.enclave_id; + } else if (this.value.reason === 'price_derivation') { + if (!hasOwn(this.value, 'price_usage')) { + return new Error('Price derivation fraud proof requires price_usage.'); + } + proof = await this.validatePriceDerivationFraudProof(commit); + if (proof instanceof Error) return proof; + proofHashPayload = { + epoch: this.value.epoch, + proof_epoch: this.value.proof_epoch, + reason: this.value.reason, + price_usage: proof.price_usage, + expected_price_root: proof.expected_price_root, + committed_price_root: proof.committed_price_root, + price_derivation_hash: proof.price_derivation_hash, + }; + slashReason = 'price_forgery'; + slashEnclaveId = proof.enclave_id; + } + + proofHash ??= await this.fraudProofHash(proofHashPayload); + const proofKey = `ev/fraud/${this.value.epoch}/${proofHash}`; + const existingProof = await this.get(proofKey); + if (existingProof) { + return { + ok: true, + op: 'fraudProof', + epoch: this.value.epoch, + idempotent: true, + proof_hash: proofHash, + voided_commit: commit.commit_hash, + banned_submitter: commit.submitted_by, + }; + } + + if (commit.status === 'void') return new Error('Epoch commit is already void.'); + if (this.value.proof_epoch > commit.provisional_until_epoch) { + return new Error('Epoch commit challenge window has closed.'); + } + + const record = { + type: 'fraud_proof', + epoch: this.value.epoch, + proof_epoch: this.value.proof_epoch, + reason: this.value.reason, + proof_hash: proofHash, + submitted_by: this.address, + submitted_at: this.tx, + at: this.value.at, + voided_commit: commit.commit_hash, + banned_submitter: commit.submitted_by, + }; + if (this.value.reason === 'over_credit') { + record.receipt_hash = proof.receipt_hash; + record.actual_au = proof.actual_au; + record.claimed_au = proof.claimed_au; + record.committed_use_root = commit.roots.use; + } else if (this.value.reason === 'price_derivation') { + record.price_usage = proof.price_usage; + record.committed_price_root = proof.committed_price_root; + record.expected_price_root = proof.expected_price_root; + record.price_derivation_hash = proof.price_derivation_hash; + record.price_derivation = proof.price_derivation; + } + const updatedCommit = { + ...commit, + status: 'void', + voided_at: this.tx, + voided_by: this.address, + fraud_reason: this.value.reason, + fraud_proof_hash: proofHash, + }; + const banKey = `committer/ban/${commit.submitted_by}`; + const existingBan = await this.get(banKey); + const ban = existingBan?.status === 'banned' ? existingBan : { + ...(existingBan ?? {}), + submitter: commit.submitted_by, + status: 'banned', + reason: 'fraud_proof', + epoch: this.value.epoch, + proof_hash: proofHash, + banned_at: this.tx, + banned_by: this.address, + }; + + let slash = null; + if ((await this.get(`prov/${commit.submitted_by}`)) !== null) { + const params = await this.activeParamsAt(this.value.at, ['fraud_slash_bps']); + slash = await this.applyProviderSlash({ + providerId: commit.submitted_by, + source: 'fraud_proof', + reason: slashReason, + evidenceHash: proofHash, + epoch: this.value.epoch, + at: this.value.at, + slashBps: params.fraud_slash_bps, + beneficiary: this.address, + enclaveId: slashEnclaveId, + banProvider: true, + tombstoneEnclave: true, + }); + if (slash instanceof Error) return slash; + } + record.slash = slash; + + await this.put(proofKey, record); + await this.put(commitKey, updatedCommit); + await this.put(banKey, ban); + console.log('mayhem fraudProof', record); + return { + ok: true, + op: 'fraudProof', + epoch: this.value.epoch, + idempotent: false, + proof_hash: proofHash, + voided_commit: commit.commit_hash, + banned_submitter: commit.submitted_by, + slash, + }; + } + + async dispute() { + if (!(await this.isAdmin())) { + const consentError = await this.requireConsent(); + if (consentError) return consentError; + } + const validationError = this.validateDisputeOpen(this.value); + if (validationError) return validationError; + + const rail = this.normalizeLedgerRail(this.value.rail, 'dispute rail'); + if (rail instanceof Error) return rail; + const balance = await this.balanceRecord(this.address, rail); + if (balance instanceof Error) return balance; + const balanceError = this.guardianValidateBalanceRecord(balance, this.address, rail); + if (balanceError) return balanceError; + const params = await this.activeParamsAt(this.value.at, [ + 'dispute_deposit_au', + 'dispute_timeout_epochs', + 'max_open_disputes_per_opener', + ]); + const depositAu = params.dispute_deposit_au; + if (this.compareAu(balance.au, depositAu) < 0) return new Error('Insufficient balance for dispute deposit.'); + const applyState = await this.epochApplyStateRecord(); + const disputeEpoch = this.value.epoch ?? applyState.updated_epoch; + if (disputeEpoch > applyState.updated_epoch) { + return new Error('Dispute epoch cannot exceed the latest applied epoch.'); + } + const hold = await this.normalizeSpendHoldRecord( + (await this.get(this.spendHoldKey(this.address, rail, disputeEpoch))) ?? null, + this.address, + rail, + disputeEpoch + ); + if (hold instanceof Error) return hold; + const linkedSession = hold.sessions.find((session) => session.session_id === this.value.session_id.toLowerCase()); + if (!linkedSession) { + return new Error('Dispute must reference an opener-linked spend reservation.'); + } + const sessionDisputeKey = `disp/session/${this.address}/${linkedSession.session_id}`; + if ((await this.get(sessionDisputeKey)) !== null) { + return new Error('Session already has a dispute from this opener.'); + } + if (linkedSession.provider !== this.value.provider.toLowerCase()) { + return new Error('Dispute provider does not match the linked session.'); + } + if (linkedSession.enclave_id !== this.value.enclave_id.toLowerCase()) { + return new Error('Dispute enclave does not match the linked session.'); + } + if ( + this.value.counterparty !== undefined && + this.value.counterparty.toLowerCase() !== linkedSession.provider + ) { + return new Error('Dispute counterparty does not match the linked session provider.'); + } + const openerCountKey = this.disputeOpenCountKey(this.address); + const openerOpenCount = await this.disputeOpenCount(openerCountKey); + if (openerOpenCount instanceof Error) return openerOpenCount; + if (openerOpenCount >= params.max_open_disputes_per_opener) { + return new Error('Open dispute limit reached.'); + } + const providerCountKey = this.providerOpenDisputeCountKey(linkedSession.provider); + const providerOpenCount = await this.disputeOpenCount(providerCountKey); + if (providerOpenCount instanceof Error) return providerOpenCount; + const expiresAfterEpoch = disputeEpoch + params.dispute_timeout_epochs; + if (!Number.isSafeInteger(expiresAfterEpoch)) return new Error('Dispute timeout epoch overflow.'); + + const nextBalanceAu = this.safeSubAu(balance.au, depositAu); + if (nextBalanceAu instanceof Error) return nextBalanceAu; + const nextBalance = { + ...balance, + rail, + au: nextBalanceAu, + updated_epoch: Math.max(balance.updated_epoch, this.value.epoch ?? 0), + updated_at: this.tx, + }; + const nextBalanceError = this.guardianValidateBalanceRecord(nextBalance, this.address, rail); + if (nextBalanceError) return nextBalanceError; + + const next = await this.get('disp/next'); + const disputeId = next?.next ?? 1; + const record = { + type: 'dispute', + dispute_id: disputeId, + status: 'open', + rail, + opened_by: this.address, + session_id: linkedSession.session_id, + reason: this.value.reason, + provider: linkedSession.provider, + counterparty: this.value.counterparty?.toLowerCase() ?? linkedSession.provider, + enclave_id: linkedSession.enclave_id, + reservation_key: this.spendHoldKey(this.address, rail, disputeEpoch), + reservation_voucher_hash: linkedSession.voucher_hash, + epoch: disputeEpoch, + at: this.value.at, + evidence_hash: this.value.evidence_hash ?? null, + evidence: cloneValue(this.value.evidence ?? null), + deposit_au: depositAu, + deposit_holder: this.address, + timeout_epochs: params.dispute_timeout_epochs, + expires_after_epoch: expiresAfterEpoch, + opened_at: this.tx, + updated_at: this.tx, + }; + record.dispute_hash = await this.opaqueHash('mayhem-dispute-v1', record); + + await this.put(this.balanceKey(this.address, rail), nextBalance); + await this.put(`disp/${disputeId}`, record); + await this.put(sessionDisputeKey, { + opener: this.address, + session_id: linkedSession.session_id, + dispute_id: disputeId, + opened_at: this.tx, + }); + await this.put('disp/next', { next: disputeId + 1, updated_at: this.tx }); + await this.put(openerCountKey, { + opener: this.address, + count: openerOpenCount + 1, + updated_at: this.tx, + }); + await this.put(providerCountKey, { + provider: linkedSession.provider, + count: providerOpenCount + 1, + updated_at: this.tx, + }); + console.log('mayhem dispute', record); + return { + ok: true, + op: 'dispute', + dispute_id: disputeId, + deposit_au: depositAu, + expires_after_epoch: expiresAfterEpoch, + dispute_hash: record.dispute_hash, + }; + } + + async disputeResolve() { + const adminError = await this.requireAdmin(); + if (adminError) return adminError; + if (!DISPUTE_OUTCOMES.has(this.value.outcome)) return new Error('Unsupported dispute outcome.'); + if (!DISPUTE_DEPOSIT_ACTIONS.has(this.value.deposit_action)) { + return new Error('Unsupported dispute deposit action.'); + } + + const key = `disp/${this.value.dispute_id}`; + const dispute = await this.get(key); + if (!dispute || dispute.type !== 'dispute') return new Error('Dispute not found.'); + if (dispute.status !== 'open') return new Error('Dispute is not open.'); + if (this.value.beneficiary !== undefined && !this.isSafeKeyPart(this.value.beneficiary)) { + return new Error('Invalid slash beneficiary.'); + } + if (this.value.outcome === 'provider_fault' && !dispute.provider) { + return new Error('Provider fault disputes require a provider.'); + } + if (this.value.outcome === 'provider_fault') { + const provider = await this.get(`prov/${dispute.provider}`); + if (!provider) return new Error('Provider not found.'); + } + if (this.value.slash === true && !dispute.provider) { + return new Error('Dispute slash requires a provider.'); + } + if (this.value.slash === true && this.value.outcome !== 'provider_fault') { + return new Error('Only provider_fault disputes may slash a provider.'); + } + + const applyState = await this.epochApplyStateRecord(); + if (applyState.updated_epoch > dispute.expires_after_epoch) { + return new Error('Dispute resolution window has closed; expire the dispute.'); + } + if ( + this.value.outcome === 'opener_fault' && + this.value.deposit_action !== 'partial_forfeit' + ) { + return new Error('Opener-fault disputes require a partial deposit forfeit.'); + } + if ( + this.value.outcome !== 'opener_fault' && + this.value.deposit_action === 'partial_forfeit' + ) { + return new Error('Partial deposit forfeit is reserved for opener-fault disputes.'); + } + + let depositRefundedAu = ZERO_AU; + let depositForfeitedAu = ZERO_AU; + const rail = this.normalizeLedgerRail(dispute.rail, 'dispute rail'); + if (rail instanceof Error) return rail; + if (this.value.deposit_action === 'refund') { + depositRefundedAu = dispute.deposit_au; + } else if (this.value.deposit_action === 'forfeit') { + depositForfeitedAu = dispute.deposit_au; + } else { + const params = await this.activeParamsAt(this.value.at, [ + 'dispute_opener_fault_forfeit_bps', + ]); + const deposit = this.parseAu(dispute.deposit_au, 'dispute deposit', { allowZero: false }); + if (deposit instanceof Error) return deposit; + if (deposit < 2n) return new Error('Dispute deposit is too small to split.'); + let forfeited = (deposit * BigInt(params.dispute_opener_fault_forfeit_bps)) / 10_000n; + if (forfeited < 1n) forfeited = 1n; + if (forfeited >= deposit) forfeited = deposit - 1n; + depositForfeitedAu = this.canonicalAu(forfeited); + depositRefundedAu = this.safeSubAu(dispute.deposit_au, depositForfeitedAu); + if (depositRefundedAu instanceof Error) return depositRefundedAu; + } + if (this.compareAu(depositRefundedAu, ZERO_AU) > 0) { + const balance = await this.balanceRecord(dispute.opened_by, rail); + if (balance instanceof Error) return balance; + const balanceError = this.guardianValidateBalanceRecord(balance, dispute.opened_by, rail); + if (balanceError) return balanceError; + const nextAu = this.safeAddAu(balance.au, depositRefundedAu); + if (nextAu instanceof Error) return nextAu; + await this.put(this.balanceKey(dispute.opened_by, rail), { + ...balance, + rail, + au: nextAu, + updated_epoch: Math.max(balance.updated_epoch, dispute.epoch ?? 0), + updated_at: this.tx, + }); + } + if (this.compareAu(depositForfeitedAu, ZERO_AU) > 0) { + const fee = await this.feeCumRecord(rail); + if (fee instanceof Error) return fee; + const feeError = this.guardianValidateFeeRecord(fee, rail); + if (feeError) return feeError; + const cumAu = this.safeAddAu(fee.cum_au, depositForfeitedAu); + if (cumAu instanceof Error) return cumAu; + const settledCumAu = this.safeAddAu(fee.settled_cum_au ?? fee.cum_au, depositForfeitedAu); + if (settledCumAu instanceof Error) return settledCumAu; + const updatedFee = { + ...fee, + cum_au: cumAu, + settled_cum_au: settledCumAu, + updated_epoch: Math.max(fee.updated_epoch, dispute.epoch ?? 0), + updated_at: this.tx, + last_dispute_forfeit_at: this.tx, + }; + const updatedFeeError = this.guardianValidateFeeRecord(updatedFee, rail); + if (updatedFeeError) return updatedFeeError; + await this.put(this.feeCumKey(rail), updatedFee); + } + + let reputationEvent = null; + let slash = null; + if (this.value.outcome === 'provider_fault' && dispute.provider) { + reputationEvent = await this.appendReputationEvent({ + provider: dispute.provider, + event_id: `dispute-${dispute.dispute_id}-lost`, + kind: 'dispute_lost', + epoch: dispute.epoch ?? 0, + at: this.value.at, + paid_au: null, + max_spend_au: null, + evidence_hash: this.value.rationale_hash, + }); + if (reputationEvent instanceof Error) return reputationEvent; + + if (this.value.slash === true) { + const params = await this.activeParamsAt(this.value.at, ['dispute_lost_slash_bps']); + slash = await this.applyProviderSlash({ + providerId: dispute.provider, + source: 'dispute', + reason: 'dispute_lost', + evidenceHash: this.value.rationale_hash, + epoch: dispute.epoch ?? 0, + at: this.value.at, + slashBps: params.dispute_lost_slash_bps, + beneficiary: this.value.beneficiary ?? dispute.opened_by, + enclaveId: dispute.enclave_id, + eventId: reputationEvent.event_id, + banProvider: false, + tombstoneEnclave: false, + }); + if (slash instanceof Error) return slash; + } + } + + const resolved = { + ...dispute, + status: 'resolved', + outcome: this.value.outcome, + deposit_action: this.value.deposit_action, + rationale_hash: this.value.rationale_hash, + resolved_by: this.address, + resolved_at: this.tx, + resolved_at_seconds: this.value.at, + deposit_refunded_au: depositRefundedAu, + deposit_forfeited_au: depositForfeitedAu, + reputation_event: reputationEvent, + slash, + updated_at: this.tx, + }; + resolved.resolution_hash = await this.opaqueHash('mayhem-dispute-resolution-v1', resolved); + const countError = await this.closeDisputeCounts(dispute); + if (countError) return countError; + await this.put(key, resolved); + console.log('mayhem disputeResolve', resolved); + return { + ok: true, + op: 'disputeResolve', + dispute_id: dispute.dispute_id, + outcome: resolved.outcome, + deposit_action: resolved.deposit_action, + deposit_refunded_au: depositRefundedAu, + deposit_forfeited_au: depositForfeitedAu, + slash, + resolution_hash: resolved.resolution_hash, + }; + } + + async disputeExpire() { + const key = `disp/${this.value.dispute_id}`; + const dispute = await this.get(key); + if (!dispute || dispute.type !== 'dispute') return new Error('Dispute not found.'); + if (dispute.status !== 'open') return new Error('Dispute is not open.'); + if (!Number.isSafeInteger(dispute.expires_after_epoch) || dispute.expires_after_epoch < 0) { + return new Error('Dispute has no valid timeout epoch.'); + } + + const applyState = await this.epochApplyStateRecord(); + if (applyState.updated_epoch < dispute.expires_after_epoch) { + return new Error('Dispute timeout epoch not reached.'); + } + + const rail = this.normalizeLedgerRail(dispute.rail, 'dispute rail'); + if (rail instanceof Error) return rail; + const balance = await this.balanceRecord(dispute.opened_by, rail); + if (balance instanceof Error) return balance; + const balanceError = this.guardianValidateBalanceRecord(balance, dispute.opened_by, rail); + if (balanceError) return balanceError; + const depositRefundedAu = dispute.deposit_au; + const nextAu = this.safeAddAu(balance.au, depositRefundedAu); + if (nextAu instanceof Error) return nextAu; + const nextBalance = { + ...balance, + rail, + au: nextAu, + updated_epoch: Math.max(balance.updated_epoch, applyState.updated_epoch, dispute.epoch ?? 0), + updated_at: this.tx, + }; + const nextBalanceError = this.guardianValidateBalanceRecord(nextBalance, dispute.opened_by, rail); + if (nextBalanceError) return nextBalanceError; + + const expired = { + ...dispute, + status: 'expired', + outcome: 'timeout_refund', + deposit_action: 'refund', + deposit_holder: null, + expired_by: this.address, + expired_at: this.tx, + expired_at_epoch: applyState.updated_epoch, + expired_at_seconds: this.value.at, + deposit_refunded_au: depositRefundedAu, + deposit_forfeited_au: ZERO_AU, + reputation_event: null, + slash: null, + updated_at: this.tx, + }; + expired.expiry_hash = await this.opaqueHash('mayhem-dispute-expiry-v1', expired); + const countError = await this.closeDisputeCounts(dispute); + if (countError) return countError; + await this.put(this.balanceKey(dispute.opened_by, rail), nextBalance); + await this.put(key, expired); + console.log('mayhem disputeExpire', expired); + return { + ok: true, + op: 'disputeExpire', + dispute_id: dispute.dispute_id, + outcome: expired.outcome, + deposit_action: expired.deposit_action, + deposit_refunded_au: depositRefundedAu, + expired_at_epoch: expired.expired_at_epoch, + expiry_hash: expired.expiry_hash, + }; + } + + async rateOracle() { + this._mayhemApplyStage = 'rate:contract:require-admin'; + const adminError = await this.requireAdmin(); + if (adminError) return adminError; + const shapeError = this.validateRateOracleValue(this.value); + if (shapeError) return shapeError; + if (!RATE_SOURCES.has(this.value.source)) return new Error('Unsupported rate source.'); + + this._mayhemApplyStage = 'rate:contract:read-latest'; + const current = await this.get('rate/latest'); + if (current && this.value.ts < current.ts) { + return new Error('Rate timestamp must not decrease.'); + } + + const record = { + denom: 'tnk_usd_au', + tnk_usd_au: this.normalizeAu(this.value.tnk_usd_au, 'TNK/USD atto-rate', { allowZero: false }), + source: this.value.source, + ts: this.value.ts, + updated_at: this.tx, + posted_by: this.address, + posted_by_role: 'admin', + }; + this._mayhemApplyStage = 'rate:contract:read-history'; + const history = await this.get(this.tx); + if (history && stableJson(history) !== stableJson(record)) { + return new Error('TNK rate oracle history collision.'); + } + if (!history) { + this._mayhemApplyStage = 'rate:contract:write-history'; + await this.put(this.tx, record); + } + this._mayhemApplyStage = 'rate:contract:write-latest'; + await this.put('rate/latest', record); + console.log('mayhem rateOracle', record); + return { ok: true, op: 'rateOracle', ts: record.ts, source: record.source }; + } + + async tapRateOracle() { + this._mayhemApplyStage = 'rate:contract:require-admin'; + const adminError = await this.requireAdmin(); + if (adminError) return adminError; + const shapeError = this.validateTapRateOracleValue(this.value); + if (shapeError) return shapeError; + + this._mayhemApplyStage = 'rate:contract:read-latest'; + const current = await this.get('tap/rate/latest'); + if (current && this.value.ts < current.ts) { + return new Error('TAP rate timestamp must not decrease.'); + } + + const record = { + denom: 'tap_usd_au', + tap_usd_au: this.normalizeAu(this.value.tap_usd_au, 'TAP/USD atto-rate', { allowZero: false }), + source: this.value.source, + ts: this.value.ts, + updated_at: this.tx, + posted_by: this.address, + posted_by_role: 'admin', + }; + this._mayhemApplyStage = 'rate:contract:read-history'; + const history = await this.get(this.tx); + if (history && stableJson(history) !== stableJson(record)) { + return new Error('TAP rate oracle history collision.'); + } + if (!history) { + this._mayhemApplyStage = 'rate:contract:write-history'; + await this.put(this.tx, record); + } + this._mayhemApplyStage = 'rate:contract:write-latest'; + await this.put('tap/rate/latest', record); + console.log('mayhem tapRateOracle', record); + return { ok: true, op: 'tapRateOracle', ts: record.ts, source: record.source }; + } + + validateRateOracleValue(value) { + const shapeError = this.validateExactObjectKeys( + value, + ['op', 'tnk_usd_au', 'source', 'ts'], + 'rate oracle' + ); + if (shapeError) return shapeError; + if (value.op !== 'rate_oracle') return new Error('Invalid rate oracle op.'); + const rate = this.normalizeAu(value.tnk_usd_au, 'TNK/USD atto-rate', { allowZero: false }); + if (rate instanceof Error) { + return new Error('Invalid TNK/USD atto-rate.'); + } + if (typeof value.source !== 'string' || value.source.length < 1 || value.source.length > 64) { + return new Error('Invalid rate source.'); + } + if (!Number.isSafeInteger(value.ts) || value.ts < 0) { + return new Error('Invalid rate timestamp.'); + } + return null; + } + + validateTapRateOracleValue(value) { + const shapeError = this.validateExactObjectKeys( + value, + ['op', 'tap_usd_au', 'source', 'ts'], + 'TAP rate oracle' + ); + if (shapeError) return shapeError; + if (value.op !== 'tap_rate_oracle') return new Error('Invalid TAP rate oracle op.'); + const rate = this.normalizeAu(value.tap_usd_au, 'TAP/USD atto-rate', { allowZero: false }); + if (rate instanceof Error) { + return new Error('Invalid TAP/USD atto-rate.'); + } + if (typeof value.source !== 'string' + || !/^[a-z0-9][a-z0-9._-]{0,63}$/.test(value.source)) { + return new Error('Invalid TAP rate source.'); + } + if (!Number.isSafeInteger(value.ts) || value.ts < 0) { + return new Error('Invalid TAP rate timestamp.'); + } + return null; + } + + async canonicalTnkPaymentConfig() { + const payments = await this.get('payments/current'); + if (!payments || payments.set_by_role !== 'admin' || !payments.tnk) { + return new Error('Canonical TNK payment config required.'); + } + if (!['mainnet', 'testnet1'].includes(payments.tnk.network)) { + return new Error('Canonical TNK payment network is invalid.'); + } + if (!this.isSafeKeyPart(payments.tnk.treasury_address)) { + return new Error('Canonical TNK treasury address is invalid.'); + } + return payments.tnk; + } + + async canonicalTapPaymentConfig({ optional = false } = {}) { + const payments = await this.get('payments/current'); + if (!payments && optional) return null; + if (!payments || payments.set_by_role !== 'admin' || !payments.tap) { + return new Error('Canonical TAP payment config required.'); + } + if (!Number.isSafeInteger(payments.tap.chain_id) || payments.tap.chain_id < 1) { + return new Error('Canonical TAP chain id is invalid.'); + } + if (!this.isEthHexBytes(payments.tap.pool_address, 20)) { + return new Error('Canonical TAP pool address is invalid.'); + } + return { + chain_id: payments.tap.chain_id, + pool_address: payments.tap.pool_address.toLowerCase(), + }; + } + + async requireCanonicalTapPool(chainId, poolAddress) { + const tap = await this.canonicalTapPaymentConfig(); + if (tap instanceof Error) return tap; + if ( + chainId !== tap.chain_id || + typeof poolAddress !== 'string' || + poolAddress.toLowerCase() !== tap.pool_address + ) { + return new Error('TAP operation does not match the canonical payment pool.'); + } + return null; + } + + guardianValidateTapScope(record, amountFields, label) { + const hasChain = record.chain_id !== undefined && record.chain_id !== null; + const hasPool = record.pool_address !== undefined && record.pool_address !== null; + if (!hasChain && !hasPool) { + for (const field of amountFields) { + if (this.compareAu(record[field] ?? ZERO_AU, ZERO_AU) !== 0) { + return new Error(`Guardian TAP ${label} scope invariant failed.`); + } + } + return null; + } + if (!Number.isSafeInteger(record.chain_id) || record.chain_id < 1) { + return new Error(`Guardian TAP ${label} chain invariant failed.`); + } + if (!this.isEthHexBytes(record.pool_address, 20)) { + return new Error(`Guardian TAP ${label} pool invariant failed.`); + } + return null; + } + + msbAddressForPublicKey(publicKey, network) { + if (!this.isHexBytes(publicKey, 32)) return new Error('Invalid MSB owner public key.'); + const prefix = network === 'mainnet' + ? 'trac' + : network === 'testnet1' + ? 'testtrac' + : null; + if (!prefix) return new Error('Invalid MSB network.'); + const address = PeerWallet.encodeBech32mSafe(prefix, b4a.from(publicKey, 'hex')); + return typeof address === 'string' && address.length > 0 + ? address + : new Error('Unable to derive MSB owner address.'); + } + + normalizeMsbTransferEvidence(value, label = 'MSB transfer evidence') { + const shapeError = this.validateExactObjectKeys( + value, + [ + 'schema_version', + 'network', + 'tx_hash', + 'confirmed_length', + 'observed_signed_length', + 'from', + 'to', + 'amount_e18', + ], + label + ); + if (shapeError) return shapeError; + if (value.schema_version !== MSB_TRANSFER_EVIDENCE_VERSION) { + return new Error(`${label} schema version is invalid.`); + } + if (!['mainnet', 'testnet1'].includes(value.network)) { + return new Error(`${label} network is invalid.`); + } + if (!this.isHexBytes(value.tx_hash, 32) || value.tx_hash !== value.tx_hash.toLowerCase()) { + return new Error(`${label} transaction hash is invalid.`); + } + if (!Number.isSafeInteger(value.confirmed_length) || value.confirmed_length < 1) { + return new Error(`${label} confirmed length is invalid.`); + } + if ( + !Number.isSafeInteger(value.observed_signed_length) || + value.observed_signed_length < value.confirmed_length + ) { + return new Error(`${label} observed signed length is invalid.`); + } + if (!this.isSafeKeyPart(value.from) || !this.isSafeKeyPart(value.to)) { + return new Error(`${label} address is invalid.`); + } + const amount = this.parseTnkE18(value.amount_e18); + if (amount instanceof Error) return new Error(`${label} amount is invalid.`); + return { + schema_version: MSB_TRANSFER_EVIDENCE_VERSION, + network: value.network, + tx_hash: value.tx_hash, + confirmed_length: value.confirmed_length, + observed_signed_length: value.observed_signed_length, + from: value.from, + to: value.to, + amount_e18: amount.toString(), + }; + } + + msbTransferSeenKey(evidence) { + return `rail/seen/msb/${evidence.network}/${evidence.tx_hash}`; + } + + normalizeStripeTransferEvidence( + value, + label = 'Stripe settlement evidence', + { expectedAttemptId = null } = {} + ) { + const providerTransfer = value?.kind === 'stripe_transfer'; + const hasAttemptId = hasOwn(value, 'attempt_id'); + const hasFxQuoteId = providerTransfer && hasOwn(value, 'fx_quote_id'); + const hasFxQuoteHash = providerTransfer && hasOwn(value, 'fx_quote_hash'); + if (hasFxQuoteId !== hasFxQuoteHash) { + return new Error(`${label} FX quote identity must be present or absent as a pair.`); + } + if ((hasAttemptId && + (!this.isHexBytes(value.attempt_id, 32) || + value.attempt_id !== value.attempt_id.toLowerCase())) || + (expectedAttemptId !== null && + (!hasAttemptId || value.attempt_id !== expectedAttemptId))) { + return new Error(`${label} attempt id does not match its canonical attempt.`); + } + const shapeError = this.validateExactObjectKeys(value, providerTransfer + ? [ + 'schema_version', + 'kind', + ...(hasAttemptId ? ['attempt_id'] : []), + 'ref', + 'destination', + 'source_currency', + 'source_amount_minor', + 'destination_currency', + 'destination_amount_minor', + ...(hasFxQuoteId ? ['fx_quote_id', 'fx_quote_hash'] : []), + 'destination_payment', + 'transfer_group', + ] + : [ + 'schema_version', + 'kind', + ...(hasAttemptId ? ['attempt_id'] : []), + 'ref', + 'destination', + 'source_currency', + 'source_amount_minor', + 'transfer_group', + ], label); + if (shapeError) return shapeError; + if (value.schema_version !== STRIPE_TRANSFER_EVIDENCE_VERSION) { + return new Error(`${label} schema version is invalid.`); + } + if (!['stripe_transfer', 'platform_balance'].includes(value.kind)) { + return new Error(`${label} kind is invalid.`); + } + if (!this.isSafeKeyPart(value.ref) || !this.isSafeKeyPart(value.destination)) { + return new Error(`${label} reference or destination is invalid.`); + } + if (value.kind === 'stripe_transfer' && !value.ref.startsWith('tr_')) { + return new Error(`${label} requires a Stripe transfer id.`); + } + if (value.kind === 'platform_balance' && !value.ref.startsWith('platform_balance:')) { + return new Error(`${label} platform balance reference is invalid.`); + } + const sourceCurrency = this.normalizeFiatCurrency(value.source_currency); + if (sourceCurrency instanceof Error || sourceCurrency !== value.source_currency) { + return new Error(`${label} source currency is invalid.`); + } + const sourceAmountMinor = this.normalizeFiatMinor(value.source_amount_minor); + if (sourceAmountMinor instanceof Error || sourceAmountMinor === '0') { + return new Error(`${label} source amount must be positive.`); + } + if (providerTransfer && + (typeof value.transfer_group !== 'string' || !this.isSafeKeyPart(value.transfer_group))) { + return new Error(`${label} transfer group is invalid.`); + } + if (!providerTransfer && value.transfer_group !== null) { + return new Error(`${label} platform balance transfer group must be null.`); + } + if (!providerTransfer) return { + schema_version: STRIPE_TRANSFER_EVIDENCE_VERSION, + kind: 'platform_balance', + ...(hasAttemptId ? { attempt_id: value.attempt_id } : {}), + ref: value.ref, + destination: value.destination, + source_currency: sourceCurrency, + source_amount_minor: sourceAmountMinor, + transfer_group: null, + }; + + const destinationCurrency = this.normalizeFiatCurrency(value.destination_currency); + if (destinationCurrency instanceof Error || + destinationCurrency !== value.destination_currency) { + return new Error(`${label} destination currency is invalid.`); + } + const destinationAmountMinor = this.normalizeFiatMinor(value.destination_amount_minor); + if (destinationAmountMinor instanceof Error || destinationAmountMinor === '0') { + return new Error(`${label} destination amount must be positive.`); + } + const requiresUsdValuationQuote = + sourceCurrency !== 'usd' || destinationCurrency !== 'usd'; + if (requiresUsdValuationQuote && + (!hasFxQuoteId || + !/^fxq_[A-Za-z0-9._-]+$/.test(String(value.fx_quote_id || '')) || + !this.isHexBytes(value.fx_quote_hash, 32) || + value.fx_quote_hash !== value.fx_quote_hash.toLowerCase())) { + return new Error(`${label} FX quote identity is required and invalid.`); + } + if (!requiresUsdValuationQuote && hasFxQuoteId && + (value.fx_quote_id !== null || value.fx_quote_hash !== null)) { + return new Error(`${label} direct USD transfer must not include an FX quote identity.`); + } + if (!/^py_[A-Za-z0-9._-]+$/.test(String(value.destination_payment || ''))) { + return new Error(`${label} destination payment readback is invalid.`); + } + const normalized = { + schema_version: STRIPE_TRANSFER_EVIDENCE_VERSION, + kind: 'stripe_transfer', + ...(hasAttemptId ? { attempt_id: value.attempt_id } : {}), + ref: value.ref, + destination: value.destination, + source_currency: sourceCurrency, + source_amount_minor: sourceAmountMinor, + destination_currency: destinationCurrency, + destination_amount_minor: destinationAmountMinor, + destination_payment: value.destination_payment, + transfer_group: value.transfer_group, + }; + if (hasFxQuoteId) { + normalized.fx_quote_id = value.fx_quote_id; + normalized.fx_quote_hash = value.fx_quote_hash; + } + return normalized; + } + + stripeTransferSeenKey(evidence) { + return evidence.kind === 'stripe_transfer' + ? `rail/seen/stripe/${evidence.ref}` + : `rail/seen/stripe-platform/${evidence.ref}`; + } + + stripeFxQuoteSeenKey(evidence) { + return evidence.kind === 'stripe_transfer' && evidence.fx_quote_id != null + ? `rail/seen/stripe-fx-quote/${evidence.fx_quote_id}` + : null; + } + + stripeDestinationPaymentSeenKey(evidence) { + return evidence.kind === 'stripe_transfer' + ? `rail/seen/stripe-destination-payment/${evidence.destination_payment}` + : null; + } + + normalizeTargetedTapRateLock(value) { + const shapeError = this.validateExactObjectKeys( + value, + [ + 'type', + 'epoch', + 'bundle_sha256', + 'denom', + 'tap_usd_au', + 'source', + 'rate_ts', + 'rate_record_key', + 'posted_by', + 'posted_by_role', + 'chain_id', + 'token_address', + 'pool_address', + 'payment_config_ver', + ], + 'targeted TAP settlement rate lock' + ); + if (shapeError) return shapeError; + const tapUsdAu = this.normalizeAu( + value.tap_usd_au, + 'targeted TAP settlement rate', + { allowZero: false } + ); + if (tapUsdAu instanceof Error || + value.type !== 'tap_settlement_rate_lock' || + value.denom !== 'tap_usd_au' || + !Number.isSafeInteger(value.epoch) || + value.epoch < 1 || + !this.isHexBytes(value.bundle_sha256, 32) || + typeof value.source !== 'string' || + !/^[a-z0-9][a-z0-9._-]{0,63}$/.test(value.source) || + !Number.isSafeInteger(value.rate_ts) || + value.rate_ts < 0 || + typeof value.rate_record_key !== 'string' || + !value.rate_record_key.startsWith(`rate/tap/${value.rate_ts}/`) || + !this.isHexBytes(value.rate_record_key.slice(`rate/tap/${value.rate_ts}/`.length), 32) || + !this.isHexBytes(value.posted_by, 32) || + value.posted_by_role !== 'admin' || + !Number.isSafeInteger(value.chain_id) || + value.chain_id < 1 || + !this.isEthHexBytes(value.token_address, 20) || + value.token_address !== value.token_address.toLowerCase() || + !this.isEthHexBytes(value.pool_address, 20) || + value.pool_address !== value.pool_address.toLowerCase() || + !Number.isSafeInteger(value.payment_config_ver) || + value.payment_config_ver < 1) { + return new Error('Invalid targeted TAP settlement rate lock.'); + } + return { + type: value.type, + epoch: value.epoch, + bundle_sha256: value.bundle_sha256.toLowerCase(), + denom: value.denom, + tap_usd_au: tapUsdAu, + source: value.source, + rate_ts: value.rate_ts, + rate_record_key: value.rate_record_key, + posted_by: value.posted_by.toLowerCase(), + posted_by_role: value.posted_by_role, + chain_id: value.chain_id, + token_address: value.token_address.toLowerCase(), + pool_address: value.pool_address.toLowerCase(), + payment_config_ver: value.payment_config_ver, + }; + } + + normalizeTargetedTapSettlementEntry(value) { + const shapeError = this.validateExactObjectKeys( + value, + ['account', 'cumulative_wei'], + 'targeted TAP settlement distribution entry' + ); + if (shapeError) return shapeError; + if (!this.isEthHexBytes(value.account, 20) || + value.account !== value.account.toLowerCase()) { + return new Error('Invalid targeted TAP settlement distribution account.'); + } + const cumulativeWei = this.parseTapWei(value.cumulative_wei); + if (cumulativeWei instanceof Error) { + return new Error('Invalid targeted TAP settlement cumulative claim.'); + } + return { + account: value.account, + cumulative_wei: cumulativeWei.toString(), + }; + } + + normalizeTargetedTapSettlementOutput(value, tapUsdAu) { + const shapeError = this.validateExactObjectKeys( + value, + [ + 'provider', + 'payout_revision', + 'to', + 'paid_cum_au_before', + 'aggregate_paid_cum_au_before', + 'paid_au', + 'tap_wei', + 'prior_cumulative_claim_wei', + 'cumulative_claim_wei', + ], + 'targeted TAP provider output' + ); + if (shapeError) return shapeError; + const paidAu = this.normalizeAu( + value.paid_au, + 'targeted TAP provider paid amount', + { allowZero: false } + ); + const paidCumAuBefore = this.normalizeAu( + value.paid_cum_au_before, + 'targeted TAP provider paid cumulative watermark', + { allowZero: true } + ); + const aggregatePaidCumAuBefore = this.normalizeAu( + value.aggregate_paid_cum_au_before, + 'targeted TAP provider aggregate paid cumulative watermark', + { allowZero: true } + ); + const tapWei = this.parseTapWei(value.tap_wei); + const priorCumulativeClaimWei = this.parseTapWei( + value.prior_cumulative_claim_wei, + { allowZero: true } + ); + const cumulativeClaimWei = this.parseTapWei(value.cumulative_claim_wei); + if (!this.isHexBytes(value.provider, 32) || + value.provider !== value.provider.toLowerCase() || + !this.isHexBytes(value.payout_revision, 32) || + value.payout_revision !== value.payout_revision.toLowerCase() || + !this.isEthHexBytes(value.to, 20) || + value.to !== value.to.toLowerCase() || + paidAu instanceof Error || + paidCumAuBefore instanceof Error || + aggregatePaidCumAuBefore instanceof Error || + tapWei instanceof Error || + priorCumulativeClaimWei instanceof Error || + cumulativeClaimWei instanceof Error) { + return new Error('Invalid targeted TAP provider output.'); + } + const rate = this.parseAu( + tapUsdAu, + 'targeted TAP settlement rate', + { allowZero: false } + ); + if (rate instanceof Error) return rate; + const expectedTapWei = (this.parseAu(paidAu, 'targeted TAP provider paid amount') * TAP_WEI) / rate; + if (expectedTapWei <= 0n || tapWei !== expectedTapWei) { + return new Error('Targeted TAP provider output does not match the locked rate.'); + } + if (cumulativeClaimWei !== priorCumulativeClaimWei + tapWei) { + return new Error('Targeted TAP provider output cumulative claim chain is invalid.'); + } + return { + provider: value.provider, + payout_revision: value.payout_revision, + to: value.to, + paid_cum_au_before: paidCumAuBefore, + aggregate_paid_cum_au_before: aggregatePaidCumAuBefore, + paid_au: paidAu, + tap_wei: tapWei.toString(), + prior_cumulative_claim_wei: priorCumulativeClaimWei.toString(), + cumulative_claim_wei: cumulativeClaimWei.toString(), + }; + } + + targetedTapSettlementLeaf(entry) { + const encoded = b4a.alloc(64); + encoded.set(b4a.from(entry.account.slice(2), 'hex'), 12); + const amount = BigInt(entry.cumulative_wei); + if (amount < 0n || amount >= (1n << 256n)) { + return new Error('Targeted TAP settlement cumulative claim exceeds uint256.'); + } + const amountHex = amount.toString(16).padStart(64, '0'); + encoded.set(b4a.from(amountHex, 'hex'), 32); + return keccak256(keccak256(encoded)); + } + + targetedTapSettlementRoot(entries) { + if (!Array.isArray(entries) || entries.length === 0) { + return new Error('Targeted TAP settlement requires distribution entries.'); + } + const leaves = []; + for (const entry of entries) { + const leaf = this.targetedTapSettlementLeaf(entry); + if (leaf instanceof Error) return leaf; + leaves.push(b4a.toString(leaf, 'hex')); + } + leaves.sort(compareCodepoint); + const tree = new Array(2 * leaves.length - 1); + for (const [index, leaf] of leaves.entries()) { + tree[tree.length - 1 - index] = leaf; + } + for (let index = tree.length - 1 - leaves.length; index >= 0; index -= 1) { + const left = tree[2 * index + 1]; + const right = tree[2 * index + 2]; + const [first, second] = compareCodepoint(left, right) <= 0 + ? [left, right] + : [right, left]; + tree[index] = b4a.toString( + keccak256(b4a.concat([b4a.from(first, 'hex'), b4a.from(second, 'hex')])), + 'hex' + ); + } + return `0x${tree[0]}`; + } + + normalizeTargetedTapSettlementValue(value) { + const shapeError = this.validateExactObjectKeys( + value, + [ + 'op', + 'epoch', + 'at', + 'rail', + 'chain_id', + 'token_address', + 'pool_address', + 'payment_config_ver', + 'epoch_apply_hash', + 'preparation_ids', + 'root_preparation_id', + 'external_effect_ids', + 'tap_rate_lock', + 'root', + 'root_confirmed', + 'proposal_tx', + 'proposal_block_number', + 'proposal_block_hash', + 'execution_tx', + 'execution_status', + 'execution_block_number', + 'execution_block_hash', + 'finalized_block_number', + 'confirmation_depth', + 'confirmation_policy', + 'cumulative_spent_wei', + 'provider_cumulative_claimed_wei', + 'buyer_refund_wei', + 'fee_bps', + 'tap_burn_bps', + 'provider_share_bps', + 'provider_count', + 'provider_paid_au', + 'provider_tap_wei', + 'provider_entries', + 'refunds', + 'entries', + 'outputs', + ], + 'targeted TAP settlement' + ); + if (shapeError) return shapeError; + const rateLock = this.normalizeTargetedTapRateLock(value.tap_rate_lock); + if (rateLock instanceof Error) return rateLock; + if (value.op !== 'settle_targeted_tap' || + value.rail !== 'tap' || + !Number.isSafeInteger(value.epoch) || + value.epoch < 1 || + !Number.isSafeInteger(value.at) || + value.at < 0 || + !Number.isSafeInteger(value.chain_id) || + value.chain_id < 1 || + !this.isEthHexBytes(value.token_address, 20) || + value.token_address !== value.token_address.toLowerCase() || + !this.isEthHexBytes(value.pool_address, 20) || + value.pool_address !== value.pool_address.toLowerCase() || + !Number.isSafeInteger(value.payment_config_ver) || + value.payment_config_ver < 1 || + !this.isHexBytes(value.epoch_apply_hash, 32) || + !Array.isArray(value.preparation_ids) || + value.preparation_ids.length < 1 || + !this.isHexBytes(value.root_preparation_id, 32) || + !Array.isArray(value.external_effect_ids) || + value.external_effect_ids.length !== 2 || + !this.isEthHexBytes(value.root, 32) || + value.root !== value.root.toLowerCase() || + value.root_confirmed !== true || + !this.isEthHexBytes(value.proposal_tx, 32) || + value.proposal_tx !== value.proposal_tx.toLowerCase() || + !Number.isSafeInteger(value.proposal_block_number) || + value.proposal_block_number < 0 || + !this.isEthHexBytes(value.proposal_block_hash, 32) || + value.proposal_block_hash !== value.proposal_block_hash.toLowerCase() || + !this.isEthHexBytes(value.execution_tx, 32) || + value.execution_tx !== value.execution_tx.toLowerCase() || + value.execution_status !== 1 || + !Number.isSafeInteger(value.execution_block_number) || + value.execution_block_number < value.proposal_block_number || + !this.isEthHexBytes(value.execution_block_hash, 32) || + value.execution_block_hash !== value.execution_block_hash.toLowerCase() || + !Number.isSafeInteger(value.finalized_block_number) || + value.finalized_block_number < value.execution_block_number || + !Number.isSafeInteger(value.confirmation_depth) || + value.confirmation_depth !== value.finalized_block_number - value.execution_block_number || + value.confirmation_depth < MIN_TAP_CONFIRMATION_DEPTH || + !this.isSafeKeyPart(value.confirmation_policy) || + (value.chain_id === 1 && value.confirmation_policy !== 'finalized-tag') || + (value.confirmation_policy !== 'finalized-tag' && + value.confirmation_policy !== `depth-${value.confirmation_depth}`) || + value.proposal_tx === value.execution_tx || + !Number.isSafeInteger(value.provider_count) || + value.provider_count < 1 || + !Number.isSafeInteger(value.fee_bps) || + value.fee_bps < 0 || + !Number.isSafeInteger(value.tap_burn_bps) || + value.tap_burn_bps !== TAP_BURN_BPS || + !Number.isSafeInteger(value.provider_share_bps) || + value.provider_share_bps <= 0 || + value.fee_bps + value.tap_burn_bps + value.provider_share_bps !== 10_000 || + !Array.isArray(value.entries) || + value.entries.length < 1 || + !Array.isArray(value.provider_entries) || + value.provider_entries.length < 1 || + !Array.isArray(value.refunds) || + !Array.isArray(value.outputs) || + value.outputs.length < 1) { + return new Error('Invalid targeted TAP settlement.'); + } + if (value.preparation_ids.some((entry) => !this.isHexBytes(entry, 32)) || + new Set(value.preparation_ids).size !== value.preparation_ids.length || + value.external_effect_ids.some((entry) => !this.isHexBytes(entry, 32)) || + new Set(value.external_effect_ids).size !== 2) { + return new Error('Invalid targeted TAP settlement preparation identities.'); + } + if (rateLock.epoch !== value.epoch || + rateLock.chain_id !== value.chain_id || + rateLock.token_address !== value.token_address || + rateLock.pool_address !== value.pool_address || + rateLock.payment_config_ver !== value.payment_config_ver) { + return new Error('Targeted TAP settlement rate lock scope mismatch.'); + } + const cumulativeSpentWei = this.parseTapWei(value.cumulative_spent_wei); + const providerCumulativeClaimedWei = this.parseTapWei( + value.provider_cumulative_claimed_wei + ); + const buyerRefundWei = this.parseTapWei(value.buyer_refund_wei, { + allowZero: true, + }); + const providerPaidAu = this.normalizeAu( + value.provider_paid_au, + 'targeted TAP provider paid total', + { allowZero: false } + ); + const providerTapWei = this.parseTapWei(value.provider_tap_wei); + if (cumulativeSpentWei instanceof Error || + providerCumulativeClaimedWei instanceof Error || + buyerRefundWei instanceof Error || + providerPaidAu instanceof Error || + providerTapWei instanceof Error) { + return new Error('Invalid targeted TAP settlement totals.'); + } + const entries = []; + const seenAccounts = new Set(); + for (const rawEntry of value.entries) { + const entry = this.normalizeTargetedTapSettlementEntry(rawEntry); + if (entry instanceof Error) return entry; + if (seenAccounts.has(entry.account)) { + return new Error('Duplicate targeted TAP settlement distribution account.'); + } + seenAccounts.add(entry.account); + entries.push(entry); + } + entries.sort((left, right) => compareCodepoint(left.account, right.account)); + if (stableJson(entries) !== stableJson(value.entries)) { + return new Error('Targeted TAP settlement distribution entries must be canonical.'); + } + const normalizeClaimEntries = (rawEntries, label) => { + const normalizedEntries = []; + const seen = new Set(); + for (const rawEntry of rawEntries) { + const entry = this.normalizeTargetedTapSettlementEntry(rawEntry); + if (entry instanceof Error) return entry; + if (seen.has(entry.account)) { + return new Error(`Duplicate targeted TAP ${label} account.`); + } + seen.add(entry.account); + normalizedEntries.push(entry); + } + normalizedEntries.sort((left, right) => compareCodepoint(left.account, right.account)); + if (stableJson(normalizedEntries) !== stableJson(rawEntries)) { + return new Error(`Targeted TAP ${label} entries must be canonical.`); + } + return normalizedEntries; + }; + const providerEntries = normalizeClaimEntries( + value.provider_entries, + 'provider claim' + ); + if (providerEntries instanceof Error) return providerEntries; + const refunds = normalizeClaimEntries(value.refunds, 'buyer refund'); + if (refunds instanceof Error) return refunds; + const providerEntryTotal = providerEntries.reduce( + (sum, entry) => sum + BigInt(entry.cumulative_wei), + 0n + ); + const refundTotal = refunds.reduce( + (sum, entry) => sum + BigInt(entry.cumulative_wei), + 0n + ); + if (providerEntryTotal !== providerCumulativeClaimedWei || + refundTotal !== buyerRefundWei) { + return new Error('Targeted TAP provider/refund totals do not match claim entries.'); + } + const combinedClaims = new Map(); + for (const entry of [...providerEntries, ...refunds]) { + combinedClaims.set( + entry.account, + (combinedClaims.get(entry.account) ?? 0n) + BigInt(entry.cumulative_wei) + ); + } + const combinedEntries = [...combinedClaims.entries()] + .map(([account, cumulativeWei]) => ({ + account, + cumulative_wei: cumulativeWei.toString(), + })) + .sort((left, right) => compareCodepoint(left.account, right.account)); + if (stableJson(combinedEntries) !== stableJson(entries)) { + return new Error('Targeted TAP executed entries do not merge provider and refund claims.'); + } + const outputs = []; + const seenLiabilities = new Set(); + for (const rawOutput of value.outputs) { + const output = this.normalizeTargetedTapSettlementOutput( + rawOutput, + rateLock.tap_usd_au + ); + if (output instanceof Error) return output; + const identity = `${output.provider}/${output.payout_revision}`; + if (seenLiabilities.has(identity)) { + return new Error('Duplicate targeted TAP settlement payout liability.'); + } + seenLiabilities.add(identity); + outputs.push(output); + } + outputs.sort((left, right) => ( + compareCodepoint(left.provider, right.provider) || + compareCodepoint(left.payout_revision, right.payout_revision) + )); + if (stableJson(outputs) !== stableJson(value.outputs)) { + return new Error('Targeted TAP settlement outputs must be canonical.'); + } + const paidTotal = outputs.reduce( + (sum, output) => sum + this.parseAu(output.paid_au, 'targeted TAP paid output'), + 0n + ); + const tapTotal = outputs.reduce( + (sum, output) => sum + BigInt(output.tap_wei), + 0n + ); + if (value.provider_count !== outputs.length || + paidTotal.toString() !== providerPaidAu || + tapTotal !== providerTapWei) { + return new Error('Targeted TAP settlement totals do not match outputs.'); + } + const entryMap = new Map(providerEntries.map((entry) => [entry.account, entry])); + const targetCursors = new Map(); + for (const output of outputs) { + const prior = targetCursors.get(output.to); + if (prior !== undefined && + prior !== output.prior_cumulative_claim_wei) { + return new Error('Targeted TAP outputs do not form a canonical per-target claim chain.'); + } + targetCursors.set(output.to, output.cumulative_claim_wei); + } + for (const [target, cumulativeClaimWei] of targetCursors) { + if (entryMap.get(target)?.cumulative_wei !== cumulativeClaimWei) { + return new Error('Targeted TAP output claim chain does not match the executed root entry.'); + } + } + const root = this.targetedTapSettlementRoot(entries); + if (root instanceof Error || root !== value.root) { + return new Error('Targeted TAP settlement root mismatch.'); + } + return { + rate_lock: rateLock, + entries, + provider_entries: providerEntries, + refunds, + outputs, + cumulative_spent_wei: cumulativeSpentWei.toString(), + provider_cumulative_claimed_wei: providerCumulativeClaimedWei.toString(), + buyer_refund_wei: buyerRefundWei.toString(), + fee_bps: value.fee_bps, + tap_burn_bps: value.tap_burn_bps, + provider_share_bps: value.provider_share_bps, + provider_paid_au: providerPaidAu, + provider_tap_wei: providerTapWei.toString(), + }; + } + + normalizeTargetedTnkSettlementValue(value) { + const shapeError = this.validateExactObjectKeys( + value, + [ + 'op', + 'epoch', + 'at', + 'rail', + 'network', + 'treasury_from', + 'operator_to', + 'epoch_apply_hash', + 'preparation_ids', + 'external_effect_ids', + 'rate_tnk_usd_au', + 'rate_source', + 'rate_ts', + 'msb_transfers', + 'transfer_root', + 'provider_count', + 'provider_au', + 'operator_fee_au', + 'gross_au', + 'tnk_e18', + 'outputs', + ], + 'targeted TNK settlement' + ); + if (shapeError) return shapeError; + if (value.op !== 'settle_targeted_tnk' || value.rail !== 'tnk' || + !Number.isSafeInteger(value.epoch) || value.epoch < 1 || + !Number.isSafeInteger(value.at) || value.at < 0 || + !this.isSafeKeyPart(value.network) || + !this.isSafeKeyPart(value.treasury_from) || + !this.isSafeKeyPart(value.operator_to) || + !this.isHexBytes(value.epoch_apply_hash, 32) || + !Array.isArray(value.preparation_ids) || + !Array.isArray(value.external_effect_ids) || + value.preparation_ids.length !== value.outputs?.length || + value.external_effect_ids.length !== value.outputs?.length || + !this.isHexBytes(value.transfer_root, 32) || + !Number.isSafeInteger(value.provider_count) || value.provider_count < 0 || + !Number.isSafeInteger(value.rate_ts) || value.rate_ts < 0 || + !RATE_SOURCES.has(value.rate_source) || + !Array.isArray(value.outputs) || value.outputs.length === 0 || + !Array.isArray(value.msb_transfers) || + value.msb_transfers.length !== value.outputs.length) { + return new Error('Invalid targeted TNK settlement.'); + } + if (value.preparation_ids.some((entry) => !this.isHexBytes(entry, 32)) || + new Set(value.preparation_ids).size !== value.preparation_ids.length || + value.external_effect_ids.some((entry) => !this.isHexBytes(entry, 32)) || + new Set(value.external_effect_ids).size !== value.external_effect_ids.length) { + return new Error('Invalid targeted TNK settlement preparation identities.'); + } + if (this.normalizeAu( + value.rate_tnk_usd_au, + 'targeted TNK settlement rate', + { allowZero: false } + ) instanceof Error || this.parseTnkE18(value.tnk_e18) instanceof Error) { + return new Error('Invalid targeted TNK settlement amount or rate.'); + } + for (const field of ['provider_au', 'operator_fee_au', 'gross_au']) { + if (this.normalizeAu( + value[field], + `targeted TNK settlement ${field}`, + { allowZero: field !== 'gross_au' } + ) instanceof Error) { + return new Error('Invalid targeted TNK settlement total.'); + } + } + const gross = this.safeAddAu(value.provider_au, value.operator_fee_au); + if (gross instanceof Error || this.compareAu(gross, value.gross_au) !== 0) { + return new Error('Targeted TNK settlement gross amount does not balance.'); + } + const seenTransfers = new Set(); + for (const entry of value.msb_transfers) { + const transfer = this.normalizeMsbTransferEvidence( + entry, + 'targeted TNK settlement transfer' + ); + if (transfer instanceof Error) return transfer; + if (seenTransfers.has(transfer.tx_hash)) { + return new Error('Duplicate targeted TNK settlement transfer.'); + } + seenTransfers.add(transfer.tx_hash); + } + const outputs = []; + const seen = new Set(); + let operatorSeen = false; + for (const output of value.outputs) { + if (output?.role === 'provider') { + const shapeError = this.validateExactObjectKeys( + output, + [ + 'role', + 'provider', + 'payout_revision', + 'to', + 'paid_cum_au_before', + 'aggregate_paid_cum_au_before', + 'au', + 'tnk_e18', + ], + 'targeted TNK provider output' + ); + if (shapeError) return shapeError; + if (!this.isHexBytes(output.payout_revision, 32) || + output.payout_revision !== output.payout_revision.toLowerCase()) { + return new Error('Invalid targeted TNK payout revision.'); + } + const identity = stableJson([output.provider, output.payout_revision]); + if (seen.has(identity)) return new Error('Duplicate targeted TNK payout liability.'); + seen.add(identity); + const normalized = this.normalizeTargetedTnkSettlementOutput({ + role: output.role, + provider: output.provider, + to: output.to, + au: output.au, + tnk_e18: output.tnk_e18, + }); + if (normalized instanceof Error) return normalized; + const paidCumAuBefore = this.normalizeAu( + output.paid_cum_au_before, + 'targeted TNK provider paid cumulative watermark', + { allowZero: true } + ); + if (paidCumAuBefore instanceof Error) return paidCumAuBefore; + const aggregatePaidCumAuBefore = this.normalizeAu( + output.aggregate_paid_cum_au_before, + 'targeted TNK provider aggregate paid cumulative watermark', + { allowZero: true } + ); + if (aggregatePaidCumAuBefore instanceof Error) { + return aggregatePaidCumAuBefore; + } + outputs.push({ + ...normalized, + payout_revision: output.payout_revision, + paid_cum_au_before: paidCumAuBefore, + aggregate_paid_cum_au_before: aggregatePaidCumAuBefore, + }); + } else if (output?.role === 'operator_fee') { + const shapeError = this.validateExactObjectKeys( + output, + ['role', 'to', 'au', 'tnk_e18'], + 'targeted TNK operator output' + ); + if (shapeError) return shapeError; + if (operatorSeen) return new Error('Duplicate targeted TNK operator output.'); + operatorSeen = true; + const normalized = this.normalizeTargetedTnkSettlementOutput(output); + if (normalized instanceof Error) return normalized; + outputs.push(normalized); + } else { + return new Error('Invalid targeted TNK settlement output role.'); + } + } + outputs.sort((left, right) => { + if (left.role !== right.role) return left.role === 'provider' ? -1 : 1; + if (left.role === 'operator_fee') return 0; + return compareCodepoint(left.provider, right.provider) || + compareCodepoint(left.payout_revision, right.payout_revision); + }); + if (stableJson(outputs) !== stableJson(value.outputs)) { + return new Error('Targeted TNK settlement outputs must be canonical.'); + } + return { outputs }; + } + + normalizeTargetedFiatSettlementValue(value) { + const shapeError = this.validateExactObjectKeys( + value, + [ + 'op', + 'epoch', + 'at', + 'rail', + 'processor', + 'source_currency', + 'operator_to', + 'epoch_apply_hash', + 'preparation_ids', + 'external_effect_ids', + 'stripe_transfers', + 'transfer_root', + 'provider_count', + 'provider_liability_au', + 'provider_paid_au', + 'operator_fee_liability_au', + 'operator_fee_retained_au', + 'gross_liability_au', + 'gross_paid_au', + 'rounding_au', + 'dust_au', + 'source_amount_minor', + 'destination_totals', + 'outputs', + ], + 'targeted fiat settlement' + ); + if (shapeError) return shapeError; + if (value.op !== 'settle_targeted_fiat' || + value.rail !== 'fiat' || + value.processor !== 'stripe' || + !Number.isSafeInteger(value.epoch) || value.epoch < 1 || + !Number.isSafeInteger(value.at) || value.at < 0 || + !this.isSafeKeyPart(value.operator_to) || + !this.isHexBytes(value.epoch_apply_hash, 32) || + !Array.isArray(value.preparation_ids) || + !Array.isArray(value.external_effect_ids) || + value.preparation_ids.length !== value.outputs?.length || + value.external_effect_ids.length !== value.outputs?.length || + !this.isHexBytes(value.transfer_root, 32) || + !Number.isSafeInteger(value.provider_count) || value.provider_count < 0 || + !Array.isArray(value.outputs) || value.outputs.length === 0 || + !Array.isArray(value.destination_totals) || + !Array.isArray(value.stripe_transfers) || + value.stripe_transfers.length !== value.outputs.length) { + return new Error('Invalid targeted fiat settlement.'); + } + if (value.preparation_ids.some((entry) => !this.isHexBytes(entry, 32)) || + new Set(value.preparation_ids).size !== value.preparation_ids.length || + value.external_effect_ids.some((entry) => !this.isHexBytes(entry, 32)) || + new Set(value.external_effect_ids).size !== value.external_effect_ids.length) { + return new Error('Invalid targeted fiat settlement preparation identities.'); + } + const sourceCurrency = this.normalizeFiatCurrency(value.source_currency); + if (sourceCurrency instanceof Error || sourceCurrency !== value.source_currency) { + return new Error('Invalid targeted fiat settlement source currency.'); + } + const sourceAmountMinor = this.normalizeFiatMinor(value.source_amount_minor); + if (sourceAmountMinor instanceof Error || sourceAmountMinor === '0' || + sourceAmountMinor !== value.source_amount_minor) { + return new Error('Invalid targeted fiat settlement source amount.'); + } + const auFields = [ + 'provider_liability_au', + 'provider_paid_au', + 'operator_fee_liability_au', + 'operator_fee_retained_au', + 'gross_liability_au', + 'gross_paid_au', + 'rounding_au', + 'dust_au', + ]; + for (const field of auFields) { + if (this.normalizeAu( + value[field], + `targeted fiat settlement ${field}`, + { allowZero: !['gross_liability_au', 'gross_paid_au'].includes(field) } + ) instanceof Error) { + return new Error('Invalid targeted fiat settlement total.'); + } + } + const grossLiability = this.safeAddAu( + value.provider_liability_au, + value.operator_fee_liability_au + ); + const grossPaid = this.safeAddAu(value.provider_paid_au, value.operator_fee_retained_au); + const paidPlusDust = this.safeAddAu(value.gross_paid_au, value.dust_au); + if (grossLiability instanceof Error || grossPaid instanceof Error || paidPlusDust instanceof Error || + this.compareAu(grossLiability, value.gross_liability_au) !== 0 || + this.compareAu(grossPaid, value.gross_paid_au) !== 0 || + this.compareAu(paidPlusDust, value.gross_liability_au) !== 0 || + this.compareAu(value.rounding_au, value.dust_au) !== 0) { + return new Error('Targeted fiat settlement canonical AU totals do not balance.'); + } + const seenTransfers = new Set(); + const seenQuotes = new Set(); + const seenDestinationPayments = new Set(); + for (const entry of value.stripe_transfers) { + const transfer = this.normalizeStripeTransferEvidence( + entry, + 'targeted fiat settlement transfer' + ); + if (transfer instanceof Error) return transfer; + if (seenTransfers.has(transfer.ref)) { + return new Error('Duplicate targeted fiat settlement transfer.'); + } + seenTransfers.add(transfer.ref); + if (transfer.kind === 'stripe_transfer') { + if (transfer.fx_quote_id != null) { + if (seenQuotes.has(transfer.fx_quote_id)) { + return new Error('Duplicate targeted fiat settlement FX quote.'); + } + seenQuotes.add(transfer.fx_quote_id); + } + if (seenDestinationPayments.has(transfer.destination_payment)) { + return new Error('Duplicate targeted fiat settlement destination payment.'); + } + seenDestinationPayments.add(transfer.destination_payment); + } + } + const outputs = []; + const seen = new Set(); + let operatorSeen = false; + for (const output of value.outputs) { + if (output?.role === 'provider') { + const hasFxQuoteId = hasOwn(output, 'fx_quote_id'); + const hasFxQuoteHash = hasOwn(output, 'fx_quote_hash'); + if (hasFxQuoteId !== hasFxQuoteHash) { + return new Error( + 'Targeted fiat provider output FX quote identity must be present or absent as a pair.' + ); + } + const shapeError = this.validateExactObjectKeys( + output, + [ + 'role', + 'provider', + 'payout_revision', + 'to', + 'paid_cum_au_before', + 'aggregate_paid_cum_au_before', + 'liability_au', + 'paid_au', + 'rounding_au', + 'dust_au', + 'source_currency', + 'source_amount_minor', + 'destination_currency', + 'destination_amount_min_minor', + 'destination_amount_max_minor', + 'destination_amount_minor', + ...(hasFxQuoteId ? ['fx_quote_id', 'fx_quote_hash'] : []), + ], + 'targeted fiat provider output' + ); + if (shapeError) return shapeError; + if (!this.isHexBytes(output.payout_revision, 32) || + output.payout_revision !== output.payout_revision.toLowerCase()) { + return new Error('Invalid targeted fiat payout revision.'); + } + const identity = stableJson([output.provider, output.payout_revision]); + if (seen.has(identity)) return new Error('Duplicate targeted fiat payout liability.'); + seen.add(identity); + const { payout_revision: payoutRevision, ...settlementOutput } = output; + const paidCumAuBefore = this.normalizeAu( + settlementOutput.paid_cum_au_before, + 'targeted fiat provider paid cumulative watermark', + { allowZero: true } + ); + if (paidCumAuBefore instanceof Error) return paidCumAuBefore; + const aggregatePaidCumAuBefore = this.normalizeAu( + settlementOutput.aggregate_paid_cum_au_before, + 'targeted fiat provider aggregate paid cumulative watermark', + { allowZero: true } + ); + if (aggregatePaidCumAuBefore instanceof Error) { + return aggregatePaidCumAuBefore; + } + delete settlementOutput.paid_cum_au_before; + delete settlementOutput.aggregate_paid_cum_au_before; + const normalized = this.normalizeTargetedFiatSettlementOutput(settlementOutput); + if (normalized instanceof Error) return normalized; + outputs.push({ + ...normalized, + payout_revision: payoutRevision, + paid_cum_au_before: paidCumAuBefore, + aggregate_paid_cum_au_before: aggregatePaidCumAuBefore, + }); + } else if (output?.role === 'operator_fee') { + const shapeError = this.validateExactObjectKeys( + output, + [ + 'role', + 'to', + 'liability_au', + 'paid_au', + 'rounding_au', + 'dust_au', + 'source_currency', + 'source_amount_minor', + ], + 'targeted fiat operator output' + ); + if (shapeError) return shapeError; + if (operatorSeen) return new Error('Duplicate targeted fiat operator output.'); + operatorSeen = true; + const normalized = this.normalizeTargetedFiatSettlementOutput(output); + if (normalized instanceof Error) return normalized; + outputs.push(normalized); + } else { + return new Error('Invalid targeted fiat settlement output role.'); + } + } + outputs.sort((left, right) => { + if (left.role !== right.role) return left.role === 'provider' ? -1 : 1; + if (left.role === 'operator_fee') return 0; + return compareCodepoint(left.provider, right.provider) || + compareCodepoint(left.payout_revision, right.payout_revision); + }); + if (stableJson(outputs) !== stableJson(value.outputs)) { + return new Error('Targeted fiat settlement outputs must be canonical.'); + } + const destinationTotals = this.normalizeFiatDestinationTotals(value.destination_totals); + if (destinationTotals instanceof Error) return destinationTotals; + return { outputs, destination_totals: destinationTotals }; + } + + async targetedTnkSettlementTransferRoot(outputs) { + return await this.opaqueHash( + 'mayhem-targeted-tnk-settlement-transfer-root-v1', + outputs + ); + } + + async targetedFiatSettlementTransferRoot(outputs) { + return await this.opaqueHash( + 'mayhem-targeted-fiat-settlement-transfer-root-v2', + outputs + ); + } + + payoutPreparationLiabilityForOutput(value, output, rail) { + if (output.role === 'operator_fee') return null; + return { + provider: output.provider, + payout_revision: output.payout_revision, + target: output.to, + currency: rail === 'fiat' ? output.destination_currency : null, + chain_id: rail === 'tap' ? value.chain_id : null, + paid_cum_au_before: output.paid_cum_au_before, + aggregate_paid_cum_au_before: output.aggregate_paid_cum_au_before, + liability_au: rail === 'fiat' ? output.liability_au : (output.au ?? output.paid_au), + paid_au: rail === 'fiat' ? output.paid_au : (output.au ?? output.paid_au), + }; + } + + targetedFiatPreparationOutputProjection(output) { + if (output.role === 'operator_fee') { + return stableValue({ + role: output.role, + to: output.to, + liability_au: output.liability_au, + paid_au: output.paid_au, + rounding_au: output.rounding_au, + dust_au: output.dust_au, + source_currency: output.source_currency, + source_amount_minor: output.source_amount_minor, + }); + } + const projection = { + role: output.role, + provider: output.provider, + payout_revision: output.payout_revision, + to: output.to, + paid_cum_au_before: output.paid_cum_au_before, + aggregate_paid_cum_au_before: output.aggregate_paid_cum_au_before, + liability_au: output.liability_au, + paid_au: output.paid_au, + rounding_au: output.rounding_au, + dust_au: output.dust_au, + source_currency: output.source_currency, + source_amount_minor: output.source_amount_minor, + destination_currency: output.destination_currency, + destination_amount_min_minor: output.destination_amount_min_minor, + destination_amount_max_minor: output.destination_amount_max_minor, + }; + return stableValue(projection); + } + + normalizeTargetedFiatPreparationOutput(output) { + if (output?.role === 'operator_fee') { + const shapeError = this.validateExactObjectKeys( + output, + [ + 'role', + 'to', + 'liability_au', + 'paid_au', + 'rounding_au', + 'dust_au', + 'source_currency', + 'source_amount_minor', + ], + 'targeted fiat preparation operator output' + ); + if (shapeError) return shapeError; + const normalized = this.normalizeTargetedFiatSettlementOutput(output); + if (normalized instanceof Error) return normalized; + if (this.compareAu(normalized.paid_au, normalized.liability_au) !== 0 || + !this.isZeroAu(normalized.rounding_au) || + !this.isZeroAu(normalized.dust_au)) { + return new Error( + 'Targeted fiat operator fee must retain its exact AU liability with zero dust.' + ); + } + return stableJson(normalized) === stableJson(output) + ? normalized + : new Error('Targeted fiat preparation operator output must be canonical.'); + } + if (output?.role !== 'provider') { + return new Error('Invalid targeted fiat preparation output role.'); + } + const shapeError = this.validateExactObjectKeys( + output, + [ + 'role', + 'provider', + 'payout_revision', + 'to', + 'paid_cum_au_before', + 'aggregate_paid_cum_au_before', + 'liability_au', + 'paid_au', + 'rounding_au', + 'dust_au', + 'source_currency', + 'source_amount_minor', + 'destination_currency', + 'destination_amount_min_minor', + 'destination_amount_max_minor', + ], + 'targeted fiat preparation provider output' + ); + if (shapeError) return shapeError; + if (!this.isHexBytes(output.payout_revision, 32) || + output.payout_revision !== output.payout_revision.toLowerCase()) { + return new Error('Invalid targeted fiat preparation payout revision.'); + } + const paidCumAuBefore = this.normalizeAu( + output.paid_cum_au_before, + 'targeted fiat preparation paid cumulative watermark', + { allowZero: true } + ); + const aggregatePaidCumAuBefore = this.normalizeAu( + output.aggregate_paid_cum_au_before, + 'targeted fiat preparation aggregate paid cumulative watermark', + { allowZero: true } + ); + if (paidCumAuBefore instanceof Error || + aggregatePaidCumAuBefore instanceof Error) { + return new Error('Invalid targeted fiat preparation cumulative watermark.'); + } + if (!this.isHexBytes(output.provider, 32) || + output.provider !== output.provider.toLowerCase() || + !this.isSafeKeyPart(output.to)) { + return new Error('Invalid targeted fiat preparation provider identity.'); + } + const sourceCurrency = this.normalizeFiatCurrency(output.source_currency); + const destinationCurrency = this.normalizeFiatCurrency(output.destination_currency); + const sourceAmountMinor = this.normalizeFiatMinor(output.source_amount_minor); + const destinationMin = this.normalizeFiatMinor(output.destination_amount_min_minor); + const destinationMax = this.normalizeFiatMinor(output.destination_amount_max_minor); + const canonicalAu = {}; + for (const [field, allowZero] of [ + ['liability_au', false], + ['paid_au', false], + ['rounding_au', true], + ['dust_au', true], + ]) { + canonicalAu[field] = this.normalizeAu( + output[field], + `targeted fiat preparation ${field}`, + { allowZero } + ); + if (canonicalAu[field] instanceof Error) { + return new Error(`Invalid targeted fiat preparation ${field}.`); + } + } + const paidPlusDust = this.safeAddAu(canonicalAu.paid_au, canonicalAu.dust_au); + if (sourceCurrency instanceof Error || + sourceCurrency !== output.source_currency || + destinationCurrency instanceof Error || + destinationCurrency !== output.destination_currency || + sourceAmountMinor instanceof Error || + destinationMin instanceof Error || + destinationMax instanceof Error || + BigInt(destinationMax) < BigInt(destinationMin) || + paidPlusDust instanceof Error || + paidPlusDust !== canonicalAu.liability_au || + canonicalAu.rounding_au !== canonicalAu.dust_au) { + return new Error('Invalid targeted fiat preparation economic terms.'); + } + const projection = { + role: 'provider', + provider: output.provider, + to: output.to, + ...canonicalAu, + source_currency: sourceCurrency, + source_amount_minor: sourceAmountMinor, + destination_currency: destinationCurrency, + destination_amount_min_minor: destinationMin, + destination_amount_max_minor: destinationMax, + payout_revision: output.payout_revision, + paid_cum_au_before: paidCumAuBefore, + aggregate_paid_cum_au_before: aggregatePaidCumAuBefore, + }; + return stableJson(projection) === stableJson(output) + ? projection + : new Error('Targeted fiat preparation provider output must be canonical.'); + } + + normalizeTargetedFiatPreparationPayload(value, payload, liability) { + const shapeError = this.validateExactObjectKeys( + payload, + [ + 'settlement_op', + 'rail', + 'epoch', + 'epoch_apply_hash', + 'plan_root', + 'economic_op_id', + 'output_index', + 'output', + 'processor', + 'source_currency', + ], + 'targeted fiat preparation payload' + ); + if (shapeError) return shapeError; + const sourceCurrency = this.normalizeFiatCurrency(payload.source_currency); + if (payload.settlement_op !== 'settle_targeted_fiat_output' || + payload.rail !== 'fiat' || + payload.epoch !== value.epoch || + payload.epoch_apply_hash !== value.epoch_apply_hash || + !this.isHexBytes(payload.plan_root, 32) || + payload.economic_op_id !== value.economic_op_id || + payload.output_index !== value.output_index || + payload.processor !== 'stripe' || + sourceCurrency instanceof Error || + sourceCurrency !== payload.source_currency) { + return new Error('Invalid targeted fiat preparation payload.'); + } + const output = this.normalizeTargetedPayoutPlanOutput( + 'fiat', + payload.output, + value.output_index + ); + if (output instanceof Error) return output; + if (output.source_currency !== sourceCurrency) { + return new Error('Targeted fiat preparation source currency mismatch.'); + } + if (value.kind === 'liability') { + const expectedLiability = this.payoutPreparationLiabilityForOutput( + value, + output, + 'fiat' + ); + if (output.role !== 'provider' || + stableJson(expectedLiability) !== stableJson(liability)) { + return new Error('Targeted fiat preparation output does not match liability.'); + } + } else if (value.kind !== 'fee' || + output.role !== 'operator_fee' || + liability !== null) { + return new Error('Targeted fiat preparation output does not match kind.'); + } + return stableValue({ + settlement_op: 'settle_targeted_fiat_output', + plan_root: payload.plan_root, + economic_op_id: value.economic_op_id, + rail: 'fiat', + epoch: value.epoch, + epoch_apply_hash: value.epoch_apply_hash, + output_index: value.output_index, + output, + processor: 'stripe', + source_currency: sourceCurrency, + }); + } + + normalizeMsbSignedPayoutPayload(payload, output, treasuryFrom) { + const payloadShape = this.validateExactObjectKeys( + payload, + ['type', 'address', 'tro'], + 'targeted TNK signed MSB payload' + ); + if (payloadShape) return payloadShape; + const transferShape = this.validateExactObjectKeys( + payload?.tro, + ['tx', 'txv', 'to', 'am', 'in', 'is'], + 'targeted TNK signed MSB transfer' + ); + if (transferShape) return transferShape; + const expectedAmountHex = BigInt(output.tnk_e18).toString(16).padStart(32, '0'); + if (!Number.isSafeInteger(payload.type) || + payload.type < 0 || + payload.address !== treasuryFrom || + !this.isHexBytes(payload.tro.tx, 32) || + payload.tro.tx !== payload.tro.tx.toLowerCase() || + !this.isHexBytes(payload.tro.txv, 32) || + payload.tro.txv !== payload.tro.txv.toLowerCase() || + payload.tro.to !== output.to || + !/^[0-9a-f]{32}$/.test(payload.tro.am) || + payload.tro.am !== expectedAmountHex || + !this.isHexBytes(payload.tro.in, 32) || + payload.tro.in !== payload.tro.in.toLowerCase() || + !this.isHexBytes(payload.tro.is, 64) || + payload.tro.is !== payload.tro.is.toLowerCase()) { + return new Error('Invalid targeted TNK signed MSB payload.'); + } + return stableValue(payload); + } + + async normalizeTargetedTnkPreparationPayload( + value, + payload, + liability, + externalEffectIds + ) { + const shapeError = this.validateExactObjectKeys( + payload, + [ + 'settlement_op', + 'rail', + 'epoch', + 'epoch_apply_hash', + 'plan_root', + 'economic_op_id', + 'output_index', + 'output', + 'network', + 'treasury_from', + 'rate_tnk_usd_au', + 'rate_source', + 'rate_ts', + 'msb_tx_hash', + 'msb_payload', + ], + 'targeted TNK preparation payload' + ); + if (shapeError) return shapeError; + if (payload.settlement_op !== 'settle_targeted_tnk_output' || + payload.rail !== 'tnk' || + payload.epoch !== value.epoch || + payload.epoch_apply_hash !== value.epoch_apply_hash || + !this.isHexBytes(payload.plan_root, 32) || + payload.economic_op_id !== value.economic_op_id || + payload.output_index !== value.output_index || + !this.isSafeKeyPart(payload.network) || + !this.isSafeKeyPart(payload.treasury_from) || + !Number.isSafeInteger(payload.rate_ts) || + payload.rate_ts < 0 || + !RATE_SOURCES.has(payload.rate_source) || + !this.isHexBytes(payload.msb_tx_hash, 32) || + payload.msb_tx_hash !== payload.msb_tx_hash.toLowerCase() || + this.normalizeAu( + payload.rate_tnk_usd_au, + 'targeted TNK preparation rate', + { allowZero: false } + ) instanceof Error) { + return new Error('Invalid targeted TNK preparation payload.'); + } + const output = this.normalizeTargetedPayoutPlanOutput( + 'tnk', + payload.output, + value.output_index + ); + if (output instanceof Error) return output; + const msbPayload = this.normalizeMsbSignedPayoutPayload( + payload.msb_payload, + output, + payload.treasury_from + ); + if (msbPayload instanceof Error) return msbPayload; + if (payload.msb_tx_hash !== msbPayload.tro.tx || + externalEffectIds.length !== 1 || + externalEffectIds[0] !== payload.msb_tx_hash) { + return new Error('Targeted TNK preparation effect must equal its signed MSB tx hash.'); + } + const expectedLiability = this.payoutPreparationLiabilityForOutput( + value, + output, + 'tnk' + ); + if ((value.kind === 'liability' && + (output.role !== 'provider' || + stableJson(expectedLiability) !== stableJson(liability))) || + (value.kind === 'fee' && + (output.role !== 'operator_fee' || liability !== null))) { + return new Error('Targeted TNK preparation output does not match its kind.'); + } + return stableValue({ + settlement_op: 'settle_targeted_tnk_output', + rail: 'tnk', + epoch: value.epoch, + epoch_apply_hash: value.epoch_apply_hash, + plan_root: payload.plan_root, + economic_op_id: value.economic_op_id, + output_index: value.output_index, + output, + network: payload.network, + treasury_from: payload.treasury_from, + rate_tnk_usd_au: payload.rate_tnk_usd_au, + rate_source: payload.rate_source, + rate_ts: payload.rate_ts, + msb_tx_hash: payload.msb_tx_hash, + msb_payload: msbPayload, + }); + } + + payoutPreparationOutputPayload(value, output, outputIndex, rail) { + const common = { + settlement_op: value.op, + rail, + epoch: value.epoch, + epoch_apply_hash: value.epoch_apply_hash, + output_index: outputIndex, + output: rail === 'fiat' + ? this.targetedFiatPreparationOutputProjection(output) + : stableValue(output), + }; + if (rail === 'tnk') { + return { + ...common, + network: value.network, + treasury_from: value.treasury_from, + rate_tnk_usd_au: value.rate_tnk_usd_au, + rate_source: value.rate_source, + rate_ts: value.rate_ts, + }; + } + if (rail === 'fiat') { + return { + ...common, + processor: value.processor, + source_currency: value.source_currency, + }; + } + return { + ...common, + chain_id: value.chain_id, + token_address: value.token_address, + pool_address: value.pool_address, + payment_config_ver: value.payment_config_ver, + fee_bps: value.fee_bps, + tap_burn_bps: value.tap_burn_bps, + provider_share_bps: value.provider_share_bps, + tap_rate_lock: stableValue(value.tap_rate_lock), + }; + } + + targetedTapPayoutSplit(feeBps) { + if (!Number.isSafeInteger(feeBps) || + feeBps !== TAP_OPERATOR_BPS) { + return new Error( + 'Targeted TAP fee schedule does not match the fixed on-chain operator split.' + ); + } + return { + fee_bps: feeBps, + tap_burn_bps: TAP_BURN_BPS, + provider_share_bps: 10_000 - feeBps - TAP_BURN_BPS, + }; + } + + async payoutPreparationTapRootPayload(value, normalized) { + return { + settlement_op: value.op, + rail: 'tap', + epoch: value.epoch, + epoch_apply_hash: value.epoch_apply_hash, + chain_id: value.chain_id, + token_address: value.token_address, + pool_address: value.pool_address, + payment_config_ver: value.payment_config_ver, + tap_rate_lock: stableValue(normalized.rate_lock), + root: value.root, + cumulative_spent_wei: normalized.cumulative_spent_wei, + provider_cumulative_claimed_wei: normalized.provider_cumulative_claimed_wei, + buyer_refund_wei: normalized.buyer_refund_wei, + provider_count: value.provider_count, + provider_paid_au: normalized.provider_paid_au, + provider_tap_wei: normalized.provider_tap_wei, + fee_bps: normalized.fee_bps, + tap_burn_bps: normalized.tap_burn_bps, + provider_share_bps: normalized.provider_share_bps, + entries_hash: await this.opaqueHash( + 'mayhem-targeted-tap-preparation-entries-v1', + normalized.entries + ), + provider_entries_hash: await this.opaqueHash( + 'mayhem-targeted-tap-preparation-provider-entries-v1', + normalized.provider_entries + ), + refunds_hash: await this.opaqueHash( + 'mayhem-targeted-tap-preparation-refunds-v1', + normalized.refunds + ), + outputs_hash: await this.opaqueHash( + 'mayhem-targeted-tap-preparation-outputs-v1', + normalized.outputs + ), + }; + } + + async validatePayoutPreparationRecord({ + economicOpId, + rail, + epoch, + epochApplyHash, + kind, + outputIndex, + payload, + liability, + externalEffectIds, + }) { + const record = await this.get(this.payoutPreparationRecordKey(rail, economicOpId)); + if (!record || + record.type !== 'targeted_payout_preparation' || + record.economic_op_id !== economicOpId || + record.rail !== rail || + record.epoch !== epoch || + record.epoch_apply_hash !== epochApplyHash || + record.kind !== kind || + record.output_index !== outputIndex || + record.consumed !== false || + stableJson(record.payload) !== stableJson(payload) || + stableJson(record.liability) !== stableJson(liability) || + stableJson(record.external_effect_ids) !== stableJson(externalEffectIds)) { + return new Error('Targeted settlement does not match an unconsumed canonical preparation.'); + } + const payloadHash = await this.opaqueHash( + 'mayhem-targeted-payout-preparation-payload-v1', + { + economic_op_id: economicOpId, + rail, + epoch, + epoch_apply_hash: epochApplyHash, + kind, + output_index: outputIndex, + payload, + } + ); + if (record.payload_hash !== payloadHash) { + return new Error('Targeted settlement preparation payload hash mismatch.'); + } + if (liability !== null) { + const liabilityLock = await this.get( + this.payoutPreparationLiabilityLockKey(rail, liability) + ); + if (liabilityLock?.economic_op_id !== economicOpId) { + return new Error('Targeted settlement preparation liability lock mismatch.'); + } + } + for (const effectId of externalEffectIds) { + const effectLock = await this.get( + this.payoutPreparationEffectLockKey(rail, effectId) + ); + if (effectLock?.economic_op_id !== economicOpId || + effectLock.consumed !== false) { + return new Error('Targeted settlement preparation effect lock mismatch.'); + } + } + return record; + } + + async payoutPreparationsForSettlement(value, normalized, rail) { + const plans = []; + for (const [outputIndex, output] of normalized.outputs.entries()) { + const economicOpId = value.preparation_ids[outputIndex]; + const liability = this.payoutPreparationLiabilityForOutput(value, output, rail); + const externalEffectIds = rail === 'tap' + ? [] + : [value.external_effect_ids[outputIndex]]; + const record = await this.validatePayoutPreparationRecord({ + economicOpId, + rail, + epoch: value.epoch, + epochApplyHash: value.epoch_apply_hash, + kind: liability === null ? 'fee' : 'liability', + outputIndex, + payload: this.payoutPreparationOutputPayload(value, output, outputIndex, rail), + liability, + externalEffectIds, + }); + if (record instanceof Error) return record; + plans.push(record); + } + if (rail === 'tap') { + const rootPayload = await this.payoutPreparationTapRootPayload(value, normalized); + if (rootPayload instanceof Error) return rootPayload; + const rootRecord = await this.validatePayoutPreparationRecord({ + economicOpId: value.root_preparation_id, + rail, + epoch: value.epoch, + epochApplyHash: value.epoch_apply_hash, + kind: 'tap_root', + outputIndex: 0, + payload: rootPayload, + liability: null, + externalEffectIds: value.external_effect_ids, + }); + if (rootRecord instanceof Error) return rootRecord; + plans.push(rootRecord); + } + return plans; + } + + async validatePayoutPreparationConsumption(plans) { + const lastByProvider = new Map(); + for (const record of plans) { + if (record.liability !== null) { + lastByProvider.set( + `${record.rail}/${record.liability.provider}`, + record + ); + } + } + const validatedTails = []; + for (const record of lastByProvider.values()) { + const tailKey = this.payoutPreparationAggregateTailKey( + record.rail, + record.liability.provider + ); + const tail = await this.get(tailKey); + if (!tail || + tail.economic_op_id !== record.economic_op_id || + tail.consumed !== false) { + return new Error('Targeted payout preparation aggregate tail mismatch.'); + } + validatedTails.push({ key: tailKey, value: tail }); + } + return validatedTails; + } + + async consumePayoutPreparations(plans, settlementKey) { + const validatedTails = await this.validatePayoutPreparationConsumption(plans); + if (validatedTails instanceof Error) return validatedTails; + for (const record of plans) { + await this.put( + this.payoutPreparationRecordKey(record.rail, record.economic_op_id), + { + ...record, + consumed: true, + consumed_by: settlementKey, + } + ); + for (const effectId of record.external_effect_ids) { + await this.put(this.payoutPreparationEffectLockKey(record.rail, effectId), { + economic_op_id: record.economic_op_id, + effect_id: effectId, + consumed: true, + consumed_by: settlementKey, + updated_at: this.tx, + }); + } + } + for (const tail of validatedTails) { + await this.put(tail.key, { + ...tail.value, + consumed: true, + consumed_by: settlementKey, + updated_at: this.tx, + }); + } + return null; + } + + async targetedPayoutSettlementUpdates(outputs, rail, epoch, at, transferIds) { + const params = await this.activeParamsAt(at, [ + 'holdback_epochs', + 'challenge_epochs', + 'canary_probe_holdback_bps', + 'canary_probe_release_min_passes', + ]); + const liabilityUpdates = []; + const paidByProvider = new Map(); + for (const [outputIndex, output] of outputs.entries()) { + if (output.role !== 'provider') continue; + const provider = await this.get(`prov/${output.provider}`); + if (!provider) return new Error('Provider not found.'); + if (provider.status !== 'active' && provider.status !== 'banned') { + return new Error('Targeted settlement provider status is not payable.'); + } + const binding = await this.get( + this.providerPayoutBindingFeatureKey( + rail, + output.provider, + output.payout_revision + ) + ); + if (!binding || + binding.verified !== true || + binding.provider !== output.provider || + binding.rail !== rail || + binding.revision !== output.payout_revision || + binding.activation_epoch > epoch) { + return new Error('Targeted settlement requires its immutable payout binding.'); + } + if (binding.target !== output.to) { + return new Error('Targeted settlement payout target mismatch.'); + } + if (rail === 'fiat' && binding.currency !== output.destination_currency) { + return new Error('Targeted settlement payout currency mismatch.'); + } + const liabilityKey = this.providerPayoutLiabilityKey( + output.provider, + rail, + output.payout_revision + ); + const liability = await this.get(liabilityKey); + if (!liability || + liability.provider !== output.provider || + liability.rail !== rail || + liability.revision !== output.payout_revision || + liability.target !== binding.target || + (liability.currency ?? null) !== binding.currency || + (liability.chain_id ?? null) !== binding.chain_id) { + return new Error('Targeted settlement payout liability mismatch.'); + } + const liabilityError = this.guardianValidatePayoutLiabilityRecord( + liability, + output.provider, + rail, + output.payout_revision + ); + if (liabilityError) return liabilityError; + const probeGate = await this.probeGateForEarning(output.provider, liability, params); + if (probeGate instanceof Error) return probeGate; + const lockedEarningEpochs = this.providerLockedEarningEpochs(provider, params); + if (lockedEarningEpochs instanceof Error) return lockedEarningEpochs; + const disputeGate = await this.providerHasOpenDispute(output.provider); + if (disputeGate instanceof Error) return disputeGate; + const refreshed = this.refreshEarningHoldback( + liability, + epoch, + lockedEarningEpochs, + probeGate, + disputeGate + ); + if (refreshed instanceof Error) return refreshed; + const payable = this.safeSubAu( + this.safeSubAu(refreshed.total_au, refreshed.held_au), + refreshed.paid_cum_au + ); + if (payable instanceof Error) return payable; + if (refreshed.paid_cum_au !== output.paid_cum_au_before) { + return new Error('Targeted settlement paid cumulative watermark mismatch.'); + } + let settledAu = output.au; + if (rail === 'fiat') { + const dust = this.safeSubAu(output.liability_au, output.paid_au); + if (dust instanceof Error || + this.isZeroAu(output.liability_au) || + this.compareAu(output.liability_au, payable) > 0 || + this.compareAu(output.dust_au, dust) !== 0 || + this.compareAu(output.rounding_au, dust) !== 0) { + return new Error('Targeted fiat settlement exceeds its frozen revision liability or mismatches dust.'); + } + settledAu = output.paid_au; + } + if (this.isZeroAu(settledAu)) { + return new Error('Targeted settlement liability has no payable earnings.'); + } + if (rail !== 'fiat' && this.compareAu(output.au, payable) > 0) { + return new Error('Targeted settlement amount exceeds revision liability.'); + } + const paidCumAu = this.safeAddAu(refreshed.paid_cum_au, settledAu); + if (paidCumAu instanceof Error) return paidCumAu; + const nextLiability = { + ...refreshed, + paid_cum_au: paidCumAu, + updated_epoch: Math.max(refreshed.updated_epoch, epoch), + last_settlement_epoch: epoch, + last_settlement_transfer: transferIds[outputIndex], + updated_at: this.tx, + }; + const nextLiabilityError = this.guardianValidatePayoutLiabilityRecord( + nextLiability, + output.provider, + rail, + output.payout_revision + ); + if (nextLiabilityError) return nextLiabilityError; + liabilityUpdates.push({ + key: liabilityKey, + value: nextLiability, + }); + const providerPaid = paidByProvider.get(output.provider) ?? { + aggregate_paid_cum_au_before: output.aggregate_paid_cum_au_before, + paid_au: ZERO_AU, + }; + const expectedWatermark = this.safeAddAu( + providerPaid.aggregate_paid_cum_au_before, + providerPaid.paid_au + ); + if (expectedWatermark instanceof Error) return expectedWatermark; + if (expectedWatermark !== output.aggregate_paid_cum_au_before) { + return new Error('Targeted settlement provider outputs have a discontinuous aggregate paid watermark.'); + } + const nextProviderPaid = this.safeAddAu(providerPaid.paid_au, settledAu); + if (nextProviderPaid instanceof Error) return nextProviderPaid; + paidByProvider.set(output.provider, { + aggregate_paid_cum_au_before: providerPaid.aggregate_paid_cum_au_before, + paid_au: nextProviderPaid, + }); + } + + const earningUpdates = []; + for (const [providerId, providerPaid] of paidByProvider) { + const paidAu = providerPaid.paid_au; + const provider = await this.get(`prov/${providerId}`); + const earning = await this.earningRecord(providerId, rail); + if (earning instanceof Error) return earning; + const earningError = this.guardianValidateEarningRecord( + earning, + providerId, + rail + ); + if (earningError) return earningError; + const probeGate = await this.probeGateForEarning(providerId, earning, params); + if (probeGate instanceof Error) return probeGate; + const lockedEarningEpochs = this.providerLockedEarningEpochs(provider, params); + if (lockedEarningEpochs instanceof Error) return lockedEarningEpochs; + const disputeGate = await this.providerHasOpenDispute(providerId); + if (disputeGate instanceof Error) return disputeGate; + const refreshed = this.refreshEarningHoldback( + earning, + epoch, + lockedEarningEpochs, + probeGate, + disputeGate + ); + if (refreshed instanceof Error) return refreshed; + const payable = this.safeSubAu( + this.safeSubAu(refreshed.total_au, refreshed.held_au), + refreshed.paid_cum_au + ); + if (payable instanceof Error) return payable; + if (refreshed.paid_cum_au !== providerPaid.aggregate_paid_cum_au_before) { + return new Error('Targeted settlement aggregate paid cumulative watermark mismatch.'); + } + if (this.compareAu(paidAu, payable) > 0) { + return new Error('Targeted settlement exceeds aggregate provider earnings.'); + } + const paidCumAu = this.safeAddAu(refreshed.paid_cum_au, paidAu); + if (paidCumAu instanceof Error) return paidCumAu; + const nextEarning = { + ...refreshed, + paid_cum_au: paidCumAu, + updated_epoch: Math.max(refreshed.updated_epoch, epoch), + last_targeted_settlement_epoch: epoch, + updated_at: this.tx, + }; + const nextError = this.guardianValidateEarningRecord( + nextEarning, + providerId, + rail + ); + if (nextError) return nextError; + earningUpdates.push(nextEarning); + } + return { liabilityUpdates, earningUpdates }; + } + + async targetedTapSettlement(value) { + const adminError = await this.requireAdmin(); + if (adminError) return adminError; + const normalized = this.normalizeTargetedTapSettlementValue(value); + if (normalized instanceof Error) return normalized; + const settlementParams = await this.activeParamsAt( + value.at, + ['max_tap_settlement_outputs', 'fee_bps'] + ); + const payoutSplit = this.targetedTapPayoutSplit(settlementParams.fee_bps); + if (payoutSplit instanceof Error) return payoutSplit; + if (normalized.fee_bps !== payoutSplit.fee_bps || + normalized.tap_burn_bps !== payoutSplit.tap_burn_bps || + normalized.provider_share_bps !== payoutSplit.provider_share_bps) { + return new Error( + 'Targeted TAP settlement fee/burn split does not match the historical schedule.' + ); + } + if (normalized.outputs.length > settlementParams.max_tap_settlement_outputs) { + return new Error('Targeted TAP settlement output count exceeds limit.'); + } + + const admin = await this.get('admin'); + const payments = await this.get('payments/current'); + if (!payments || payments.set_by !== admin || payments.set_by_role !== 'admin' || + payments.ver !== value.payment_config_ver || + payments.tap?.chain_id !== value.chain_id || + payments.tap?.token_address?.toLowerCase() !== value.token_address || + payments.tap?.pool_address?.toLowerCase() !== value.pool_address) { + return new Error('Targeted TAP settlement does not match canonical payment configuration.'); + } + const rate = await this.guardianRequireHistoricalTapRateLock( + normalized.rate_lock, + value.at + ); + if (rate instanceof Error) return rate; + + const record = { + type: 'targeted_tap_settlement', + ...value, + tap_rate_lock: normalized.rate_lock, + entries: normalized.entries, + outputs: normalized.outputs, + fee_bps: payoutSplit.fee_bps, + tap_burn_bps: payoutSplit.tap_burn_bps, + provider_share_bps: payoutSplit.provider_share_bps, + settled_by: this.address, + settled_by_role: 'admin', + }; + const recordKey = `settle/targeted/tap/${value.epoch}`; + const existing = await this.get(recordKey); + if (existing) { + if (stableJson(existing) === stableJson(record)) { + return { + ok: true, + op: 'targetedTapSettlement', + epoch: value.epoch, + rail: 'tap', + root: value.root, + execution_tx: value.execution_tx, + idempotent: true, + }; + } + return new Error('Targeted TAP settlement already exists for epoch.'); + } + + const applyAnchor = await this.requireEpochApplyAnchor( + value.epoch, + value.epoch_apply_hash, + 'Targeted TAP settlement' + ); + if (applyAnchor instanceof Error) return applyAnchor; + const preparations = await this.payoutPreparationsForSettlement( + value, + normalized, + 'tap' + ); + if (preparations instanceof Error) return preparations; + + const scope = `${value.chain_id}/${value.pool_address}`; + const rootSeenKey = `rail/seen/tap-settlement-root/${scope}/${value.root}`; + const proposalSeenKey = `rail/seen/tap-settlement-proposal/${scope}/${value.proposal_tx}`; + const executionSeenKey = `rail/seen/tap-settlement-execution/${scope}/${value.execution_tx}`; + if ((await this.get(rootSeenKey)) !== null) { + return new Error('Targeted TAP settlement root was already consumed.'); + } + if ((await this.get(proposalSeenKey)) !== null) { + return new Error('Targeted TAP settlement proposal transaction was already consumed.'); + } + if ((await this.get(executionSeenKey)) !== null) { + return new Error('Targeted TAP settlement execution transaction was already consumed.'); + } + + const stateKey = `settle/targeted/tap/state/${scope}`; + const state = (await this.get(stateKey)) ?? { + type: 'targeted_tap_settlement_state', + chain_id: value.chain_id, + token_address: value.token_address, + pool_address: value.pool_address, + payment_config_ver: value.payment_config_ver, + last_epoch: 0, + cumulative_spent_wei: '0', + cumulative_provider_claimed_wei: '0', + cumulative_buyer_refund_wei: '0', + last_root: null, + last_execution_tx: null, + updated_at: null, + }; + if (state.type !== 'targeted_tap_settlement_state' || + state.chain_id !== value.chain_id || + state.token_address !== value.token_address || + state.pool_address !== value.pool_address || + state.payment_config_ver !== value.payment_config_ver || + !Number.isSafeInteger(state.last_epoch) || + state.last_epoch < 0 || + typeof state.cumulative_spent_wei !== 'string' || + !/^(0|[1-9][0-9]*)$/.test(state.cumulative_spent_wei) || + typeof state.cumulative_provider_claimed_wei !== 'string' || + !/^(0|[1-9][0-9]*)$/.test(state.cumulative_provider_claimed_wei) || + typeof state.cumulative_buyer_refund_wei !== 'string' || + !/^(0|[1-9][0-9]*)$/.test(state.cumulative_buyer_refund_wei)) { + return new Error('Invalid targeted TAP settlement state.'); + } + if (value.epoch <= state.last_epoch) { + return new Error('Targeted TAP settlement epoch must advance.'); + } + const priorSpentWei = BigInt(state.cumulative_spent_wei); + const nextSpentWei = BigInt(normalized.cumulative_spent_wei); + const priorProviderClaimedWei = BigInt(state.cumulative_provider_claimed_wei); + const nextProviderClaimedWei = BigInt(normalized.provider_cumulative_claimed_wei); + const priorBuyerRefundWei = BigInt(state.cumulative_buyer_refund_wei); + const nextBuyerRefundWei = BigInt(normalized.buyer_refund_wei); + if (nextSpentWei <= priorSpentWei) { + return new Error('Targeted TAP cumulative gross spend must advance.'); + } + const grossSpendDeltaWei = nextSpentWei - priorSpentWei; + const expectedProviderDeltaWei = + (grossSpendDeltaWei * BigInt(payoutSplit.provider_share_bps)) / 10_000n; + if (BigInt(normalized.provider_tap_wei) !== expectedProviderDeltaWei || + nextProviderClaimedWei !== + priorProviderClaimedWei + BigInt(normalized.provider_tap_wei)) { + return new Error( + 'Targeted TAP provider entitlement does not match the historical fee/burn split.' + ); + } + if (nextBuyerRefundWei < priorBuyerRefundWei) { + return new Error('Targeted TAP cumulative buyer refunds cannot decrease.'); + } + + const outputsByTarget = new Map(); + for (const output of normalized.outputs) { + const targetOutputs = outputsByTarget.get(output.to) ?? []; + targetOutputs.push(output); + outputsByTarget.set(output.to, targetOutputs); + } + const claimUpdates = []; + for (const entry of normalized.provider_entries) { + const claimKey = `settle/targeted/tap/claim/${scope}/${entry.account}`; + const claim = (await this.get(claimKey)) ?? { + type: 'targeted_tap_cumulative_claim', + chain_id: value.chain_id, + token_address: value.token_address, + pool_address: value.pool_address, + account: entry.account, + cumulative_claim_wei: '0', + updated_epoch: 0, + updated_at: null, + }; + if (claim.type !== 'targeted_tap_cumulative_claim' || + claim.chain_id !== value.chain_id || + claim.token_address !== value.token_address || + claim.pool_address !== value.pool_address || + claim.account !== entry.account || + typeof claim.cumulative_claim_wei !== 'string' || + !/^(0|[1-9][0-9]*)$/.test(claim.cumulative_claim_wei)) { + return new Error('Invalid targeted TAP cumulative claim state.'); + } + const targetOutputs = outputsByTarget.get(entry.account) ?? []; + let cursor = BigInt(claim.cumulative_claim_wei); + for (const output of targetOutputs) { + const prior = BigInt(output.prior_cumulative_claim_wei); + const next = BigInt(output.cumulative_claim_wei); + if (prior !== cursor || next !== prior + BigInt(output.tap_wei)) { + return new Error('Targeted TAP cumulative claim output chain does not advance from canonical state.'); + } + cursor = next; + } + if (BigInt(entry.cumulative_wei) !== cursor) { + return new Error('Targeted TAP cumulative claim chain does not match executed root.'); + } + outputsByTarget.delete(entry.account); + claimUpdates.push({ + key: claimKey, + value: { + ...claim, + cumulative_claim_wei: entry.cumulative_wei, + updated_epoch: value.epoch, + updated_at: this.tx, + }, + }); + } + if (outputsByTarget.size !== 0) { + return new Error('Targeted TAP provider output target is missing from executed root.'); + } + const priorSettlement = state.last_epoch === 0 + ? null + : await this.get(`settle/targeted/tap/${state.last_epoch}`); + const priorRefunds = priorSettlement?.refunds ?? []; + if (state.last_epoch > 0 && !Array.isArray(priorRefunds)) { + return new Error('Prior targeted TAP refund distribution is invalid.'); + } + const refundUpdates = []; + const refundMap = new Map(normalized.refunds.map((entry) => [entry.account, entry])); + for (const priorRefund of priorRefunds) { + const nextRefund = refundMap.get(priorRefund.account); + if (!nextRefund || + BigInt(nextRefund.cumulative_wei) < BigInt(priorRefund.cumulative_wei)) { + return new Error('Targeted TAP refund claim cannot be removed or decreased.'); + } + } + for (const entry of normalized.refunds) { + const refundKey = `settle/targeted/tap/refund/${scope}/${entry.account}`; + const refund = (await this.get(refundKey)) ?? { + type: 'targeted_tap_cumulative_refund', + chain_id: value.chain_id, + token_address: value.token_address, + pool_address: value.pool_address, + account: entry.account, + cumulative_refund_wei: '0', + updated_epoch: 0, + updated_at: null, + }; + if (refund.type !== 'targeted_tap_cumulative_refund' || + refund.chain_id !== value.chain_id || + refund.token_address !== value.token_address || + refund.pool_address !== value.pool_address || + refund.account !== entry.account || + typeof refund.cumulative_refund_wei !== 'string' || + !/^(0|[1-9][0-9]*)$/.test(refund.cumulative_refund_wei) || + BigInt(entry.cumulative_wei) < BigInt(refund.cumulative_refund_wei)) { + return new Error('Invalid or decreasing targeted TAP cumulative refund state.'); + } + refundUpdates.push({ + key: refundKey, + value: { + ...refund, + cumulative_refund_wei: entry.cumulative_wei, + updated_epoch: value.epoch, + updated_at: this.tx, + }, + }); + } + + const settlementOutputs = normalized.outputs.map((output) => ({ + role: 'provider', + provider: output.provider, + payout_revision: output.payout_revision, + to: output.to, + paid_cum_au_before: output.paid_cum_au_before, + aggregate_paid_cum_au_before: output.aggregate_paid_cum_au_before, + au: output.paid_au, + })); + const updates = await this.targetedPayoutSettlementUpdates( + settlementOutputs, + 'tap', + value.epoch, + value.at, + normalized.outputs.map(() => value.execution_tx) + ); + if (updates instanceof Error) return updates; + + for (const update of updates.liabilityUpdates) await this.put(update.key, update.value); + for (const earning of updates.earningUpdates) { + await this.put(this.earningKey(earning.provider, 'tap'), earning); + } + for (const update of claimUpdates) await this.put(update.key, update.value); + for (const update of refundUpdates) await this.put(update.key, update.value); + const replayEvidence = { + rail: 'tap', + purpose: 'targeted_settlement', + epoch: value.epoch, + root: value.root, + proposal_tx: value.proposal_tx, + execution_tx: value.execution_tx, + epoch_apply_hash: value.epoch_apply_hash, + consumed_at: this.tx, + }; + await this.put(rootSeenKey, replayEvidence); + await this.put(proposalSeenKey, replayEvidence); + await this.put(executionSeenKey, replayEvidence); + await this.put(stateKey, { + ...state, + last_epoch: value.epoch, + cumulative_spent_wei: normalized.cumulative_spent_wei, + cumulative_provider_claimed_wei: normalized.provider_cumulative_claimed_wei, + cumulative_buyer_refund_wei: normalized.buyer_refund_wei, + last_root: value.root, + last_execution_tx: value.execution_tx, + updated_at: this.tx, + }); + const preparationError = await this.consumePayoutPreparations( + preparations, + recordKey + ); + if (preparationError) return preparationError; + await this.put(recordKey, record); + return { + ok: true, + op: 'targetedTapSettlement', + epoch: value.epoch, + rail: 'tap', + provider_paid_au: normalized.provider_paid_au, + provider_tap_wei: normalized.provider_tap_wei, + cumulative_spent_wei: normalized.cumulative_spent_wei, + root: value.root, + execution_tx: value.execution_tx, + idempotent: false, + }; + } + + async targetedTnkSettlement(value) { + const adminError = await this.requireAdmin(); + if (adminError) return adminError; + const normalized = this.normalizeTargetedTnkSettlementValue(value); + if (normalized instanceof Error) return normalized; + const settlementParams = await this.activeParamsAt( + value.at, + ['max_tnk_settlement_outputs'] + ); + if (value.outputs.length > settlementParams.max_tnk_settlement_outputs) { + return new Error('Targeted TNK settlement output count exceeds limit.'); + } + const outputs = normalized.outputs; + const transfers = value.msb_transfers.map((entry) => ( + this.normalizeMsbTransferEvidence( + entry, + 'Targeted TNK settlement MSB transfer evidence' + ) + )); + const transferError = transfers.find((entry) => entry instanceof Error); + if (transferError) return transferError; + if (stableJson(transfers) !== stableJson(value.msb_transfers)) { + return new Error('Targeted TNK settlement transfers must be canonical.'); + } + const payment = await this.canonicalTnkPaymentConfig(); + if (payment instanceof Error) return payment; + if (value.network !== payment.network || + value.treasury_from !== payment.treasury_address) { + return new Error('Targeted TNK settlement source does not match payment config.'); + } + for (const [index, output] of outputs.entries()) { + const transfer = transfers[index]; + if (transfer.network !== value.network || + transfer.from !== value.treasury_from || + transfer.to !== output.to || + transfer.amount_e18 !== output.tnk_e18) { + return new Error('Targeted TNK transfer does not match output.'); + } + } + const totals = this.targetedTnkSettlementTotals(outputs, value.rate_tnk_usd_au); + if (totals instanceof Error) return totals; + if (totals.provider_count !== value.provider_count || + this.compareAu(totals.provider_au, value.provider_au) !== 0 || + this.compareAu(totals.operator_fee_au, value.operator_fee_au) !== 0 || + this.compareAu(totals.gross_au, value.gross_au) !== 0 || + totals.tnk_e18 !== value.tnk_e18) { + return new Error('Targeted TNK settlement totals do not match outputs.'); + } + const transferRoot = await this.targetedTnkSettlementTransferRoot(outputs); + if (transferRoot !== value.transfer_root) { + return new Error('Targeted TNK settlement transfer root mismatch.'); + } + const record = { + type: 'targeted_tnk_settlement', + ...value, + outputs, + msb_transfers: transfers, + settled_by: this.address, + settled_by_role: 'admin', + }; + const recordKey = `settle/targeted/tnk/${value.epoch}`; + const existing = await this.get(recordKey); + if (existing) { + if (stableJson(existing) === stableJson(record)) { + return { + ok: true, + op: 'targetedTnkSettlement', + epoch: value.epoch, + rail: 'tnk', + idempotent: true, + msb_transfers: transfers, + }; + } + return new Error('Targeted TNK settlement already exists for epoch.'); + } + const applyAnchor = await this.requireEpochApplyAnchor( + value.epoch, + value.epoch_apply_hash, + 'Targeted TNK settlement' + ); + if (applyAnchor instanceof Error) return applyAnchor; + const preparations = await this.payoutPreparationsForSettlement( + value, + normalized, + 'tnk' + ); + if (preparations instanceof Error) return preparations; + for (const transfer of transfers) { + if ((await this.get(this.msbTransferSeenKey(transfer))) !== null) { + return new Error('Targeted TNK transfer was already consumed.'); + } + } + const rate = await this.guardianRequireHistoricalTnkRate(value, value.at); + if (rate instanceof Error) return rate; + const updates = await this.targetedPayoutSettlementUpdates( + outputs, + 'tnk', + value.epoch, + value.at, + transfers.map((entry) => entry.tx_hash) + ); + if (updates instanceof Error) return updates; + + const fee = await this.feeCumRecord('tnk'); + if (fee instanceof Error) return fee; + const feeError = this.guardianValidateFeeRecord(fee, 'tnk'); + if (feeError) return feeError; + const payableFee = this.safeSubAu(fee.cum_au, fee.swept_cum_au); + if (payableFee instanceof Error) return payableFee; + if (this.compareAu(payableFee, value.operator_fee_au) < 0) { + return new Error('Targeted TNK operator fee does not match fee state.'); + } + const operatorOutputs = outputs.filter((entry) => entry.role === 'operator_fee'); + if ((this.isZeroAu(value.operator_fee_au) && operatorOutputs.length !== 0) || + (this.compareAu(value.operator_fee_au, ZERO_AU) > 0 && + (operatorOutputs.length !== 1 || operatorOutputs[0].to !== value.operator_to))) { + return new Error('Targeted TNK operator output mismatch.'); + } + const sweptCumAu = this.safeAddAu(fee.swept_cum_au, value.operator_fee_au); + if (sweptCumAu instanceof Error) return sweptCumAu; + const operatorIndex = outputs.findIndex((entry) => entry.role === 'operator_fee'); + const nextFee = { + ...fee, + swept_cum_au: sweptCumAu, + updated_epoch: Math.max(fee.updated_epoch, value.epoch), + last_targeted_settlement_epoch: value.epoch, + last_targeted_settlement_transfer: operatorIndex >= 0 + ? transfers[operatorIndex].tx_hash + : null, + }; + const nextFeeError = this.guardianValidateFeeRecord(nextFee, 'tnk'); + if (nextFeeError) return nextFeeError; + for (const update of updates.liabilityUpdates) await this.put(update.key, update.value); + for (const earning of updates.earningUpdates) { + await this.put(this.earningKey(earning.provider, 'tnk'), earning); + } + for (const [index, transfer] of transfers.entries()) { + await this.put(this.msbTransferSeenKey(transfer), { + rail: 'tnk', + purpose: 'targeted_settlement', + epoch: value.epoch, + output_index: index, + transfer_root: value.transfer_root, + consumed_at: this.tx, + }); + } + await this.put(this.feeCumKey('tnk'), nextFee); + const preparationError = await this.consumePayoutPreparations( + preparations, + recordKey + ); + if (preparationError) return preparationError; + await this.put(recordKey, record); + return { + ok: true, + op: 'targetedTnkSettlement', + epoch: value.epoch, + rail: 'tnk', + idempotent: false, + provider_au: value.provider_au, + operator_fee_au: value.operator_fee_au, + gross_au: value.gross_au, + msb_transfers: transfers, + transfer_root: value.transfer_root, + }; + } + + async targetedFiatSettlement(value) { + const adminError = await this.requireAdmin(); + if (adminError) return adminError; + const normalized = this.normalizeTargetedFiatSettlementValue(value); + if (normalized instanceof Error) return normalized; + const settlementParams = await this.activeParamsAt( + value.at, + ['max_fiat_settlement_outputs'] + ); + if (value.outputs.length > settlementParams.max_fiat_settlement_outputs) { + return new Error('Targeted fiat settlement output count exceeds limit.'); + } + const outputs = normalized.outputs; + const payments = await this.get('payments/current'); + const admin = await this.get('admin'); + if (!payments || payments.set_by !== admin || payments.set_by_role !== 'admin' || + payments.denom !== PRICE_DENOMINATION || + payments.fiat?.processor !== 'stripe' || + payments.fiat?.integration_currency !== 'usd' || + payments.fiat?.adaptive_pricing !== true || + !Array.isArray(payments.fiat?.payout_currencies)) { + return new Error('Targeted fiat settlement does not match canonical payment configuration.'); + } + const transfers = value.stripe_transfers.map((entry) => ( + this.normalizeStripeTransferEvidence( + entry, + 'Targeted fiat settlement Stripe evidence' + ) + )); + const transferError = transfers.find((entry) => entry instanceof Error); + if (transferError) return transferError; + if (stableJson(transfers) !== stableJson(value.stripe_transfers)) { + return new Error('Targeted fiat settlement transfers must be canonical.'); + } + const expectedGroup = + `mayhem_fiat_epoch_${value.epoch}_${value.epoch_apply_hash.slice(0, 16)}`; + for (const [index, output] of outputs.entries()) { + const transfer = transfers[index]; + const expectedKind = output.role === 'provider' + ? 'stripe_transfer' + : 'platform_balance'; + if (transfer.kind !== expectedKind || + transfer.destination !== output.to || + transfer.source_currency !== output.source_currency || + transfer.source_amount_minor !== output.source_amount_minor || + (expectedKind === 'stripe_transfer' && + (transfer.destination_currency !== output.destination_currency || + transfer.destination_amount_minor !== output.destination_amount_minor || + transfer.fx_quote_id !== output.fx_quote_id || + transfer.fx_quote_hash !== output.fx_quote_hash || + transfer.transfer_group !== expectedGroup))) { + return new Error('Targeted fiat transfer does not match output.'); + } + if (output.source_currency !== value.source_currency) { + return new Error('Targeted fiat output source currency mismatch.'); + } + if (output.role === 'provider' && + !payments.fiat.payout_currencies.includes(output.destination_currency)) { + return new Error('Targeted fiat provider destination currency is not canonical.'); + } + } + const totals = this.targetedFiatSettlementTotals(outputs); + if (totals instanceof Error) return totals; + if (totals.provider_count !== value.provider_count || + this.compareAu(totals.provider_liability_au, value.provider_liability_au) !== 0 || + this.compareAu(totals.provider_paid_au, value.provider_paid_au) !== 0 || + this.compareAu(totals.operator_fee_liability_au, value.operator_fee_liability_au) !== 0 || + this.compareAu(totals.operator_fee_retained_au, value.operator_fee_retained_au) !== 0 || + this.compareAu(totals.gross_liability_au, value.gross_liability_au) !== 0 || + this.compareAu(totals.gross_paid_au, value.gross_paid_au) !== 0 || + this.compareAu(totals.rounding_au, value.rounding_au) !== 0 || + this.compareAu(totals.dust_au, value.dust_au) !== 0 || + totals.source_currency !== value.source_currency || + totals.source_amount_minor !== value.source_amount_minor || + stableJson(totals.destination_totals) !== stableJson(normalized.destination_totals)) { + return new Error('Targeted fiat settlement totals do not match outputs.'); + } + const transferRoot = await this.targetedFiatSettlementTransferRoot(outputs); + if (transferRoot !== value.transfer_root) { + return new Error('Targeted fiat settlement transfer root mismatch.'); + } + const record = { + type: 'targeted_fiat_settlement', + ...value, + outputs, + stripe_transfers: transfers, + settled_by: this.address, + settled_by_role: 'admin', + }; + const recordKey = `settle/targeted/fiat/${value.epoch}`; + const existing = await this.get(recordKey); + if (existing) { + if (stableJson(existing) === stableJson(record)) { + return { + ok: true, + op: 'targetedFiatSettlement', + epoch: value.epoch, + rail: 'fiat', + processor: 'stripe', + idempotent: true, + stripe_transfers: transfers, + }; + } + return new Error('Targeted fiat settlement already exists for epoch.'); + } + const applyAnchor = await this.requireEpochApplyAnchor( + value.epoch, + value.epoch_apply_hash, + 'Targeted fiat settlement' + ); + if (applyAnchor instanceof Error) return applyAnchor; + const preparations = await this.payoutPreparationsForSettlement( + value, + normalized, + 'fiat' + ); + if (preparations instanceof Error) return preparations; + for (const transfer of transfers) { + for (const seenKey of [ + this.stripeTransferSeenKey(transfer), + this.stripeFxQuoteSeenKey(transfer), + this.stripeDestinationPaymentSeenKey(transfer), + ]) { + if (seenKey && (await this.get(seenKey)) !== null) { + return new Error('Targeted Stripe transfer evidence was already consumed.'); + } + } + } + const updates = await this.targetedPayoutSettlementUpdates( + outputs, + 'fiat', + value.epoch, + value.at, + transfers.map((entry) => entry.ref) + ); + if (updates instanceof Error) return updates; + + const fee = await this.feeCumRecord('fiat'); + if (fee instanceof Error) return fee; + const feeError = this.guardianValidateFeeRecord(fee, 'fiat'); + if (feeError) return feeError; + const payableFee = this.safeSubAu(fee.cum_au, fee.swept_cum_au); + if (payableFee instanceof Error) return payableFee; + const operatorOutputs = outputs.filter((entry) => entry.role === 'operator_fee'); + const retainedFee = operatorOutputs[0]?.paid_au ?? ZERO_AU; + if ((operatorOutputs.length === 0 && + (!this.isZeroAu(value.operator_fee_liability_au) || + !this.isZeroAu(value.operator_fee_retained_au))) || + (operatorOutputs.length === 1 && + (operatorOutputs[0].to !== value.operator_to || + this.compareAu(operatorOutputs[0].liability_au, payableFee) > 0 || + this.compareAu(operatorOutputs[0].paid_au, value.operator_fee_retained_au) !== 0))) { + return new Error('Targeted fiat operator output mismatch.'); + } + const sweptCumAu = this.safeAddAu(fee.swept_cum_au, retainedFee); + if (sweptCumAu instanceof Error) return sweptCumAu; + const operatorIndex = outputs.findIndex((entry) => entry.role === 'operator_fee'); + const nextFee = { + ...fee, + swept_cum_au: sweptCumAu, + updated_epoch: Math.max(fee.updated_epoch, value.epoch), + last_targeted_settlement_epoch: value.epoch, + last_targeted_settlement_transfer: operatorIndex >= 0 + ? transfers[operatorIndex].ref + : null, + }; + const nextFeeError = this.guardianValidateFeeRecord(nextFee, 'fiat'); + if (nextFeeError) return nextFeeError; + for (const update of updates.liabilityUpdates) await this.put(update.key, update.value); + for (const earning of updates.earningUpdates) { + await this.put(this.earningKey(earning.provider, 'fiat'), earning); + } + for (const [index, transfer] of transfers.entries()) { + for (const seenKey of [ + this.stripeTransferSeenKey(transfer), + this.stripeFxQuoteSeenKey(transfer), + this.stripeDestinationPaymentSeenKey(transfer), + ]) { + if (!seenKey) continue; + await this.put(seenKey, { + rail: 'fiat', + purpose: 'targeted_settlement', + epoch: value.epoch, + output_index: index, + transfer_root: value.transfer_root, + consumed_at: this.tx, + }); + } + } + await this.put(this.feeCumKey('fiat'), nextFee); + const preparationError = await this.consumePayoutPreparations( + preparations, + recordKey + ); + if (preparationError) return preparationError; + await this.put(recordKey, record); + return { + ok: true, + op: 'targetedFiatSettlement', + epoch: value.epoch, + rail: 'fiat', + processor: 'stripe', + idempotent: false, + provider_liability_au: value.provider_liability_au, + provider_paid_au: value.provider_paid_au, + operator_fee_liability_au: value.operator_fee_liability_au, + operator_fee_retained_au: value.operator_fee_retained_au, + gross_liability_au: value.gross_liability_au, + gross_paid_au: value.gross_paid_au, + rounding_au: value.rounding_au, + dust_au: value.dust_au, + source_currency: value.source_currency, + source_amount_minor: value.source_amount_minor, + destination_totals: normalized.destination_totals, + stripe_transfers: transfers, + transfer_root: value.transfer_root, + }; + } + + normalizeTargetedTnkSettlementOutput(output) { + if (!this.isSafeKeyPart(output.to)) return new Error('Invalid TNK settlement output address.'); + const au = this.normalizeAu(output.au, 'TNK settlement output amount', { allowZero: false }); + if (au instanceof Error) { + return new Error('Invalid TNK settlement output amount.'); + } + const tnkE18 = this.parseTnkE18(output.tnk_e18); + if (tnkE18 instanceof Error) return tnkE18; + return output.role === 'provider' + ? { + role: 'provider', + provider: output.provider, + to: output.to, + au, + tnk_e18: output.tnk_e18, + } + : { + role: 'operator_fee', + to: output.to, + au, + tnk_e18: output.tnk_e18, + }; + } + + targetedTnkSettlementTotals(outputs, rateTnkUsdAu) { + let providerAu = ZERO_AU; + let operatorFeeAu = ZERO_AU; + let tnkE18 = 0n; + let providerCount = 0; + for (const output of outputs) { + const expectedTnkE18 = this.auToTnkE18Ceil(output.au, rateTnkUsdAu); + if (expectedTnkE18 instanceof Error) return expectedTnkE18; + if (output.tnk_e18 !== expectedTnkE18.toString()) { + return new Error('TNK settlement output amount does not match oracle rate.'); + } + const parsed = this.parseTnkE18(output.tnk_e18); + if (parsed instanceof Error) return parsed; + tnkE18 += parsed; + if (output.role === 'provider') { + providerCount += 1; + providerAu = this.safeAddAu(providerAu, output.au); + if (providerAu instanceof Error) return providerAu; + } else { + operatorFeeAu = this.safeAddAu(operatorFeeAu, output.au); + if (operatorFeeAu instanceof Error) return operatorFeeAu; + } + } + const grossAu = this.safeAddAu(providerAu, operatorFeeAu); + if (grossAu instanceof Error) return grossAu; + return { + provider_count: providerCount, + provider_au: providerAu, + operator_fee_au: operatorFeeAu, + gross_au: grossAu, + tnk_e18: tnkE18.toString(), + }; + } + + auToTnkE18Ceil(au, rateTnkUsdAu) { + const amount = this.parseAu(au, 'TNK settlement amount', { allowZero: false }); + if (amount instanceof Error) return new Error('Invalid TNK settlement amount.'); + const rate = this.parseAu(rateTnkUsdAu, 'TNK/USD atto-rate', { allowZero: false }); + if (rate instanceof Error) return new Error('Invalid TNK/USD atto-rate.'); + const numerator = amount * TNK_E18; + const denominator = rate; + // Payout conversion rounds up so the provider is never underpaid in TNK atomic units. + return (numerator + denominator - 1n) / denominator; + } + + async fiatDustSweep() { + const adminError = await this.requireAdmin(); + if (adminError) return adminError; + const shapeError = this.validateFiatDustSweepValue(this.value); + if (shapeError) return shapeError; + + const recordKey = `settle/fiat-dust/${this.value.provider}/${this.value.epoch}`; + const existing = await this.get(recordKey); + if (existing) { + return { + ok: true, + op: 'fiatDustSweep', + provider: existing.provider, + epoch: existing.epoch, + dust_au: existing.dust_au, + idempotent: true, + }; + } + + const applyState = await this.epochApplyStateRecord(); + if (applyState.updated_epoch !== this.value.epoch) { + return new Error('Fiat dust sweep epoch must equal the latest applied epoch.'); + } + const provider = await this.get(`prov/${this.value.provider}`); + if (!provider) return new Error('Provider not found.'); + if (!Array.isArray(provider.enclaves)) return new Error('Invalid provider enclave state.'); + if (provider.enclaves.length > 0) { + return new Error('Fiat dust sweep requires a provider with no active enclaves.'); + } + + const params = await this.activeParamsAt(this.value.at, [ + 'holdback_epochs', + 'challenge_epochs', + 'canary_probe_holdback_bps', + 'canary_probe_release_min_passes', + ]); + const earning = await this.earningRecord(this.value.provider, 'fiat'); + if (earning instanceof Error) return earning; + const earningError = this.guardianValidateEarningRecord(earning, this.value.provider, 'fiat'); + if (earningError) return earningError; + const probeGate = await this.probeGateForEarning(this.value.provider, earning, params); + if (probeGate instanceof Error) return probeGate; + const lockedEarningEpochs = this.providerLockedEarningEpochs(provider, params); + if (lockedEarningEpochs instanceof Error) return lockedEarningEpochs; + const disputeGate = await this.providerHasOpenDispute(this.value.provider); + if (disputeGate instanceof Error) return disputeGate; + const refreshed = this.refreshEarningHoldback( + earning, + this.value.epoch, + lockedEarningEpochs, + probeGate, + disputeGate + ); + if (refreshed instanceof Error) return refreshed; + if (!this.isZeroAu(refreshed.held_au)) { + return new Error('Fiat dust cannot be swept while provider earnings are held.'); + } + const unpaid = this.safeSubAu(refreshed.total_au, refreshed.paid_cum_au); + if (unpaid instanceof Error) return unpaid; + const unpaidValue = this.parseAu(unpaid, 'Fiat unpaid provider earnings'); + if (unpaidValue instanceof Error) return unpaidValue; + const dustValue = unpaidValue % USD_CENT_AU; + if (dustValue === 0n) return new Error('Provider has no fiat dust to sweep.'); + const dustAu = this.canonicalAu(dustValue); + const totalAu = this.safeSubAu(refreshed.total_au, dustAu); + if (totalAu instanceof Error) return totalAu; + const dustSweptCumAu = this.safeAddAu(refreshed.fiat_dust_swept_cum_au ?? ZERO_AU, dustAu); + if (dustSweptCumAu instanceof Error) return dustSweptCumAu; + const nextEarning = { + ...refreshed, + total_au: totalAu, + fiat_dust_swept_cum_au: dustSweptCumAu, + updated_epoch: Math.max(refreshed.updated_epoch, this.value.epoch), + updated_at: this.tx, + last_fiat_dust_sweep_epoch: this.value.epoch, + last_fiat_dust_sweep_at: this.tx, + }; + const nextEarningError = this.guardianValidateEarningRecord( + nextEarning, + this.value.provider, + 'fiat' + ); + if (nextEarningError) return nextEarningError; + + const fee = await this.feeCumRecord('fiat'); + if (fee instanceof Error) return fee; + const feeError = this.guardianValidateFeeRecord(fee, 'fiat'); + if (feeError) return feeError; + const cumAu = this.safeAddAu(fee.cum_au, dustAu); + if (cumAu instanceof Error) return cumAu; + const settledCumAu = this.safeAddAu(fee.settled_cum_au ?? fee.cum_au, dustAu); + if (settledCumAu instanceof Error) return settledCumAu; + const nextFee = { + ...fee, + cum_au: cumAu, + settled_cum_au: settledCumAu, + updated_epoch: Math.max(fee.updated_epoch, this.value.epoch), + updated_at: this.tx, + last_fiat_dust_sweep_at: this.tx, + }; + const nextFeeError = this.guardianValidateFeeRecord(nextFee, 'fiat'); + if (nextFeeError) return nextFeeError; + + const record = { + type: 'fiat_dust_sweep', + provider: this.value.provider, + epoch: this.value.epoch, + at: this.value.at, + dust_au: dustAu, + provider_total_before_au: refreshed.total_au, + provider_total_after_au: totalAu, + provider_paid_cum_au: refreshed.paid_cum_au, + destination: 'operator_fee', + swept_by: this.address, + swept_by_role: 'admin', + swept_at: this.tx, + }; + await this.put(this.earningKey(this.value.provider, 'fiat'), nextEarning); + await this.put(this.feeCumKey('fiat'), nextFee); + await this.put(recordKey, record); + return { + ok: true, + op: 'fiatDustSweep', + provider: this.value.provider, + epoch: this.value.epoch, + dust_au: dustAu, + idempotent: false, + }; + } + + validateFiatDustSweepValue(value) { + const shapeError = this.validateExactObjectKeys( + value, + ['op', 'provider', 'epoch', 'at'], + 'Fiat dust sweep' + ); + if (shapeError) return shapeError; + if (value.op !== 'fiat_dust_sweep') return new Error('Invalid fiat dust sweep op.'); + if (!this.isHexBytes(value.provider, 32) || value.provider !== value.provider.toLowerCase()) { + return new Error('Invalid fiat dust sweep provider.'); + } + if (!Number.isSafeInteger(value.epoch) || value.epoch < 1) { + return new Error('Invalid fiat dust sweep epoch.'); + } + if (!Number.isSafeInteger(value.at) || value.at < 0) { + return new Error('Invalid fiat dust sweep timestamp.'); + } + return null; + } + + normalizeTargetedFiatSettlementOutput(output) { + if (!this.isSafeKeyPart(output.to)) return new Error('Invalid fiat settlement output target.'); + const sourceCurrency = this.normalizeFiatCurrency(output.source_currency); + if (sourceCurrency instanceof Error || sourceCurrency !== output.source_currency) { + return new Error('Invalid fiat settlement source currency.'); + } + const sourceAmountMinor = this.normalizeFiatMinor(output.source_amount_minor); + if (sourceAmountMinor instanceof Error || sourceAmountMinor === '0') { + return new Error('Fiat settlement source amount must be positive.'); + } + const canonicalAu = {}; + for (const [field, allowZero] of [ + ['liability_au', false], + ['paid_au', false], + ['rounding_au', true], + ['dust_au', true], + ]) { + canonicalAu[field] = this.normalizeAu( + output[field], + `Fiat settlement output ${field}`, + { allowZero } + ); + if (canonicalAu[field] instanceof Error) { + return new Error(`Invalid fiat settlement output ${field}.`); + } + } + const paidPlusDust = this.safeAddAu(canonicalAu.paid_au, canonicalAu.dust_au); + if (paidPlusDust instanceof Error || + this.compareAu(paidPlusDust, canonicalAu.liability_au) !== 0 || + this.compareAu(canonicalAu.rounding_au, canonicalAu.dust_au) !== 0) { + return new Error('Fiat settlement output liability, paid amount, rounding, and dust do not balance.'); + } + if (output.role === 'operator_fee') return { + role: 'operator_fee', + to: output.to, + ...canonicalAu, + source_currency: sourceCurrency, + source_amount_minor: sourceAmountMinor, + }; + if (!this.isHexBytes(output.provider, 32) || + output.provider !== output.provider.toLowerCase()) { + return new Error('Invalid fiat settlement provider.'); + } + const destinationCurrency = this.normalizeFiatCurrency(output.destination_currency); + if (destinationCurrency instanceof Error || + destinationCurrency !== output.destination_currency) { + return new Error('Invalid fiat settlement destination currency.'); + } + const destinationAmountMinor = this.normalizeFiatMinor(output.destination_amount_minor); + if (destinationAmountMinor instanceof Error || destinationAmountMinor === '0') { + return new Error('Fiat settlement destination amount must be positive.'); + } + const destinationAmountMinMinor = this.normalizeFiatMinor( + output.destination_amount_min_minor + ); + const destinationAmountMaxMinor = this.normalizeFiatMinor( + output.destination_amount_max_minor + ); + if (destinationAmountMinMinor instanceof Error || + destinationAmountMaxMinor instanceof Error || + destinationAmountMinMinor === '0' || + BigInt(destinationAmountMaxMinor) < BigInt(destinationAmountMinMinor) || + BigInt(destinationAmountMinor) < BigInt(destinationAmountMinMinor) || + BigInt(destinationAmountMinor) > BigInt(destinationAmountMaxMinor)) { + return new Error( + 'Fiat settlement destination amount must be within its authorized range.' + ); + } + const hasFxQuoteId = hasOwn(output, 'fx_quote_id'); + const hasFxQuoteHash = hasOwn(output, 'fx_quote_hash'); + if (hasFxQuoteId !== hasFxQuoteHash) { + return new Error('Fiat settlement FX quote identity must be present or absent as a pair.'); + } + const requiresUsdValuationQuote = + sourceCurrency !== 'usd' || destinationCurrency !== 'usd'; + if (requiresUsdValuationQuote && + (!hasFxQuoteId || + !/^fxq_[A-Za-z0-9._-]+$/.test(String(output.fx_quote_id || '')) || + !this.isHexBytes(output.fx_quote_hash, 32) || + output.fx_quote_hash !== output.fx_quote_hash.toLowerCase())) { + return new Error('Fiat settlement FX quote identity is required and invalid.'); + } + if (!requiresUsdValuationQuote && hasFxQuoteId && + (output.fx_quote_id !== null || output.fx_quote_hash !== null)) { + return new Error('Direct USD fiat settlement must not include an FX quote identity.'); + } + const normalized = { + role: 'provider', + provider: output.provider, + to: output.to, + ...canonicalAu, + source_currency: sourceCurrency, + source_amount_minor: sourceAmountMinor, + destination_currency: destinationCurrency, + destination_amount_min_minor: destinationAmountMinMinor, + destination_amount_max_minor: destinationAmountMaxMinor, + destination_amount_minor: destinationAmountMinor, + }; + if (hasFxQuoteId) { + normalized.fx_quote_id = output.fx_quote_id; + normalized.fx_quote_hash = output.fx_quote_hash; + } + return normalized; + } + + targetedFiatSettlementTotals(outputs) { + let providerLiabilityAu = ZERO_AU; + let providerPaidAu = ZERO_AU; + let operatorFeeLiabilityAu = ZERO_AU; + let operatorFeeRetainedAu = ZERO_AU; + let roundingAu = ZERO_AU; + let dustAu = ZERO_AU; + let sourceAmountMinor = 0n; + let sourceCurrency = null; + let providerCount = 0; + const destinationTotals = new Map(); + for (const output of outputs) { + if (sourceCurrency === null) sourceCurrency = output.source_currency; + if (sourceCurrency !== output.source_currency) { + return new Error('Targeted fiat outputs must use one platform source currency.'); + } + sourceAmountMinor += BigInt(output.source_amount_minor); + roundingAu = this.safeAddAu(roundingAu, output.rounding_au); + if (roundingAu instanceof Error) return roundingAu; + dustAu = this.safeAddAu(dustAu, output.dust_au); + if (dustAu instanceof Error) return dustAu; + if (output.role === 'provider') { + providerCount += 1; + providerLiabilityAu = this.safeAddAu(providerLiabilityAu, output.liability_au); + if (providerLiabilityAu instanceof Error) return providerLiabilityAu; + providerPaidAu = this.safeAddAu(providerPaidAu, output.paid_au); + if (providerPaidAu instanceof Error) return providerPaidAu; + destinationTotals.set( + output.destination_currency, + (destinationTotals.get(output.destination_currency) ?? 0n) + + BigInt(output.destination_amount_minor) + ); + } else { + operatorFeeLiabilityAu = this.safeAddAu( + operatorFeeLiabilityAu, + output.liability_au + ); + if (operatorFeeLiabilityAu instanceof Error) return operatorFeeLiabilityAu; + operatorFeeRetainedAu = this.safeAddAu(operatorFeeRetainedAu, output.paid_au); + if (operatorFeeRetainedAu instanceof Error) return operatorFeeRetainedAu; + } + } + const grossLiabilityAu = this.safeAddAu(providerLiabilityAu, operatorFeeLiabilityAu); + if (grossLiabilityAu instanceof Error) return grossLiabilityAu; + const grossPaidAu = this.safeAddAu(providerPaidAu, operatorFeeRetainedAu); + if (grossPaidAu instanceof Error) return grossPaidAu; + return { + provider_count: providerCount, + provider_liability_au: providerLiabilityAu, + provider_paid_au: providerPaidAu, + operator_fee_liability_au: operatorFeeLiabilityAu, + operator_fee_retained_au: operatorFeeRetainedAu, + gross_liability_au: grossLiabilityAu, + gross_paid_au: grossPaidAu, + rounding_au: roundingAu, + dust_au: dustAu, + source_currency: sourceCurrency, + source_amount_minor: sourceAmountMinor.toString(), + destination_totals: [...destinationTotals] + .sort(([left], [right]) => compareCodepoint(left, right)) + .map(([currency, amount]) => ({ currency, amount_minor: amount.toString() })), + }; + } + + normalizeFiatDestinationTotals(value) { + const totals = []; + const seen = new Set(); + for (const entry of value) { + const shapeError = this.validateExactObjectKeys( + entry, + ['currency', 'amount_minor'], + 'targeted fiat destination total' + ); + if (shapeError) return shapeError; + const currency = this.normalizeFiatCurrency(entry.currency); + const amountMinor = this.normalizeFiatMinor(entry.amount_minor); + if (currency instanceof Error || currency !== entry.currency || + amountMinor instanceof Error || amountMinor === '0' || + amountMinor !== entry.amount_minor || + seen.has(currency)) { + return new Error('Invalid targeted fiat destination total.'); + } + seen.add(currency); + totals.push({ currency, amount_minor: amountMinor }); + } + totals.sort((left, right) => compareCodepoint(left.currency, right.currency)); + if (stableJson(totals) !== stableJson(value)) { + return new Error('Targeted fiat destination totals must be canonical.'); + } + return totals; + } + + normalizeFiatMinor(value) { + if (typeof value !== 'string' || !/^(0|[1-9][0-9]*)$/.test(value)) { + return new Error('Fiat minor amount must be a canonical decimal string.'); + } + const parsed = BigInt(value); + if (parsed <= 0n) return new Error('Fiat minor amount must be positive.'); + return parsed.toString(); + } + + async depositTnk() { + const consentError = await this.requireConsent(); + if (consentError) return consentError; + if (!this.isSafeKeyPart(this.value.memo_hash)) return new Error('Invalid deposit memo hash.'); + if (!this.isSafeKeyPart(this.value.treasury_address)) return new Error('Invalid TNK treasury address.'); + const tnkE18 = this.parseTnkE18(this.value.tnk_e18); + if (tnkE18 instanceof Error) return tnkE18; + const quotedAu = this.normalizeAu(this.value.quoted_au, 'TNK quoted credit', { allowZero: false }); + if (quotedAu instanceof Error) return quotedAu; + const quotedRate = this.normalizeAu(this.value.rate_tnk_usd_au, 'TNK quoted rate', { allowZero: false }); + if (quotedRate instanceof Error) return quotedRate; + + const payment = await this.canonicalTnkPaymentConfig(); + if (payment instanceof Error) return payment; + if (this.value.treasury_address !== payment.treasury_address) { + return new Error('TNK deposit intent treasury does not match canonical payment config.'); + } + const rateLock = await this.guardianAcceptTnkDepositIntentRate(this.value); + if (rateLock instanceof Error) return rateLock; + const msbFrom = this.msbAddressForPublicKey(this.address, payment.network); + if (msbFrom instanceof Error) return msbFrom; + + const key = `dep/pending/${this.value.memo_hash}`; + if ((await this.get(key)) !== null) return new Error('TNK deposit memo already pending.'); + if ((await this.get(`dep/tnk-credited/${this.value.memo_hash}`)) !== null) { + return new Error('TNK deposit memo already credited.'); + } + + const record = { + memo_hash: this.value.memo_hash, + user: this.address, + status: 'pending', + requested_at: this.tx, + msb_network: payment.network, + msb_from: msbFrom, + treasury_address: this.value.treasury_address, + tnk_e18: this.value.tnk_e18, + quoted_au: quotedAu, + rate_tnk_usd_au: quotedRate, + rate_source: rateLock.source, + rate_ts: rateLock.ts, + rate_record_key: rateLock.updated_at, + }; + await this.put(key, record); + console.log('mayhem depositTnk', record); + return { + ok: true, + op: 'depositTnk', + memo_hash: this.value.memo_hash, + user: this.address, + }; + } + + async tnkDeposit() { + const adminError = await this.requireAdmin(); + if (adminError) return adminError; + const shapeError = this.validateExactObjectKeys( + this.value, + ['op', 'memo_hash', 'msb_transfer', 'epoch', 'at'], + 'TNK deposit credit' + ); + if (shapeError) return shapeError; + if (this.value.op !== 'tnk_deposit') return new Error('Invalid TNK deposit credit op.'); + if (!this.isSafeKeyPart(this.value.memo_hash)) return new Error('Invalid deposit memo hash.'); + if (!Number.isSafeInteger(this.value.epoch) || this.value.epoch < 1) { + return new Error('Invalid TNK deposit epoch.'); + } + if (!Number.isSafeInteger(this.value.at) || this.value.at < 0) { + return new Error('Invalid TNK deposit timestamp.'); + } + + const transfer = this.normalizeMsbTransferEvidence( + this.value.msb_transfer, + 'TNK deposit MSB transfer evidence' + ); + if (transfer instanceof Error) return transfer; + const creditedKey = `dep/tnk-credited/${this.value.memo_hash}`; + const existingCredit = await this.get(creditedKey); + if (existingCredit) { + if (stableJson(existingCredit.msb_transfer) !== stableJson(transfer)) { + return new Error('TNK deposit memo already credited by a different MSB transfer.'); + } + return { + ok: true, + op: 'tnkDeposit', + who: existingCredit.user, + au: existingCredit.au, + epoch: existingCredit.epoch, + deposit_root: existingCredit.deposit_root, + rate_ts: existingCredit.rate_ts, + msb_tx_hash: transfer.tx_hash, + idempotent: true, + }; + } + + const pendingKey = `dep/pending/${this.value.memo_hash}`; + const pending = await this.get(pendingKey); + if (!pending || pending.status !== 'pending') return new Error('Pending TNK deposit intent not found.'); + + const payment = await this.canonicalTnkPaymentConfig(); + if (payment instanceof Error) return payment; + if ( + pending.msb_network !== payment.network || + pending.treasury_address !== payment.treasury_address + ) { + return new Error('Pending TNK deposit no longer matches canonical payment config.'); + } + if ( + transfer.network !== payment.network || + transfer.from !== pending.msb_from || + transfer.to !== payment.treasury_address + ) { + return new Error('TNK deposit MSB transfer identity does not match pending intent.'); + } + const transferSeenKey = this.msbTransferSeenKey(transfer); + if ((await this.get(transferSeenKey)) !== null) { + return new Error('MSB transfer already consumed by Mayhem.'); + } + + const tnkE18 = this.parseTnkE18(transfer.amount_e18); + if (tnkE18 instanceof Error) return tnkE18; + const pendingTnkE18 = this.parseTnkE18(pending.tnk_e18); + if (pendingTnkE18 instanceof Error) return pendingTnkE18; + if (pendingTnkE18 !== tnkE18) return new Error('TNK deposit amount does not match pending intent.'); + const rate = await this.guardianRequireTnkDepositRateLock(pending, this.value.at); + if (rate instanceof Error) return rate; + const au = this.tnkE18ToAu(tnkE18, pending.rate_tnk_usd_au); + if (au instanceof Error) return au; + if (this.isZeroAu(au)) return new Error('TNK deposit converts to zero au.'); + if (this.compareAu(pending.quoted_au, au) !== 0) { + return new Error('TNK deposit credit does not match pending intent.'); + } + + const ledgerRail = 'tnk'; + const balance = await this.balanceRecord(pending.user, ledgerRail); + if (balance instanceof Error) return balance; + const nextAu = this.safeAddAu(balance.au, au); + if (nextAu instanceof Error) return nextAu; + const leaf = await this.depositLeafHash({ + rail: 'tnk', + memo_hash: this.value.memo_hash, + user_hash: await this.opaqueHash('deposit-user', pending.user), + au, + msb_transfer: transfer, + rate_ts: rate.ts, + treasury_address_hash: await this.opaqueHash('deposit-treasury', pending.treasury_address), + quoted_au: pending.quoted_au, + rate_tnk_usd_au: pending.rate_tnk_usd_au, + }); + const depositRoot = await this.nextDepositRoot({ + epoch: this.value.epoch, + leaf, + au, + at: this.value.at, + }); + if (depositRoot instanceof Error) return depositRoot; + + const record = { + ...balance, + rail: ledgerRail, + au: nextAu, + updated_at: this.tx, + last_deposit_rail: ledgerRail, + last_deposit_rate_ts: rate.ts, + }; + await this.put(this.balanceKey(pending.user, ledgerRail), record); + await this.put(`ev/dep/${this.value.epoch}`, depositRoot); + await this.put(creditedKey, { + rail: 'tnk', + memo_hash: this.value.memo_hash, + user: pending.user, + au, + epoch: this.value.epoch, + rate_ts: rate.ts, + rate_source: rate.source, + rate_tnk_usd_au: pending.rate_tnk_usd_au, + deposit_root: depositRoot.merkle_root, + msb_transfer: transfer, + credited_at: this.tx, + }); + await this.put(transferSeenKey, { + rail: 'tnk', + purpose: 'deposit', + memo_hash: this.value.memo_hash, + user: pending.user, + amount_e18: transfer.amount_e18, + consumed_at: this.tx, + }); + await this.del(pendingKey); + console.log('mayhem tnkDeposit', { + who: pending.user, + au, + tnk_e18: transfer.amount_e18, + msb_tx_hash: transfer.tx_hash, + rate_ts: rate.ts, + rate_tnk_usd_au: pending.rate_tnk_usd_au, + epoch: this.value.epoch, + }); + return { + ok: true, + op: 'tnkDeposit', + who: pending.user, + au, + epoch: this.value.epoch, + deposit_root: depositRoot.merkle_root, + rate_ts: rate.ts, + msb_tx_hash: transfer.tx_hash, + idempotent: false, + }; + } + + normalizeTapAccountBinding(value) { + const shapeError = this.validateExactObjectKeys( + value, + [ + 'op', + 'user', + 'ethereum_address', + 'chain_id', + 'pool_address', + 'user_sig', + 'ethereum_sig', + ], + 'TAP account binding' + ); + if (shapeError) return shapeError; + if (value.op !== 'tap_account_bind') return new Error('Invalid TAP account binding op.'); + if (!this.isHexBytes(value.user, 32)) return new Error('Invalid TAP account user.'); + if (!this.isEthHexBytes(value.ethereum_address, 20)) { + return new Error('Invalid TAP Ethereum account.'); + } + if (!Number.isSafeInteger(value.chain_id) || value.chain_id < 1) { + return new Error('Invalid TAP account chain id.'); + } + if (!this.isEthHexBytes(value.pool_address, 20)) { + return new Error('Invalid TAP account pool address.'); + } + if (!this.isHexBytes(value.user_sig, 64)) { + return new Error('Invalid TAP account user signature.'); + } + if (!this.isEthHexBytes(value.ethereum_sig, 65)) { + return new Error('Invalid TAP account Ethereum signature.'); + } + return { + op: 'tap_account_bind', + user: value.user.toLowerCase(), + ethereum_address: value.ethereum_address.toLowerCase(), + chain_id: value.chain_id, + pool_address: value.pool_address.toLowerCase(), + user_sig: value.user_sig.toLowerCase(), + ethereum_sig: value.ethereum_sig.toLowerCase(), + }; + } + + async tapDeposit() { + const adminError = await this.requireAdmin(); + if (adminError) return adminError; + const shapeError = this.validateTapDepositValue(this.value); + if (shapeError) return shapeError; + const poolError = await this.requireCanonicalTapPool( + this.value.chain_id, + this.value.pool_address + ); + if (poolError) return poolError; + + const ethereumAddress = this.value.who.toLowerCase(); + const ethTxHash = this.value.eth_tx_hash.toLowerCase(); + const blockHash = this.value.block_hash.toLowerCase(); + const poolAddress = this.value.pool_address.toLowerCase(); + const eventSignature = this.value.event_signature.toLowerCase(); + const seenKey = `dep/tap/${this.tapDepositIdentity(this.value)}`; + const existing = await this.get(seenKey); + if (existing !== null) { + return { + ok: true, + op: 'tapDeposit', + duplicate: true, + who: existing.who, + ethereum_address: existing.ethereum_address ?? this.value.who.toLowerCase(), + au: ZERO_AU, + credited_au: existing.au ?? null, + epoch: existing.epoch ?? this.value.epoch, + deposit_root: (await this.get(`ev/dep/${existing.epoch ?? this.value.epoch}`))?.merkle_root ?? null, + }; + } + + const tapWei = this.parseTapWei(this.value.tap_wei); + if (tapWei instanceof Error) return tapWei; + const rate = await this.guardianRequireFreshTapRate(this.value.at); + if (rate instanceof Error) return rate; + const au = this.tapWeiToAu(tapWei, rate.tap_usd_au); + if (au instanceof Error) return au; + if (this.isZeroAu(au)) return new Error('TAP deposit converts to zero au.'); + + const binding = await this.get( + this.tapAccountAddressKey(ethereumAddress, this.value.chain_id, poolAddress) + ); + if (!binding || binding.status !== 'active') { + return new Error('TAP account binding required before deposit credit.'); + } + if (!this.isHexBytes(binding.user, 32)) { + return new Error('Invalid TAP account binding user.'); + } + if ( + binding.ethereum_address !== ethereumAddress || + binding.chain_id !== this.value.chain_id || + binding.pool_address !== poolAddress + ) { + return new Error('TAP account binding does not match deposit evidence.'); + } + const who = binding.user; + + const ledgerRail = 'tap'; + const balance = await this.balanceRecord(who, ledgerRail); + if (balance instanceof Error) return balance; + const balanceError = this.guardianValidateBalanceRecord(balance, who, ledgerRail); + if (balanceError) return balanceError; + const nextAu = this.safeAddAu(balance.au, au); + if (nextAu instanceof Error) return nextAu; + const leaf = await this.depositLeafHash({ + rail: 'tap', + user_hash: await this.opaqueHash('deposit-user', who), + ethereum_address_hash: await this.opaqueHash('deposit-ethereum-account', ethereumAddress), + au, + tap_wei: this.value.tap_wei, + tap_usd_au: rate.tap_usd_au, + rate_ts: rate.ts, + rate_source: rate.source, + eth_tx_hash: ethTxHash, + log_index: this.value.log_index, + block_number: this.value.block_number, + block_hash: blockHash, + chain_id: this.value.chain_id, + pool_address_hash: await this.opaqueHash('deposit-pool', poolAddress), + finalized_block_number: this.value.finalized_block_number, + confirmation_depth: this.value.confirmation_depth, + confirmation_policy: this.value.confirmation_policy, + event_signature: eventSignature, + watcher_id: this.value.watcher_id, + }); + const depositRoot = await this.nextDepositRoot({ + epoch: this.value.epoch, + leaf, + au, + at: this.value.at, + }); + if (depositRoot instanceof Error) return depositRoot; + + const seen = { + rail: 'tap', + who, + ethereum_address: ethereumAddress, + tap_wei: this.value.tap_wei, + tap_usd_au: rate.tap_usd_au, + rate_ts: rate.ts, + rate_source: rate.source, + au, + eth_tx_hash: ethTxHash, + log_index: this.value.log_index, + block_number: this.value.block_number, + block_hash: blockHash, + pool_address: poolAddress, + chain_id: this.value.chain_id, + finalized_block_number: this.value.finalized_block_number, + confirmation_depth: this.value.confirmation_depth, + confirmation_policy: this.value.confirmation_policy, + event_signature: eventSignature, + watcher_id: this.value.watcher_id, + epoch: this.value.epoch, + at: this.value.at, + credited_at: this.tx, + credited_by: this.address, + credited_by_role: 'admin', + }; + const record = { + ...balance, + user: who, + rail: ledgerRail, + au: nextAu, + updated_epoch: Math.max(balance.updated_epoch, this.value.epoch), + updated_at: this.tx, + last_deposit_rail: 'tap', + last_deposit_rate_ts: rate.ts, + last_deposit_rate_source: rate.source, + last_deposit_tap_usd_au: rate.tap_usd_au, + }; + await this.put(seenKey, seen); + await this.put(this.balanceKey(who, ledgerRail), record); + await this.put(`ev/dep/${this.value.epoch}`, depositRoot); + console.log('mayhem tapDeposit', { + who, + ethereum_address: ethereumAddress, + au, + tap_wei: this.value.tap_wei, + tap_usd_au: rate.tap_usd_au, + rate_ts: rate.ts, + eth_tx_hash: ethTxHash, + log_index: this.value.log_index, + epoch: this.value.epoch, + }); + return { + ok: true, + op: 'tapDeposit', + duplicate: false, + who, + ethereum_address: ethereumAddress, + au, + epoch: this.value.epoch, + deposit_root: depositRoot.merkle_root, + eth_tx_hash: ethTxHash, + log_index: this.value.log_index, + rate_ts: rate.ts, + }; + } + + validateTapDepositValue(value) { + const shapeError = this.validateExactObjectKeys( + value, + [ + 'op', + 'who', + 'tap_wei', + 'eth_tx_hash', + 'log_index', + 'block_number', + 'block_hash', + 'pool_address', + 'chain_id', + 'finalized_block_number', + 'confirmation_depth', + 'confirmation_policy', + 'event_signature', + 'watcher_id', + 'epoch', + 'at', + ], + 'TAP deposit' + ); + if (shapeError) return shapeError; + if (value.op !== 'tap_deposit') return new Error('Invalid TAP deposit op.'); + if (!this.isSafeKeyPart(value.who)) return new Error('Invalid TAP deposit recipient.'); + if (!this.isEthHexBytes(value.eth_tx_hash, 32)) return new Error('Invalid Ethereum tx hash.'); + if (!Number.isSafeInteger(value.log_index) || value.log_index < 0) { + return new Error('Invalid TAP deposit log index.'); + } + if (!Number.isSafeInteger(value.block_number) || value.block_number < 0) { + return new Error('Invalid TAP deposit block number.'); + } + if (!this.isEthHexBytes(value.block_hash, 32)) return new Error('Invalid TAP deposit block hash.'); + if (!this.isEthHexBytes(value.pool_address, 20)) return new Error('Invalid TAP pool address.'); + if (!Number.isSafeInteger(value.chain_id) || value.chain_id < 1) return new Error('Invalid TAP chain id.'); + if (!Number.isSafeInteger(value.finalized_block_number) || value.finalized_block_number < value.block_number) { + return new Error('Invalid TAP finalized block number.'); + } + if (!Number.isSafeInteger(value.confirmation_depth) || value.confirmation_depth < 0) { + return new Error('Invalid TAP confirmation depth.'); + } + if (value.confirmation_depth !== value.finalized_block_number - value.block_number) { + return new Error('TAP confirmation depth does not match finalized block.'); + } + if (!this.isSafeKeyPart(value.confirmation_policy)) return new Error('Invalid TAP confirmation policy.'); + if (value.confirmation_depth < MIN_TAP_CONFIRMATION_DEPTH) { + return new Error(`TAP confirmation depth below minimum ${MIN_TAP_CONFIRMATION_DEPTH}.`); + } + if (value.chain_id === 1 && value.confirmation_policy !== 'finalized-tag') { + return new Error('Ethereum mainnet TAP deposits require finalized-tag policy.'); + } + if (value.confirmation_policy !== 'finalized-tag') { + if (value.confirmation_policy !== `depth-${value.confirmation_depth}`) { + return new Error('TAP confirmation policy must match the confirmed depth or use finalized-tag.'); + } + } + if (!this.isEthHexBytes(value.event_signature, 32)) return new Error('Invalid TAP event signature.'); + if (value.event_signature.toLowerCase() !== TAP_DEPOSIT_EVENT_SIGNATURE) { + return new Error('TAP deposit event signature mismatch.'); + } + if (value.watcher_id !== TAP_DEPOSIT_WATCHER_ID) return new Error('Invalid TAP deposit watcher id.'); + if (!Number.isSafeInteger(value.epoch) || value.epoch < 1) return new Error('Invalid TAP deposit epoch.'); + if (!Number.isSafeInteger(value.at) || value.at < 0) return new Error('Invalid TAP deposit timestamp.'); + const tapWei = this.parseTapWei(value.tap_wei); + if (tapWei instanceof Error) return tapWei; + return null; + } + + async tapDepositReversal() { + const adminError = await this.requireAdmin(); + if (adminError) return adminError; + const shapeError = this.validateTapDepositReversalValue(this.value); + if (shapeError) return shapeError; + const poolError = await this.requireCanonicalTapPool( + this.value.chain_id, + this.value.pool_address + ); + if (poolError) return poolError; + + const depositSeenKey = `dep/tap/${this.tapDepositIdentity(this.value)}`; + const depositSeen = await this.get(depositSeenKey); + if (depositSeen === null) return new Error('TAP deposit event not found.'); + if (depositSeen.block_number !== this.value.block_number) { + return new Error('TAP reversal block number does not match credited event.'); + } + const reversalSeenKey = `${depositSeenKey}/reversal`; + const existing = await this.get(reversalSeenKey); + if (existing !== null) { + return { + ok: true, + op: 'tapDepositReversal', + duplicate: true, + who: existing.who, + au: ZERO_AU, + reversed_au: existing.au, + clawback_au: ZERO_AU, + credited_clawback_au: existing.clawback_au, + network_absorbed_au: ZERO_AU, + credited_network_absorbed_au: existing.network_absorbed_au, + frozen: existing.frozen, + epoch: existing.epoch, + }; + } + if (depositSeen.reversed === true) return new Error('TAP deposit is already reversed.'); + + const who = depositSeen.who; + const au = this.normalizeAu(depositSeen.au, 'credited TAP deposit amount', { allowZero: false }); + if (au instanceof Error) return au; + const balance = await this.balanceRecord(who, 'tap'); + if (balance instanceof Error) return balance; + const balanceError = this.guardianValidateBalanceRecord(balance, who, 'tap'); + if (balanceError) return balanceError; + const clawbackAu = this.compareAu(balance.au, au) < 0 ? balance.au : au; + const networkAbsorbedAu = this.safeSubAu(au, clawbackAu); + if (networkAbsorbedAu instanceof Error) return networkAbsorbedAu; + const nextAu = this.safeSubAu(balance.au, clawbackAu); + if (nextAu instanceof Error) return nextAu; + const frozen = !this.isZeroAu(networkAbsorbedAu); + + const leaf = await this.depositLeafHash({ + rail: 'tap', + user_hash: await this.opaqueHash('deposit-user', who), + ethereum_address_hash: await this.opaqueHash( + 'deposit-ethereum-account', + depositSeen.ethereum_address + ), + au, + clawback_au: clawbackAu, + network_absorbed_au: networkAbsorbedAu, + chain_id: this.value.chain_id, + pool_address_hash: await this.opaqueHash('deposit-pool', this.value.pool_address), + eth_tx_hash: this.value.eth_tx_hash, + log_index: this.value.log_index, + block_number: this.value.block_number, + block_hash: this.value.block_hash, + reconciliation_from_block: this.value.reconciliation_from_block, + reconciliation_to_block: this.value.reconciliation_to_block, + finalized_block_number: this.value.finalized_block_number, + confirmation_policy: this.value.confirmation_policy, + watcher_id: this.value.watcher_id, + reason: this.value.reason, + reversed: true, + }); + const depositRoot = await this.nextDepositReversalRoot({ + epoch: this.value.epoch, + leaf, + disputedAu: au, + clawbackAu, + absorbedAu: networkAbsorbedAu, + at: this.value.at, + }); + if (depositRoot instanceof Error) return depositRoot; + + const reversalSeen = { + rail: 'tap', + who, + ethereum_address: depositSeen.ethereum_address, + au, + clawback_au: clawbackAu, + network_absorbed_au: networkAbsorbedAu, + chain_id: this.value.chain_id, + pool_address: this.value.pool_address.toLowerCase(), + eth_tx_hash: this.value.eth_tx_hash.toLowerCase(), + log_index: this.value.log_index, + block_number: this.value.block_number, + block_hash: this.value.block_hash.toLowerCase(), + reconciliation_from_block: this.value.reconciliation_from_block, + reconciliation_to_block: this.value.reconciliation_to_block, + finalized_block_number: this.value.finalized_block_number, + confirmation_policy: this.value.confirmation_policy, + watcher_id: this.value.watcher_id, + reason: this.value.reason, + frozen, + epoch: this.value.epoch, + at: this.value.at, + reversed_at: this.tx, + reversed_by: this.address, + reversed_by_role: 'admin', + }; + let freezeRecord = null; + if (frozen) { + const existingFrozen = await this.get(`frozen/${who}`); + const disputedAuCum = this.safeAddAu(existingFrozen?.disputed_au_cum ?? ZERO_AU, au); + if (disputedAuCum instanceof Error) return disputedAuCum; + const clawbackAuCum = this.safeAddAu(existingFrozen?.clawback_au_cum ?? ZERO_AU, clawbackAu); + if (clawbackAuCum instanceof Error) return clawbackAuCum; + const absorbedAuCum = this.safeAddAu( + existingFrozen?.network_absorbed_au_cum ?? ZERO_AU, + networkAbsorbedAu + ); + if (absorbedAuCum instanceof Error) return absorbedAuCum; + freezeRecord = { + user: who, + status: 'frozen', + reason: 'tap_deposit_reorg_shortfall', + rail: 'tap', + first_frozen_at: existingFrozen?.first_frozen_at ?? this.tx, + first_frozen_at_seconds: existingFrozen?.first_frozen_at_seconds ?? this.value.at, + updated_at: this.tx, + updated_at_seconds: this.value.at, + updated_epoch: Math.max(existingFrozen?.updated_epoch ?? 0, this.value.epoch), + dispute_count: (existingFrozen?.dispute_count ?? 0) + 1, + disputed_au_cum: disputedAuCum, + clawback_au_cum: clawbackAuCum, + network_absorbed_au_cum: absorbedAuCum, + last_tap_deposit_identity: this.tapDepositIdentity(this.value), + }; + } + + await this.put(this.balanceKey(who, 'tap'), { + ...balance, + au: nextAu, + updated_epoch: Math.max(balance.updated_epoch, this.value.epoch), + updated_at: this.tx, + last_deposit_reversal_rail: 'tap', + last_deposit_reversal_at: this.tx, + }); + await this.put(depositSeenKey, { + ...depositSeen, + reversed: true, + reversed_au: au, + clawback_au: clawbackAu, + network_absorbed_au: networkAbsorbedAu, + reversed_at: this.tx, + reversed_at_seconds: this.value.at, + reversed_epoch: this.value.epoch, + }); + await this.put(reversalSeenKey, reversalSeen); + if (freezeRecord) await this.put(`frozen/${who}`, freezeRecord); + await this.put(`ev/dep/${this.value.epoch}`, depositRoot); + return { + ok: true, + op: 'tapDepositReversal', + duplicate: false, + who, + au, + clawback_au: clawbackAu, + network_absorbed_au: networkAbsorbedAu, + frozen, + epoch: this.value.epoch, + deposit_root: depositRoot.merkle_root, + }; + } + + validateTapDepositReversalValue(value) { + const shapeError = this.validateExactObjectKeys( + value, + [ + 'op', + 'chain_id', + 'pool_address', + 'eth_tx_hash', + 'log_index', + 'block_number', + 'block_hash', + 'reconciliation_from_block', + 'reconciliation_to_block', + 'finalized_block_number', + 'confirmation_policy', + 'watcher_id', + 'reason', + 'epoch', + 'at', + ], + 'TAP deposit reversal' + ); + if (shapeError) return shapeError; + if (value.op !== 'tap_deposit_reversal') return new Error('Invalid TAP deposit reversal op.'); + const identityError = this.validateTapDepositIdentity(value); + if (identityError) return identityError; + if (!Number.isSafeInteger(value.block_number) || value.block_number < 0) { + return new Error('Invalid TAP reversal block number.'); + } + if (!Number.isSafeInteger(value.reconciliation_from_block) + || value.reconciliation_from_block > value.block_number) { + return new Error('Invalid TAP reversal reconciliation start.'); + } + if (!Number.isSafeInteger(value.reconciliation_to_block) + || value.reconciliation_to_block < value.block_number) { + return new Error('Invalid TAP reversal reconciliation end.'); + } + if (!Number.isSafeInteger(value.finalized_block_number) + || value.finalized_block_number - value.reconciliation_to_block < MIN_TAP_CONFIRMATION_DEPTH) { + return new Error('TAP reversal is not sufficiently behind the finalized reference.'); + } + if (value.confirmation_policy !== 'finalized-tag') { + return new Error('TAP reversal requires finalized-tag policy.'); + } + if (value.watcher_id !== TAP_DEPOSIT_WATCHER_ID) return new Error('TAP watcher id mismatch.'); + if (value.reason !== 'canonical_event_missing') return new Error('Invalid TAP reversal reason.'); + if (!Number.isSafeInteger(value.epoch) || value.epoch < 1) return new Error('Invalid TAP reversal epoch.'); + if (!Number.isSafeInteger(value.at) || value.at < 0) return new Error('Invalid TAP reversal timestamp.'); + return null; + } + + async fiatDeposit() { + const adminError = await this.requireAdmin(); + if (adminError) return adminError; + if (!FIAT_DEPOSIT_RAILS.has(this.value.rail)) return new Error('Unsupported fiat deposit rail.'); + if (!this.isSafeKeyPart(this.value.who)) return new Error('Invalid deposit recipient.'); + if (!this.isSafeKeyPart(this.value.ext_ref_hash)) return new Error('Invalid external reference hash.'); + const fiat = this.fiatEvidenceFields(); + if (fiat instanceof Error) return fiat; + const au = this.normalizeAu(this.value.au, 'fiat deposit amount', { allowZero: false }); + if (au instanceof Error) return au; + + const seenKey = `dep/fiat/${this.value.ext_ref_hash}`; + const existing = await this.get(seenKey); + if (existing !== null) { + return { + ok: true, + op: 'fiatDeposit', + duplicate: true, + rail: existing.rail ?? 'fiat', + processor_rail: existing.processor_rail ?? this.value.rail, + who: existing.who, + au: ZERO_AU, + credited_au: existing.au ?? null, + epoch: existing.epoch ?? this.value.epoch, + deposit_root: (await this.get(`ev/dep/${existing.epoch ?? this.value.epoch}`))?.merkle_root ?? null, + }; + } + + const ledgerRail = 'fiat'; + const balance = await this.balanceRecord(this.value.who, ledgerRail); + if (balance instanceof Error) return balance; + const balanceError = this.guardianValidateBalanceRecord(balance, this.value.who, ledgerRail); + if (balanceError) return balanceError; + const nextAu = this.safeAddAu(balance.au, au); + if (nextAu instanceof Error) return nextAu; + const leaf = await this.depositLeafHash({ + rail: ledgerRail, + processor_rail: this.value.rail, + user_hash: await this.opaqueHash('deposit-user', this.value.who), + au, + ext_ref_hash: this.value.ext_ref_hash, + ...fiat, + }); + const depositRoot = await this.nextDepositRoot({ + epoch: this.value.epoch, + leaf, + au, + at: this.value.at, + }); + if (depositRoot instanceof Error) return depositRoot; + + const record = { + ...balance, + rail: ledgerRail, + au: nextAu, + updated_epoch: Math.max(balance.updated_epoch, this.value.epoch), + updated_at: this.tx, + last_deposit_rail: ledgerRail, + last_deposit_processor_rail: this.value.rail, + ...(fiat.fiat_currency ? { last_deposit_fiat_currency: fiat.fiat_currency } : {}), + }; + const seen = { + rail: ledgerRail, + processor_rail: this.value.rail, + who: this.value.who, + au, + ext_ref_hash: this.value.ext_ref_hash, + ...fiat, + epoch: this.value.epoch, + at: this.value.at, + credited_at: this.tx, + credited_by: this.address, + credited_by_role: 'admin', + chargeback_au_cum: ZERO_AU, + network_absorbed_au_cum: ZERO_AU, + }; + await this.put(seenKey, seen); + await this.put(this.balanceKey(this.value.who, ledgerRail), record); + await this.put(`ev/dep/${this.value.epoch}`, depositRoot); + console.log('mayhem fiatDeposit', { + rail: ledgerRail, + processor_rail: this.value.rail, + who: this.value.who, + au, + ...fiat, + epoch: this.value.epoch, + }); + return { + ok: true, + op: 'fiatDeposit', + rail: ledgerRail, + processor_rail: this.value.rail, + who: this.value.who, + au, + duplicate: false, + epoch: this.value.epoch, + deposit_root: depositRoot.merkle_root, + ...fiat, + }; + } + + async fiatChargeback() { + const adminError = await this.requireAdmin(); + if (adminError) return adminError; + if (!FIAT_DEPOSIT_RAILS.has(this.value.rail)) return new Error('Unsupported fiat chargeback rail.'); + if (!this.isSafeKeyPart(this.value.who)) return new Error('Invalid chargeback account.'); + if (!this.isSafeKeyPart(this.value.ext_ref_hash)) return new Error('Invalid external reference hash.'); + if (!this.isSafeKeyPart(this.value.dispute_ref_hash)) return new Error('Invalid dispute reference hash.'); + const fiat = this.fiatEvidenceFields(); + if (fiat instanceof Error) return fiat; + const au = this.normalizeAu(this.value.au, 'fiat chargeback amount', { allowZero: false }); + if (au instanceof Error) return au; + + const depositSeenKey = `dep/fiat/${this.value.ext_ref_hash}`; + const depositSeen = await this.get(depositSeenKey); + if (depositSeen === null) return new Error('Fiat deposit reference not found.'); + if (depositSeen.who !== this.value.who) return new Error('Fiat chargeback recipient mismatch.'); + if (depositSeen.processor_rail !== this.value.rail) return new Error('Fiat chargeback processor rail mismatch.'); + const chargebackSeenKey = `${depositSeenKey}/chargeback/${this.value.dispute_ref_hash}`; + const existingChargeback = await this.get(chargebackSeenKey); + if (existingChargeback !== null) { + return { + ok: true, + op: 'fiatChargeback', + duplicate: true, + rail: existingChargeback.rail ?? 'fiat', + processor_rail: existingChargeback.processor_rail ?? this.value.rail, + who: existingChargeback.who, + au: ZERO_AU, + disputed_au: existingChargeback.au ?? null, + clawback_au: ZERO_AU, + credited_clawback_au: existingChargeback.clawback_au ?? null, + network_absorbed_au: ZERO_AU, + frozen: true, + epoch: existingChargeback.epoch ?? this.value.epoch, + deposit_root: (await this.get(`ev/dep/${existingChargeback.epoch ?? this.value.epoch}`))?.merkle_root ?? null, + ...fiat, + }; + } + const depositDisputedAuCum = this.safeAddAu(depositSeen.disputed_au_cum ?? ZERO_AU, au); + if (depositDisputedAuCum instanceof Error) return depositDisputedAuCum; + if (this.compareAu(depositDisputedAuCum, depositSeen.au) > 0) { + return new Error('Fiat chargeback exceeds original deposit.'); + } + + const ledgerRail = 'fiat'; + const balance = await this.balanceRecord(this.value.who, ledgerRail); + if (balance instanceof Error) return balance; + const balanceError = this.guardianValidateBalanceRecord(balance, this.value.who, ledgerRail); + if (balanceError) return balanceError; + const clawbackAu = this.compareAu(balance.au, au) < 0 ? balance.au : au; + const networkAbsorbedAu = this.safeSubAu(au, clawbackAu); + if (networkAbsorbedAu instanceof Error) return networkAbsorbedAu; + const nextAu = this.safeSubAu(balance.au, clawbackAu); + if (nextAu instanceof Error) return nextAu; + + const leaf = await this.depositLeafHash({ + rail: ledgerRail, + processor_rail: this.value.rail, + user_hash: await this.opaqueHash('deposit-user', this.value.who), + au, + clawback_au: clawbackAu, + network_absorbed_au: networkAbsorbedAu, + ext_ref_hash: this.value.ext_ref_hash, + dispute_ref_hash: this.value.dispute_ref_hash, + reversed: true, + ...fiat, + }); + const depositRoot = await this.nextDepositReversalRoot({ + epoch: this.value.epoch, + leaf, + disputedAu: au, + clawbackAu, + absorbedAu: networkAbsorbedAu, + at: this.value.at, + }); + if (depositRoot instanceof Error) return depositRoot; + + const frozen = await this.get(`frozen/${this.value.who}`); + const frozenDisputedAuCum = this.safeAddAu(frozen?.disputed_au_cum ?? ZERO_AU, au); + if (frozenDisputedAuCum instanceof Error) return frozenDisputedAuCum; + const clawbackAuCum = this.safeAddAu(frozen?.clawback_au_cum ?? ZERO_AU, clawbackAu); + if (clawbackAuCum instanceof Error) return clawbackAuCum; + const absorbedAuCum = this.safeAddAu(frozen?.network_absorbed_au_cum ?? ZERO_AU, networkAbsorbedAu); + if (absorbedAuCum instanceof Error) return absorbedAuCum; + const freezeRecord = { + user: this.value.who, + status: 'frozen', + reason: 'fiat_chargeback', + rail: ledgerRail, + processor_rail: this.value.rail, + first_frozen_at: frozen?.first_frozen_at ?? this.tx, + first_frozen_at_seconds: frozen?.first_frozen_at_seconds ?? this.value.at, + updated_at: this.tx, + updated_at_seconds: this.value.at, + updated_epoch: Math.max(frozen?.updated_epoch ?? 0, this.value.epoch), + dispute_count: (frozen?.dispute_count ?? 0) + 1, + disputed_au_cum: frozenDisputedAuCum, + clawback_au_cum: clawbackAuCum, + network_absorbed_au_cum: absorbedAuCum, + last_ext_ref_hash: this.value.ext_ref_hash, + last_dispute_ref_hash: this.value.dispute_ref_hash, + last_fiat_currency: fiat.fiat_currency, + }; + const depositChargebackAuCum = this.safeAddAu(depositSeen.chargeback_au_cum ?? ZERO_AU, clawbackAu); + if (depositChargebackAuCum instanceof Error) return depositChargebackAuCum; + const depositAbsorbedAuCum = this.safeAddAu(depositSeen.network_absorbed_au_cum ?? ZERO_AU, networkAbsorbedAu); + if (depositAbsorbedAuCum instanceof Error) return depositAbsorbedAuCum; + const chargebackSeen = { + rail: ledgerRail, + processor_rail: this.value.rail, + who: this.value.who, + au, + clawback_au: clawbackAu, + network_absorbed_au: networkAbsorbedAu, + ext_ref_hash: this.value.ext_ref_hash, + dispute_ref_hash: this.value.dispute_ref_hash, + ...fiat, + epoch: this.value.epoch, + at: this.value.at, + credited_at: this.tx, + credited_by: this.address, + credited_by_role: 'admin', + }; + + await this.put(this.balanceKey(this.value.who, ledgerRail), { + ...balance, + rail: ledgerRail, + au: nextAu, + updated_epoch: Math.max(balance.updated_epoch, this.value.epoch), + updated_at: this.tx, + last_chargeback_rail: ledgerRail, + last_chargeback_processor_rail: this.value.rail, + last_chargeback_fiat_currency: fiat.fiat_currency, + }); + await this.put(depositSeenKey, { + ...depositSeen, + disputed_au_cum: depositDisputedAuCum, + chargeback_au_cum: depositChargebackAuCum, + network_absorbed_au_cum: depositAbsorbedAuCum, + last_dispute_ref_hash: this.value.dispute_ref_hash, + last_chargeback_at: this.tx, + last_chargeback_at_seconds: this.value.at, + last_chargeback_epoch: this.value.epoch, + }); + await this.put(chargebackSeenKey, chargebackSeen); + await this.put(`frozen/${this.value.who}`, freezeRecord); + await this.put(`ev/dep/${this.value.epoch}`, depositRoot); + console.log('mayhem fiatChargeback', { + rail: ledgerRail, + processor_rail: this.value.rail, + who: this.value.who, + au, + duplicate: false, + clawback_au: clawbackAu, + network_absorbed_au: networkAbsorbedAu, + ...fiat, + epoch: this.value.epoch, + }); + return { + ok: true, + op: 'fiatChargeback', + rail: ledgerRail, + processor_rail: this.value.rail, + who: this.value.who, + au: this.value.au, + clawback_au: clawbackAu, + network_absorbed_au: networkAbsorbedAu, + frozen: true, + epoch: this.value.epoch, + deposit_root: depositRoot.merkle_root, + ...fiat, + }; + } + + async currentRules() { + return await this.get(CURRENT_RULES_KEY); + } + + async isAdmin(sender = this.address) { + const admin = await this.get('admin'); + return typeof admin === 'string' && admin === sender; + } + + async requireAdmin(sender = this.address) { + if (await this.isAdmin(sender)) return null; + return new Error('Admin required.'); + } + + async requireConsent(sender = this.address) { + if (!sender) return new Error('Consent required.'); + + const rules = await this.currentRules(); + if (!rules) return new Error('Rules are not set.'); + + const consent = await this.get(`consent/${sender}`); + if (!consent || consent.ver !== rules.ver || consent.hash !== rules.hash) { + return new Error(`Consent required for rules version ${rules.ver}.`); + } + const frozen = await this.get(`frozen/${sender}`); + if (frozen?.status === 'frozen') return new Error('Account frozen.'); + return null; + } + + async requireProvider(sender = this.address) { + const provider = await this.get(`prov/${sender}`); + if (!provider || provider.status !== 'active') return new Error('Provider registration required.'); + return null; + } + + async requireCurrentAdminPrice(enclaveId, ctxBracket = null) { + const enclave = await this.get(`enclave/${enclaveId}`); + const resolvedCtxBracket = + ctxBracket ?? + (enclave && this.enclaveUsesCtxPrice(enclave) + ? await this.defaultPriceCtxBracketForEnclave(enclave, 0) + : null); + if (resolvedCtxBracket instanceof Error) return resolvedCtxBracket; + const schedule = await this.get(this.priceScheduleKey(enclaveId, resolvedCtxBracket)); + const current = schedule?.current; + if (!current) { + return new Error('Current admin price required before provider serving.'); + } + if (schedule.denom !== PRICE_DENOMINATION || current.denom !== PRICE_DENOMINATION) { + return new Error('Provider serving requires a current au_usd admin price.'); + } + if (current.enclave_id !== enclaveId) { + return new Error('Provider serving requires a current admin-set enclave price.'); + } + if ((current.ctx_bracket ?? null) !== (resolvedCtxBracket ?? null)) { + return new Error('Provider serving requires a current admin-set enclave price for the context bracket.'); + } + if (!current.set_by || typeof current.set_by !== 'string') { + return new Error('Provider serving requires a current admin-set enclave price.'); + } + const admin = await this.get('admin'); + if (admin === null) { + return new Error('Provider serving requires a current admin key.'); + } + if (current.set_by !== admin) { + return new Error('Provider serving requires a current price set by the current admin.'); + } + if (current.set_by_role !== 'admin') { + return new Error('Provider serving requires a current admin-set enclave price.'); + } + return null; + } + + enclaveUsesCtxPrice(enclave) { + return this.modelClassFor(enclave) === DEFAULT_MODEL_CLASS; + } + + enclaveCtxCapacity(enclave) { + const caps = enclave?.caps && typeof enclave.caps === 'object' && !Array.isArray(enclave.caps) + ? enclave.caps + : {}; + const value = caps.ctx_max ?? caps.ctx ?? 0; + if (!Number.isSafeInteger(value) || value < 0) { + return new Error('Invalid enclave context capacity.'); + } + return value; + } + + async defaultPriceCtxBracketForEnclave(enclave, at) { + const table = await this.ctxBracketTableAt(at); + if (table instanceof Error) return table; + const ctx = this.enclaveCtxCapacity(enclave); + if (ctx instanceof Error) return ctx; + const bracket = ctxBracketForTokens(ctx, table.brackets); + if (!bracket) return new Error('No context bracket covers enclave context capacity.'); + return bracket; + } + + async priceCtxMetaForEnclave(enclave, ctxBracket, at, label = 'Price') { + if (!this.enclaveUsesCtxPrice(enclave)) { + if (ctxBracket !== undefined && ctxBracket !== null) { + return new Error(`${label} context bracket is only valid for text-generation enclaves.`); + } + return null; + } + const table = await this.ctxBracketTableAt(at); + if (table instanceof Error) return table; + const bracket = ctxBracket ?? await this.defaultPriceCtxBracketForEnclave(enclave, at); + if (bracket instanceof Error) return bracket; + if (!this.isSafeKeyPart(bracket)) return new Error(`Invalid ${label} context bracket.`); + if (!Array.isArray(table.brackets) || !table.brackets.some((entry) => entry.id === bracket)) { + return new Error(`${label} context bracket is not in the active admin table.`); + } + return { ctx_bracket: bracket, ctx_bracket_table_ver: table.ver }; + } + + priceScheduleKey(enclaveId, ctxBracket = null) { + return ctxBracket ? `price/${enclaveId}/${ctxBracket}` : `price/${enclaveId}`; + } + + priceRecordKey(enclaveId, ver, ctxBracket = null) { + return ctxBracket ? `price/${enclaveId}/${ctxBracket}/v/${ver}` : `price/${enclaveId}/v/${ver}`; + } + + priceMarketKey(enclaveId, ctxBracket = null) { + return stableJson([enclaveId, ctxBracket ?? null]); + } + + requireAdminCreatedEnclave(enclave) { + if (enclave?.created_by_role !== 'admin') { + return new Error('Canonical serving requires an admin-created enclave.'); + } + return null; + } + + requireAdminCreatedRoom(room) { + if (room?.creator_role !== 'admin') { + return new Error('Provider room serving requires an admin-created room.'); + } + return null; + } + + validateExactCommandValue(allowedKeys, opName, optionalKeys = []) { + if (!this.value || typeof this.value !== 'object' || Array.isArray(this.value)) { + return new Error(`${opName} value must be an object.`); + } + const allowed = new Set([...allowedKeys, ...optionalKeys]); + const unknown = Object.keys(this.value).filter((key) => !allowed.has(key)).sort(); + if (unknown.length > 0) { + return new Error(`${opName} does not accept provider-authored fields: ${unknown.join(', ')}.`); + } + for (const key of allowedKeys) { + if (!hasOwn(this.value, key)) return new Error(`${opName} is missing ${key}.`); + } + if (hasOwn(this.value, 'op') && this.value.op !== opName) { + return new Error(`Invalid ${opName} op.`); + } + return null; + } + + validateExactObjectKeys(value, allowedKeys, label) { + if (!value || typeof value !== 'object' || Array.isArray(value)) { + return new Error(`${label} value must be an object.`); + } + const allowed = new Set(allowedKeys); + const unknown = Object.keys(value).filter((key) => !allowed.has(key)).sort(); + if (unknown.length > 0) { + return new Error(`${label} does not accept fields: ${unknown.join(', ')}.`); + } + for (const key of allowedKeys) { + if (!hasOwn(value, key)) return new Error(`${label} is missing ${key}.`); + } + return null; + } + + normalizeCtxBracketTable(brackets) { + if (!Array.isArray(brackets) || brackets.length === 0 || brackets.length > 32) { + return new Error('Context bracket table must be a non-empty array.'); + } + const ids = new Set(); + let previousMax = 0; + const normalized = []; + for (let idx = 0; idx < brackets.length; idx += 1) { + const entry = brackets[idx]; + const shapeError = this.validateExactObjectKeys(entry, ['id', 'max_ctx'], 'context bracket'); + if (shapeError) return shapeError; + if (!this.isSafeKeyPart(entry.id)) return new Error('Invalid context bracket id.'); + if (ids.has(entry.id)) return new Error('Duplicate context bracket id.'); + ids.add(entry.id); + + const isLast = idx === brackets.length - 1; + if (isLast) { + if (entry.max_ctx !== null) return new Error('Last context bracket max_ctx must be null.'); + normalized.push({ id: entry.id, max_ctx: null }); + continue; + } + if (!Number.isSafeInteger(entry.max_ctx) || entry.max_ctx <= previousMax) { + return new Error('Context bracket max_ctx values must increase.'); + } + previousMax = entry.max_ctx; + normalized.push({ id: entry.id, max_ctx: entry.max_ctx }); + } + return normalized; + } + + defaultCtxBracketTableRecord() { + return { + ver: CTX_BRACKET_TABLE_VERSION, + brackets: cloneValue(CTX_BRACKETS), + submitted_at: 0, + effective_at: 0, + effective_from: null, + updated_at: null, + set_by: null, + set_by_role: 'genesis', + }; + } + + async ctxBracketSchedule() { + if (!this.storage) return { current: this.defaultCtxBracketTableRecord(), pending: null }; + const stored = await this.get('ctx_brackets'); + const fallback = this.defaultCtxBracketTableRecord(); + if (!stored) return { current: fallback, pending: null }; + return { + current: stored.current ?? fallback, + pending: stored.pending ?? null, + }; + } + + ctxBracketLatestEntry(schedule) { + if (schedule.pending && schedule.pending.ver > schedule.current.ver) return schedule.pending; + return schedule.current; + } + + ctxBracketActiveEntry(schedule, at) { + if (schedule.pending && schedule.pending.effective_at <= at) return schedule.pending; + return schedule.current; + } + + async ctxBracketTableAt(at) { + if (!Number.isSafeInteger(at) || at < 0) return new Error('Invalid context bracket timestamp.'); + return cloneValue(this.ctxBracketActiveEntry(await this.ctxBracketSchedule(), at)); + } + + async ctxBracketTableByVersion(ver) { + if (!Number.isSafeInteger(ver) || ver < 1) return new Error('Invalid context bracket table version.'); + if (ver === CTX_BRACKET_TABLE_VERSION) return this.defaultCtxBracketTableRecord(); + const record = await this.get(`ctx_brackets/v/${ver}`); + if (!record) return new Error('Unknown context bracket table version.'); + return cloneValue(record); + } + + validateCtxBracketEvidence(tokens, bracket, tableVer, table, label) { + if (typeof bracket !== 'string') return new Error(`Invalid ${label} context bracket.`); + if (!Number.isSafeInteger(tableVer) || tableVer < 1) { + return new Error(`Invalid ${label} context bracket table version.`); + } + if (!table || table.ver !== tableVer) { + return new Error(`${label} context bracket table version mismatch.`); + } + if (!Array.isArray(table.brackets) || !table.brackets.some((entry) => entry.id === bracket)) { + return new Error(`Invalid ${label} context bracket.`); + } + if (ctxBracketForTokens(tokens, table.brackets) !== bracket) { + return new Error(`${label} context bracket does not match served context.`); + } + return null; + } + + async normalizeCtxBracketEvidenceForEnclave(enclaveId, tokens, bracket, tableVer, table, label) { + const enclave = await this.get(`enclave/${enclaveId}`); + if (!enclave) return new Error(`${label} enclave not found.`); + if (!this.enclaveUsesCtxPrice(enclave)) { + if (bracket !== undefined && bracket !== null) { + return new Error(`${label} context bracket is only valid for text-generation enclaves.`); + } + if (tableVer !== undefined && tableVer !== null) { + return new Error(`${label} context bracket table version is only valid for text-generation enclaves.`); + } + return { + enclave, + ctx_bracket: null, + ctx_bracket_table_ver: null, + }; + } + const ctxError = this.validateCtxBracketEvidence(tokens, bracket, tableVer, table, label); + if (ctxError) return ctxError; + return { + enclave, + ctx_bracket: bracket, + ctx_bracket_table_ver: tableVer, + }; + } + + validateProviderLifecycleIntent(intent) { + if (!PROVIDER_LIFECYCLE_OPS.has(intent.op)) return new Error('Unsupported provider lifecycle op.'); + const allowed = intent.op === 'register_provider' + ? ['op', 'provider', 'nonce'] + : intent.op === 'set_provider_rails' + ? ['op', 'provider', 'rails', 'nonce'] + : intent.op === 'join_room' || intent.op === 'leave_room' + ? ['op', 'provider', 'enclave_id', 'room_id', 'nonce'] + : intent.op === 'join_enclave' + ? [ + 'op', + 'provider', + 'enclave_id', + 'nonce', + 'att_tier', + 'attestation_head', + 'served_ctx', + 'served_modalities', + 'served_specialities', + 'ctx_bracket', + 'ctx_bracket_table_ver', + 'hardware_fingerprint', + 'device_key', + ] + : ['op', 'provider', 'enclave_id', 'nonce']; + const required = intent.op === 'join_enclave' + ? [ + 'op', + 'provider', + 'enclave_id', + 'nonce', + 'att_tier', + 'attestation_head', + 'served_ctx', + 'served_modalities', + 'served_specialities', + 'ctx_bracket', + 'ctx_bracket_table_ver', + ] + : allowed; + const allowedSet = new Set(allowed); + const unknown = Object.keys(intent).filter((key) => !allowedSet.has(key)).sort(); + if (unknown.length > 0) { + return new Error(`provider lifecycle intent does not accept fields: ${unknown.join(', ')}.`); + } + for (const key of required) { + if (!hasOwn(intent, key)) return new Error(`provider lifecycle intent is missing ${key}.`); + } + if (!this.isHexBytes(intent.provider, 32)) return new Error('Invalid provider id.'); + if (!this.isHexBytes(intent.nonce, 32)) return new Error('Invalid lifecycle nonce.'); + if (hasOwn(intent, 'enclave_id') && !this.isSafeKeyPart(intent.enclave_id)) { + return new Error('Invalid enclave id.'); + } + if (hasOwn(intent, 'room_id') && !this.isSafeKeyPart(intent.room_id)) { + return new Error('Invalid room id.'); + } + if ( + hasOwn(intent, 'served_ctx') && + (!Number.isSafeInteger(intent.served_ctx) || intent.served_ctx < 0) + ) { + return new Error('Invalid provider served context.'); + } + if (hasOwn(intent, 'served_modalities')) { + const modalitiesError = this.validateModalitySet(intent.served_modalities, 'provider served_modalities'); + if (modalitiesError) return modalitiesError; + } + if (hasOwn(intent, 'served_specialities')) { + const specialitiesError = this.validateSpecialityLevelMap( + intent.served_specialities, + 'provider served_specialities', + { allowEmpty: true } + ); + if (specialitiesError) return specialitiesError; + } + if ( + hasOwn(intent, 'ctx_bracket') && + intent.ctx_bracket !== null && + !this.isSafeKeyPart(intent.ctx_bracket) + ) { + return new Error('Invalid provider context bracket.'); + } + if ( + hasOwn(intent, 'ctx_bracket_table_ver') && + intent.ctx_bracket_table_ver !== null && + (!Number.isSafeInteger(intent.ctx_bracket_table_ver) || intent.ctx_bracket_table_ver < 1) + ) { + return new Error('Invalid provider context bracket table version.'); + } + if (hasOwn(intent, 'hardware_fingerprint') && !this.isHexBytes(intent.hardware_fingerprint, 32)) { + return new Error('Invalid provider hardware fingerprint.'); + } + if (hasOwn(intent, 'device_key') && !this.isHexBytes(intent.device_key, 32)) { + return new Error('Invalid provider device key.'); + } + if ( + hasOwn(intent, 'att_tier') && + (!Number.isSafeInteger(intent.att_tier) || intent.att_tier < 1 || intent.att_tier > MAX_LAUNCH_ENCLAVE_ATTESTATION_TIER) + ) { + return new Error('Invalid provider attestation tier.'); + } + if (hasOwn(intent, 'attestation_head') && !this.isHexBytes(intent.attestation_head, 32)) { + return new Error('Invalid provider attestation head.'); + } + if (hasOwn(intent, 'rails')) { + const rails = this.normalizeProviderAcceptedRails(intent.rails); + if (rails instanceof Error) return rails; + } + return null; + } + + async normalizeProviderServeTerms(enclaveId, terms, label) { + if (terms === null || terms === undefined) { + return new Error(`${label} terms are required.`); + } + if (!terms || typeof terms !== 'object' || Array.isArray(terms)) { + return new Error(`${label} terms must be an object.`); + } + for (const field of ['served_ctx', 'served_modalities', 'served_specialities', 'ctx_bracket', 'ctx_bracket_table_ver']) { + if (!hasOwn(terms, field)) return new Error(`${label} terms are missing ${field}.`); + } + if (!Number.isSafeInteger(terms.served_ctx) || terms.served_ctx < 0) { + return new Error(`Invalid ${label} served context.`); + } + const servedModalitiesError = this.validateModalitySet( + terms.served_modalities, + `${label} served_modalities` + ); + if (servedModalitiesError) return servedModalitiesError; + const enclave = await this.get(`enclave/${enclaveId}`); + if (!enclave || enclave.status !== 'active') return new Error('Enclave is not active.'); + const enclaveModalitiesError = this.validateModalitySet( + enclave.caps?.modality_set, + 'admin enclave modality_set' + ); + if (enclaveModalitiesError) return enclaveModalitiesError; + const enclaveModalities = new Set(enclave.caps.modality_set); + if (terms.served_modalities.some((modality) => !enclaveModalities.has(modality))) { + return new Error(`${label} served_modalities must be a subset of the admin enclave modality_set.`); + } + const coreModalities = this.coreModalitiesForModelClass(this.modelClassFor(enclave)); + if ([...coreModalities].some((modality) => !terms.served_modalities.includes(modality))) { + return new Error(`${label} cannot disable a core model modality.`); + } + const enclaveSpecialitiesError = this.validateSpecialityLevelMap( + enclave.caps?.speciality_levels, + 'admin enclave speciality_levels', + { allowEmpty: true } + ); + if (enclaveSpecialitiesError) return enclaveSpecialitiesError; + const servedSpecialitiesError = this.validateSpecialityLevelMap( + terms.served_specialities, + `${label} served_specialities`, + { allowEmpty: true } + ); + if (servedSpecialitiesError) return servedSpecialitiesError; + const adminSpecialityNames = Object.keys(enclave.caps.speciality_levels).sort(compareCodepoint); + const servedSpecialityNames = Object.keys(terms.served_specialities).sort(compareCodepoint); + if (stableJson(servedSpecialityNames) !== stableJson(adminSpecialityNames)) { + return new Error(`${label} served_specialities must cover exactly the admin enclave speciality names.`); + } + for (const name of adminSpecialityNames) { + const available = new Set(enclave.caps.speciality_levels[name]); + if (terms.served_specialities[name].some((level) => !available.has(level))) { + return new Error(`${label} served_specialities ${name} must be a subset of the admin enclave levels.`); + } + } + const table = terms.ctx_bracket_table_ver === null || terms.ctx_bracket_table_ver === undefined + ? null + : await this.ctxBracketTableByVersion(terms.ctx_bracket_table_ver); + if (table instanceof Error) return table; + const ctxMeta = await this.normalizeCtxBracketEvidenceForEnclave( + enclaveId, + terms.served_ctx, + terms.ctx_bracket, + terms.ctx_bracket_table_ver, + table, + label + ); + if (ctxMeta instanceof Error) return ctxMeta; + return { + served_ctx: terms.served_ctx, + served_modalities: terms.served_modalities.slice(), + served_specialities: Object.fromEntries( + Object.entries(terms.served_specialities) + .sort(([left], [right]) => compareCodepoint(left, right)) + .map(([name, levels]) => [name, levels.slice()]) + ), + ctx_bracket: ctxMeta.ctx_bracket, + ctx_bracket_table_ver: ctxMeta.ctx_bracket_table_ver, + }; + } + + validateCommittedServeTerms(serve, normalized) { + if (!Number.isSafeInteger(serve.served_ctx) || serve.served_ctx < 0) { + return new Error('Provider serve record is missing committed context terms.'); + } + if (serve.served_ctx !== normalized.served_ctx) { + return new Error('Spend reservation served context does not match provider committed context.'); + } + const servedModalitiesError = this.validateModalitySet( + serve.served_modalities, + 'provider committed served_modalities' + ); + if (servedModalitiesError) return servedModalitiesError; + const requiredModalitiesError = this.validateModalitySet( + normalized.required_modalities, + 'spend reservation required_modalities' + ); + if (requiredModalitiesError) return requiredModalitiesError; + const servedModalities = new Set(serve.served_modalities); + if (normalized.required_modalities.some((modality) => !servedModalities.has(modality))) { + return new Error('Provider committed modalities do not cover the spend reservation.'); + } + const servedSpecialitiesError = this.validateSpecialityLevelMap( + serve.served_specialities, + 'provider committed served_specialities', + { allowEmpty: true } + ); + if (servedSpecialitiesError) return servedSpecialitiesError; + const requiredSpecialitiesError = this.validateSpecialitySelection( + normalized.required_specialities, + 'spend reservation required_specialities' + ); + if (requiredSpecialitiesError) return requiredSpecialitiesError; + const servedSpecialityNames = Object.keys(serve.served_specialities).sort(compareCodepoint); + const requiredSpecialityNames = Object.keys(normalized.required_specialities).sort(compareCodepoint); + if (stableJson(servedSpecialityNames) !== stableJson(requiredSpecialityNames)) { + return new Error('Spend reservation must bind every provider committed speciality.'); + } + for (const [name, level] of Object.entries(normalized.required_specialities)) { + if (!serve.served_specialities[name].includes(level)) { + return new Error('Provider committed specialities do not cover the spend reservation.'); + } + } + if ((serve.ctx_bracket ?? null) !== (normalized.ctx_bracket ?? null)) { + return new Error('Spend reservation context bracket does not match provider committed context.'); + } + if ((serve.ctx_bracket_table_ver ?? null) !== (normalized.ctx_bracket_table_ver ?? null)) { + return new Error('Spend reservation context bracket table does not match provider committed context.'); + } + return null; + } + + banRecordKey(targetType, target) { + switch (targetType) { + case 'provider': + return `ban/provider/${target}`; + case 'device': + return `ban/device/${target}`; + case 'fingerprint': + return `ban/fingerprint/${target}`; + case 'committer': + return `committer/ban/${target}`; + default: + return `ban/unknown/${target}`; + } + } + + normalizedKybIdentityValues(kyb) { + if (!kyb || typeof kyb !== 'object') return new Error('Invalid KYB identity.'); + const legalName = typeof kyb.legal_name === 'string' + ? kyb.legal_name.trim().replace(/\s+/g, ' ').toLowerCase() + : ''; + const kybRef = typeof kyb.kyb_ref === 'string' ? kyb.kyb_ref.trim() : ''; + const proofHash = typeof kyb.proof_hash === 'string' ? kyb.proof_hash.toLowerCase() : ''; + if (!legalName) return new Error('Invalid KYB legal name.'); + if (!this.isSafeExternalRef(kybRef)) return new Error('Invalid provider KYB reference.'); + if (!this.isHexBytes(proofHash, 32)) return new Error('Invalid provider KYB proof hash.'); + return { + legal_name: legalName, + kyb_ref: kybRef, + proof_hash: proofHash, + }; + } + + async kybBanIndexKey(kind, value) { + return `ban/kyb/${kind}/${await this.opaqueHash('mayhem-kyb-ban-index-v1', { kind, value })}`; + } + + async kybBanIndexKeys(kyb) { + const values = this.normalizedKybIdentityValues(kyb); + if (values instanceof Error) return values; + return [ + await this.kybBanIndexKey('legal_name', values.legal_name), + await this.kybBanIndexKey('kyb_ref', values.kyb_ref), + await this.kybBanIndexKey('proof_hash', values.proof_hash), + ]; + } + + async rejectBannedProviderKyb(kyb) { + const keys = await this.kybBanIndexKeys(kyb); + if (keys instanceof Error) return keys; + for (const key of keys) { + const ban = await this.get(key); + if (ban?.status === 'banned' || ban?.status === 'revoked') { + return new Error('Provider KYB identity is banned or revoked.'); + } + } + return null; + } + + async writeProviderKybBanIndexes(kyb, meta) { + const keys = await this.kybBanIndexKeys(kyb); + if (keys instanceof Error) return keys; + const values = this.normalizedKybIdentityValues(kyb); + if (values instanceof Error) return values; + for (const key of keys) { + const current = await this.get(key); + await this.put(key, { + ...(current ?? {}), + target_type: 'kyb', + target: key.split('/').at(-1), + status: meta.status, + provider: meta.provider, + source: meta.source, + reason_hash: meta.reason_hash, + recorded_at: meta.recorded_at, + recorded_by: meta.recorded_by, + recorded_by_role: meta.recorded_by_role, + legal_name_hash: await this.kybBanIndexKey('legal_name', values.legal_name).then((k) => k.split('/').at(-1)), + kyb_ref_hash: await this.kybBanIndexKey('kyb_ref', values.kyb_ref).then((k) => k.split('/').at(-1)), + proof_hash: values.proof_hash, + providers: { + ...(current?.providers ?? {}), + [meta.provider]: { + provider: meta.provider, + source: meta.source, + reason_hash: meta.reason_hash, + recorded_at: meta.recorded_at, + }, + }, + reversible: true, + }); + } + return null; + } + + async providerLifecycleFeatureKey(intent) { + const digest = await blake3(b4a.from(providerLifecycleIntentMessage(intent))); + return `intent/provider/${intent.provider}/${intent.op}/${b4a.toString(digest, 'hex')}`; + } + + async providerLifecycleFeatureKeys(intent) { + return [await this.providerLifecycleFeatureKey(intent)]; + } + + async providerPayoutBindingRevision(intent) { + const digest = await blake3(b4a.from(providerPayoutBindingMessage(intent))); + return b4a.toString(digest, 'hex'); + } + + async targetedSpendReservationFeatureKey(value) { + const normalized = await this.normalizeTargetedSpendReserveValue(value); + if (normalized instanceof Error) return normalized; + const digest = await blake3(b4a.from(stableJson({ + domain: 'mayhem-targeted-spend-reservation-feature-v1', + value: targetedSpendReservationEvidence({ + ...normalized, + payout_revision: value.payout_revision, + }), + }))); + return `hold/targeted/${normalized.rail}/${normalized.user}/${normalized.epoch}/${normalized.session_id}/${b4a.toString(digest, 'hex')}`; + } + + async providerPayoutPaymentConfigHash(payments) { + return await this.opaqueHash('mayhem-payout-payment-config-v1', payments); + } + + async providerPayoutContextFeatureKey(value) { + const revision = await this.providerPayoutContextRevision(value); + return this.providerPayoutContextRecordKey(value.payment_config_version, revision); + } + + async providerPayoutContextRevision(value) { + const digest = await blake3(b4a.from(stableJson({ + domain: 'mayhem-payout-context-feature-v1', + value, + }))); + return b4a.toString(digest, 'hex'); + } + + providerPayoutContextRecordKey(paymentConfigVersion, revision) { + return `payout/context/${paymentConfigVersion}/${revision}`; + } + + payoutParameterKey(key) { + return `payout/params/${key}`; + } + + async payoutParameterFeatureKey(value) { + const digest = await blake3(b4a.from(stableJson({ + domain: 'mayhem-payout-parameter-feature-v1', + value, + }))); + return `payout/params/${value.key}/${b4a.toString(digest, 'hex')}`; + } + + async payoutParameterRecord(key) { + const definition = PAYOUT_PARAM_DEFINITIONS[key]; + if (!definition) return new Error('Unknown payout parameter.'); + return (await this.get(this.payoutParameterKey(key))) ?? { + key, + current: { + key, + value: definition.default, + effective_epoch: 0, + scheduled_at: null, + scheduled_by: null, + scheduled_by_role: 'default', + }, + pending: null, + }; + } + + async activePayoutParamsAtEpoch(epoch) { + if (!Number.isSafeInteger(epoch) || epoch < 0) { + return new Error('Invalid payout parameter epoch.'); + } + const values = {}; + for (const key of Object.keys(PAYOUT_PARAM_DEFINITIONS)) { + const schedule = await this.payoutParameterRecord(key); + if (schedule instanceof Error) return schedule; + const active = schedule.pending && schedule.pending.effective_epoch <= epoch + ? schedule.pending + : schedule.current; + values[key] = active.value; + } + return values; + } + + providerPayoutBindingFeatureKey(rail, provider, revision) { + return `payout/binding/${rail}/${provider}/${revision}`; + } + + providerPayoutBindingPointerKey(provider, rail) { + return `payout/current/${rail}/${provider}`; + } + + providerPayoutBindingNonceKey(provider, nonce) { + return `payout/nonce/${provider}/${nonce}`; + } + + providerPayoutLiabilityKey(provider, rail, revision) { + return `payout/liability/${rail}/${provider}/${revision}`; + } + + providerPayoutLiabilityIndexKey(rail) { + return `payout/liability-index/${rail}`; + } + + // MAYHEM PATCH: canonical per-rail liability index avoids ledger-wide scans + // when targeted payout epochs collect provider earnings. + normalizeProviderPayoutLiabilityIndex(value, rail) { + const shapeError = this.validateExactObjectKeys( + value, + ['type', 'rail', 'entries', 'updated_epoch', 'updated_at'], + 'provider payout liability index' + ); + if (shapeError) return shapeError; + if (value.type !== 'provider_payout_liability_index' || + value.rail !== rail || + !PROVIDER_PAYOUT_BINDING_RAILS.has(rail) || + !Array.isArray(value.entries) || + !Number.isSafeInteger(value.updated_epoch) || + value.updated_epoch < 0 || + (value.updated_at !== null && + (typeof value.updated_at !== 'string' || value.updated_at.length === 0))) { + return new Error('Invalid provider payout liability index.'); + } + const entries = []; + for (const entry of value.entries) { + const entryError = this.validateExactObjectKeys( + entry, + ['provider', 'payout_revision'], + 'provider payout liability index entry' + ); + if (entryError) return entryError; + if (!this.isHexBytes(entry.provider, 32) || + entry.provider !== entry.provider.toLowerCase() || + !this.isHexBytes(entry.payout_revision, 32) || + entry.payout_revision !== entry.payout_revision.toLowerCase()) { + return new Error('Invalid provider payout liability index entry.'); + } + entries.push({ + provider: entry.provider, + payout_revision: entry.payout_revision, + }); + } + entries.sort((left, right) => + compareCodepoint(left.provider, right.provider) || + compareCodepoint(left.payout_revision, right.payout_revision) + ); + const identities = entries.map((entry) => + `${entry.provider}/${entry.payout_revision}` + ); + if (new Set(identities).size !== entries.length || + stableJson(entries) !== stableJson(value.entries)) { + return new Error('Provider payout liability index must be canonical and unique.'); + } + return { + type: 'provider_payout_liability_index', + rail, + entries, + updated_epoch: value.updated_epoch, + updated_at: value.updated_at, + }; + } + + async providerPayoutLiabilityIndex(rail) { + if (!PROVIDER_PAYOUT_BINDING_RAILS.has(rail)) { + return new Error('Invalid provider payout liability index rail.'); + } + const current = await this.get(this.providerPayoutLiabilityIndexKey(rail)); + if (current === null) { + return { + type: 'provider_payout_liability_index', + rail, + entries: [], + updated_epoch: 0, + updated_at: null, + }; + } + return this.normalizeProviderPayoutLiabilityIndex(current, rail); + } + + async nextProviderPayoutLiabilityIndexes(liabilityUpdates, epoch, featureKey) { + const byRail = new Map(); + for (const update of liabilityUpdates) { + const { provider, rail, revision: payoutRevision } = update.value; + let index = byRail.get(rail); + if (!index) { + index = await this.providerPayoutLiabilityIndex(rail); + if (index instanceof Error) return index; + } + const identity = `${provider}/${payoutRevision}`; + if (!index.entries.some((entry) => + `${entry.provider}/${entry.payout_revision}` === identity + )) { + index = { + ...index, + entries: [ + ...index.entries, + { provider, payout_revision: payoutRevision }, + ].sort((left, right) => + compareCodepoint(left.provider, right.provider) || + compareCodepoint(left.payout_revision, right.payout_revision) + ), + }; + } + byRail.set(rail, { + ...index, + updated_epoch: Math.max(index.updated_epoch, epoch), + updated_at: featureKey, + }); + } + return [...byRail.values()].map((value) => ({ + key: this.providerPayoutLiabilityIndexKey(value.rail), + value, + })); + } + + providerStripePayoutVerificationTargetKey(provider, target) { + return `payout/stripe-verified/target/${provider}/${target}`; + } + + async providerStripePayoutVerificationForTarget(provider, target) { + const pointer = await this.get( + this.providerStripePayoutVerificationTargetKey(provider, target) + ); + const verification = pointer?.record_key + ? await this.get(pointer.record_key) + : null; + if (!verification || + pointer.provider !== provider || + pointer.target !== target || + pointer.revision !== verification.revision || + pointer.processor_revision !== verification.processor_revision || + verification.type !== 'stripe_payout_verification' || + verification.provider !== provider || + verification.target !== target) { + return null; + } + return verification; + } + + providerPayoutEpochSnapshotKey(epoch, page, provider, rail) { + return `payout/epoch/${epoch}/${page}/${rail}/${provider}`; + } + + async targetedEpochFeatureKey(value) { + const normalized = await this.normalizeTargetedEpochFeatureValue(value); + if (normalized instanceof Error) return normalized; + const digest = await blake3(b4a.from(stableJson({ + domain: 'mayhem-targeted-epoch-feature-v1', + value, + }))); + return `epoch/targeted/${value.epoch}/${b4a.toString(digest, 'hex')}`; + } + + async commitTargetedEpochPageZeroFeatureKey(value) { + const normalized = await this.normalizeCommitTargetedEpochPageZeroFeatureValue(value); + if (normalized instanceof Error) return normalized; + const digest = await blake3(b4a.from(stableJson({ + domain: 'mayhem-commit-targeted-epoch-page-zero-feature-v1', + value, + }))); + return `epoch/targeted/${value.epoch}/${b4a.toString(digest, 'hex')}`; + } + + async providerPayoutBindingContext(intent) { + const context = { + network: intent.network, + admin: intent.admin, + bootstrap: intent.bootstrap, + context_revision: intent.context_revision, + payment_config_version: intent.payment_config_version, + }; + if (!this.isSafeKeyPart(context.network) || + !this.isHexBytes(context.admin, 32) || + !this.isHexBytes(context.bootstrap, 32) || + !this.isHexBytes(context.context_revision, 32) || + !Number.isSafeInteger(context.payment_config_version) || + context.payment_config_version < 1) { + return new Error('Invalid provider payout binding canonical context.'); + } + return context; + } + + async providerPayoutBindingForEpoch( + provider, + rail, + revision, + epoch, + { requireCurrentReadiness = false } = {} + ) { + if (!PROVIDER_PAYOUT_BINDING_RAILS.has(rail)) { + return new Error('Targeted payout rail is not supported.'); + } + if (!this.isHexBytes(provider, 32) || + !this.isHexBytes(revision, 32) || + !Number.isSafeInteger(epoch) || + epoch < 1) { + return new Error('Invalid targeted payout binding reference.'); + } + const binding = await this.get( + this.providerPayoutBindingFeatureKey(rail, provider, revision) + ); + if (!binding || binding.verified !== true || + binding.provider !== provider || + binding.rail !== rail || + binding.revision !== revision) { + return new Error('Active verified provider payout binding required.'); + } + if (binding.activation_epoch > epoch) { + return new Error('Provider payout binding is not active for targeted epoch.'); + } + const pointer = await this.get(this.providerPayoutBindingPointerKey(provider, rail)); + if (!pointer) return new Error('Provider payout binding pointer required.'); + const activeRevision = pointer.pending_revision !== null && + pointer.pending_activation_epoch <= epoch + ? pointer.pending_revision + : pointer.current_revision; + if (activeRevision !== revision) { + return new Error('Provider payout binding revision is not active for targeted epoch.'); + } + if (requireCurrentReadiness && rail === 'fiat') { + const verification = await this.providerStripePayoutVerificationForTarget( + provider, + binding.target + ); + if (!verification || + verification.ready !== true || + verification.target !== binding.target || + verification.processor_revision !== binding.stripe_processor_revision) { + return new Error('Provider Stripe payout binding is not currently ready.'); + } + } + return binding; + } + + receiptConsumptionRecord(epoch, allocation, featureKey) { + return { + type: 'canonical_receipt_consumption', + epoch, + billing_epoch: allocation.billing_epoch, + billing_id: allocation.billing_id, + billing_attempt: allocation.billing_attempt, + receipt_seq: allocation.receipt_seq, + receipt_hash: allocation.receipt_hash, + session_id: allocation.session_id, + user: allocation.user, + rail: allocation.rail, + provider: allocation.provider, + payout_revision: allocation.payout_revision, + au: allocation.au, + feature_key: featureKey, + consumed_at: featureKey, + }; + } + + async validateTargetedEpochReservationBindings(value, earnings, featureKey) { + const debitTotals = new Map(); + for (const debit of value.debits) { + const rail = this.normalizeLedgerRail(debit.rail, 'targeted epoch debit rail'); + if (rail instanceof Error) return rail; + const key = stableJson([rail, debit.user]); + const next = this.safeAddAu(debitTotals.get(key) ?? ZERO_AU, debit.au); + if (next instanceof Error) return next; + debitTotals.set(key, next); + } + const earningTotals = new Map( + earnings.map((earning) => [ + stableJson([earning.rail, earning.provider, earning.payout_revision]), + earning.gross_au, + ]) + ); + const allocatedDebits = new Map(); + const allocatedEarnings = new Map(); + const holds = new Map(); + const summaries = new Map(); + const legacyReleases = new Map(); + const sessionDeletes = []; + const sessions = new Set(); + const billingAttempts = new Set(); + const marketUsage = new Map(); + for (const allocation of value.allocations) { + if (sessions.has(allocation.session_id)) { + return new Error('Targeted epoch session allocation is duplicated.'); + } + sessions.add(allocation.session_id); + const billingAttempt = `${allocation.billing_id}:${allocation.billing_attempt}`; + if (billingAttempts.has(billingAttempt)) { + return new Error('Targeted epoch billing attempt allocation is duplicated.'); + } + billingAttempts.add(billingAttempt); + const head = await this.get( + this.receiptHeadKey(allocation.billing_id, allocation.billing_attempt) + ); + if (!head || + head.type !== 'canonical_receipt_head' || + head.epoch !== value.epoch || + head.settlement_epoch !== value.epoch || + head.billing_epoch !== allocation.billing_epoch || + head.settlement_ready !== true || + head.billing_id !== allocation.billing_id || + head.billing_attempt !== allocation.billing_attempt || + head.receipt_seq !== allocation.receipt_seq || + head.receipt_hash !== allocation.receipt_hash || + head.session_id !== allocation.session_id || + head.user !== allocation.user || + head.rail !== allocation.rail || + head.provider !== allocation.provider || + head.payout_revision !== allocation.payout_revision || + this.compareAu(head.incremental_au, allocation.au) !== 0) { + return new Error('Targeted epoch allocation does not match its canonical receipt head.'); + } + const receiptBody = head.receipt?.body; + if (!receiptBody || + receiptBody.session_id !== allocation.session_id || + receiptBody.provider !== allocation.provider || + !this.isSafeKeyPart(receiptBody.enclave_id) || + (receiptBody.ctx_bracket !== undefined && + receiptBody.ctx_bracket !== null && + !this.isSafeKeyPart(receiptBody.ctx_bracket)) || + (receiptBody.ctx_bracket_table_ver !== undefined && + receiptBody.ctx_bracket_table_ver !== null && + (!Number.isSafeInteger(receiptBody.ctx_bracket_table_ver) || + receiptBody.ctx_bracket_table_ver < 1))) { + return new Error('Canonical receipt market identity is invalid.'); + } + const marketKey = this.priceMarketKey( + receiptBody.enclave_id, + receiptBody.ctx_bracket ?? null + ); + const currentMarket = marketUsage.get(marketKey) ?? { + enclave_id: receiptBody.enclave_id, + ...(receiptBody.ctx_bracket ? { ctx_bracket: receiptBody.ctx_bracket } : {}), + ...(receiptBody.ctx_bracket_table_ver ? { + ctx_bracket_table_ver: receiptBody.ctx_bracket_table_ver, + } : {}), + demand_au: ZERO_AU, + settled_usage: {}, + session_count: 0, + providers: new Set(), + }; + if ((currentMarket.ctx_bracket_table_ver ?? null) !== + (receiptBody.ctx_bracket_table_ver ?? currentMarket.ctx_bracket_table_ver ?? null)) { + return new Error('Canonical receipt market context version changed within an epoch.'); + } + const marketDemandAu = this.safeAddAu(currentMarket.demand_au, allocation.au); + const marketSessionCount = this.safeAddCount( + currentMarket.session_count, + 1, + 'canonical receipt market session count' + ); + if (marketDemandAu instanceof Error || marketSessionCount instanceof Error) { + return new Error('Canonical receipt market usage overflow.'); + } + const increment = this.incrementalSettledUsage(receiptBody); + if (increment instanceof Error) return increment; + const settledUsage = this.addSettledUsage(currentMarket.settled_usage, increment); + if (settledUsage instanceof Error) return settledUsage; + currentMarket.settled_usage = settledUsage; + currentMarket.demand_au = marketDemandAu; + currentMarket.session_count = marketSessionCount; + currentMarket.providers.add(allocation.provider); + marketUsage.set(marketKey, currentMarket); + const consumeKey = this.receiptConsumedKey( + allocation.billing_id, + allocation.billing_attempt + ); + const expectedConsumption = this.receiptConsumptionRecord( + value.epoch, + allocation, + featureKey + ); + const existingConsumption = await this.get(consumeKey); + if (existingConsumption !== null && + stableJson(existingConsumption) !== stableJson(expectedConsumption)) { + return new Error('Canonical receipt billing attempt is already consumed.'); + } + if (existingConsumption === null) { + const reservationState = await this.targetedSpendReservationState( + allocation.user, + allocation.rail, + head.reservation_id, + allocation.session_id + ); + if (reservationState instanceof Error) return reservationState; + if (reservationState.kind === 'missing') { + return new Error('Targeted epoch allocation does not match its reserved session.'); + } + const holdIdentity = stableJson([allocation.rail, allocation.user]); + const isLegacy = reservationState.kind === 'legacy' || + reservationState.kind === 'legacy_overlay'; + const session = isLegacy ? reservationState.session : reservationState.session; + if (!session || + session.billing_id !== allocation.billing_id || + session.billing_attempt !== allocation.billing_attempt || + session.billing_epoch !== allocation.billing_epoch || + session.provider !== allocation.provider || + session.payout_revision !== allocation.payout_revision || + session.settlement_ready !== true) { + return new Error('Targeted epoch allocation does not match its reserved session.'); + } + if (session.reservation_id !== head.reservation_id || + session.user !== allocation.user || + session.rail !== allocation.rail || + this.compareAu(allocation.au, session.max_spend_au) !== 0 || + this.compareAu(allocation.au, head.incremental_au) !== 0) { + return new Error('Targeted epoch allocation does not exactly consume its reserved receipt.'); + } + if (isLegacy) { + let legacyRelease = legacyReleases.get(holdIdentity) ?? + reservationState.legacyRelease; + const nextReleasedAu = this.safeAddAu(legacyRelease.released_au, allocation.au); + if (nextReleasedAu instanceof Error || + this.compareAu(nextReleasedAu, reservationState.hold.reserved_au) > 0) { + return new Error('Targeted epoch allocation exceeds outstanding legacy holds.'); + } + legacyRelease = { + ...legacyRelease, + released_au: nextReleasedAu, + updated_at: featureKey, + }; + legacyReleases.set(holdIdentity, legacyRelease); + if (reservationState.kind === 'legacy_overlay') { + sessionDeletes.push(reservationState.legacySessionKey); + } + } else { + let summary = summaries.get(holdIdentity) ?? reservationState.summary; + const nextReservedAu = this.safeSubAu(summary.reserved_au, allocation.au); + if (nextReservedAu instanceof Error) { + return new Error('Targeted epoch allocation exceeds outstanding sharded holds.'); + } + summary = { + ...summary, + reserved_au: nextReservedAu, + updated_at: featureKey, + }; + summaries.set(holdIdentity, summary); + sessionDeletes.push( + reservationState.sessionKey, + reservationState.sessionIndexKey, + reservationState.billingAttemptKey + ); + } + } + const debitKey = stableJson([allocation.rail, allocation.user]); + const nextDebit = this.safeAddAu( + allocatedDebits.get(debitKey) ?? ZERO_AU, + allocation.au + ); + if (nextDebit instanceof Error) return nextDebit; + allocatedDebits.set(debitKey, nextDebit); + const earningKey = stableJson([ + allocation.rail, + allocation.provider, + allocation.payout_revision, + ]); + const nextEarning = this.safeAddAu( + allocatedEarnings.get(earningKey) ?? ZERO_AU, + allocation.au + ); + if (nextEarning instanceof Error) return nextEarning; + allocatedEarnings.set(earningKey, nextEarning); + } + for (const [key, total] of debitTotals) { + if (allocatedDebits.get(key) !== total) { + return new Error('Targeted epoch debit does not equal session allocations.'); + } + } + if (allocatedDebits.size !== debitTotals.size) { + return new Error('Targeted epoch allocation has no matching debit.'); + } + for (const [key, total] of earningTotals) { + if (allocatedEarnings.get(key) !== total) { + return new Error('Targeted epoch earning does not equal session allocations.'); + } + } + if (allocatedEarnings.size !== earningTotals.size) { + return new Error('Targeted epoch allocation has no matching earning.'); + } + return { + hold_updates: Array.from(holds.values()).map((hold) => ({ + key: this.targetedSpendHoldKey(hold.user, hold.rail), + value: hold, + })), + summary_updates: Array.from(summaries.values()).map((summary) => ({ + key: this.targetedSpendSummaryKey(summary.user, summary.rail), + value: summary, + })), + legacy_release_updates: Array.from(legacyReleases.values()).map((summary) => ({ + key: this.targetedSpendLegacyReleaseSummaryKey(summary.user, summary.rail), + value: summary, + })), + session_deletes: [...new Set(sessionDeletes)], + market_usage: Array.from(marketUsage.values()) + .sort((left, right) => ( + compareCodepoint(left.enclave_id, right.enclave_id) || + compareCodepoint(left.ctx_bracket ?? '', right.ctx_bracket ?? '') + )) + .map((entry) => ({ + enclave_id: entry.enclave_id, + ...(entry.ctx_bracket ? { ctx_bracket: entry.ctx_bracket } : {}), + ...(entry.ctx_bracket_table_ver ? { + ctx_bracket_table_ver: entry.ctx_bracket_table_ver, + } : {}), + demand_au: entry.demand_au, + settled_usage: entry.settled_usage, + session_count: entry.session_count, + providers: Array.from(entry.providers).sort(compareCodepoint), + })), + }; + } + + async spendReservationFeatureKey(value) { + const normalized = value.voucher_body ? value : await this.normalizeSpendReserveValue(value); + if (normalized instanceof Error) return normalized; + const digest = b4a.toString( + await blake3(b4a.from(stableJson({ + domain: 'mayhem-spend-reservation-feature-v1', + value: spendReservationEvidence(normalized), + }))), + 'hex' + ); + return `hold/reserve/${normalized.rail}/${normalized.user}/${normalized.epoch}/${normalized.session_id}/${digest}`; + } + + async activeParamsAt(at, keys = Object.keys(PARAM_DEFINITIONS)) { + const params = {}; + for (const key of keys) { + params[key] = this.paramActiveEntry(await this.paramRecord(key), at).value; + } + return params; + } + + async paramRecord(key) { + const existing = await this.get(`params/${key}`); + if (existing) return this.sanitizeMarketBoundRecord(key, existing); + return { + key, + current: { + value: PARAM_DEFINITIONS[key].default, + ver: 0, + submitted_at: 0, + effective_at: 0, + set_at: null, + }, + pending: null, + }; + } + + paramActiveEntry(record, at) { + if (record.pending && record.pending.effective_at <= at) return cloneValue(record.pending); + return cloneValue(record.current); + } + + validateParamValues(values) { + if (!values || typeof values !== 'object' || Array.isArray(values)) { + return new Error('Parameter values must be an object.'); + } + const keys = Object.keys(values); + if (keys.length === 0) return new Error('At least one parameter is required.'); + const keyError = this.validateParamKeys(keys); + if (keyError) return keyError; + + for (const key of keys) { + const value = values[key]; + const def = PARAM_DEFINITIONS[key]; + if (def.deprecated) return new Error(`Parameter ${key} is deprecated and read-only.`); + if (def.money) { + const au = this.normalizeAu(value, `Parameter ${key}`, { allowZero: def.min === ZERO_AU }); + if (au instanceof Error) return au; + if (this.compareAu(au, def.min) < 0) return new Error(`Parameter ${key} is out of range.`); + continue; + } + if (!Number.isInteger(value)) return new Error(`Parameter ${key} must be an integer.`); + if (value < def.min || value > def.max) return new Error(`Parameter ${key} is out of range.`); + } + return null; + } + + normalizeParamValues(values) { + const normalized = {}; + for (const [key, value] of Object.entries(values)) { + const def = PARAM_DEFINITIONS[key]; + normalized[key] = def.money + ? this.normalizeAu(value, `Parameter ${key}`, { allowZero: def.min === ZERO_AU }) + : value; + if (normalized[key] instanceof Error) return normalized[key]; + } + return normalized; + } + + validateParamKeys(keys) { + if (!Array.isArray(keys) || keys.length === 0 || keys.length > 64) { + return new Error('Invalid parameter keys.'); + } + for (const key of keys) { + if (!hasOwn(PARAM_DEFINITIONS, key)) return new Error(`Unknown parameter ${key}.`); + } + return null; + } + + validateParamBounds(params) { + if (params.price_min_bps > params.price_max_bps) { + return new Error('price_min_bps must not exceed price_max_bps.'); + } + return null; + } + + validateModelRef(value) { + if (!this.isSafeModelId(value.model_id)) return new Error('Invalid model id.'); + const classError = this.validateModelClass(this.modelClassFor(value), 'Model reference model_class'); + if (classError) return classError; + const rateError = this.validateRateMap(value.rate_map, this.modelClassFor(value), 'Model reference rate_map'); + if (rateError) return rateError; + if (value.source_hash !== undefined && !this.isSafeKeyPart(value.source_hash)) { + return new Error('Invalid model reference source hash.'); + } + if (value.activity_calibration !== undefined && value.activity_calibration !== null) { + return this.validateActivityCalibration(value.activity_calibration, this.modelClassFor(value), value.rate_map); + } + return null; + } + + normalizePaymentConfig(value) { + const shapeError = this.validateExactObjectKeys( + value, + ['op', 'ver', 'fiat', 'tap', 'tnk'], + 'payment config' + ); + if (shapeError) return shapeError; + if (value.op !== 'set_payments') return new Error('Invalid set_payments op.'); + if (!Number.isSafeInteger(value.ver) || value.ver <= 0) { + return new Error('Payment config version must be a positive safe integer.'); + } + + const fiatShape = this.validateExactObjectKeys( + value.fiat, + [ + 'processor', + 'integration_currency', + 'adaptive_pricing', + 'payout_currencies', + 'locale', + ], + 'fiat payment config' + ); + if (fiatShape) return fiatShape; + if (value.fiat.processor !== 'stripe') return new Error('Fiat processor must be stripe.'); + if (value.fiat.integration_currency !== 'usd') { + return new Error('Stripe integration currency must be usd for au_usd accounting.'); + } + if (value.fiat.adaptive_pricing !== true) { + return new Error('Stripe Adaptive Pricing must be enabled.'); + } + if (!Array.isArray(value.fiat.payout_currencies) || + value.fiat.payout_currencies.length < REQUIRED_FIAT_PAYOUT_CURRENCIES.length) { + return new Error('Fiat payout currencies must include EUR, GBP, and USD.'); + } + const payoutCurrencies = []; + for (const currency of value.fiat.payout_currencies) { + const normalized = this.normalizeFiatCurrency(currency); + if (normalized instanceof Error) return normalized; + if (normalized !== currency) { + return new Error('Fiat payout currencies must be canonical lowercase codes.'); + } + if (payoutCurrencies.includes(normalized)) { + return new Error('Duplicate fiat payout currency.'); + } + payoutCurrencies.push(normalized); + } + const sortedPayoutCurrencies = payoutCurrencies.slice().sort(compareCodepoint); + if (stableJson(payoutCurrencies) !== stableJson(sortedPayoutCurrencies)) { + return new Error('Fiat payout currencies must be sorted.'); + } + for (const required of REQUIRED_FIAT_PAYOUT_CURRENCIES) { + if (!payoutCurrencies.includes(required)) { + return new Error('Fiat payout currencies must include EUR, GBP, and USD.'); + } + } + if (value.fiat.locale !== 'en') return new Error('Stripe checkout locale must be en.'); + + const tapShape = this.validateExactObjectKeys( + value.tap, + ['chain_id', 'token_address', 'pool_address'], + 'TAP payment config' + ); + if (tapShape) return tapShape; + if (!Number.isSafeInteger(value.tap.chain_id) || value.tap.chain_id <= 0) { + return new Error('Invalid TAP chain id.'); + } + if (!this.isEthHexBytes(value.tap.token_address, 20)) { + return new Error('Invalid TAP token address.'); + } + if (!this.isEthHexBytes(value.tap.pool_address, 20)) { + return new Error('Invalid TAP pool address.'); + } + + const tnkShape = this.validateExactObjectKeys( + value.tnk, + ['network', 'treasury_address'], + 'TNK payment config' + ); + if (tnkShape) return tnkShape; + if (!['mainnet', 'testnet1'].includes(value.tnk.network)) { + return new Error('TNK network must be mainnet or testnet1.'); + } + if (typeof value.tnk.treasury_address !== 'string' || + !/^(trac1|testtrac1)[a-z0-9]{20,120}$/.test(value.tnk.treasury_address)) { + return new Error('Invalid TNK treasury address.'); + } + if (value.tnk.network === 'mainnet' && !value.tnk.treasury_address.startsWith('trac1')) { + return new Error('TNK mainnet treasury must use a trac1 address.'); + } + if (value.tnk.network === 'testnet1' && !value.tnk.treasury_address.startsWith('testtrac1')) { + return new Error('TNK testnet1 treasury must use a testtrac1 address.'); + } + + return { + fiat: { + processor: 'stripe', + integration_currency: 'usd', + adaptive_pricing: true, + payout_currencies: payoutCurrencies, + locale: 'en', + }, + tap: { + chain_id: value.tap.chain_id, + token_address: value.tap.token_address.toLowerCase(), + pool_address: value.tap.pool_address.toLowerCase(), + }, + tnk: { + network: value.tnk.network, + treasury_address: value.tnk.treasury_address, + }, + }; + } + + validateCatalogRelease(value) { + const releaseKeys = [ + 'op', + 'catalog_id', + 'source_kind', + 'catalog_url', + 'signature_url', + 'catalog_hash', + 'signature_hash', + 'key_id', + 'public_key', + 'model_count', + 'artifact_count', + 'canaries', + ]; + for (const optionalKey of ['parts_anchor', 'blessed_runtimes', 'outcome_classes']) { + if (hasOwn(value, optionalKey)) releaseKeys.push(optionalKey); + } + const shapeError = this.validateExactObjectKeys( + value, + releaseKeys, + 'catalog release' + ); + if (shapeError) return shapeError; + if (value.op !== 'publish_catalog') return new Error('Invalid publish_catalog op.'); + if (!this.isSafeKeyPart(value.catalog_id)) return new Error('Invalid catalog id.'); + if (!CATALOG_SOURCE_KINDS.has(value.source_kind)) { + return new Error('Unsupported catalog source kind.'); + } + if (!this.isHttpsUrl(value.catalog_url) || !this.isHttpsUrl(value.signature_url)) { + return new Error('Catalog release URLs must be HTTPS.'); + } + if (value.source_kind === 'huggingface') { + if (!this.isPinnedHuggingFaceResolveUrl(value.catalog_url)) { + return new Error('Hugging Face catalog URL must use huggingface.co/resolve/<40-hex-revision>/.'); + } + if (!this.isPinnedHuggingFaceResolveUrl(value.signature_url)) { + return new Error('Hugging Face catalog signature URL must use huggingface.co/resolve/<40-hex-revision>/.'); + } + } + if (!this.isHexBytes(value.catalog_hash, 32)) { + return new Error('Catalog hash must be a 32-byte hex BLAKE3 hash.'); + } + if (!this.isHexBytes(value.signature_hash, 32)) { + return new Error('Catalog signature hash must be a 32-byte hex BLAKE3 hash.'); + } + if (!this.isSafeKeyPart(value.key_id)) return new Error('Invalid catalog key id.'); + if (!this.isHexBytes(value.public_key, 32)) { + return new Error('Catalog public key must be 32-byte hex.'); + } + const seen = new Set(); + for (const entry of value.canaries) { + const entryError = this.validateCatalogCanaryRef(entry); + if (entryError) return entryError; + if (value.source_kind === 'huggingface' && !this.isPinnedHuggingFaceResolveUrl(entry.url)) { + return new Error('Hugging Face catalog canary URL must use huggingface.co/resolve/<40-hex-revision>/.'); + } + if (seen.has(entry.set_id)) return new Error('Duplicate catalog canary set.'); + seen.add(entry.set_id); + } + if (hasOwn(value, 'parts_anchor')) { + const partsAnchorError = this.validateCatalogPartsAnchor(value.parts_anchor); + if (partsAnchorError) return partsAnchorError; + } + if (hasOwn(value, 'blessed_runtimes')) { + if (!Array.isArray(value.blessed_runtimes)) { + return new Error('Catalog blessed_runtimes must be an array.'); + } + const runtimeIds = new Set(); + for (const entry of value.blessed_runtimes) { + const runtimeError = this.validateCatalogBlessedRuntime(entry); + if (runtimeError) return runtimeError; + if (runtimeIds.has(entry.runtime_id)) return new Error('Duplicate catalog blessed runtime id.'); + runtimeIds.add(entry.runtime_id); + } + } + if (hasOwn(value, 'outcome_classes')) { + if (!Array.isArray(value.outcome_classes)) { + return new Error('Catalog outcome_classes must be an array.'); + } + const classIds = new Set(); + for (const entry of value.outcome_classes) { + const classError = this.validateCatalogOutcomeClassRef(entry); + if (classError) return classError; + if (classIds.has(entry.class_id)) return new Error('Duplicate catalog outcome class id.'); + classIds.add(entry.class_id); + } + } + return null; + } + + validateCatalogPartsAnchor(anchor) { + const shapeError = this.validateExactObjectKeys( + anchor, + [ + 'index_ver', + 'source_kind', + 'index_url', + 'anchor_url', + 'anchor_hash', + 'index_root', + 'record_count', + 'repo_revision', + ], + 'catalog parts anchor' + ); + if (shapeError) return shapeError; + if (!Number.isSafeInteger(anchor.index_ver) || anchor.index_ver <= 0) { + return new Error('Catalog parts anchor index_ver must be a positive integer.'); + } + if (!CATALOG_SOURCE_KINDS.has(anchor.source_kind)) { + return new Error('Unsupported catalog parts anchor source kind.'); + } + if (!this.isHttpsUrl(anchor.index_url) || !this.isHttpsUrl(anchor.anchor_url)) { + return new Error('Catalog parts anchor URLs must be HTTPS.'); + } + if (anchor.source_kind === 'huggingface') { + const indexRevision = this.pinnedHuggingFaceResolveRevision(anchor.index_url); + const anchorRevision = this.pinnedHuggingFaceResolveRevision(anchor.anchor_url); + if (indexRevision === null) { + return new Error('Hugging Face parts index URL must use huggingface.co/resolve/<40-hex-revision>/.'); + } + if (anchorRevision === null) { + return new Error('Hugging Face parts anchor URL must use huggingface.co/resolve/<40-hex-revision>/.'); + } + if (indexRevision !== anchor.repo_revision || anchorRevision !== anchor.repo_revision) { + return new Error('Hugging Face parts anchor URLs must match repo_revision.'); + } + } else if (!this.isSafeExternalRef(anchor.repo_revision)) { + return new Error('Invalid catalog parts repo revision.'); + } + if (!this.isHexBytes(anchor.anchor_hash, 32)) { + return new Error('Catalog parts anchor hash must be a 32-byte hex hash.'); + } + if (!this.isHexBytes(anchor.index_root, 32)) { + return new Error('Catalog parts index root must be a 32-byte hex hash.'); + } + if (!Number.isSafeInteger(anchor.record_count) || anchor.record_count < 1) { + return new Error('Catalog parts anchor record_count must be a positive integer.'); + } + return null; + } + + validateCatalogBlessedRuntime(entry) { + const shapeError = this.validateExactObjectKeys( + entry, + [ + 'runtime_id', + 'comfy_release_hash', + 'env_lock_hash', + 'whitelist_ver', + 'status', + 'min_grace_epochs', + ], + 'catalog blessed runtime' + ); + if (shapeError) return shapeError; + if (!this.isSafeExternalRef(entry.runtime_id)) return new Error('Invalid catalog runtime id.'); + if (!this.isHexBytes(entry.comfy_release_hash, 32)) { + return new Error('Catalog runtime comfy_release_hash must be a 32-byte hex hash.'); + } + if (!this.isHexBytes(entry.env_lock_hash, 32)) { + return new Error('Catalog runtime env_lock_hash must be a 32-byte hex hash.'); + } + if (!Number.isSafeInteger(entry.whitelist_ver) || entry.whitelist_ver <= 0) { + return new Error('Catalog runtime whitelist_ver must be a positive integer.'); + } + if (!CATALOG_RUNTIME_STATUSES.has(entry.status)) return new Error('Invalid catalog runtime status.'); + if (!Number.isSafeInteger(entry.min_grace_epochs) || entry.min_grace_epochs < 0) { + return new Error('Catalog runtime min_grace_epochs must be a non-negative integer.'); + } + return null; + } + + validateCatalogOutcomeClassRef(entry) { + const shapeError = this.validateExactObjectKeys( + entry, + ['class_id', 'enclave_id', 'definition_hash', 'status'], + 'catalog outcome class' + ); + if (shapeError) return shapeError; + if (!this.isSafeExternalRef(entry.class_id)) return new Error('Invalid catalog outcome class id.'); + if (!this.isHexBytes(entry.enclave_id, 32)) return new Error('Invalid catalog outcome class enclave id.'); + if (!this.isHexBytes(entry.definition_hash, 32)) { + return new Error('Catalog outcome class definition_hash must be a 32-byte hex hash.'); + } + if (!CATALOG_OUTCOME_CLASS_STATUSES.has(entry.status)) { + return new Error('Invalid catalog outcome class status.'); + } + return null; + } + + validateCatalogCanaryRef(entry) { + const shapeError = this.validateExactObjectKeys( + entry, + ['set_id', 'url', 'hash', 'prompt_ids'], + 'catalog canary ref' + ); + if (shapeError) return shapeError; + if (!this.isSafeKeyPart(entry.set_id)) return new Error('Invalid catalog canary set id.'); + if (!this.isHttpsUrl(entry.url)) return new Error('Catalog canary URL must be HTTPS.'); + if (!this.isHexBytes(entry.hash, 32)) { + return new Error('Catalog canary hash must be a 32-byte hex BLAKE3 hash.'); + } + if (!Array.isArray(entry.prompt_ids) || entry.prompt_ids.length < 1 || entry.prompt_ids.length > 1_024) { + return new Error('Catalog canary prompt_ids must be a non-empty bounded array.'); + } + const promptIds = new Set(); + for (const promptId of entry.prompt_ids) { + if (!this.isSafeKeyPart(promptId)) return new Error('Invalid catalog canary prompt id.'); + if (promptIds.has(promptId)) return new Error('Duplicate catalog canary prompt id.'); + promptIds.add(promptId); + } + return null; + } + + validateEnclaveCaps(caps, modelClass = DEFAULT_MODEL_CLASS) { + if (!caps || typeof caps !== 'object' || Array.isArray(caps)) { + return new Error('Enclave caps must be an object.'); + } + const classError = this.validateModelClass(modelClass, 'Enclave caps model_class'); + if (classError) return classError; + const unknown = Object.keys(caps).filter((key) => !ENCLAVE_CAP_FIELDS.has(key)).sort(); + if (unknown.length > 0) { + return new Error(`Unsupported enclave caps field: ${unknown.join(', ')}.`); + } + for (const key of ENCLAVE_CAP_BOOLEAN_FIELDS) { + if (hasOwn(caps, key) && typeof caps[key] !== 'boolean') { + return new Error(`Enclave caps ${key} must be a boolean.`); + } + } + const hasCtx = hasOwn(caps, 'ctx'); + const hasCtxMax = hasOwn(caps, 'ctx_max'); + for (const key of ENCLAVE_CAP_INTEGER_FIELDS) { + if (hasOwn(caps, key) && (!Number.isSafeInteger(caps[key]) || caps[key] <= 0)) { + return new Error(`Enclave caps ${key} must be a positive integer.`); + } + } + if (hasOwn(caps, 'vllm_gpu_memory_utilization_pct') && caps.vllm_gpu_memory_utilization_pct > 100) { + return new Error('Enclave caps vllm_gpu_memory_utilization_pct must be between 1 and 100.'); + } + for (const key of ENCLAVE_CAP_STRING_FIELDS) { + if (hasOwn(caps, key) && (typeof caps[key] !== 'string' || caps[key].length === 0 || caps[key].length > 64)) { + return new Error(`Enclave caps ${key} must be a non-empty string with at most 64 characters.`); + } + } + if (hasCtx && hasCtxMax && caps.ctx !== caps.ctx_max) { + return new Error('Enclave caps ctx and ctx_max must match when both are set.'); + } + const modalitySetError = this.validateModalitySet(caps.modality_set, 'Enclave caps modality_set'); + if (modalitySetError) return modalitySetError; + const specialityLevelsError = this.validateSpecialityLevelMap( + caps.speciality_levels, + 'Enclave caps speciality_levels', + { allowEmpty: true } + ); + if (specialityLevelsError) return specialityLevelsError; + const coreModalities = this.coreModalitiesForModelClass(modelClass); + if ([...coreModalities].some((modality) => !caps.modality_set.includes(modality))) { + return new Error(`Enclave caps modality_set is missing a core modality for model_class ${modelClass}.`); + } + if (caps.vision === true && !caps.modality_set.includes('image')) { + return new Error('Enclave caps vision requires image in modality_set.'); + } + if (caps.audio === true && !caps.modality_set.includes('audio')) { + return new Error('Enclave caps audio requires audio in modality_set.'); + } + if (caps.video === true && !caps.modality_set.includes('video')) { + return new Error('Enclave caps video requires video in modality_set.'); + } + const allowedOutputModalities = MODEL_CLASS_OUTPUT_MODALITIES[modelClass] ?? new Set(); + const validateOutputModality = (modality, label) => { + if (typeof modality !== 'string' || modality.length === 0 || modality.length > 32) { + return new Error(`Enclave caps ${label} must be a non-empty string.`); + } + if (!CAP_OUTPUT_MODALITIES.has(modality)) { + return new Error(`Unsupported enclave caps ${label}: ${modality}.`); + } + if (!allowedOutputModalities.has(modality)) { + return new Error(`Enclave caps ${label} ${modality} is not allowed for model_class ${modelClass}.`); + } + return null; + }; + const outputModalities = new Set(); + if (hasOwn(caps, 'output_modality')) { + const error = validateOutputModality(caps.output_modality, 'output_modality'); + if (error) return error; + outputModalities.add(caps.output_modality); + } + if (hasOwn(caps, 'output_modalities')) { + if (!Array.isArray(caps.output_modalities) || caps.output_modalities.length === 0 || caps.output_modalities.length > 8) { + return new Error('Enclave caps output_modalities must be a non-empty array with at most 8 entries.'); + } + const seenOutputModalities = new Set(); + for (const modality of caps.output_modalities) { + const error = validateOutputModality(modality, 'output_modalities entry'); + if (error) return error; + if (seenOutputModalities.has(modality)) { + return new Error(`Enclave caps output_modalities has duplicate modality ${modality}.`); + } + seenOutputModalities.add(modality); + outputModalities.add(modality); + } + if (hasOwn(caps, 'output_modality') && !caps.output_modalities.includes(caps.output_modality)) { + return new Error('Enclave caps output_modalities must include output_modality.'); + } + } + for (const [flag, modality] of [['image', 'image'], ['video', 'video']]) { + if (caps[flag] === true && !allowedOutputModalities.has(modality)) { + return new Error(`Enclave caps ${flag} output is not allowed for model_class ${modelClass}.`); + } + } + return null; + } + + validateModalitySet(value, label) { + if (!Array.isArray(value) || value.length === 0 || value.length > 8) { + return new Error(`${label} must be a non-empty array with at most 8 entries.`); + } + const seen = new Set(); + for (const modality of value) { + if (typeof modality !== 'string' || !ENCLAVE_MODALITIES.has(modality)) { + return new Error(`${label} contains unsupported modality ${String(modality)}.`); + } + if (seen.has(modality)) return new Error(`${label} contains duplicate modality ${modality}.`); + seen.add(modality); + } + return null; + } + + validateSpecialityLevelMap(value, label, { allowEmpty = false } = {}) { + if (!value || typeof value !== 'object' || Array.isArray(value)) { + return new Error(`${label} must be an object.`); + } + const names = Object.keys(value); + if ((!allowEmpty && names.length === 0) || names.length > 16) { + return new Error(`${label} must contain between ${allowEmpty ? 0 : 1} and 16 specialities.`); + } + for (const name of names) { + if (!this.isSafeKeyPart(name) || name.length > 128) { + return new Error(`${label} contains invalid speciality ${String(name)}.`); + } + const levels = value[name]; + if (!Array.isArray(levels) || levels.length === 0 || levels.length > 16) { + return new Error(`${label} ${name} must contain between 1 and 16 levels.`); + } + const seen = new Set(); + for (const level of levels) { + if (typeof level !== 'string' || !this.isSafeKeyPart(level) || level.length > 128) { + return new Error(`${label} ${name} contains an invalid level.`); + } + if (seen.has(level)) return new Error(`${label} ${name} contains duplicate level ${level}.`); + seen.add(level); + } + } + return null; + } + + validateSpecialitySelection(value, label, { allowEmpty = true } = {}) { + if (!value || typeof value !== 'object' || Array.isArray(value)) { + return new Error(`${label} must be an object.`); + } + const names = Object.keys(value); + if ((!allowEmpty && names.length === 0) || names.length > 16) { + return new Error(`${label} must contain between ${allowEmpty ? 0 : 1} and 16 specialities.`); + } + for (const name of names) { + const level = value[name]; + if (!this.isSafeKeyPart(name) || name.length > 128) { + return new Error(`${label} contains an invalid speciality name.`); + } + if (typeof level !== 'string' || !this.isSafeKeyPart(level) || level.length > 128) { + return new Error(`${label} ${name} contains an invalid level.`); + } + } + return null; + } + + coreModalitiesForModelClass(modelClass) { + switch (modelClass) { + case DEFAULT_MODEL_CLASS: + return new Set(['text']); + case 'embedding': + return new Set(['embedding']); + case 'image-generation': + return new Set(['image']); + case 'video-generation': + return new Set(['video']); + case 'stt': + return new Set(['audio', 'text']); + case 'tts': + case 'audio-generation': + case 'music-generation': + return new Set(['audio']); + default: + return new Set(); + } + } + + validateEnclaveArtifactBinding(value) { + const classError = this.validateModelClass(this.modelClassFor(value), 'Enclave model_class'); + if (classError) return classError; + const backendError = this.validateEnclaveBackend(value.backend); + if (backendError) return backendError; + if (!this.isHexBytes(value.artifact_root, 32)) { + return new Error('Enclave artifact_root must be a 32-byte hex Merkle root.'); + } + if (value.artifact_root_kind !== ENCLAVE_ARTIFACT_ROOT_KIND) { + return new Error(`Enclave artifact_root_kind must be ${ENCLAVE_ARTIFACT_ROOT_KIND}.`); + } + if (!this.isHexBytes(value.manifest_hash, 32)) { + return new Error('Enclave manifest_hash must be 32-byte hex.'); + } + if (!this.isHexBytes(value.binary_hash, 32)) { + return new Error('Enclave binary_hash must be 32-byte hex.'); + } + if ( + value.source_sha256 !== undefined && + value.source_sha256 !== null && + !this.isHexBytes(value.source_sha256, 32) + ) { + return new Error('Enclave source_sha256 must be 32-byte hex.'); + } + const sourceError = this.validateHuggingFaceArtifactSource(value.artifact_source, 'enclave artifact_source'); + if (sourceError) return sourceError; + return this.validateEnclaveArtifactSidecars(value.artifact_sidecars ?? {}); + } + + validateEnclaveBackend(value) { + if ( + typeof value !== 'string' || + value.length === 0 || + value.length > ENCLAVE_BACKEND_MAX_LENGTH || + !ENCLAVE_BACKEND_PATTERN.test(value) + ) { + return new Error('Enclave backend must be a lowercase canonical identifier of at most 64 ASCII characters.'); + } + return null; + } + + normalizeApprovedBinaryHashes(primary, values) { + const hashes = [primary, ...(Array.isArray(values) ? values : [])] + .filter((value) => typeof value === 'string') + .map((value) => value.toLowerCase()); + return [...new Set(hashes)].sort(); + } + + validateApprovedBinaryHashes(primary, values) { + if (!Array.isArray(values) || values.length === 0) { + return new Error('Enclave approved_binary_hashes must be a non-empty array.'); + } + if (values.length > ENCLAVE_APPROVED_BINARY_HASHES_MAX) { + return new Error(`Enclave approved_binary_hashes may contain at most ${ENCLAVE_APPROVED_BINARY_HASHES_MAX} entries.`); + } + const normalizedPrimary = typeof primary === 'string' ? primary.toLowerCase() : primary; + const seen = new Set(); + for (const hash of values) { + if (!this.isHexBytes(hash, 32)) { + return new Error('Enclave approved_binary_hashes entries must be 32-byte hex.'); + } + const normalized = hash.toLowerCase(); + if (seen.has(normalized)) { + return new Error('Enclave approved_binary_hashes must not contain duplicates.'); + } + seen.add(normalized); + } + if (!seen.has(normalizedPrimary)) { + return new Error('Enclave approved_binary_hashes must include binary_hash.'); + } + return null; + } + + normalizeEnclaveLaunchMeasurements(value) { + if (value === undefined || value === null) return null; + if (!value || typeof value !== 'object' || Array.isArray(value)) return cloneValue(value); + if (hasOwn(value, 'layers')) { + return { + schema_version: value.schema_version ?? 1, + effective_epoch: value.effective_epoch ?? 0, + ...(hasOwn(value, 'platform') ? { platform: value.platform } : {}), + layers: cloneValue(value.layers), + }; + } + if (hasOwn(value, 'measurements')) { + return { + schema_version: value.schema_version ?? 1, + effective_epoch: value.effective_epoch ?? 0, + ...(hasOwn(value, 'platform') ? { platform: value.platform } : {}), + layers: { workload: cloneValue(value.measurements) }, + }; + } + const measurements = cloneValue(value); + delete measurements.schema_version; + delete measurements.effective_epoch; + delete measurements.platform; + delete measurements.layers; + return { + schema_version: 1, + effective_epoch: value.effective_epoch ?? 0, + ...(hasOwn(value, 'platform') ? { platform: value.platform } : {}), + layers: { workload: measurements }, + }; + } + + validateEnclaveLaunchMeasurements(value, attTier) { + if (attTier < 3 && (value === undefined || value === null)) return null; + if (attTier >= 3 && (value === undefined || value === null)) { + return new Error('Tier-3 enclaves require admin-published launch_measurements.'); + } + if (!value || typeof value !== 'object' || Array.isArray(value)) { + return new Error('Enclave launch_measurements must be an object.'); + } + const allowed = new Set(['schema_version', 'effective_epoch', 'platform', 'layers']); + const unknown = Object.keys(value).filter((key) => !allowed.has(key)).sort(); + if (unknown.length > 0) { + return new Error(`enclave launch measurements does not accept fields: ${unknown.join(', ')}.`); + } + if (value.schema_version !== 1) { + return new Error('Enclave launch_measurements schema_version must be 1.'); + } + if (!Number.isSafeInteger(value.effective_epoch) || value.effective_epoch < 0) { + return new Error('Enclave launch_measurements effective_epoch must be a non-negative integer.'); + } + if (attTier >= 3 && !hasOwn(value, 'platform')) { + return new Error('Tier-3 launch_measurements must include a platform label.'); + } + if (hasOwn(value, 'platform') && !this.isSafeKeyPart(value.platform)) { + return new Error('Enclave launch_measurements platform must be a safe label.'); + } + const layers = value.layers; + if (!layers || typeof layers !== 'object' || Array.isArray(layers)) { + return new Error('Enclave launch_measurements layers must be an object.'); + } + let count = 0; + let workloadCount = 0; + let names = 0; + for (const [layer, measurements] of Object.entries(layers)) { + if (!this.isTier3MeasurementLayer(layer)) { + return new Error('Enclave launch_measurements layers must be vendor or workload.'); + } + if (!measurements || typeof measurements !== 'object' || Array.isArray(measurements)) { + return new Error('Enclave launch_measurements layer values must be objects.'); + } + const layerNames = Object.keys(measurements); + const layerCount = this.countLaunchMeasurementValues(measurements); + names += layerNames.length; + count += layerCount; + if (layer === 'workload') workloadCount += layerCount; + for (const [name, measurement] of Object.entries(measurements)) { + if (!this.isSafeLaunchMeasurementName(name)) { + return new Error('Enclave launch_measurements names must be non-empty safe labels.'); + } + const measurementError = this.validateLaunchMeasurementValueList(name, measurement, `Enclave launch_measurements ${layer}`); + if (measurementError) return measurementError; + } + } + if (attTier >= 3 && count === 0) { + return new Error('Tier-3 enclaves require at least one launch measurement.'); + } + if (attTier >= 3 && workloadCount === 0) { + return new Error('Tier-3 enclaves require workload PCR/stack measurements.'); + } + if (names > TIER3_MEASUREMENT_MAX_NAMES) { + return new Error('Enclave launch_measurements may contain at most 32 measurements.'); + } + if (count > TIER3_MEASUREMENT_MAX_VALUES) { + return new Error('Enclave launch_measurements may contain at most 128 measurement values.'); + } + return null; + } + + countLaunchMeasurementValues(measurements) { + let count = 0; + for (const value of Object.values(measurements ?? {})) { + if (typeof value === 'string') count += 1; + else if (Array.isArray(value)) count += value.length; + else if (value && typeof value === 'object' && Array.isArray(value.values)) count += value.values.length; + else if (value && typeof value === 'object' && typeof value.measurement === 'string') count += 1; + } + return count; + } + + isSafeLaunchMeasurementName(value) { + return typeof value === 'string' && value.length > 0 && value.length <= 64 && /^[A-Za-z0-9_.:-]+$/.test(value); + } + + isTier3MeasurementLayer(value) { + return value === 'vendor' || value === 'workload'; + } + + validateLaunchMeasurementHex(label, measurement) { + if ( + typeof measurement !== 'string' || + !/^[0-9a-fA-F]+$/.test(measurement) || + measurement.length < 64 || + measurement.length > 256 || + measurement.length % 2 !== 0 + ) { + return new Error(`${label} must be a 32-128 byte hex value.`); + } + return null; + } + + validateLaunchMeasurementValueList(name, value, label) { + if (typeof value === 'string') { + return this.validateLaunchMeasurementHex(`${label} ${name}`, value); + } + if (Array.isArray(value)) { + if (value.length === 0) return new Error(`${label} ${name} must not be empty.`); + for (const item of value) { + const measurement = typeof item === 'string' ? item : item?.measurement; + const error = this.validateLaunchMeasurementHex(`${label} ${name}`, measurement); + if (error) return error; + } + return null; + } + if (value && typeof value === 'object' && !Array.isArray(value)) { + if (Array.isArray(value.values)) return this.validateLaunchMeasurementValueList(name, value.values, label); + return this.validateLaunchMeasurementHex(`${label} ${name}`, value.measurement); + } + return new Error(`${label} ${name} must be a 32-128 byte hex value or array of values.`); + } + + validateTier3MeasurementBlessValue(value) { + if (!value || typeof value !== 'object' || Array.isArray(value)) { + return new Error('Tier-3 measurement blessing value must be an object.'); + } + const required = ['op', 'platform', 'layer', 'measurement_name', 'measurement', 'effective_epoch', 'at']; + const allowed = new Set([...required, 'region', 'derivation_hash', 'source']); + const unknown = Object.keys(value).filter((key) => !allowed.has(key)).sort(); + if (unknown.length > 0) { + return new Error(`tier3 measurement blessing does not accept fields: ${unknown.join(', ')}.`); + } + for (const key of required) { + if (!hasOwn(value, key)) return new Error(`Tier-3 measurement blessing is missing ${key}.`); + } + if (value.op !== 'tier3_bless_measurement') return new Error('Invalid Tier-3 measurement blessing op.'); + if (!this.isSafeKeyPart(value.platform)) return new Error('Invalid Tier-3 platform.'); + if (!this.isTier3MeasurementLayer(value.layer)) return new Error('Invalid Tier-3 measurement layer.'); + if (!this.isSafeLaunchMeasurementName(value.measurement_name)) return new Error('Invalid Tier-3 measurement name.'); + const measurementError = this.validateLaunchMeasurementHex('Tier-3 measurement', value.measurement); + if (measurementError) return measurementError; + if (!Number.isSafeInteger(value.effective_epoch) || value.effective_epoch < 0) { + return new Error('Invalid Tier-3 measurement effective epoch.'); + } + if (!Number.isSafeInteger(value.at) || value.at < 0) { + return new Error('Invalid Tier-3 measurement timestamp.'); + } + if (hasOwn(value, 'region') && value.region !== null && !this.isSafeKeyPart(value.region)) { + return new Error('Invalid Tier-3 measurement region.'); + } + if (hasOwn(value, 'derivation_hash') && value.derivation_hash !== null && !this.isHexBytes(value.derivation_hash, 32)) { + return new Error('Invalid Tier-3 derivation hash.'); + } + if (hasOwn(value, 'source') && (typeof value.source !== 'string' || value.source.length < 1 || value.source.length > 64)) { + return new Error('Invalid Tier-3 measurement source.'); + } + return null; + } + + normalizeEnclaveQuant(value) { + const normalized = String(value ?? 'unknown').trim().toLowerCase().replace(/_/g, '-'); + if (ENCLAVE_QUANT_BUCKETS.has(normalized)) return normalized; + return this.quantBucketFromDescriptor(normalized); + } + + quantBucketFromDescriptor(descriptor) { + const normalized = String(descriptor).toLowerCase().replace(/_/g, '-'); + const tokens = normalized.split(/[^a-z0-9]+/); + if (normalized.includes('nvfp4')) return 'nvfp4'; + if (normalized.includes('mxfp8')) return 'mxfp8'; + if (normalized.includes('mxfp6')) return 'mxfp6'; + if (normalized.includes('mxfp4')) return 'mxfp4'; + if (tokens.includes('nf4')) return 'nf4'; + if (normalized.includes('fp8')) return 'fp8'; + if (normalized.includes('fp6')) return 'fp6'; + if (normalized.includes('fp4')) return 'fp4'; + if (normalized.includes('bf16')) return 'bf16'; + if (normalized.includes('fp16') || normalized.includes('f16')) return 'fp16'; + if (normalized.includes('tf32')) return 'tf32'; + if (normalized.includes('fp32') || normalized.includes('f32')) return 'fp32'; + if (normalized.includes('fp64') || normalized.includes('f64')) return 'fp64'; + for (let bits = 8; bits >= 1; bits -= 1) { + const aliases = new Set([`int${bits}`, `${bits}bit`, `q${bits}`, `iq${bits}`, `tq${bits}`]); + if (tokens.some((token) => aliases.has(token))) return `int${bits}`; + } + return normalized; + } + + validateEnclaveQuant(value) { + if (typeof value !== 'string') return new Error('Enclave quant must be a string.'); + const quant = this.normalizeEnclaveQuant(value); + if (quant.length > ENCLAVE_QUANT_BUCKET_MAX_LENGTH || !ENCLAVE_QUANT_BUCKET_PATTERN.test(quant)) { + return new Error('Enclave quant must be a lowercase canonical identifier of at most 32 ASCII characters.'); + } + return null; + } + + validateEnclaveArtifactSidecars(sidecars) { + if (!sidecars || typeof sidecars !== 'object' || Array.isArray(sidecars)) { + return new Error('Enclave artifact_sidecars must be an object.'); + } + const names = Object.keys(sidecars).sort(); + if (names.length > ENCLAVE_ARTIFACT_SIDECARS_MAX) { + return new Error('Enclave artifact_sidecars has too many entries.'); + } + for (const name of names) { + if (!this.isSafeHuggingFacePathSegment(name)) { + return new Error('Enclave artifact_sidecars keys must be safe names.'); + } + const sidecar = sidecars[name]; + const shapeError = this.validateExactObjectKeys( + sidecar, + ['source', 'path', 'artifact_root', 'artifact_root_kind', 'weights_bytes', 'source_sha256'], + `enclave artifact_sidecars.${name}` + ); + if (shapeError) return shapeError; + const sourceError = this.validateHuggingFaceArtifactSource( + sidecar.source, + `enclave artifact_sidecars.${name}.source` + ); + if (sourceError) return sourceError; + if (!this.isSafeHuggingFacePath(sidecar.path)) { + return new Error(`Enclave artifact_sidecars.${name}.path must be a safe relative Hugging Face artifact path.`); + } + if (sidecar.source.path !== sidecar.path) { + return new Error(`Enclave artifact_sidecars.${name}.source.path must match path.`); + } + if (!this.isHexBytes(sidecar.artifact_root, 32)) { + return new Error(`Enclave artifact_sidecars.${name}.artifact_root must be a 32-byte hex Merkle root.`); + } + if (sidecar.artifact_root_kind !== ENCLAVE_ARTIFACT_ROOT_KIND) { + return new Error(`Enclave artifact_sidecars.${name}.artifact_root_kind must be ${ENCLAVE_ARTIFACT_ROOT_KIND}.`); + } + if (!Number.isSafeInteger(sidecar.weights_bytes) || sidecar.weights_bytes <= 0) { + return new Error(`Enclave artifact_sidecars.${name}.weights_bytes must be a positive integer.`); + } + if (!this.isHexBytes(sidecar.source_sha256, 32)) { + return new Error(`Enclave artifact_sidecars.${name}.source_sha256 must be 32-byte hex.`); + } + } + return null; + } + + validateHuggingFaceArtifactSource(source, label = 'enclave artifact_source') { + const shapeError = this.validateExactObjectKeys( + source, + ['kind', 'repo', 'revision', 'path'], + label + ); + if (shapeError) return shapeError; + if (source.kind !== 'huggingface') { + return new Error(`${label}.kind must be huggingface.`); + } + if (!this.isSafeHuggingFaceRepo(source.repo)) { + return new Error(`${label}.repo must be a safe namespace/name repo id.`); + } + if (!this.isHexBytes(source.revision, 20)) { + return new Error(`${label}.revision must be a 20-byte git commit hex.`); + } + if (!this.isSafeHuggingFacePath(source.path)) { + return new Error(`${label}.path must be a safe relative Hugging Face artifact path.`); + } + return null; + } + + validateRoomPolicy(policy) { + if (!policy || typeof policy !== 'object' || Array.isArray(policy)) { + return new Error('Room policy must be an object.'); + } + const unknown = Object.keys(policy).filter((key) => !ROOM_POLICY_FIELDS.has(key)).sort(); + if (unknown.length > 0) { + return new Error(`Unsupported room policy field: ${unknown.join(', ')}.`); + } + for (const key of ['region_hint', 'canary_set']) { + if ( + hasOwn(policy, key) && + ( + typeof policy[key] !== 'string' || + policy[key].length === 0 || + policy[key].length > 128 + ) + ) { + return new Error(`Room policy ${key} must be a non-empty string.`); + } + } + if ( + hasOwn(policy, 'min_reputation') && + ( + typeof policy.min_reputation !== 'number' || + !Number.isFinite(policy.min_reputation) || + policy.min_reputation < 0 || + policy.min_reputation > 1 + ) + ) { + return new Error('Room policy min_reputation must be between 0 and 1.'); + } + if ( + hasOwn(policy, 'max_price_mult') && + ( + typeof policy.max_price_mult !== 'number' || + !Number.isFinite(policy.max_price_mult) || + policy.max_price_mult <= 0 + ) + ) { + return new Error('Room policy max_price_mult must be positive.'); + } + return null; + } + + async priceSchedule(key, enclave, ctxMeta = null) { + const existing = await this.get(key); + if (existing) return existing; + return { + enclave_id: enclave.enclave_id, + model_id: enclave.model_id, + denom: PRICE_DENOMINATION, + ...(ctxMeta ? { + ctx_bracket: ctxMeta.ctx_bracket, + ctx_bracket_table_ver: ctxMeta.ctx_bracket_table_ver, + } : {}), + current: null, + pending: null, + }; + } + + priceScheduleAt(schedule, at) { + const updated = cloneValue(schedule); + if (updated.pending && updated.pending.effective_at <= at) { + updated.current = updated.pending; + updated.pending = null; + } + return updated; + } + + priceActiveEntry(schedule, at) { + if (schedule.pending && schedule.pending.effective_at <= at) return cloneValue(schedule.pending); + return schedule.current ? cloneValue(schedule.current) : null; + } + + priceLatestEntry(schedule) { + return schedule.pending ?? schedule.current; + } + + priceSeedSnapshot(record) { + if (!record) return null; + return { + enclave_id: record.enclave_id, + model_id: record.model_id, + denom: record.denom, + ver: record.seed_ver ?? record.ver, + ...(record.ctx_bracket ? { ctx_bracket: record.ctx_bracket } : {}), + ...(record.ctx_bracket_table_ver ? { ctx_bracket_table_ver: record.ctx_bracket_table_ver } : {}), + rate_map: cloneValue(record.seed_rate_map ?? record.rate_map), + per_req_au: record.seed_per_req_au ?? record.per_req_au, + min_session_au: record.seed_min_session_au ?? record.min_session_au, + effective_at: record.seed_effective_at ?? record.effective_at, + effective_from: record.seed_effective_from ?? record.effective_from, + updated_at: record.seed_updated_at ?? record.updated_at, + set_by: record.seed_by ?? record.set_by, + set_by_role: record.seed_by_role ?? record.set_by_role, + }; + } + + priceSeedEntry(record) { + return record?.seed ? cloneValue(record.seed) : this.priceSeedSnapshot(record); + } + + priceLatestSeedEntry(schedule) { + if (schedule.pending) return this.priceSeedEntry(schedule.pending); + if (schedule.current) return this.priceSeedEntry(schedule.current); + return null; + } + + sanitizeMarketBoundRecord(key, record) { + if (key !== 'price_min_bps' && key !== 'price_max_bps') return record; + const safe = (entry) => Number.isSafeInteger(entry?.value) && + entry.value >= 2_500 && entry.value <= 40_000; + const next = cloneValue(record); + if (!safe(next.current)) next.current = { + ...next.current, value: PARAM_DEFINITIONS[key].default, + policy_repair: 'market_activity_v2_hard_bounds', + }; + if (next.pending && !safe(next.pending)) next.pending = null; + return next; + } + + async migrateMarketPricing() { + const adminError = await this.requireAdmin(); + if (adminError) return adminError; + const shape = this.validateExactCommandValue(['op', 'at', 'markets'], 'migrate_market_pricing'); + if (shape) return shape; + if (!Number.isSafeInteger(this.value.at) || this.value.at < 0 || + !Array.isArray(this.value.markets) || this.value.markets.length > 128) { + return new Error('Migration requires a timestamp and at most 128 active market rows.'); + } + const key = 'market/activity/migration-v2'; + const existing = await this.get(key); + const state = await this.epochApplyStateRecord(); + if (state.pending_epoch !== null && state.pending_epoch !== undefined) { + return new Error('Market pricing migration requires a completed epoch boundary.'); + } + const priorIndex = await this.get('market/activity/index') ?? []; + const index = new Map(priorIndex.map((row) => [ + this.priceMarketKey(row.enclave_id, row.ctx_bracket ?? null), row, + ])); + const seen = new Set(); + for (const row of this.value.markets) { + const fields = ['enclave_id', ...(row?.ctx_bracket !== undefined + ? ['ctx_bracket', 'ctx_bracket_table_ver'] : [])]; + const rowShape = this.validateExactObjectKeys(row, fields, 'Migration market'); + if (rowShape) return rowShape; + if (!this.isSafeKeyPart(row.enclave_id)) return new Error('Invalid migration market enclave.'); + const enclave = await this.get(`enclave/${row.enclave_id}`); + if (!enclave || enclave.status !== 'active') return new Error('Migration market enclave is not active.'); + const ctx = await this.priceCtxMetaForEnclave(enclave, row.ctx_bracket, this.value.at, 'Migration market'); + if (ctx instanceof Error) return ctx; + if ((ctx?.ctx_bracket_table_ver ?? null) !== (row.ctx_bracket_table_ver ?? null)) { + return new Error('Migration market context table version is not active.'); + } + const marketKey = this.priceMarketKey(row.enclave_id, row.ctx_bracket ?? null); + if (seen.has(marketKey)) return new Error('Duplicate migration market.'); + seen.add(marketKey); + const schedule = await this.priceSchedule(this.priceScheduleKey(row.enclave_id, row.ctx_bracket ?? null), enclave, ctx); + const price = this.priceActiveEntry(schedule, this.value.at); + if (!price || this.priceSeedEntry(price)?.set_by_role !== 'admin' || + !(await this.get(`modelref/${enclave.model_id}`))) { + return new Error('Migration market requires an active admin price and model reference.'); + } + index.set(marketKey, cloneValue(row)); + } + if (index.size > DEFAULT_MAX_MARKET_USAGE_ENTRIES) return new Error('Migration market index capacity exceeded.'); + const repairs = []; + for (const name of ['price_min_bps', 'price_max_bps']) { + const before = await this.get(`params/${name}`); + if (!before) continue; + const after = this.sanitizeMarketBoundRecord(name, before); + if (stableJson(before) !== stableJson(after)) repairs.push({ key: name, before, after }); + } + const nextIndex = Array.from(index.values()).sort((a, b) => + compareCodepoint(a.enclave_id, b.enclave_id) || compareCodepoint(a.ctx_bracket ?? '', b.ctx_bracket ?? '')); + if (existing && repairs.length === 0 && stableJson(priorIndex) === stableJson(nextIndex)) { + return { ok: true, op: 'migrateMarketPricing', idempotent: true, market_count: index.size }; + } + // Complete validation before any writes. Historical params/update records stay immutable. + for (const repair of repairs) await this.put(`params/${repair.key}`, repair.after); + await this.put('market/activity/index', nextIndex); + await this.put(key, { + schema_version: 2, contract_version: CONTRACT_VERSION, + hard_min_bps: 2_500, hard_max_bps: 40_000, + previous_applied_epoch: state.updated_epoch ?? 0, + repairs: [...(existing?.repairs ?? []), ...repairs], market_count: index.size, + migrated_at: existing?.migrated_at ?? this.tx, updated_at: this.tx, migrated_by: this.address, + }); + return { ok: true, op: 'migrateMarketPricing', idempotent: false, repaired: repairs.length, market_count: index.size }; + } + + validateActivityCalibration(value, modelClass, rateMap = null) { + const shape = this.validateExactObjectKeys(value, + ['schema_version', 'source_hash', 'dimensions'], 'Activity calibration'); + if (shape) return shape; + if (value.schema_version !== 1 || !this.isHexBytes(value.source_hash, 32) || + !Array.isArray(value.dimensions) || value.dimensions.length < 1 || + value.dimensions.length > RATE_MAP_MAX_ENTRIES) { + return new Error('Invalid activity calibration metadata.'); + } + const allowed = MODEL_CLASS_RATE_UNITS[modelClass]; + const seen = new Set(); + let positive = false; + for (const row of value.dimensions) { + const error = this.validateExactObjectKeys(row, ['unit', 'units', 'work_us'], + 'Activity calibration dimension'); + if (error) return error; + if (!allowed?.has(row.unit) || seen.has(row.unit) || + !/^[1-9][0-9]{0,17}$/.test(row.units) || + !/^[1-9][0-9]{0,17}$/.test(row.work_us)) { + return new Error('Invalid activity calibration dimension.'); + } + seen.add(row.unit); + positive ||= BigInt(row.work_us) > 0n; + if ((row.unit === 'input_token' || row.unit === 'output_token') && + BigInt(row.work_us) === 0n) { + return new Error('Token activity calibration requires positive prefill/decode work.'); + } + } + if (rateMap && stableJson([...seen].sort(compareCodepoint)) !== + stableJson(rateMap.map((row) => row.unit).sort(compareCodepoint))) { + return new Error('Activity calibration must cover every model reference rate unit exactly.'); + } + if (!positive || (modelClass === DEFAULT_MODEL_CLASS && + (!seen.has('input_token') || !seen.has('output_token')))) { + return new Error('Activity calibration requires calibrated workload dimensions.'); + } + if (stableJson(value.dimensions) !== stableJson(value.dimensions.slice().sort( + (a, b) => compareCodepoint(a.unit, b.unit)))) { + return new Error('Activity calibration dimensions must be sorted.'); + } + return null; + } + + incrementalSettledUsage(body) { + const usage = this.normalizeReceiptUsage(body.usage); + const prior = this.normalizeReceiptUsage(body.billing_prior_usage); + if (usage instanceof Error || prior instanceof Error) { + return new Error('Canonical receipt activity usage is invalid.'); + } + const billed = this.normalizeLockedRateMap(body.locked_rate_map, 'activity locked rates'); + if (billed instanceof Error) return billed; + const paidUnits = new Set(billed.map((row) => row.unit)); + const result = {}; + for (const unit of new Set([...Object.keys(usage), ...Object.keys(prior)])) { + const count = BigInt(usage[unit] ?? 0) - BigInt(prior[unit] ?? 0); + if (count < 0n) return new Error('Canonical receipt activity regressed below billing baseline.'); + if (count > 0n && paidUnits.has(unit)) result[unit] = count.toString(); + } + return stableValue(result); + } + + addSettledUsage(left, right) { + if (!left || !right || typeof left !== 'object' || typeof right !== 'object' || + Array.isArray(left) || Array.isArray(right)) return new Error('Invalid settled activity units.'); + const result = {}; + for (const unit of new Set([...Object.keys(left), ...Object.keys(right)])) { + if (!this.isSafeKeyPart(unit)) return new Error('Invalid settled activity unit.'); + const a = this.parseAu(left[unit] ?? '0', 'settled activity count'); + const b = this.parseAu(right[unit] ?? '0', 'settled activity count'); + if (a instanceof Error || b instanceof Error) return new Error('Invalid settled activity count.'); + const sum = this.safeAddAu(a.toString(), b.toString()); + if (sum instanceof Error) return sum; + if (sum !== '0') result[unit] = sum; + } + return stableValue(result); + } + + calibratedActivityWork(usage, calibration) { + const dimensions = new Map(calibration.dimensions.map((row) => [row.unit, row])); + let work = 0n; + for (const [unit, count] of Object.entries(usage)) { + const dimension = dimensions.get(unit); + if (!dimension) return new Error('Settled unit is missing its signed activity calibration.'); + const n = this.parseAu(count, 'settled activity count'); + if (n instanceof Error) return n; + // Round once per epoch/axis, not per receipt or page (split-resistant). + work += (n * BigInt(dimension.work_us) * 1_000_000n) / BigInt(dimension.units); + } + return work.toString(); // picoseconds of calibrated reference work. + } + + marketActivityMomentum(currentRate, previousRate, constants) { + const current = BigInt(currentRate); + const previous = BigInt(previousRate); + const raw = previous > 0n ? current * 10_000n / previous + : (current > 0n ? BigInt(constants.max_momentum_bps) : 0n); + return Number(raw > BigInt(constants.max_momentum_bps) + ? BigInt(constants.max_momentum_bps) : raw); + } + + marketActivityEma(previousRate, currentRate, constants) { + return ((BigInt(previousRate) * BigInt(10_000 - constants.ema_alpha_bps) + + BigInt(currentRate) * BigInt(constants.ema_alpha_bps)) / 10_000n).toString(); + } + + marketActivityVector(usage, epochSeconds) { + return Object.fromEntries(Object.entries(usage).map(([unit, count]) => [ + unit, (BigInt(count) * 1_000_000_000_000n / BigInt(epochSeconds)).toString(), + ])); + } + + marketVectorMomentum(current, previous, constants) { + const units = [...new Set([...Object.keys(current), ...Object.keys(previous)])] + .filter((unit) => BigInt(current[unit] ?? '0') > 0n || BigInt(previous[unit] ?? '0') > 0n) + .sort(compareCodepoint); + if (!units.length) return 0; + let sum = 0n; + for (const unit of units) sum += BigInt(this.marketActivityMomentum( + current[unit] ?? '0', previous[unit] ?? '0', constants)); + return Number(sum / BigInt(units.length)); + } + + marketVectorEma(previous, current, constants) { + return Object.fromEntries([...new Set([...Object.keys(current), ...Object.keys(previous)])] + .sort(compareCodepoint).map((unit) => [unit, this.marketActivityEma( + previous[unit] ?? '0', current[unit] ?? '0', constants)])); + } + + async activityMarketEntries(usageMap, includeDormant) { + const entries = this.mapMarketUsageEntriesForHash(usageMap); + const known = await this.get('market/activity/index') ?? []; + if (!Array.isArray(known) || known.length > DEFAULT_MAX_MARKET_USAGE_ENTRIES) { + return new Error('Market activity index exceeds its deterministic bound.'); + } + const keys = new Set(entries.map((row) => this.priceMarketKey(row.enclave_id, row.ctx_bracket ?? null))); + for (const row of known) { + const key = this.priceMarketKey(row.enclave_id, row.ctx_bracket ?? null); + if (includeDormant && !keys.has(key)) entries.push({ ...row, demand_au: '0', session_count: 0, provider_count: 0, _activity_dormant: true }); + } + if (new Set([...keys, ...known.map((row) => this.priceMarketKey(row.enclave_id, row.ctx_bracket ?? null))]).size > DEFAULT_MAX_MARKET_USAGE_ENTRIES) { + return new Error('Market activity index capacity exceeded.'); + } + return entries.sort((a, b) => compareCodepoint(a.enclave_id, b.enclave_id) || + compareCodepoint(a.ctx_bracket ?? '', b.ctx_bracket ?? '')); + } + + async writeActivityMarketIndex(updates) { + const index = new Map((await this.get('market/activity/index') ?? []).map((row) => [ + this.priceMarketKey(row.enclave_id, row.ctx_bracket ?? null), row, + ])); + for (const update of updates) { + if (!update.record.market.activity_initialized) continue; + index.set(update.market_key, { + enclave_id: update.enclave_id, + ...(update.ctx_bracket ? { ctx_bracket: update.ctx_bracket, + ctx_bracket_table_ver: update.ctx_bracket_table_ver } : {}), + }); + } + if (index.size > DEFAULT_MAX_MARKET_USAGE_ENTRIES) return new Error('Market activity index overflow.'); + if (updates.length) await this.put('market/activity/index', Array.from(index.values()).sort( + (a, b) => compareCodepoint(a.enclave_id, b.enclave_id) || + compareCodepoint(a.ctx_bracket ?? '', b.ctx_bracket ?? ''))); + } + + marketPriceParamKeys() { + return ['price_min_bps', 'price_max_bps', 'market_ema_alpha_bps', + 'market_gain_bps', 'market_max_step_bps']; + } + + marketPriceConstants(params) { + return { + schema_version: 2, + ema_alpha_bps: params.market_ema_alpha_bps, + gain_bps: params.market_gain_bps, + max_step_bps: params.market_max_step_bps, + max_momentum_bps: 50_000, + }; + } + + scalePriceTerm(term, multiplierBps) { + const amount = this.parseAu(term, 'price term'); + if (amount instanceof Error || !Number.isSafeInteger(multiplierBps) || multiplierBps < 0) { + return new Error('Invalid price term.'); + } + if (amount === 0n) return ZERO_AU; + const scaled = (amount * BigInt(multiplierBps) + 5_000n) / 10_000n; + return this.canonicalAu(scaled > 0n ? scaled : 1n); + } + + stepPriceTerm(current, desired, constants) { + const currentAu = this.parseAu(current, 'current price term'); + const desiredAu = this.parseAu(desired, 'desired price term'); + if (currentAu instanceof Error || desiredAu instanceof Error) return new Error('Invalid price term.'); + if (currentAu === desiredAu) return this.canonicalAu(currentAu); + if (currentAu === 0n) return this.canonicalAu(desiredAu); + const delta = currentAu > desiredAu ? currentAu - desiredAu : desiredAu - currentAu; + const gainedRaw = (delta * BigInt(constants.gain_bps)) / 10_000n; + const maxStepRaw = (currentAu * BigInt(constants.max_step_bps)) / 10_000n; + const gained = gainedRaw > 0n ? gainedRaw : 1n; + const maxStep = maxStepRaw > 0n ? maxStepRaw : 1n; + const step = gained < maxStep ? gained : maxStep; + return this.canonicalAu( + desiredAu > currentAu + ? currentAu + step + : (step > currentAu ? 0n : currentAu - step) + ); + } + + scaleRateMap(rateMap, multiplierBps) { + const scaled = []; + for (const entry of rateMap) { + const perUnitAu = this.scalePriceTerm(entry.per_unit_au, multiplierBps); + if (perUnitAu instanceof Error) return perUnitAu; + scaled.push({ ...entry, per_unit_au: perUnitAu }); + } + return this.normalizeRateMap(scaled); + } + + stepRateMap(currentRateMap, desiredRateMap, constants) { + const desiredByUnit = this.rateMapByUnit(desiredRateMap); + const stepped = []; + for (const entry of currentRateMap) { + const desired = desiredByUnit.get(entry.unit); + if (!desired || desired.granularity !== entry.granularity) { + return new Error('Market price rate_map shape changed.'); + } + const perUnitAu = this.stepPriceTerm(entry.per_unit_au, desired.per_unit_au, constants); + if (perUnitAu instanceof Error) return perUnitAu; + stepped.push({ ...entry, per_unit_au: perUnitAu }); + } + return this.normalizeRateMap(stepped); + } + + clampRateMapBounds(priceRateMap, referenceRateMap, params) { + const priceByUnit = this.rateMapByUnit(priceRateMap); + const referenceByUnit = this.rateMapByUnit(referenceRateMap); + if (priceByUnit.size !== referenceByUnit.size) { + return new Error('Market price rate_map units must match model reference rate_map units.'); + } + const bounded = []; + for (const entry of priceRateMap) { + const reference = referenceByUnit.get(entry.unit); + const price = this.parseAu(entry.per_unit_au, 'market price per_unit_au'); + const referencePrice = this.parseAu( + reference?.per_unit_au, + 'market reference per_unit_au', + { allowZero: false } + ); + if ( + price instanceof Error || + referencePrice instanceof Error || + !Number.isSafeInteger(entry.granularity) || + entry.granularity <= 0 || + !Number.isSafeInteger(reference?.granularity) || + reference.granularity <= 0 + ) { + return new Error('Invalid market price rate_map bounds.'); + } + const denominator = BigInt(reference.granularity) * 10_000n; + const scaledReference = referencePrice * BigInt(entry.granularity); + const lowerNumerator = scaledReference * BigInt(Math.max(2_500, params.price_min_bps)); + const upperNumerator = scaledReference * BigInt(Math.min(40_000, params.price_max_bps)); + const lower = (lowerNumerator + denominator - 1n) / denominator; + const upper = upperNumerator / denominator; + if (lower > upper) return new Error(`Model reference bounds cannot represent unit ${entry.unit}.`); + const clamped = price < lower ? lower : (price > upper ? upper : price); + bounded.push({ ...entry, per_unit_au: this.canonicalAu(clamped) }); + } + return this.normalizeRateMap(bounded); + } + + priceTermsEqual(left, right) { + return ( + stableJson(left.rate_map) === stableJson(right.rate_map) && + this.compareAu(left.per_req_au, right.per_req_au) === 0 && + this.compareAu(left.min_session_au, right.min_session_au) === 0 + ); + } + + async computeMarketPriceUpdates(marketUsageMap, context = {}) { + const at = context.at ?? this.value.at; + const epoch = context.epoch ?? this.value.epoch; + const epochSeconds = context.epochSeconds ?? null; + const tx = context.tx ?? this.tx; + if (!Number.isSafeInteger(epochSeconds) || epochSeconds < 1) { + return new Error('Market activity requires a calibrated epoch duration.'); + } + const marketParams = await this.activeParamsAt(at, this.marketPriceParamKeys()); + const constants = this.marketPriceConstants(marketParams); + const entries = await this.activityMarketEntries(marketUsageMap, context.includeDormant === true); + if (entries instanceof Error) return entries; + const updates = []; + for (const usage of entries) { + const marketKey = this.priceMarketKey(usage.enclave_id, usage.ctx_bracket ?? null); + const enclave = await this.get(`enclave/${usage.enclave_id}`); + // Retired markets have no new orders and leave their immutable history intact. + if ((!enclave || enclave.status !== 'active') && usage.session_count === 0) continue; + if (!enclave || enclave.status !== 'active') return new Error('Market usage enclave is not active.'); + const ctxMeta = await this.priceCtxMetaForEnclave(enclave, usage.ctx_bracket, at, 'Market usage'); + if (ctxMeta instanceof Error) { + if (usage._activity_dormant) continue; + return ctxMeta; + } + if (ctxMeta && usage.ctx_bracket_table_ver !== undefined && usage.ctx_bracket_table_ver !== ctxMeta.ctx_bracket_table_ver) { + if (usage._activity_dormant) continue; + return new Error('Market usage context bracket table version is not active for the epoch.'); + } + const scheduleKey = this.priceScheduleKey(usage.enclave_id, ctxMeta?.ctx_bracket ?? null); + const schedule = this.priceScheduleAt(await this.priceSchedule(scheduleKey, enclave, ctxMeta), at); + const current = this.priceActiveEntry(schedule, at); + if (!current) return new Error('Market usage enclave has no admin price seed.'); + const seed = this.priceSeedEntry(current); + if (!seed || seed.set_by_role !== 'admin') return new Error('Market price requires an admin seed.'); + const modelRef = await this.get(`modelref/${enclave.model_id}`); + if (!modelRef) return new Error('Market price model reference not found.'); + const previousMarket = current.market ?? {}; + if (previousMarket.schema_version === 2 && previousMarket.epoch >= epoch) { + return new Error('Market activity epoch must increase exactly once per settled epoch.'); + } + const canonical = context.canonicalActivity?.get(marketKey) ?? + (context.includeDormant && usage.session_count === 0 ? { settled_usage: {} } : null); + const settledUsage = canonical?.settled_usage ?? null; + const calibration = modelRef.activity_calibration ?? null; + const calibrationError = calibration && this.validateActivityCalibration(calibration, modelRef.model_class, modelRef.rate_map); + if (calibrationError) return calibrationError; + const calibrationHash = calibration + ? await this.opaqueHash('mayhem-market-activity-calibration-v1', calibration) : null; + const work = calibration && settledUsage !== null + ? this.calibratedActivityWork(settledUsage, calibration) : null; + if (work instanceof Error) return work; + const activityRate = work === null ? null : (BigInt(work) / BigInt(epochSeconds)).toString(); + const vector = settledUsage === null ? null : this.marketActivityVector(settledUsage, epochSeconds); + // With no machine-readable calibration, compare each signed dimension only + // against its own history. No synthetic GPU capacity or monetary weights. + const activityBasis = work === null ? 'relative_dimension_vector_v1' : 'calibrated_work_v1'; + const initialized = previousMarket.schema_version === 2 && + previousMarket.activity_initialized === true && + previousMarket.calibration_hash === calibrationHash && + previousMarket.activity_basis === activityBasis && + previousMarket.epoch === epoch - 1; + const previousEma = initialized ? previousMarket.ema_activity_rate : activityRate; + const activeSupply = usage.provider_count; + if (canonical && canonical.session_count !== undefined && + (canonical.session_count !== usage.session_count || + canonical.demand_au !== usage.demand_au || + (canonical.provider_count ?? canonical.providers?.length) !== usage.provider_count)) { + return new Error('Market activity totals do not match canonical receipt evidence.'); + } + // Nonzero direction follows the previous epoch; empty epochs keep decaying. + // EMA is telemetry only; the bootstrap still holds for one epoch. + const rawMomentum = initialized && vector !== null + ? activityBasis === 'calibrated_work_v1' + ? this.marketActivityMomentum(activityRate, previousMarket.activity_rate, constants) + : this.marketVectorMomentum(vector, previousMarket.activity_vector, constants) + : 10_000; + const frozenReason = settledUsage === null ? 'missing_canonical_activity' + : !initialized ? 'activity_baseline_bootstrap' : null; + const frozen = frozenReason !== null; + const multiplierBps = frozen ? 10_000 : rawMomentum; + const activityInitialized = vector !== null; + const emaActivityRate = activityRate === null ? null : initialized + ? this.marketActivityEma(previousEma, activityRate, constants) : activityRate; + // Momentum moves the current price. The admin seed is provenance, not a dollar target. + const desiredRateMap = this.scaleRateMap(current.rate_map, multiplierBps); + const desiredPerReqAu = this.scalePriceTerm(current.per_req_au, multiplierBps); + const desiredMinSessionAu = this.scalePriceTerm(current.min_session_au, multiplierBps); + const nextTerms = { + rate_map: this.stepRateMap(current.rate_map, desiredRateMap, constants), + per_req_au: this.stepPriceTerm(current.per_req_au, desiredPerReqAu, constants), + min_session_au: this.stepPriceTerm(current.min_session_au, desiredMinSessionAu, constants), + }; + for (const term of Object.values(nextTerms)) if (term instanceof Error) return term; + for (const field of ['per_req_au', 'min_session_au']) { + const lower = BigInt(this.scalePriceTerm(seed[field], 2_500)); + const upper = BigInt(this.scalePriceTerm(seed[field], 40_000)); + const amount = BigInt(nextTerms[field]); + nextTerms[field] = (amount < lower ? lower : amount > upper ? upper : amount).toString(); + } + nextTerms.rate_map = this.clampRateMapBounds(nextTerms.rate_map, modelRef.rate_map, marketParams); + if (nextTerms.rate_map instanceof Error) return nextTerms.rate_map; + const record = { + enclave_id: current.enclave_id, model_id: current.model_id, denom: PRICE_DENOMINATION, + ver: (this.priceLatestEntry(schedule)?.ver ?? 0) + 1, + ...nextTerms, effective_at: at, effective_from: tx, updated_at: tx, + set_by: seed.set_by, set_by_role: 'admin', + ...(ctxMeta ? { ctx_bracket: ctxMeta.ctx_bracket, + ctx_bracket_table_ver: ctxMeta.ctx_bracket_table_ver } : {}), + price_source: frozen ? 'market_activity_hold' : 'market_activity_momentum', seed, + market: { + schema_version: 2, source: 'canonical_settled_work', epoch, epoch_seconds: epochSeconds, + active_supply: activeSupply, + // Gross AU remains accounting evidence only; it never enters the controller. + active_demand_au: usage.demand_au, session_count: usage.session_count, + settled_usage: settledUsage, calibration_hash: calibrationHash, + activity_basis: activityBasis, activity_vector: vector, + previous_activity_vector: initialized ? previousMarket.activity_vector : vector, + previous_activity_rate: initialized ? previousMarket.activity_rate : activityRate, + previous_ema_activity_vector: initialized ? previousMarket.ema_activity_vector : vector, + ema_activity_vector: vector === null ? null : initialized + ? this.marketVectorEma(previousMarket.ema_activity_vector, vector, constants) : vector, + calibration: cloneValue(calibration), modelref_ver: modelRef.ver ?? null, + calibrated_work_ps: work, activity_rate: activityRate, + previous_ema_activity_rate: previousEma, ema_activity_rate: emaActivityRate, + activity_initialized: activityInitialized, momentum_bps: rawMomentum, + multiplier_bps: multiplierBps, frozen, frozen_reason: frozenReason, constants, + desired_rate_map: desiredRateMap, desired_per_req_au: desiredPerReqAu, + desired_min_session_au: desiredMinSessionAu, + previous_price_ver: current.ver, previous_rate_map: cloneValue(current.rate_map), + previous_per_req_au: current.per_req_au, previous_min_session_au: current.min_session_au, + seed_price_ver: seed.ver, + }, + }; + updates.push({ + enclave_id: usage.enclave_id, + ...(ctxMeta ? { ctx_bracket: ctxMeta.ctx_bracket, + ctx_bracket_table_ver: ctxMeta.ctx_bracket_table_ver } : {}), + market_key: marketKey, ver: record.ver, rate_map: record.rate_map, + momentum_bps: rawMomentum, activity_rate: activityRate, ema_activity_rate: emaActivityRate, + active_supply: activeSupply, active_demand_au: usage.demand_au, frozen, + schedule_key: scheduleKey, schedule: { ...schedule, current: record }, + record_key: this.priceRecordKey(usage.enclave_id, record.ver, ctxMeta?.ctx_bracket ?? null), record, + }); + } + return updates; + } + + modelClassFor(value) { + if (!value || !hasOwn(value, 'model_class') || value.model_class === null || value.model_class === undefined) { + return undefined; + } + return value.model_class; + } + + validateModelClass(modelClass, label) { + if (typeof modelClass !== 'string' || modelClass.length === 0 || modelClass.length > 64) { + return new Error(`${label} must be a non-empty string.`); + } + if (!MODEL_CLASSES.has(modelClass)) return new Error(`Unsupported ${label}.`); + return null; + } + + validateLaunchEnclaveAttestationTier(attTier) { + if (attTier <= MAX_LAUNCH_ENCLAVE_ATTESTATION_TIER) return null; + return new Error( + `Enclave attestation tiers above ${MAX_LAUNCH_ENCLAVE_ATTESTATION_TIER} are not launch-advertisable; Tier 4 is provider KYB, not enclave hardware.` + ); + } + + async currentAppliedEpoch() { + const state = await this.get('epoch/apply/state'); + const epoch = state?.epoch ?? 0; + return Number.isSafeInteger(epoch) && epoch >= 0 ? epoch : 0; + } + + async enclaveMinTierPolicy(enclave, currentEpoch = null) { + const epoch = currentEpoch ?? await this.currentAppliedEpoch(); + const policy = await this.get(`tierpolicy/enclave/${enclave.enclave_id}`); + const baseMinTier = policy?.current_min_att_tier ?? enclave.min_att_tier ?? enclave.att_tier ?? 1; + const pending = policy?.pending ?? enclave.pending_min_att_tier ?? null; + if (pending && pending.effective_epoch <= epoch) { + return { + min_att_tier: pending.min_att_tier, + pending: null, + effective_epoch: pending.effective_epoch, + current_epoch: epoch, + }; + } + return { + min_att_tier: baseMinTier, + pending, + current_epoch: epoch, + }; + } + + validateRateMap(rateMap, modelClass, label, { allowZeroPrice = false } = {}) { + if (!Array.isArray(rateMap) || rateMap.length === 0 || rateMap.length > RATE_MAP_MAX_ENTRIES) { + return new Error(`${label} must be a non-empty array with at most ${RATE_MAP_MAX_ENTRIES} entries.`); + } + const validUnits = MODEL_CLASS_RATE_UNITS[modelClass]; + if (!validUnits) return new Error(`No rate units configured for model_class ${modelClass}.`); + const seen = new Set(); + for (const entry of rateMap) { + const shapeError = this.validateExactObjectKeys(entry, ['unit', 'per_unit_au', 'granularity'], `${label} entry`); + if (shapeError) return shapeError; + if (typeof entry.unit !== 'string' || entry.unit.length === 0 || entry.unit.length > 64) { + return new Error(`${label} unit must be a non-empty string.`); + } + if (!validUnits.has(entry.unit)) return new Error(`${label} unit ${entry.unit} is not allowed for model_class ${modelClass}.`); + if (seen.has(entry.unit)) return new Error(`${label} has duplicate unit ${entry.unit}.`); + seen.add(entry.unit); + const perUnitAu = this.normalizeAu(entry.per_unit_au, `${label} per_unit_au`, { allowZero: allowZeroPrice }); + if (perUnitAu instanceof Error || (!allowZeroPrice && this.isZeroAu(perUnitAu))) { + return new Error(`${label} per_unit_au must be ${allowZeroPrice ? 'a non-negative' : 'a positive'} integer.`); + } + if (!Number.isSafeInteger(entry.granularity) || entry.granularity <= 0) { + return new Error(`${label} granularity must be a positive integer.`); + } + } + return null; + } + + validateEnclaveModalityRateMap(enclave, rateMap) { + const modalityError = this.validateModalitySet( + enclave?.caps?.modality_set, + 'Enclave caps modality_set' + ); + if (modalityError) return modalityError; + const modelClass = this.modelClassFor(enclave); + const required = new Set(); + if (modelClass === DEFAULT_MODEL_CLASS) { + required.add('input_token'); + required.add('output_token'); + } else if (modelClass === 'embedding') { + required.add('input_token'); + } else if (modelClass === 'image-generation') { + required.add('image'); + required.add('step'); + } else if (modelClass === 'video-generation') { + required.add('video_second'); + required.add('frame'); + } else if (modelClass === 'tts' || modelClass === 'audio-generation' || modelClass === 'music-generation') { + required.add('input_character'); + required.add('audio_second'); + } else if (modelClass === 'stt') { + required.add('audio_second'); + } + const units = new Set(rateMap.map((entry) => entry.unit)); + for (const unit of required) { + if (!units.has(unit)) { + return new Error(`Enclave price rate_map is missing required modality unit ${unit}.`); + } + } + return null; + } + + normalizeRateMap(rateMap) { + return rateMap + .map((entry) => ({ + unit: entry.unit, + per_unit_au: this.normalizeAu(entry.per_unit_au), + granularity: entry.granularity, + })) + .sort((left, right) => compareCodepoint(left.unit, right.unit)); + } + + normalizeLockedRateMap(rateMap, label) { + if (!Array.isArray(rateMap) || rateMap.length === 0 || rateMap.length > RATE_MAP_MAX_ENTRIES) { + return new Error(`${label} must be a non-empty array with at most ${RATE_MAP_MAX_ENTRIES} entries.`); + } + const seen = new Set(); + const normalized = []; + for (const entry of rateMap) { + const shapeError = this.validateExactObjectKeys(entry, ['unit', 'per_unit_au', 'granularity'], `${label} entry`); + if (shapeError) return shapeError; + if (typeof entry.unit !== 'string' || entry.unit.length === 0 || entry.unit.length > 64) { + return new Error(`${label} unit must be a non-empty string.`); + } + const unit = this.canonicalUsageUnit(entry.unit); + if (!this.isSafeKeyPart(unit)) return new Error(`${label} unit is invalid.`); + if (seen.has(unit)) return new Error(`${label} has duplicate unit ${unit}.`); + seen.add(unit); + const perUnitAu = this.normalizeAu(entry.per_unit_au, `${label} per_unit_au`, { allowZero: false }); + if (perUnitAu instanceof Error) { + return new Error(`${label} per_unit_au must be a positive integer.`); + } + if (!Number.isSafeInteger(entry.granularity) || entry.granularity <= 0) { + return new Error(`${label} granularity must be a positive integer.`); + } + normalized.push({ + unit, + per_unit_au: perUnitAu, + granularity: entry.granularity, + }); + } + return normalized.sort((left, right) => compareCodepoint(left.unit, right.unit)); + } + + rateMapByUnit(rateMap) { + const byUnit = new Map(); + for (const entry of rateMap ?? []) byUnit.set(entry.unit, entry); + return byUnit; + } + + validateRateMapBounds(priceRateMap, referenceRateMap, params) { + const priceByUnit = this.rateMapByUnit(priceRateMap); + const referenceByUnit = this.rateMapByUnit(referenceRateMap); + if (priceByUnit.size !== referenceByUnit.size) { + return new Error('Price rate_map units must match model reference rate_map units.'); + } + for (const [unit, ref] of referenceByUnit.entries()) { + const price = priceByUnit.get(unit); + if (!price) return new Error(`Price rate_map is missing model reference unit ${unit}.`); + if (!this.rateWithinBounds(price, ref, params)) { + return new Error(`Price rate_map unit ${unit} outside model reference bounds.`); + } + } + return null; + } + + rateWithinBounds(price, ref, params = { + price_min_bps: PARAM_DEFINITIONS.price_min_bps.default, + price_max_bps: PARAM_DEFINITIONS.price_max_bps.default, + }) { + const refPerUnit = this.parseAu(ref?.per_unit_au, 'reference rate per_unit_au', { allowZero: false }); + const pricePerUnit = this.parseAu(price?.per_unit_au, 'price rate per_unit_au'); + if ( + refPerUnit instanceof Error || + !Number.isSafeInteger(ref?.granularity) || + ref.granularity <= 0 || + pricePerUnit instanceof Error || + !Number.isSafeInteger(price?.granularity) || + price.granularity <= 0 + ) { + return false; + } + const priceScaled = pricePerUnit * BigInt(ref.granularity) * 10_000n; + const refScaled = refPerUnit * BigInt(price.granularity); + return ( + priceScaled >= refScaled * BigInt(Math.max(2_500, params.price_min_bps)) && + priceScaled <= refScaled * BigInt(Math.min(40_000, params.price_max_bps)) + ); + } + + validateReputationEvent(value) { + if (!this.isSafeKeyPart(value.event_id)) return new Error('Invalid reputation event id.'); + if (!REPUTATION_EVENT_KINDS.has(value.kind)) return new Error('Unsupported reputation event kind.'); + if ( + (value.kind === 'session_ok' || value.kind === 'session_partial') && + this.normalizeAu(value.paid_au, 'reputation paid amount') instanceof Error + ) { + return new Error('Reputation event requires paid_au.'); + } + if ( + value.kind === 'session_fail' && + this.normalizeAu(value.max_spend_au, 'reputation max spend') instanceof Error + ) { + return new Error('Reputation event requires max_spend_au.'); + } + return null; + } + + validateReputationAnchor(value) { + if (!value || typeof value !== 'object' || Array.isArray(value)) { + return new Error('Invalid reputation anchor payload.'); + } + if (value.op !== 'anchor_reputation') return new Error('Invalid reputation anchor op.'); + if (!this.isSafeKeyPart(value.provider)) return new Error('Invalid reputation provider.'); + if (!Number.isSafeInteger(value.epoch) || value.epoch < 0) { + return new Error('Invalid reputation anchor epoch.'); + } + if (!Number.isSafeInteger(value.folded_at) || value.folded_at < 0) { + return new Error('Invalid reputation anchor folded_at.'); + } + if (!this.isHexBytes(value.events_head, 32)) return new Error('Invalid reputation events head.'); + if (!Number.isSafeInteger(value.r_bps) || value.r_bps < 0 || value.r_bps > 10_000) { + return new Error('Invalid reputation r_bps.'); + } + if (!Number.isSafeInteger(value.raw_milli)) return new Error('Invalid reputation raw_milli.'); + if (!Number.isSafeInteger(value.successful_sessions) || value.successful_sessions < 0) { + return new Error('Invalid reputation successful_sessions.'); + } + if ( + value.provenance_violation !== undefined && + typeof value.provenance_violation !== 'boolean' + ) { + return new Error('Invalid reputation provenance_violation.'); + } + return null; + } + + validateProbeResult(value) { + if (!this.isSafeKeyPart(value.probe_id)) return new Error('Invalid probe id.'); + if (!PROBE_KINDS.has(value.probe_kind)) return new Error('Unsupported probe kind.'); + if (value.probe_kind === 'canary') { + if (!value.enclave_id) return new Error('Canary probe requires enclave_id.'); + if (!value.binary_hash) return new Error('Canary probe requires binary_hash.'); + if (!Number.isInteger(value.match_bps)) return new Error('Canary probe requires match_bps.'); + if (typeof value.pass !== 'boolean') return new Error('Canary probe requires pass.'); + if (!value.canary_set) return new Error('Canary probe requires canary_set.'); + if (!value.canary_prompt_id) return new Error('Canary probe requires canary_prompt_id.'); + if (value.challenge_epoch === undefined) return new Error('Canary probe requires challenge_epoch.'); + if (!value.challenge_apply_hash) return new Error('Canary probe requires challenge_apply_hash.'); + if (!value.challenge_seed) return new Error('Canary probe requires challenge_seed.'); + if (!value.verification_method) return new Error('Canary probe requires verification_method.'); + if (!PROBE_VERIFICATION_METHODS.has(value.verification_method)) { + return new Error('Unsupported canary verification_method.'); + } + if (!value.session_receipt_hash) return new Error('Canary probe requires session_receipt_hash.'); + if (!value.evidence_hash) return new Error('Canary probe requires evidence_hash.'); + if (!value.auditor_sig) return new Error('Canary probe requires auditor_sig.'); + if (!this.isSafeKeyPart(value.enclave_id)) return new Error('Invalid canary enclave id.'); + if (!this.isHexBytes(value.binary_hash, 32)) return new Error('Invalid canary binary hash.'); + if (!this.isHexBytes(value.session_receipt_hash, 32)) { + return new Error('Invalid canary session receipt hash.'); + } + if (!this.isHexBytes(value.evidence_hash, 32)) return new Error('Invalid canary evidence hash.'); + if (!this.isHexBytes(value.auditor_sig, 64)) return new Error('Invalid canary auditor signature.'); + if (!this.isSafeKeyPart(value.canary_prompt_id)) return new Error('Invalid canary prompt id.'); + if (!Number.isSafeInteger(value.challenge_epoch) || value.challenge_epoch < 0) { + return new Error('Invalid canary challenge epoch.'); + } + if (!this.isHexBytes(value.challenge_apply_hash, 32)) { + return new Error('Invalid canary challenge apply hash.'); + } + if (!this.isHexBytes(value.challenge_seed, 32)) return new Error('Invalid canary challenge seed.'); + } + return null; + } + + async normalizeSpendVoucherForReserve(voucher) { + const voucherFields = [ + 'schema_version', + 'session_id', + 'billing_id', + 'billing_attempt', + 'billing_prior_usage', + 'billing_prior_au_owed_cum', + 'billing_epoch', + 'reservation_id', + 'reservation_expires_after_epoch', + 'reservation_receipt_grace_epochs', + 'user', + 'provider', + 'payout_revision', + 'rail', + 'enclave_id', + 'model_id', + 'price_ver', + 'locked_rate_map', + 'locked_per_req_au', + 'locked_min_session_au', + 'served_ctx', + 'required_modalities', + 'ctx_bracket', + 'ctx_bracket_table_ver', + 'rules_ver', + 'max_spend_au', + 'checkpoint_every', + 'user_sig', + ]; + if (hasOwn(voucher, 'required_specialities')) voucherFields.push('required_specialities'); + if (hasOwn(voucher, 'workflow')) voucherFields.push('workflow'); + const shapeError = this.validateExactObjectKeys( + voucher, + voucherFields, + 'spend voucher' + ); + if (shapeError) return shapeError; + if (voucher.schema_version !== SPEND_VOUCHER_SCHEMA_VERSION) { + return new Error('Unsupported spend voucher schema version.'); + } + const checkpointError = this.validateExactObjectKeys( + voucher.checkpoint_every, + ['tokens', 'ms'], + 'spend voucher checkpoint policy' + ); + if (checkpointError) return checkpointError; + const rail = this.normalizeLedgerRail(voucher.rail, 'spend voucher rail'); + if (rail instanceof Error) return rail; + if (!this.isHexBytes(voucher.session_id, 32)) return new Error('Invalid spend voucher session id.'); + if (!this.isHexBytes(voucher.billing_id, 32)) return new Error('Invalid spend voucher billing id.'); + if (!Number.isSafeInteger(voucher.billing_attempt) || voucher.billing_attempt < 0) { + return new Error('Invalid spend voucher billing attempt.'); + } + if (!Number.isSafeInteger(voucher.billing_epoch) || voucher.billing_epoch < 1) { + return new Error('Invalid spend voucher billing epoch.'); + } + if (!this.isHexBytes(voucher.reservation_id, 32)) { + return new Error('Invalid spend voucher reservation id.'); + } + if (!Number.isSafeInteger(voucher.reservation_expires_after_epoch) || + voucher.reservation_expires_after_epoch <= voucher.billing_epoch || + !Number.isSafeInteger(voucher.reservation_receipt_grace_epochs) || + voucher.reservation_receipt_grace_epochs < 0) { + return new Error('Invalid spend voucher reservation expiry policy.'); + } + if (!this.isHexBytes(voucher.user, 32)) return new Error('Invalid spend voucher user.'); + if (!this.isHexBytes(voucher.provider, 32)) return new Error('Invalid spend voucher provider.'); + if (!this.isHexBytes(voucher.payout_revision, 32)) { + return new Error('Invalid spend voucher payout revision.'); + } + const billingPriorUsage = this.normalizeReceiptUsage(voucher.billing_prior_usage); + if (billingPriorUsage instanceof Error) return billingPriorUsage; + if (stableJson(billingPriorUsage) !== stableJson(voucher.billing_prior_usage)) { + return new Error('Spend voucher billing prior usage must be canonical.'); + } + const billingPriorAuOwedCum = this.normalizeAu( + voucher.billing_prior_au_owed_cum, + 'spend voucher billing prior cumulative amount' + ); + if (billingPriorAuOwedCum instanceof Error) { + return new Error('Invalid spend voucher billing prior cumulative amount.'); + } + if ( + voucher.billing_attempt === 0 && + (Object.keys(billingPriorUsage).length > 0 || !this.isZeroAu(billingPriorAuOwedCum)) + ) { + return new Error('Initial spend voucher billing attempt must have an empty baseline.'); + } + if (this.isZeroAu(billingPriorAuOwedCum) && Object.keys(billingPriorUsage).length > 0) { + return new Error('Spend voucher billing prior usage requires a prior cumulative amount.'); + } + if (!this.isHexBytes(voucher.enclave_id, 32)) return new Error('Invalid spend voucher enclave id.'); + if (typeof voucher.model_id !== 'string' || + voucher.model_id.length === 0 || + voucher.model_id.length > 256) { + return new Error('Invalid spend voucher model id.'); + } + if (!Number.isSafeInteger(voucher.price_ver) || voucher.price_ver < 1) { + return new Error('Invalid spend voucher price version.'); + } + if (!Number.isSafeInteger(voucher.rules_ver) || voucher.rules_ver < 1) { + return new Error('Invalid spend voucher rules version.'); + } + const lockedRateMap = this.normalizeLockedRateMap(voucher.locked_rate_map, 'spend voucher locked_rate_map'); + if (lockedRateMap instanceof Error) return lockedRateMap; + const lockedPerReqAu = this.normalizeAu(voucher.locked_per_req_au, 'spend voucher locked per-request price'); + if (lockedPerReqAu instanceof Error) { + return new Error('Invalid spend voucher locked per-request price.'); + } + const lockedMinSessionAu = this.normalizeAu(voucher.locked_min_session_au, 'spend voucher locked minimum session price'); + if (lockedMinSessionAu instanceof Error) { + return new Error('Invalid spend voucher locked minimum session price.'); + } + if (!Number.isSafeInteger(voucher.served_ctx) || voucher.served_ctx < 0) { + return new Error('Invalid spend voucher served context.'); + } + const modalityError = this.validateModalitySet( + voucher.required_modalities, + 'spend voucher required_modalities' + ); + if (modalityError) return modalityError; + const requiredSpecialities = voucher.required_specialities ?? {}; + const specialitiesError = this.validateSpecialitySelection( + requiredSpecialities, + 'spend voucher required_specialities' + ); + if (specialitiesError) return specialitiesError; + const normalizedSpecialities = Object.fromEntries( + Object.entries(requiredSpecialities).sort(([left], [right]) => compareCodepoint(left, right)) + ); + let workflow = null; + if (hasOwn(voucher, 'workflow')) { + workflow = this.normalizeWorkflowBinding( + voucher.workflow, + 'spend voucher workflow', + lockedRateMap + ); + if (workflow instanceof Error) return workflow; + if (stableJson(workflow) !== stableJson(canonicalWorkflowBinding(voucher.workflow))) { + return new Error('Spend voucher workflow must be canonical.'); + } + } + const table = voucher.ctx_bracket_table_ver === null || voucher.ctx_bracket_table_ver === undefined + ? null + : await this.ctxBracketTableByVersion(voucher.ctx_bracket_table_ver); + if (table instanceof Error) return table; + const ctxMeta = await this.normalizeCtxBracketEvidenceForEnclave( + voucher.enclave_id.toLowerCase(), + voucher.served_ctx, + voucher.ctx_bracket, + voucher.ctx_bracket_table_ver, + table, + 'spend voucher' + ); + if (ctxMeta instanceof Error) return ctxMeta; + const maxSpendAu = this.normalizeAu(voucher.max_spend_au, 'spend voucher max spend', { allowZero: false }); + if (maxSpendAu instanceof Error) { + return new Error('Invalid spend voucher max spend.'); + } + if (!Number.isSafeInteger(voucher.checkpoint_every.tokens) || voucher.checkpoint_every.tokens < 1) { + return new Error('Invalid spend voucher checkpoint tokens.'); + } + if (!Number.isSafeInteger(voucher.checkpoint_every.ms) || voucher.checkpoint_every.ms < 1) { + return new Error('Invalid spend voucher checkpoint milliseconds.'); + } + if (!this.isHexBytes(voucher.user_sig, 64)) return new Error('Invalid spend voucher user signature.'); + const body = { + schema_version: SPEND_VOUCHER_SCHEMA_VERSION, + session_id: voucher.session_id.toLowerCase(), + billing_id: voucher.billing_id.toLowerCase(), + billing_attempt: voucher.billing_attempt, + billing_prior_usage: billingPriorUsage, + billing_prior_au_owed_cum: billingPriorAuOwedCum, + billing_epoch: voucher.billing_epoch, + reservation_id: voucher.reservation_id.toLowerCase(), + reservation_expires_after_epoch: voucher.reservation_expires_after_epoch, + reservation_receipt_grace_epochs: voucher.reservation_receipt_grace_epochs, + user: voucher.user.toLowerCase(), + provider: voucher.provider.toLowerCase(), + payout_revision: voucher.payout_revision.toLowerCase(), + rail, + enclave_id: voucher.enclave_id.toLowerCase(), + model_id: voucher.model_id, + price_ver: voucher.price_ver, + locked_rate_map: lockedRateMap, + locked_per_req_au: lockedPerReqAu, + locked_min_session_au: lockedMinSessionAu, + served_ctx: voucher.served_ctx, + required_modalities: voucher.required_modalities.slice(), + required_specialities: normalizedSpecialities, + ...(workflow ? { workflow } : {}), + ctx_bracket: ctxMeta.ctx_bracket, + ctx_bracket_table_ver: ctxMeta.ctx_bracket_table_ver, + rules_ver: voucher.rules_ver, + max_spend_au: maxSpendAu, + checkpoint_every: { + tokens: voucher.checkpoint_every.tokens, + ms: voucher.checkpoint_every.ms, + }, + }; + return { + ...body, + user_sig: voucher.user_sig.toLowerCase(), + body, + }; + } + + async normalizeTargetedSpendReserveValue(value) { + const reserveFields = [ + 'op', + 'payout_revision', + 'contract_version', + 'session_id', + 'reservation_id', + 'reservation_expires_after_epoch', + 'reservation_receipt_grace_epochs', + 'epoch', + 'at', + 'rail', + 'user', + 'provider', + 'enclave_id', + 'enclave_pubkey', + 'model_id', + 'price_ver', + 'rules_ver', + 'served_ctx', + 'required_modalities', + 'ctx_bracket', + 'ctx_bracket_table_ver', + 'max_spend_au', + 'voucher', + 'provider_sig', + ]; + if (hasOwn(value, 'required_specialities')) reserveFields.push('required_specialities'); + if (hasOwn(value, 'workflow')) reserveFields.push('workflow'); + const shapeError = this.validateExactObjectKeys( + value, + reserveFields, + 'spend reservation feature' + ); + if (shapeError) return shapeError; + if (value.op !== 'spend_reserve_targeted') return new Error('Invalid targeted spend reservation op.'); + if (!this.isHexBytes(value.payout_revision, 32) || + value.payout_revision !== value.payout_revision.toLowerCase()) { + return new Error('Invalid targeted spend reservation payout revision.'); + } + if (value.contract_version !== CONTRACT_VERSION) return new Error('Invalid spend reservation contract version.'); + if (!this.isHexBytes(value.session_id, 32)) return new Error('Invalid spend reservation session id.'); + if (!this.isHexBytes(value.reservation_id, 32) || + value.reservation_id !== value.reservation_id.toLowerCase()) { + return new Error('Invalid spend reservation reservation id.'); + } + if (!Number.isSafeInteger(value.reservation_expires_after_epoch) || + value.reservation_expires_after_epoch <= value.epoch || + !Number.isSafeInteger(value.reservation_receipt_grace_epochs) || + value.reservation_receipt_grace_epochs < 0) { + return new Error('Invalid spend reservation expiry policy.'); + } + if (!Number.isSafeInteger(value.epoch) || value.epoch < 1) return new Error('Invalid spend reservation epoch.'); + if (!Number.isSafeInteger(value.at) || value.at < 0) return new Error('Invalid spend reservation timestamp.'); + const rail = this.normalizeLedgerRail(value.rail, 'spend reservation rail'); + if (rail instanceof Error) return rail; + if (!this.isHexBytes(value.user, 32)) return new Error('Invalid spend reservation user.'); + if (!this.isHexBytes(value.provider, 32)) return new Error('Invalid spend reservation provider.'); + if (!this.isHexBytes(value.enclave_id, 32)) return new Error('Invalid spend reservation enclave.'); + if (!this.isHexBytes(value.enclave_pubkey, 32)) { + return new Error('Invalid spend reservation enclave public key.'); + } + if (typeof value.model_id !== 'string' || + value.model_id.length === 0 || + value.model_id.length > 256) { + return new Error('Invalid spend reservation model id.'); + } + if (!Number.isSafeInteger(value.price_ver) || value.price_ver < 1) { + return new Error('Invalid spend reservation price version.'); + } + if (!Number.isSafeInteger(value.rules_ver) || value.rules_ver < 1) { + return new Error('Invalid spend reservation rules version.'); + } + if (!Number.isSafeInteger(value.served_ctx) || value.served_ctx < 0) { + return new Error('Invalid spend reservation served context.'); + } + const modalityError = this.validateModalitySet( + value.required_modalities, + 'spend reservation required_modalities' + ); + if (modalityError) return modalityError; + const requiredSpecialities = value.required_specialities ?? {}; + const specialitiesError = this.validateSpecialitySelection( + requiredSpecialities, + 'spend reservation required_specialities' + ); + if (specialitiesError) return specialitiesError; + const normalizedSpecialities = Object.fromEntries( + Object.entries(requiredSpecialities).sort(([left], [right]) => compareCodepoint(left, right)) + ); + const activeTable = value.ctx_bracket_table_ver === null || value.ctx_bracket_table_ver === undefined + ? null + : await this.ctxBracketTableAt(value.at); + if (activeTable instanceof Error) return activeTable; + if (activeTable && value.ctx_bracket_table_ver !== activeTable.ver) { + return new Error('Spend reservation context bracket table is not active.'); + } + const ctxMeta = await this.normalizeCtxBracketEvidenceForEnclave( + value.enclave_id.toLowerCase(), + value.served_ctx, + value.ctx_bracket, + value.ctx_bracket_table_ver, + activeTable, + 'spend reservation' + ); + if (ctxMeta instanceof Error) return ctxMeta; + const maxSpendAu = this.normalizeAu(value.max_spend_au, 'spend reservation max spend', { allowZero: false }); + if (maxSpendAu instanceof Error) { + return new Error('Invalid spend reservation max spend.'); + } + if (!this.isHexBytes(value.provider_sig, 64)) { + return new Error('Invalid spend reservation provider signature.'); + } + const voucher = await this.normalizeSpendVoucherForReserve(value.voucher); + if (voucher instanceof Error) return voucher; + if (voucher.session_id !== value.session_id.toLowerCase()) { + return new Error('Spend reservation voucher session mismatch.'); + } + if (voucher.reservation_id !== value.reservation_id.toLowerCase()) { + return new Error('Spend reservation voucher reservation mismatch.'); + } + if (voucher.reservation_expires_after_epoch !== value.reservation_expires_after_epoch || + voucher.reservation_receipt_grace_epochs !== value.reservation_receipt_grace_epochs) { + return new Error('Spend reservation voucher expiry policy mismatch.'); + } + if (voucher.billing_epoch !== value.epoch) { + return new Error('Spend reservation voucher billing epoch mismatch.'); + } + if (voucher.user !== value.user.toLowerCase()) { + return new Error('Spend reservation voucher user mismatch.'); + } + if (voucher.provider !== value.provider.toLowerCase()) { + return new Error('Spend reservation voucher provider mismatch.'); + } + if (voucher.payout_revision !== value.payout_revision.toLowerCase()) { + return new Error('Spend reservation voucher payout revision mismatch.'); + } + if (voucher.rail !== rail) return new Error('Spend reservation voucher rail mismatch.'); + if (voucher.enclave_id !== value.enclave_id.toLowerCase()) { + return new Error('Spend reservation voucher enclave mismatch.'); + } + if (voucher.model_id !== value.model_id) { + return new Error('Spend reservation voucher model mismatch.'); + } + if (voucher.price_ver !== value.price_ver) { + return new Error('Spend reservation voucher price mismatch.'); + } + if (voucher.body.served_ctx !== value.served_ctx) { + return new Error('Spend reservation voucher served context mismatch.'); + } + if (stableJson(voucher.body.required_modalities) !== stableJson(value.required_modalities)) { + return new Error('Spend reservation voucher required modalities mismatch.'); + } + if (stableJson(voucher.body.required_specialities) !== stableJson(normalizedSpecialities)) { + return new Error('Spend reservation voucher required specialities mismatch.'); + } + let workflow = null; + if (hasOwn(value, 'workflow')) { + workflow = this.normalizeWorkflowBinding( + value.workflow, + 'spend reservation workflow', + voucher.body.locked_rate_map + ); + if (workflow instanceof Error) return workflow; + if (stableJson(workflow) !== stableJson(value.workflow)) { + return new Error('Spend reservation workflow must be canonical.'); + } + } + if (stableJson(voucher.body.workflow ?? null) !== stableJson(workflow)) { + return new Error('Spend reservation voucher workflow mismatch.'); + } + if (voucher.body.ctx_bracket !== value.ctx_bracket) { + return new Error('Spend reservation voucher context bracket mismatch.'); + } + if (voucher.body.ctx_bracket_table_ver !== value.ctx_bracket_table_ver) { + return new Error('Spend reservation voucher context bracket table version mismatch.'); + } + if (voucher.rules_ver !== value.rules_ver) { + return new Error('Spend reservation voucher rules version mismatch.'); + } + if (this.compareAu(voucher.max_spend_au, maxSpendAu) !== 0) { + return new Error('Spend reservation voucher max spend mismatch.'); + } + const reservationParams = await this.activeParamsAt(value.at, [ + 'reservation_max_lifetime_epochs', + 'reservation_receipt_grace_epochs', + ]); + if ( + value.epoch > Number.MAX_SAFE_INTEGER - reservationParams.reservation_max_lifetime_epochs || + value.reservation_expires_after_epoch !== + value.epoch + reservationParams.reservation_max_lifetime_epochs || + value.reservation_receipt_grace_epochs !== + reservationParams.reservation_receipt_grace_epochs + ) { + return new Error('Spend reservation expiry policy does not match active parameters.'); + } + return { + op: 'spend_reserve_targeted', + payout_revision: value.payout_revision, + contract_version: CONTRACT_VERSION, + session_id: value.session_id.toLowerCase(), + reservation_id: value.reservation_id.toLowerCase(), + reservation_expires_after_epoch: value.reservation_expires_after_epoch, + reservation_receipt_grace_epochs: value.reservation_receipt_grace_epochs, + epoch: value.epoch, + at: value.at, + rail, + user: value.user.toLowerCase(), + provider: value.provider.toLowerCase(), + enclave_id: value.enclave_id.toLowerCase(), + enclave_pubkey: value.enclave_pubkey.toLowerCase(), + model_id: value.model_id, + price_ver: value.price_ver, + locked_rate_map: voucher.body.locked_rate_map, + locked_per_req_au: voucher.body.locked_per_req_au, + locked_min_session_au: voucher.body.locked_min_session_au, + served_ctx: value.served_ctx, + required_modalities: value.required_modalities.slice(), + required_specialities: normalizedSpecialities, + ...(workflow ? { workflow } : {}), + ctx_bracket: ctxMeta.ctx_bracket, + ctx_bracket_table_ver: ctxMeta.ctx_bracket_table_ver, + rules_ver: value.rules_ver, + max_spend_au: maxSpendAu, + voucher: { ...cloneValue(voucher.body), user_sig: voucher.user_sig }, + voucher_body: voucher.body, + provider_sig: value.provider_sig.toLowerCase(), + }; + } + + + + async normalizeSpendReserveValue(value) { + if (CONTRACT_VERSION >= 17) { + return new Error('Aggregate spend reservations are disabled; use spend_reserve_targeted.'); + } + const reserveFields = [ + 'op', + 'contract_version', + 'session_id', + 'epoch', + 'at', + 'rail', + 'user', + 'provider', + 'enclave_id', + 'price_ver', + 'rules_ver', + 'served_ctx', + 'required_modalities', + 'ctx_bracket', + 'ctx_bracket_table_ver', + 'max_spend_au', + 'voucher', + 'provider_sig', + ]; + if (hasOwn(value, 'required_specialities')) reserveFields.push('required_specialities'); + const shapeError = this.validateExactObjectKeys( + value, + reserveFields, + 'spend reservation feature' + ); + if (shapeError) return shapeError; + if (value.op !== 'spend_reserve') return new Error('Invalid spend reservation op.'); + if (value.contract_version !== CONTRACT_VERSION) return new Error('Invalid spend reservation contract version.'); + if (!this.isHexBytes(value.session_id, 32)) return new Error('Invalid spend reservation session id.'); + if (!Number.isSafeInteger(value.epoch) || value.epoch < 1) return new Error('Invalid spend reservation epoch.'); + if (!Number.isSafeInteger(value.at) || value.at < 0) return new Error('Invalid spend reservation timestamp.'); + const rail = this.normalizeLedgerRail(value.rail, 'spend reservation rail'); + if (rail instanceof Error) return rail; + if (!this.isHexBytes(value.user, 32)) return new Error('Invalid spend reservation user.'); + if (!this.isHexBytes(value.provider, 32)) return new Error('Invalid spend reservation provider.'); + if (!this.isHexBytes(value.enclave_id, 32)) return new Error('Invalid spend reservation enclave.'); + if (!Number.isSafeInteger(value.price_ver) || value.price_ver < 1) { + return new Error('Invalid spend reservation price version.'); + } + if (!Number.isSafeInteger(value.rules_ver) || value.rules_ver < 1) { + return new Error('Invalid spend reservation rules version.'); + } + if (!Number.isSafeInteger(value.served_ctx) || value.served_ctx < 0) { + return new Error('Invalid spend reservation served context.'); + } + const modalityError = this.validateModalitySet( + value.required_modalities, + 'spend reservation required_modalities' + ); + if (modalityError) return modalityError; + const requiredSpecialities = value.required_specialities ?? {}; + const specialitiesError = this.validateSpecialitySelection( + requiredSpecialities, + 'spend reservation required_specialities' + ); + if (specialitiesError) return specialitiesError; + const normalizedSpecialities = Object.fromEntries( + Object.entries(requiredSpecialities).sort(([left], [right]) => compareCodepoint(left, right)) + ); + const activeTable = value.ctx_bracket_table_ver === null || value.ctx_bracket_table_ver === undefined + ? null + : await this.ctxBracketTableAt(value.at); + if (activeTable instanceof Error) return activeTable; + if (activeTable && value.ctx_bracket_table_ver !== activeTable.ver) { + return new Error('Spend reservation context bracket table is not active.'); + } + const ctxMeta = await this.normalizeCtxBracketEvidenceForEnclave( + value.enclave_id.toLowerCase(), + value.served_ctx, + value.ctx_bracket, + value.ctx_bracket_table_ver, + activeTable, + 'spend reservation' + ); + if (ctxMeta instanceof Error) return ctxMeta; + const maxSpendAu = this.normalizeAu(value.max_spend_au, 'spend reservation max spend', { allowZero: false }); + if (maxSpendAu instanceof Error) { + return new Error('Invalid spend reservation max spend.'); + } + if (!this.isHexBytes(value.provider_sig, 64)) { + return new Error('Invalid spend reservation provider signature.'); + } + const voucher = await this.normalizeSpendVoucherForReserve(value.voucher); + if (voucher instanceof Error) return voucher; + if (voucher.session_id !== value.session_id.toLowerCase()) { + return new Error('Spend reservation voucher session mismatch.'); + } + if (voucher.rail !== rail) return new Error('Spend reservation voucher rail mismatch.'); + if (voucher.enclave_id !== value.enclave_id.toLowerCase()) { + return new Error('Spend reservation voucher enclave mismatch.'); + } + if (voucher.price_ver !== value.price_ver) { + return new Error('Spend reservation voucher price mismatch.'); + } + if (voucher.body.served_ctx !== value.served_ctx) { + return new Error('Spend reservation voucher served context mismatch.'); + } + if (stableJson(voucher.body.required_modalities) !== stableJson(value.required_modalities)) { + return new Error('Spend reservation voucher required modalities mismatch.'); + } + if (stableJson(voucher.body.required_specialities) !== stableJson(normalizedSpecialities)) { + return new Error('Spend reservation voucher required specialities mismatch.'); + } + let workflow = null; + if (hasOwn(value, 'workflow')) { + workflow = this.normalizeWorkflowBinding( + value.workflow, + 'spend reservation workflow', + voucher.body.locked_rate_map + ); + if (workflow instanceof Error) return workflow; + if (stableJson(workflow) !== stableJson(value.workflow)) { + return new Error('Spend reservation workflow must be canonical.'); + } + } + if (stableJson(voucher.body.workflow ?? null) !== stableJson(workflow)) { + return new Error('Spend reservation voucher workflow mismatch.'); + } + if (voucher.body.ctx_bracket !== value.ctx_bracket) { + return new Error('Spend reservation voucher context bracket mismatch.'); + } + if (voucher.body.ctx_bracket_table_ver !== value.ctx_bracket_table_ver) { + return new Error('Spend reservation voucher context bracket table version mismatch.'); + } + if (this.compareAu(voucher.max_spend_au, maxSpendAu) !== 0) { + return new Error('Spend reservation voucher max spend mismatch.'); + } + return { + op: 'spend_reserve', + contract_version: CONTRACT_VERSION, + session_id: value.session_id.toLowerCase(), + epoch: value.epoch, + at: value.at, + rail, + user: value.user.toLowerCase(), + provider: value.provider.toLowerCase(), + enclave_id: value.enclave_id.toLowerCase(), + price_ver: value.price_ver, + locked_rate_map: voucher.body.locked_rate_map, + locked_per_req_au: voucher.body.locked_per_req_au, + locked_min_session_au: voucher.body.locked_min_session_au, + served_ctx: value.served_ctx, + required_modalities: value.required_modalities.slice(), + required_specialities: normalizedSpecialities, + ...(workflow ? { workflow } : {}), + ctx_bracket: ctxMeta.ctx_bracket, + ctx_bracket_table_ver: ctxMeta.ctx_bracket_table_ver, + rules_ver: value.rules_ver, + max_spend_au: maxSpendAu, + voucher: { + session_id: voucher.body.session_id, + billing_id: voucher.body.billing_id, + billing_attempt: voucher.body.billing_attempt, + billing_prior_usage: voucher.body.billing_prior_usage, + billing_prior_au_owed_cum: voucher.body.billing_prior_au_owed_cum, + rail: voucher.body.rail, + enclave_id: voucher.body.enclave_id, + price_ver: voucher.body.price_ver, + locked_rate_map: voucher.body.locked_rate_map, + locked_per_req_au: voucher.body.locked_per_req_au, + locked_min_session_au: voucher.body.locked_min_session_au, + served_ctx: voucher.body.served_ctx, + required_modalities: voucher.body.required_modalities, + required_specialities: voucher.body.required_specialities, + ...(voucher.body.workflow ? { workflow: voucher.body.workflow } : {}), + ctx_bracket: voucher.body.ctx_bracket, + ctx_bracket_table_ver: voucher.body.ctx_bracket_table_ver, + max_spend_au: voucher.body.max_spend_au, + checkpoint_every: voucher.body.checkpoint_every, + user_sig: voucher.user_sig, + }, + voucher_body: voucher.body, + provider_sig: value.provider_sig.toLowerCase(), + }; + } + + billingReservationIdentity(normalized) { + return { + billing_id: normalized.voucher_body.billing_id, + billing_attempt: normalized.voucher_body.billing_attempt, + billing_epoch: normalized.epoch, + reservation_id: normalized.voucher_body.reservation_id, + reservation_expires_after_epoch: normalized.reservation_expires_after_epoch, + reservation_receipt_grace_epochs: normalized.reservation_receipt_grace_epochs, + session_id: normalized.session_id, + user: normalized.user, + provider: normalized.provider, + rail: normalized.rail, + payout_revision: normalized.payout_revision, + }; + } + + async validateExistingBillingReservation(normalized) { + const identity = this.billingReservationIdentity(normalized); + const anchor = await this.get(this.receiptBillingKey(identity.billing_id)); + if (!anchor || + anchor.type !== 'receipt_billing_anchor' || + anchor.billing_id !== identity.billing_id || + anchor.user !== identity.user || + anchor.rail !== identity.rail || + anchor.epoch !== identity.billing_epoch || + !Number.isSafeInteger(anchor.latest_attempt) || + anchor.latest_attempt < identity.billing_attempt) { + return new Error('Billing reservation anchor is missing or inconsistent.'); + } + const reservation = await this.get(this.receiptReservationKey(identity.reservation_id)); + if (!reservation || + reservation.type !== 'receipt_reservation_identity' || + reservation.status !== 'active' || + stableJson({ + billing_id: reservation.billing_id, + billing_attempt: reservation.billing_attempt, + billing_epoch: reservation.billing_epoch, + reservation_id: reservation.reservation_id, + reservation_expires_after_epoch: reservation.reservation_expires_after_epoch, + reservation_receipt_grace_epochs: reservation.reservation_receipt_grace_epochs, + session_id: reservation.session_id, + user: reservation.user, + provider: reservation.provider, + rail: reservation.rail, + payout_revision: reservation.payout_revision, + }) !== stableJson(identity)) { + return new Error('Reservation identity is missing or inconsistent.'); + } + return null; + } + + async prepareBillingReservation(normalized, key) { + const identity = this.billingReservationIdentity(normalized); + const anchorKey = this.receiptBillingKey(identity.billing_id); + const existingAnchor = await this.get(anchorKey); + let nextAnchor; + if (existingAnchor === null) { + if (identity.billing_attempt !== 0) { + return new Error('Initial billing reservation attempt must be zero.'); + } + nextAnchor = { + type: 'receipt_billing_anchor', + billing_id: identity.billing_id, + user: identity.user, + rail: identity.rail, + epoch: identity.billing_epoch, + latest_attempt: 0, + created_at: key, + updated_at: key, + }; + } else { + if (existingAnchor.type !== 'receipt_billing_anchor' || + existingAnchor.billing_id !== identity.billing_id || + existingAnchor.user !== identity.user || + existingAnchor.rail !== identity.rail || + existingAnchor.epoch !== identity.billing_epoch || + !Number.isSafeInteger(existingAnchor.latest_attempt) || + existingAnchor.latest_attempt < 0) { + return new Error('Billing id cannot move across user, rail, or epoch.'); + } + if (identity.billing_attempt === existingAnchor.latest_attempt) { + const currentHead = await this.get( + this.receiptHeadKey(identity.billing_id, identity.billing_attempt) + ); + if (currentHead) { + return new Error('Billing attempt must advance exactly once.'); + } + const hold = await this.normalizeTargetedSpendHoldRecord( + (await this.get(this.targetedSpendHoldKey(identity.user, identity.rail))) ?? null, + identity.user, + identity.rail + ); + if (hold instanceof Error) return hold; + const billingAttemptIndex = this.normalizeTargetedSpendReservationIndexRecord( + await this.get( + this.targetedSpendBillingAttemptKey( + identity.user, + identity.rail, + identity.billing_id, + identity.billing_attempt + ) + ), + { + user: identity.user, + rail: identity.rail, + billingId: identity.billing_id, + billingAttempt: identity.billing_attempt, + } + ); + if (billingAttemptIndex instanceof Error) return billingAttemptIndex; + let activeLegacyReservation = false; + for (const session of hold.sessions) { + if (session.billing_id !== identity.billing_id || + session.billing_attempt !== identity.billing_attempt) { + continue; + } + const reservation = await this.get(this.receiptReservationKey(session.reservation_id)); + if (!reservation || + reservation.type !== 'receipt_reservation_identity' || + reservation.status === 'active') { + activeLegacyReservation = true; + break; + } + } + if (activeLegacyReservation || billingAttemptIndex !== null) { + return new Error('Billing attempt already has an active reservation.'); + } + nextAnchor = { + ...existingAnchor, + updated_at: key, + }; + } else { + if (identity.billing_attempt !== existingAnchor.latest_attempt + 1) { + return new Error('Billing attempt must advance exactly once.'); + } + const priorHead = await this.get( + this.receiptHeadKey(identity.billing_id, identity.billing_attempt - 1) + ); + if (!priorHead || + priorHead.type !== 'canonical_receipt_head' || + priorHead.billing_id !== identity.billing_id || + priorHead.billing_attempt !== identity.billing_attempt - 1) { + return new Error('Higher billing attempt requires the prior canonical receipt head.'); + } + if (stableJson(normalized.voucher_body.billing_prior_usage) !== + stableJson(priorHead.receipt.body.usage) || + this.compareAu( + normalized.voucher_body.billing_prior_au_owed_cum, + priorHead.receipt.body.au_owed_cum + ) !== 0) { + return new Error('Higher billing attempt does not exactly chain from the prior attempt.'); + } + nextAnchor = { + ...existingAnchor, + latest_attempt: identity.billing_attempt, + updated_at: key, + }; + } + } + + const reservationKey = this.receiptReservationKey(identity.reservation_id); + if ((await this.get(reservationKey)) !== null) { + return new Error('Reservation id is already in use.'); + } + return { + anchor_key: anchorKey, + anchor: nextAnchor, + reservation_key: reservationKey, + reservation: { + type: 'receipt_reservation_identity', + ...identity, + status: 'active', + closed_at: null, + close_record_key: null, + recorded_at: key, + }, + }; + } + + async normalizeSpendHoldRecord(record, user, rail, epoch, { targeted = false } = {}) { + if (!record) { + const empty = { + user, + rail, + denom: PRICE_DENOMINATION, + reserved_au: ZERO_AU, + balance_au_at_last_reserve: null, + sessions: [], + updated_at: null, + }; + return targeted + ? { type: 'targeted_spend_hold', ...empty } + : { ...empty, epoch }; + } + if (typeof record !== 'object' || Array.isArray(record)) { + return new Error('Spend hold record must be an object.'); + } + if (record.user !== user || record.rail !== rail || + (targeted + ? record.type !== 'targeted_spend_hold' || hasOwn(record, 'epoch') + : record.epoch !== epoch)) { + return new Error('Spend hold record key mismatch.'); + } + if (record.denom !== PRICE_DENOMINATION) return new Error('Spend hold denomination mismatch.'); + const reservedAu = this.normalizeAu(record.reserved_au, 'spend hold reserved amount'); + if (reservedAu instanceof Error) { + return new Error('Invalid spend hold reserved amount.'); + } + if (!Array.isArray(record.sessions)) return new Error('Spend hold sessions must be an array.'); + let targetedReservedAu = ZERO_AU; + for (const session of record.sessions) { + if (!session || typeof session !== 'object' || Array.isArray(session)) { + return new Error('Invalid spend hold session.'); + } + if (!this.isHexBytes(session.session_id, 32)) return new Error('Invalid spend hold session id.'); + if (!this.isHexBytes(session.billing_id, 32)) return new Error('Invalid spend hold billing id.'); + if (!Number.isSafeInteger(session.billing_attempt) || session.billing_attempt < 0) { + return new Error('Invalid spend hold billing attempt.'); + } + if (!Number.isSafeInteger(session.billing_epoch) || + session.billing_epoch < 1 || + (!targeted && session.billing_epoch !== epoch)) { + return new Error('Invalid spend hold billing epoch.'); + } + if (!this.isHexBytes(session.reservation_id, 32)) { + return new Error('Invalid spend hold reservation id.'); + } + if (!Number.isSafeInteger(session.reservation_expires_after_epoch) || + session.reservation_expires_after_epoch <= session.billing_epoch || + !Number.isSafeInteger(session.reservation_receipt_grace_epochs) || + session.reservation_receipt_grace_epochs < 0) { + return new Error('Invalid spend hold reservation expiry policy.'); + } + if (session.user !== user || session.rail !== rail) { + return new Error('Spend hold session user or rail mismatch.'); + } + if (!this.isHexBytes(session.provider, 32)) return new Error('Invalid spend hold provider.'); + if (!this.isHexBytes(session.payout_revision, 32)) { + return new Error('Invalid spend hold payout revision.'); + } + if (!this.isHexBytes(session.enclave_id, 32)) return new Error('Invalid spend hold enclave.'); + if (!this.isHexBytes(session.enclave_pubkey, 32)) { + return new Error('Invalid spend hold enclave public key.'); + } + if (typeof session.model_id !== 'string' || + session.model_id.length === 0 || + session.model_id.length > 256) { + return new Error('Invalid spend hold model id.'); + } + const billingPriorUsage = this.normalizeReceiptUsage(session.billing_prior_usage); + if (billingPriorUsage instanceof Error || + stableJson(billingPriorUsage) !== stableJson(session.billing_prior_usage)) { + return new Error('Invalid spend hold billing prior usage.'); + } + const billingPriorAuOwedCum = this.normalizeAu( + session.billing_prior_au_owed_cum, + 'spend hold billing prior cumulative amount' + ); + if (billingPriorAuOwedCum instanceof Error) { + return new Error('Invalid spend hold billing prior cumulative amount.'); + } + if (!Number.isSafeInteger(session.price_ver) || session.price_ver < 1) { + return new Error('Invalid spend hold price version.'); + } + if (!Number.isSafeInteger(session.rules_ver) || session.rules_ver < 1) { + return new Error('Invalid spend hold rules version.'); + } + const lockedRateMap = this.normalizeLockedRateMap( + session.locked_rate_map, + 'spend hold locked_rate_map' + ); + if (lockedRateMap instanceof Error) return lockedRateMap; + if (stableJson(lockedRateMap) !== stableJson(session.locked_rate_map)) { + return new Error('Spend hold locked_rate_map must be canonical.'); + } + const lockedPerReqAu = this.normalizeAu(session.locked_per_req_au, 'spend hold locked per-request price'); + if (lockedPerReqAu instanceof Error) { + return new Error('Invalid spend hold locked per-request price.'); + } + const lockedMinSessionAu = this.normalizeAu(session.locked_min_session_au, 'spend hold locked minimum session price'); + if (lockedMinSessionAu instanceof Error) { + return new Error('Invalid spend hold locked minimum session price.'); + } + if (!Number.isSafeInteger(session.served_ctx) || session.served_ctx < 0) { + return new Error('Invalid spend hold served context.'); + } + const modalityError = this.validateModalitySet( + session.required_modalities, + 'spend hold required_modalities' + ); + if (modalityError) return modalityError; + const specialitiesError = this.validateSpecialitySelection( + session.required_specialities ?? {}, + 'spend hold required_specialities' + ); + if (specialitiesError) return specialitiesError; + if (hasOwn(session, 'workflow')) { + const workflow = this.normalizeWorkflowBinding( + session.workflow, + 'spend hold workflow', + lockedRateMap + ); + if (workflow instanceof Error) return workflow; + if (stableJson(workflow) !== stableJson(session.workflow)) { + return new Error('Spend hold workflow must be canonical.'); + } + } + const table = session.ctx_bracket_table_ver === null || session.ctx_bracket_table_ver === undefined + ? null + : await this.ctxBracketTableByVersion(session.ctx_bracket_table_ver); + if (table instanceof Error) return table; + const ctxMeta = await this.normalizeCtxBracketEvidenceForEnclave( + session.enclave_id, + session.served_ctx, + session.ctx_bracket, + session.ctx_bracket_table_ver, + table, + 'spend hold' + ); + if (ctxMeta instanceof Error) return ctxMeta; + const maxSpendAu = this.normalizeAu(session.max_spend_au, 'spend hold max spend', { allowZero: false }); + if (maxSpendAu instanceof Error) { + return new Error('Invalid spend hold max spend.'); + } + if (targeted) { + targetedReservedAu = this.safeAddAu(targetedReservedAu, maxSpendAu); + if (targetedReservedAu instanceof Error) return targetedReservedAu; + } + if (!this.isHexBytes(session.voucher_hash, 32)) return new Error('Invalid spend hold voucher hash.'); + } + if (targeted && this.compareAu(targetedReservedAu, reservedAu) !== 0) { + return new Error('Targeted spend hold reserved amount does not equal its sessions.'); + } + return { + ...record, + reserved_au: reservedAu, + sessions: record.sessions.map((session) => ({ + ...session, + billing_prior_usage: this.normalizeReceiptUsage(session.billing_prior_usage), + billing_prior_au_owed_cum: this.normalizeAu( + session.billing_prior_au_owed_cum, + 'spend hold billing prior cumulative amount' + ), + locked_per_req_au: this.normalizeAu(session.locked_per_req_au, 'spend hold locked per-request price'), + locked_min_session_au: this.normalizeAu(session.locked_min_session_au, 'spend hold locked minimum session price'), + required_modalities: session.required_modalities.slice(), + required_specialities: cloneValue(session.required_specialities ?? {}), + ...(hasOwn(session, 'workflow') ? { + workflow: this.normalizeWorkflowBinding( + session.workflow, + 'spend hold workflow', + this.normalizeLockedRateMap(session.locked_rate_map, 'spend hold locked_rate_map') + ), + } : {}), + max_spend_au: this.normalizeAu(session.max_spend_au, 'spend hold max spend', { allowZero: false }), + })), + }; + } + + async normalizeTargetedSpendHoldRecord(record, user, rail) { + return await this.normalizeSpendHoldRecord( + record, + user, + rail, + null, + { targeted: true } + ); + } + + normalizePendingReservationDebitTotals(entries) { + const out = new Map(); + if (entries === null || entries === undefined) return out; + if (!Array.isArray(entries)) return new Error('Pending reservation debits must be an array.'); + for (const entry of entries) { + if (!entry || typeof entry !== 'object' || Array.isArray(entry)) { + return new Error('Invalid pending reservation debit entry.'); + } + const rail = this.normalizeLedgerRail(entry.rail, 'pending reservation debit rail'); + if (rail instanceof Error) return rail; + if (!this.isSafeKeyPart(entry.user)) return new Error('Invalid pending reservation debit user.'); + const au = this.normalizeAu(entry.au, 'pending reservation debit amount', { allowZero: false }); + if (au instanceof Error) return new Error('Invalid pending reservation debit amount.'); + const key = stableJson([rail, entry.user]); + if (out.has(key)) return new Error('Duplicate pending reservation debit entry.'); + out.set(key, { rail, user: entry.user, au }); + } + return out; + } + + reservationDebitTotalEntries(map) { + if (!map) return []; + return this.sortedRailRecords(map, 'user').map((entry) => ({ + rail: entry.rail, + user: entry.user, + au: entry.au, + })); + } + + nextReservationDebitTotals(applyState, epoch, page, debitMap) { + const base = page === 0 + ? new Map() + : this.normalizePendingReservationDebitTotals(applyState.pending_reserved_debits); + if (base instanceof Error) return base; + if (page > 0 && applyState.pending_epoch === epoch && !Array.isArray(applyState.pending_reserved_debits)) { + return new Error('Pending reservation debit state missing for paged epoch apply.'); + } + const out = new Map(base); + for (const debit of debitMap.values()) { + const key = stableJson([debit.rail, debit.user]); + const current = out.get(key) ?? { rail: debit.rail, user: debit.user, au: ZERO_AU }; + const nextAu = this.safeAddAu(current.au, debit.au); + if (nextAu instanceof Error) return nextAu; + out.set(key, { ...current, au: nextAu }); + } + return out; + } + + async validateEpochDebitReservations(epoch, debitTotals) { + for (const debit of debitTotals.values()) { + const hold = await this.normalizeSpendHoldRecord( + (await this.get(this.spendHoldKey(debit.user, debit.rail, epoch))) ?? null, + debit.user, + debit.rail, + epoch + ); + if (hold instanceof Error) return hold; + if (this.compareAu(hold.reserved_au, debit.au) < 0) { + return new Error('Epoch debit exceeds reserved spend hold.'); + } + } + return null; + } + + async requireBoundCanaryProbe(value, auditor) { + const catalogError = await this.requirePublishedCanarySet(value.canary_set); + if (catalogError) return catalogError; + + const enclave = await this.get(`enclave/${value.enclave_id}`); + if (!enclave) return new Error('Canary probe enclave not found.'); + const approvedBinaryHashes = this.normalizeApprovedBinaryHashes( + enclave.binary_hash, + enclave.approved_binary_hashes + ); + if (!approvedBinaryHashes.includes(value.binary_hash.toLowerCase())) { + return new Error('Canary probe binary_hash is not approved for enclave.'); + } + + const challengeError = await this.requireCanaryChallenge(value, auditor); + if (challengeError) return challengeError; + + if (!this.verifyProbeResultSignature(auditor, value)) { + return new Error('Invalid canary auditor signature.'); + } + return null; + } + + async requirePublishedCanarySet(canarySet) { + const catalog = await this.get('catalog/current'); + if (!catalog || catalog.status !== 'active') { + return new Error('Published catalog required for canary probe.'); + } + if (!Array.isArray(catalog.canaries) || !catalog.canaries.some((entry) => entry.set_id === canarySet)) { + return new Error('Canary set is not published in the active catalog.'); + } + return null; + } + + async requireCanaryChallenge(value, auditor) { + if (value.epoch !== value.challenge_epoch + 1) { + return new Error('Canary probe epoch must immediately follow its challenge epoch.'); + } + const anchor = await this.canaryChallengeAnchor(value.challenge_epoch); + if (!anchor) return new Error('Canary challenge epoch is not anchored.'); + if (anchor.apply_hash !== value.challenge_apply_hash.toLowerCase()) { + return new Error('Canary challenge apply hash mismatch.'); + } + const catalog = await this.get('catalog/current'); + const canary = catalog?.canaries?.find((entry) => entry.set_id === value.canary_set); + if (!canary) return new Error('Published canary challenge set not found.'); + const expectedSeed = await this.opaqueHash('mayhem-canary-challenge-v1', { + challenge_epoch: value.challenge_epoch, + challenge_apply_hash: anchor.apply_hash, + probe_epoch: value.epoch, + auditor, + provider: value.provider, + enclave_id: value.enclave_id, + canary_set: value.canary_set, + catalog_hash: catalog.catalog_hash, + }); + if (expectedSeed !== value.challenge_seed.toLowerCase()) { + return new Error('Canary challenge seed mismatch.'); + } + const selectedIndex = Number(BigInt(`0x${expectedSeed}`) % BigInt(canary.prompt_ids.length)); + if (value.canary_prompt_id !== canary.prompt_ids[selectedIndex]) { + return new Error('Canary prompt does not match the unpredictable challenge selection.'); + } + return null; + } + + validateDisputeOpen(value) { + const rail = this.normalizeLedgerRail(value.rail, 'dispute rail'); + if (rail instanceof Error) return rail; + if (!this.isHexBytes(value.session_id, 32)) return new Error('Invalid dispute session id.'); + if (!this.isSafeKeyPart(value.reason)) return new Error('Invalid dispute reason.'); + for (const key of ['provider', 'enclave_id']) { + if (!this.isHexBytes(value[key], 32)) return new Error(`Invalid dispute ${key}.`); + } + for (const key of ['counterparty']) { + if (value[key] !== undefined && !this.isSafeKeyPart(value[key])) { + return new Error(`Invalid dispute ${key}.`); + } + } + if (value.evidence !== undefined) { + const bytes = b4a.from(stableJson(value.evidence)).byteLength; + if (bytes > DISPUTE_EVIDENCE_MAX_BYTES) { + return new Error('Dispute evidence bundle is too large.'); + } + } + return null; + } + + validateEpochApplyShape(value) { + const arrays = [ + ['debits', value.debits], + ['earnings', value.earnings], + ]; + if (value.market_usage !== undefined) arrays.push(['market_usage', value.market_usage]); + if (value.earning_finals !== undefined) arrays.push(['earning_finals', value.earning_finals]); + for (const [name, entries] of arrays) { + if (!Array.isArray(entries)) return new Error(`Epoch apply ${name} must be an array.`); + } + return null; + } + + epochApplyPage(value) { + if (!hasOwn(value, 'page')) return 0; + if (!Number.isSafeInteger(value.page) || value.page < 0) { + return new Error('Invalid epochApply page.'); + } + return value.page; + } + + epochApplyLastPage(value) { + if (!hasOwn(value, 'last_page')) return !hasOwn(value, 'page'); + if (typeof value.last_page !== 'boolean') { + return new Error('Invalid epochApply last_page.'); + } + return value.last_page; + } + + isEpochApplyPagePositionReplay(applyState, epoch, page, lastPage) { + if (applyState.last_page !== page) return false; + if (lastPage) { + return applyState.updated_epoch === epoch && (applyState.pending_epoch ?? null) === null; + } + return ( + applyState.pending_epoch === epoch && + applyState.pending_next_page === page + 1 + ); + } + + isIdempotentEpochApplyPage(applyState, epoch, page, lastPage, applyHash) { + if (applyState.last_apply_hash !== applyHash) return false; + return this.isEpochApplyPagePositionReplay(applyState, epoch, page, lastPage); + } + + validateEpochApplyPageOrder(applyState, epoch, page) { + const pendingEpoch = applyState.pending_epoch ?? null; + if (page === 0) { + if (pendingEpoch !== null) return new Error('Guardian monotonic epoch invariant failed: pending epoch apply page exists.'); + if (epoch <= applyState.updated_epoch) return new Error('Guardian monotonic epoch invariant failed.'); + if (epoch !== applyState.updated_epoch + 1) { + return new Error('Guardian monotonic epoch invariant failed: epoch apply must be contiguous.'); + } + return null; + } + if (pendingEpoch !== epoch) { + return new Error('Guardian monotonic epoch invariant failed: epoch apply page has no pending predecessor.'); + } + if (epoch !== applyState.updated_epoch + 1) { + return new Error('Guardian monotonic epoch invariant failed: epoch apply must be contiguous.'); + } + if (applyState.pending_next_page !== page) { + return new Error('Guardian monotonic epoch invariant failed: epoch apply page is not contiguous.'); + } + if (!this.isHexBytes(applyState.last_apply_hash, 32)) { + return new Error('Guardian monotonic epoch invariant failed: pending epoch apply hash missing.'); + } + return null; + } + + async validateEpochCadenceTime(applyState, epoch, page, settledAt, epochSeconds) { + if (!Number.isSafeInteger(settledAt) || settledAt < 0 || + !Number.isSafeInteger(epochSeconds) || epochSeconds < 1) { + return new Error('Invalid canonical epoch settlement time.'); + } + if (page > 0) { + if (applyState.pending_epoch !== epoch || + applyState.pending_settlement_unix !== settledAt) { + return new Error('Paged epoch apply settlement time changed between pages.'); + } + return null; + } + if (applyState.updated_epoch === 0) return null; + const priorSettlementUnix = await this.priorEpochSettlementUnix(applyState); + if (priorSettlementUnix instanceof Error) return priorSettlementUnix; + if (priorSettlementUnix > Number.MAX_SAFE_INTEGER - epochSeconds) { + return new Error('Canonical epoch settlement time overflow.'); + } + if (settledAt < priorSettlementUnix + epochSeconds) { + return new Error('Epoch settlement cadence has not matured.'); + } + return null; + } + + async priorEpochSettlementUnix(applyState) { + if (Number.isSafeInteger(applyState.last_settlement_unix) && + applyState.last_settlement_unix >= 0) { + return applyState.last_settlement_unix; + } + const epoch = applyState.updated_epoch; + if (!Number.isSafeInteger(epoch) || epoch < 1 || + !this.isHexBytes(applyState.last_apply_hash, 32)) { + return new Error('Prior canonical epoch settlement identity is invalid.'); + } + const seal = await this.get(`epoch/seal/${epoch}`); + if (seal !== null) { + if (seal.type !== 'epoch_empty_seal' || + seal.epoch !== epoch || + seal.seal_hash !== applyState.last_apply_hash || + !Number.isSafeInteger(seal.at) || + seal.at < 0) { + return new Error('Prior canonical empty-seal settlement identity is invalid.'); + } + const expectedSealHash = await this.epochEmptySealHash( + this.epochEmptySealHashValue(seal) + ); + if (expectedSealHash !== seal.seal_hash) { + return new Error('Prior canonical empty-seal settlement hash is invalid.'); + } + return seal.at; + } + const commit = await this.get(`epoch/commit/${epoch}`); + if (!commit || + commit.type !== 'epoch_commit' || + commit.epoch !== epoch || + !Number.isSafeInteger(commit.at) || + commit.at < 0 || + !this.isHexBytes(commit.commit_hash, 32)) { + return new Error('Prior canonical epoch commit settlement identity is invalid.'); + } + const expectedCommitHash = await this.epochCommitHash({ + epoch, + epoch_seconds: commit.epoch_seconds, + roots: commit.roots, + totals: commit.totals, + }); + if (expectedCommitHash !== commit.commit_hash || + (applyState.last_receipt_commit_hash != null && + applyState.last_receipt_commit_hash !== commit.commit_hash)) { + return new Error('Prior canonical epoch commit settlement hash is invalid.'); + } + const usageRoot = await this.get(`ev/use/${epoch}`); + if (!usageRoot || + usageRoot.type !== 'usage_root' || + usageRoot.epoch !== epoch || + usageRoot.ts !== commit.at || + usageRoot.merkle_root !== commit.roots?.use) { + return new Error('Prior canonical epoch apply evidence is missing.'); + } + return commit.at; + } + + nextEpochApplyState({ + applyState, + epoch, + page, + lastPage, + applyHash, + epochSeconds, + settledAt, + reservationDebitTotals = null, + receiptApply = null, + }) { + const base = { + ...applyState, + updated_at: this.tx, + last_apply_previous_hash: applyState.last_apply_hash ?? null, + last_apply_hash: applyHash, + last_epoch_seconds: epochSeconds, + ...(receiptApply ? { + last_receipt_index_count: receiptApply.index_count, + last_receipt_index_revision: receiptApply.index_revision, + last_receipt_index_page_count: receiptApply.index_page_count, + last_receipt_index_updated_at: receiptApply.index_updated_at, + last_receipt_commit_hash: receiptApply.commit_hash, + last_receipt_allocation_count: receiptApply.allocation_count, + last_receipt_provider_count: receiptApply.provider_count, + last_receipt_market_count: receiptApply.market_count, + last_receipt_earn_cum_au: receiptApply.earn_cum_au, + last_receipt_fee_au: receiptApply.fee_au, + last_receipt_burn_au: receiptApply.burn_au, + } : {}), + }; + if (lastPage) { + return { + ...base, + updated_epoch: epoch, + last_settlement_unix: settledAt, + pending_epoch: null, + pending_next_page: 0, + pending_settlement_unix: null, + pending_reserved_debits: null, + ...(receiptApply ? { + pending_receipt_index_count: null, + pending_receipt_index_revision: null, + pending_receipt_index_page_count: null, + pending_receipt_index_updated_at: null, + pending_receipt_commit_hash: null, + pending_receipt_allocation_count: null, + pending_receipt_provider_count: null, + pending_receipt_market_count: null, + pending_receipt_earn_cum_au: null, + pending_receipt_fee_au: null, + pending_receipt_burn_au: null, + } : {}), + last_page: page, + }; + } + return { + ...base, + pending_epoch: epoch, + pending_next_page: page + 1, + pending_settlement_unix: settledAt, + pending_reserved_debits: this.reservationDebitTotalEntries(reservationDebitTotals), + ...(receiptApply ? { + pending_receipt_index_count: receiptApply.index_count, + pending_receipt_index_revision: receiptApply.index_revision, + pending_receipt_index_page_count: receiptApply.index_page_count, + pending_receipt_index_updated_at: receiptApply.index_updated_at, + pending_receipt_commit_hash: receiptApply.commit_hash, + pending_receipt_allocation_count: receiptApply.allocation_count, + pending_receipt_provider_count: receiptApply.provider_count, + pending_receipt_market_count: receiptApply.market_count, + pending_receipt_earn_cum_au: receiptApply.earn_cum_au, + pending_receipt_fee_au: receiptApply.fee_au, + pending_receipt_burn_au: receiptApply.burn_au, + } : {}), + updated_epoch: applyState.updated_epoch, + last_page: page, + }; + } + + validateEpochApplyFeatureValue(value) { + if (CONTRACT_VERSION >= 17) { + return new Error('Aggregate epoch_apply is disabled; use receipt-bound apply_targeted_epoch.'); + } + if (!value || typeof value !== 'object' || Array.isArray(value)) { + return new Error('epochApply feature value must be an object.'); + } + const required = ['op', 'epoch', 'at', 'debits', 'earnings']; + const allowed = new Set([...required, 'market_usage', 'roots', 'totals', 'page', 'last_page']); + const unknown = Object.keys(value).filter((key) => !allowed.has(key)).sort(); + if (unknown.length > 0) { + return new Error(`epochApply feature does not accept fields: ${unknown.join(', ')}.`); + } + for (const key of required) { + if (!hasOwn(value, key)) return new Error(`epochApply feature is missing ${key}.`); + } + if (value.op !== 'epoch_apply') return new Error('Invalid epochApply feature op.'); + if (!Number.isSafeInteger(value.epoch) || value.epoch < 1) { + return new Error('Invalid epochApply feature epoch.'); + } + if (!Number.isSafeInteger(value.at) || value.at < 0) { + return new Error('Invalid epochApply feature timestamp.'); + } + const page = this.epochApplyPage(value); + if (page instanceof Error) return page; + const lastPage = this.epochApplyLastPage(value); + if (lastPage instanceof Error) return lastPage; + return this.validateEpochApplyShape(value); + } + + async normalizeCommitTargetedEpochPageZeroFeatureValue(value) { + if (!value || typeof value !== 'object' || Array.isArray(value)) { + return new Error('Commit-plus-page-zero feature value must be an object.'); + } + const required = [ + 'op', + 'epoch', + 'at', + 'epoch_commit_hash', + 'receipt_index', + 'debits', + 'earnings', + 'allocations', + 'last_page', + 'roots', + 'totals', + ]; + const allowed = new Set([ + ...required, + 'earning_finals', + 'market_usage', + 'supersedes_commit_hash', + ]); + const unknown = Object.keys(value).filter((key) => !allowed.has(key)).sort(); + if (unknown.length > 0) { + return new Error(`Commit-plus-page-zero feature does not accept fields: ${unknown.join(', ')}.`); + } + for (const field of required) { + if (!hasOwn(value, field)) { + return new Error(`Commit-plus-page-zero feature is missing ${field}.`); + } + } + if (value.op !== 'commit_apply_targeted_epoch_page0') { + return new Error('Invalid commit-plus-page-zero feature op.'); + } + if (hasOwn(value, 'supersedes_commit_hash') && + (!this.isHexBytes(value.supersedes_commit_hash, 32) || + value.supersedes_commit_hash !== value.supersedes_commit_hash.toLowerCase())) { + return new Error('Invalid superseded epoch commit hash.'); + } + const roots = this.normalizeEpochRoots(value.roots); + if (roots instanceof Error) return roots; + const totals = this.normalizeEpochTotals(value.totals); + if (totals instanceof Error) return totals; + if (totals.price_count !== 0) { + return new Error('Receipt settlement page zero cannot carry market price derivations.'); + } + const emptyPriceRoot = await this.priceDerivationRoot([]); + if (roots.price !== emptyPriceRoot) { + return new Error('Receipt settlement price root must be the canonical empty root.'); + } + const targetedValue = { + op: 'apply_targeted_epoch', + epoch: value.epoch, + at: value.at, + epoch_commit_hash: value.epoch_commit_hash, + receipt_index: value.receipt_index, + debits: value.debits, + earnings: value.earnings, + allocations: value.allocations, + ...(hasOwn(value, 'earning_finals') ? { earning_finals: value.earning_finals } : {}), + ...(hasOwn(value, 'market_usage') ? { market_usage: value.market_usage } : {}), + page: 0, + last_page: value.last_page, + }; + const normalized = await this.normalizeTargetedEpochFeatureValue(targetedValue); + if (normalized instanceof Error) return normalized; + return { + epoch: value.epoch, + at: value.at, + roots, + totals, + receipt_index: normalized.ledger_value.receipt_index, + epoch_commit_hash: value.epoch_commit_hash, + supersedes_commit_hash: value.supersedes_commit_hash ?? null, + targeted_value: targetedValue, + normalized, + }; + } + + async prepareTargetedEpochCommitTransition(prepared, applyState, featureKey) { + const currentReceiptIndex = this.normalizeReceiptEpochIndexMetadata( + await this.get(this.receiptEpochIndexKey(prepared.epoch)), + prepared.epoch + ); + if (currentReceiptIndex instanceof Error) return currentReceiptIndex; + if (stableJson(currentReceiptIndex) !== stableJson(prepared.receipt_index)) { + return new Error('Canonical receipt epoch index changed before commit-plus-page-zero.'); + } + if (prepared.totals.use_count !== currentReceiptIndex.count) { + return new Error('Epoch commit receipt count must match the canonical receipt index.'); + } + const params = await this.activeParamsAt(prepared.at, ['challenge_epochs', 'epoch_seconds']); + const commitHash = await this.epochCommitHash({ + epoch: prepared.epoch, + epoch_seconds: params.epoch_seconds, + roots: prepared.roots, + totals: prepared.totals, + }); + if (commitHash !== prepared.epoch_commit_hash) { + return new Error('Commit-plus-page-zero epoch commit hash is invalid.'); + } + const key = `epoch/commit/${prepared.epoch}`; + const existing = await this.get(key); + if (existing?.commit_hash === commitHash) { + if ( + existing.type !== 'epoch_commit' || + existing.status !== 'provisional' || + existing.apply_mode !== 'targeted_receipt_pages_v1' || + existing.epoch !== prepared.epoch || + existing.at !== prepared.at || + stableJson(existing.roots) !== stableJson(prepared.roots) || + stableJson(existing.totals) !== stableJson(prepared.totals) + ) { + return new Error('Existing epoch commit does not match commit-plus-page-zero.'); + } + return { key, record: existing, archive: null, write: false }; + } + if (existing !== null) { + if ( + existing.type !== 'epoch_commit' || + existing.status !== 'provisional' || + existing.epoch !== prepared.epoch || + !this.isHexBytes(existing.commit_hash, 32) || + existing.commit_hash !== existing.commit_hash.toLowerCase() || + (existing.replacement_count !== undefined && + (!Number.isSafeInteger(existing.replacement_count) || existing.replacement_count < 0)) + ) { + return new Error('Only a canonical provisional epoch commit can be superseded.'); + } + if (prepared.supersedes_commit_hash !== existing.commit_hash) { + return new Error('Commit-plus-page-zero must identify the current provisional commit.'); + } + if (applyState.updated_epoch !== prepared.epoch - 1 || + (applyState.pending_epoch ?? null) !== null) { + return new Error('Epoch commit cannot be superseded after targeted apply has started.'); + } + for (const evidenceKey of ['use', 'earn', 'fee', 'price']) { + if (await this.get(`ev/${evidenceKey}/${prepared.epoch}`)) { + return new Error('Epoch commit cannot be superseded after settlement evidence exists.'); + } + } + if (!Number.isSafeInteger(existing.totals?.use_count) || + prepared.totals.use_count <= existing.totals.use_count) { + return new Error('Replacement epoch commit must strictly extend the canonical receipt count.'); + } + if (!Number.isSafeInteger(existing.at) || prepared.at < existing.at) { + return new Error('Replacement epoch commit timestamp cannot precede the superseded commit.'); + } + } else if (prepared.supersedes_commit_hash !== null) { + return new Error('Epoch commit has nothing to supersede.'); + } + const record = { + type: 'epoch_commit', + epoch: prepared.epoch, + epoch_seconds: params.epoch_seconds, + apply_mode: 'targeted_receipt_pages_v1', + roots: prepared.roots, + totals: prepared.totals, + status: 'provisional', + challenge_epochs: params.challenge_epochs, + provisional_until_epoch: prepared.epoch + params.challenge_epochs, + commit_hash: commitHash, + submitted_by: this.address, + submitted_at: featureKey, + at: prepared.at, + ...(existing ? { + supersedes: existing.commit_hash, + replacement_count: (existing.replacement_count ?? 0) + 1, + } : {}), + }; + const archive = existing ? { + key: `epoch/commit/superseded/${prepared.epoch}/${existing.commit_hash}`, + value: { + ...existing, + status: 'superseded', + superseded_by: commitHash, + superseded_at: featureKey, + }, + } : null; + return { key, record, archive, write: true }; + } + + async normalizeTargetedEpochFeatureValue(value) { + if (!value || typeof value !== 'object' || Array.isArray(value)) { + return new Error('Targeted epoch feature value must be an object.'); + } + const required = [ + 'op', + 'epoch', + 'at', + 'epoch_commit_hash', + 'receipt_index', + 'debits', + 'earnings', + 'allocations', + 'page', + 'last_page', + ]; + const allowed = new Set([...required, 'market_usage', 'earning_finals']); + const unknown = Object.keys(value).filter((key) => !allowed.has(key)).sort(); + if (unknown.length > 0) { + return new Error(`Targeted epoch feature does not accept fields: ${unknown.join(', ')}.`); + } + for (const key of required) { + if (!hasOwn(value, key)) return new Error(`Targeted epoch feature is missing ${key}.`); + } + if (value.op !== 'apply_targeted_epoch') { + return new Error('Invalid targeted epoch feature op.'); + } + if (!this.isHexBytes(value.epoch_commit_hash, 32) || + value.epoch_commit_hash !== value.epoch_commit_hash.toLowerCase()) { + return new Error('Invalid targeted epoch commit hash.'); + } + const receiptIndex = this.normalizeReceiptEpochIndexMetadata( + value.receipt_index, + value.epoch + ); + if (receiptIndex instanceof Error) return receiptIndex; + if (stableJson(receiptIndex) !== stableJson(value.receipt_index)) { + return new Error('Targeted epoch receipt index snapshot must be canonical.'); + } + if (!Array.isArray(value.earnings)) { + return new Error('Targeted epoch earnings must be an array.'); + } + if (!Array.isArray(value.allocations) || value.allocations.length === 0) { + return new Error('Targeted epoch allocations must be a non-empty array.'); + } + const allocations = []; + const allocationSessions = new Set(); + for (const entry of value.allocations) { + const entryError = this.validateExactObjectKeys( + entry, + [ + 'session_id', + 'billing_id', + 'billing_attempt', + 'billing_epoch', + 'receipt_seq', + 'receipt_hash', + 'user', + 'rail', + 'provider', + 'payout_revision', + 'au', + ], + 'targeted epoch allocation' + ); + if (entryError) return entryError; + const rail = this.normalizeLedgerRail(entry.rail, 'targeted epoch allocation rail'); + if (rail instanceof Error) return rail; + if (!this.isHexBytes(entry.session_id, 32) || + !this.isHexBytes(entry.billing_id, 32) || + !this.isHexBytes(entry.receipt_hash, 32) || + !this.isHexBytes(entry.user, 32) || + !this.isHexBytes(entry.provider, 32) || + !this.isHexBytes(entry.payout_revision, 32)) { + return new Error('Invalid targeted epoch allocation identity.'); + } + if (!Number.isSafeInteger(entry.billing_attempt) || entry.billing_attempt < 0 || + !Number.isSafeInteger(entry.billing_epoch) || entry.billing_epoch < 1 || + !Number.isSafeInteger(entry.receipt_seq) || entry.receipt_seq < 0) { + return new Error('Invalid targeted epoch allocation receipt position.'); + } + const attemptIdentity = `${entry.billing_id}:${entry.billing_attempt}`; + if (allocationSessions.has(entry.session_id) || + allocationSessions.has(attemptIdentity)) { + return new Error('Duplicate targeted epoch receipt allocation.'); + } + allocationSessions.add(entry.session_id); + allocationSessions.add(attemptIdentity); + const au = this.normalizeAu( + entry.au, + 'targeted epoch allocation amount', + { allowZero: false } + ); + if (au instanceof Error) return au; + allocations.push({ + session_id: entry.session_id, + billing_id: entry.billing_id, + billing_attempt: entry.billing_attempt, + billing_epoch: entry.billing_epoch, + receipt_seq: entry.receipt_seq, + receipt_hash: entry.receipt_hash, + user: entry.user, + rail, + provider: entry.provider, + payout_revision: entry.payout_revision, + au, + }); + } + allocations.sort((left, right) => ( + PROVIDER_PAYOUT_BINDING_RAIL_ORDER.indexOf(left.rail) - + PROVIDER_PAYOUT_BINDING_RAIL_ORDER.indexOf(right.rail) || + compareCodepoint(left.user, right.user) || + left.billing_epoch - right.billing_epoch || + compareCodepoint(left.billing_id, right.billing_id) || + left.billing_attempt - right.billing_attempt || + compareCodepoint(left.session_id, right.session_id) + )); + if (stableJson(allocations) !== stableJson(value.allocations)) { + return new Error('Targeted epoch allocations must be canonical.'); + } + const aggregated = new Map(); + const providerRails = new Map(); + for (const entry of value.earnings) { + const entryError = this.validateExactObjectKeys( + entry, + ['rail', 'provider', 'gross_au', 'payout_revision'], + 'targeted epoch earning' + ); + if (entryError) return entryError; + const rail = this.normalizeLedgerRail(entry.rail, 'targeted epoch earning rail'); + if (rail instanceof Error) return rail; + if (!PROVIDER_PAYOUT_BINDING_RAILS.has(rail)) { + return new Error('Targeted epoch earning rail has no payout binding path.'); + } + if (!this.isHexBytes(entry.provider, 32) || + entry.provider !== entry.provider.toLowerCase()) { + return new Error('Invalid targeted epoch earning provider.'); + } + if (!this.isHexBytes(entry.payout_revision, 32) || + entry.payout_revision !== entry.payout_revision.toLowerCase()) { + return new Error('Invalid targeted epoch payout revision.'); + } + const grossAu = this.normalizeAu( + entry.gross_au, + 'targeted epoch gross earning', + { allowZero: false } + ); + if (grossAu instanceof Error) return grossAu; + const providerRailKey = stableJson([rail, entry.provider]); + const selected = providerRails.get(providerRailKey); + if (selected && selected !== entry.payout_revision) { + return new Error('Targeted epoch provider rail cannot substitute payout revisions.'); + } + providerRails.set(providerRailKey, entry.payout_revision); + const key = stableJson([rail, entry.provider, entry.payout_revision]); + const current = aggregated.get(key) ?? { + rail, + provider: entry.provider, + gross_au: ZERO_AU, + payout_revision: entry.payout_revision, + }; + const next = this.safeAddAu(current.gross_au, grossAu); + if (next instanceof Error) return next; + aggregated.set(key, { ...current, gross_au: next }); + } + const targetedEarnings = Array.from(aggregated.values()).sort((left, right) => ( + PROVIDER_PAYOUT_BINDING_RAIL_ORDER.indexOf(left.rail) - + PROVIDER_PAYOUT_BINDING_RAIL_ORDER.indexOf(right.rail) || + compareCodepoint(left.provider, right.provider) || + compareCodepoint(left.payout_revision, right.payout_revision) + )); + let earningFinals = null; + if (hasOwn(value, 'earning_finals')) { + if (value.last_page !== true || !Array.isArray(value.earning_finals) || + value.earning_finals.length === 0) { + return new Error('Targeted epoch earning_finals require a non-empty final page.'); + } + earningFinals = []; + const identities = new Set(); + for (const entry of value.earning_finals) { + const entryError = this.validateExactObjectKeys( + entry, + ['rail', 'provider', 'gross_au', 'net_au', 'cumulative_au'], + 'targeted epoch final earning' + ); + if (entryError) return entryError; + const rail = this.normalizeLedgerRail(entry.rail, 'targeted epoch final earning rail'); + if (rail instanceof Error) return rail; + if (!this.isHexBytes(entry.provider, 32) || + entry.provider !== entry.provider.toLowerCase()) { + return new Error('Invalid targeted epoch final earning provider.'); + } + const identity = stableJson([rail, entry.provider]); + if (identities.has(identity)) { + return new Error('Duplicate targeted epoch final earning provider.'); + } + identities.add(identity); + const grossAu = this.normalizeAu( + entry.gross_au, + 'targeted epoch final gross earning', + { allowZero: false } + ); + const netAu = this.normalizeAu( + entry.net_au, + 'targeted epoch final net earning', + { allowZero: false } + ); + const cumulativeAu = this.normalizeAu( + entry.cumulative_au, + 'targeted epoch final cumulative earning', + { allowZero: false } + ); + if (grossAu instanceof Error || netAu instanceof Error || cumulativeAu instanceof Error) { + return new Error('Invalid targeted epoch final earning amount.'); + } + if (this.compareAu(netAu, grossAu) > 0 || this.compareAu(cumulativeAu, netAu) < 0) { + return new Error('Targeted epoch final earning totals are inconsistent.'); + } + earningFinals.push({ + rail, + provider: entry.provider, + gross_au: grossAu, + net_au: netAu, + cumulative_au: cumulativeAu, + }); + } + earningFinals.sort((left, right) => ( + PROVIDER_PAYOUT_BINDING_RAIL_ORDER.indexOf(left.rail) - + PROVIDER_PAYOUT_BINDING_RAIL_ORDER.indexOf(right.rail) || + compareCodepoint(left.provider, right.provider) + )); + if (stableJson(earningFinals) !== stableJson(value.earning_finals)) { + return new Error('Targeted epoch final earnings must be canonical.'); + } + } + const { + allocations: _allocations, + op: _op, + earnings: _earnings, + earning_finals: _earningFinals, + ...ledgerFields + } = value; + const ledgerValue = { + ...ledgerFields, + receipt_index: receiptIndex, + earnings: targetedEarnings.map(({ payout_revision: _revision, ...earning }) => earning), + ...(earningFinals ? { earning_finals: earningFinals } : {}), + }; + if (!Number.isSafeInteger(ledgerValue.epoch) || ledgerValue.epoch < 1) { + return new Error('Invalid targeted epoch.'); + } + if (!Number.isSafeInteger(ledgerValue.at) || ledgerValue.at < 0) { + return new Error('Invalid targeted epoch timestamp.'); + } + const page = this.epochApplyPage(ledgerValue); + if (page instanceof Error) return page; + const lastPage = this.epochApplyLastPage(ledgerValue); + if (lastPage instanceof Error) return lastPage; + const ledgerError = this.validateEpochApplyShape(ledgerValue); + if (ledgerError) return ledgerError; + return { + ledger_value: ledgerValue, + allocations, + targeted_earnings: targetedEarnings, + earning_finals: earningFinals, + revision_bindings: targetedEarnings.map((earning) => ({ + provider: earning.provider, + rail: earning.rail, + revision: earning.payout_revision, + })), + }; + } + + async validateProviderPayoutBindingIntent(intent, { currentState = true } = {}) { + const shapeError = this.validateExactObjectKeys( + intent, + [ + 'op', + 'network', + 'admin', + 'bootstrap', + 'context_revision', + 'provider', + 'rail', + 'currency', + 'chain_id', + 'target', + 'target_wallet', + 'target_signature', + 'previous_revision', + 'payment_config_version', + 'nonce', + 'expires_after_epoch', + ], + 'provider payout binding intent' + ); + if (shapeError) return shapeError; + if (intent.op !== 'bind_provider_payout') { + return new Error('Invalid provider payout binding intent op.'); + } + for (const field of ['admin', 'bootstrap', 'provider', 'nonce']) { + if (!this.isHexBytes(intent[field], 32) || intent[field] !== intent[field].toLowerCase()) { + return new Error(`Invalid provider payout binding ${field}.`); + } + } + if (!this.isHexBytes(intent.context_revision, 32) || + intent.context_revision !== intent.context_revision.toLowerCase()) { + return new Error('Invalid provider payout binding context revision.'); + } + if (!this.isSafeKeyPart(intent.network)) { + return new Error('Invalid provider payout binding network.'); + } + if (!PROVIDER_PAYOUT_BINDING_RAILS.has(intent.rail)) { + return new Error('Invalid provider payout binding rail.'); + } + if (intent.rail !== 'fiat' && intent.currency !== null) { + return new Error('TAP/TNK payout binding currency must be null.'); + } + if ( + intent.previous_revision !== null && + (!this.isHexBytes(intent.previous_revision, 32) || + intent.previous_revision !== intent.previous_revision.toLowerCase()) + ) { + return new Error('Invalid previous provider payout binding revision.'); + } + if (!Number.isSafeInteger(intent.payment_config_version) || + intent.payment_config_version < 1) { + return new Error('Invalid provider payout payment config version.'); + } + if (!Number.isSafeInteger(intent.expires_after_epoch) || + intent.expires_after_epoch < 1) { + return new Error('Invalid provider payout binding expiry epoch.'); + } + + if (intent.rail === 'fiat') { + const currency = this.normalizeFiatCurrency(intent.currency); + if (currency instanceof Error || + currency !== intent.currency || + intent.chain_id !== null || + intent.target_wallet !== null || + intent.target_signature !== null || + typeof intent.target !== 'string' || + !/^acct_[A-Za-z0-9._-]+$/.test(intent.target)) { + return new Error('Invalid verified Stripe payout target.'); + } + } else if (intent.rail === 'tap') { + if (!Number.isSafeInteger(intent.chain_id) || intent.chain_id < 1) { + return new Error('Invalid TAP payout binding chain id.'); + } + if (!this.isEthHexBytes(intent.target, 20) || + intent.target !== intent.target.toLowerCase()) { + return new Error('Invalid TAP payout target.'); + } + if (intent.target_wallet !== null) { + return new Error('TAP payout binding target_wallet must be null.'); + } + if (!this.isEthHexBytes(intent.target_signature, 65) || + intent.target_signature !== intent.target_signature.toLowerCase()) { + return new Error('Invalid TAP payout target ownership signature.'); + } + } else { + if (intent.chain_id !== null) { + return new Error('TNK payout binding chain_id must be null.'); + } + if (!this.isSafeKeyPart(intent.target) || intent.target !== intent.target.toLowerCase()) { + return new Error('Invalid TNK payout target.'); + } + if (!this.isHexBytes(intent.target_wallet, 32) || + intent.target_wallet !== intent.target_wallet.toLowerCase()) { + return new Error('Invalid TNK payout target wallet.'); + } + if (!this.isHexBytes(intent.target_signature, 64) || + intent.target_signature !== intent.target_signature.toLowerCase()) { + return new Error('Invalid TNK payout target ownership signature.'); + } + const address = this.msbAddressForPublicKey(intent.target_wallet, intent.network); + if (address instanceof Error) return address; + if (intent.target !== address) { + return new Error('TNK payout target does not match target wallet.'); + } + } + if (!currentState) return null; + + const admin = await this.get('admin'); + if (intent.admin !== admin) { + return new Error('Provider payout binding admin does not match canonical admin.'); + } + const payments = await this.get('payments/current'); + if (!payments || payments.set_by !== admin || payments.set_by_role !== 'admin') { + return new Error('Canonical payment configuration required.'); + } + if (intent.payment_config_version !== payments.ver) { + return new Error('Provider payout binding payment config version is stale.'); + } + if (!Array.isArray(payments.rails) || !payments.rails.includes(intent.rail)) { + return new Error('Provider payout binding rail is not enabled.'); + } + if (intent.network !== payments.tnk?.network) { + return new Error('Provider payout binding network is not canonical.'); + } + + if (intent.rail === 'fiat') { + if (!payments.fiat?.payout_currencies?.includes(intent.currency)) { + return new Error('Invalid verified Stripe payout target.'); + } + const verification = await this.providerStripePayoutVerificationForTarget( + intent.provider, + intent.target + ); + if (!verification || + verification.type !== 'stripe_payout_verification' || + verification.provider !== intent.provider || + verification.target !== intent.target || + verification.currency !== intent.currency || + verification.context_revision !== intent.context_revision || + verification.payment_config_version !== intent.payment_config_version || + verification.details_submitted !== true || + verification.payouts_enabled !== true || + verification.transfers_enabled !== true || + verification.verified_by !== admin || + verification.verified_by_role !== 'admin') { + return new Error('Current ready provider-scoped Stripe verification required.'); + } + return null; + } + + if (intent.rail === 'tap') { + if (intent.chain_id !== payments.tap?.chain_id) { + return new Error('TAP payout binding chain id is not canonical.'); + } + return null; + } + return null; + } + + validateDepositTnkIntent(intent) { + const fields = [ + 'op', + 'memo_hash', + 'treasury_address', + 'tnk_e18', + 'quoted_au', + 'rate_tnk_usd_au', + 'rate_source', + ]; + if (hasOwn(intent, 'rate_ts')) fields.push('rate_ts'); + if (hasOwn(intent, 'rate_record_key')) fields.push('rate_record_key'); + const shapeError = this.validateExactObjectKeys( + intent, + fields, + 'deposit TNK intent' + ); + if (shapeError) return shapeError; + if (intent.op !== 'deposit_tnk') return new Error('Invalid deposit TNK intent op.'); + if (!this.isSafeKeyPart(intent.memo_hash)) return new Error('Invalid deposit memo hash.'); + if (!this.isSafeKeyPart(intent.treasury_address)) return new Error('Invalid TNK treasury address.'); + const quotedAu = this.normalizeAu(intent.quoted_au, 'TNK quoted credit', { allowZero: false }); + if (quotedAu instanceof Error) { + return new Error('Invalid TNK quoted credit.'); + } + const rate = this.normalizeAu(intent.rate_tnk_usd_au, 'TNK quoted rate', { allowZero: false }); + if (rate instanceof Error) { + return new Error('Invalid TNK quoted rate.'); + } + if (!this.isSafeKeyPart(intent.rate_source)) return new Error('Invalid TNK rate source.'); + const hasRateTs = hasOwn(intent, 'rate_ts'); + const hasRateRecordKey = hasOwn(intent, 'rate_record_key'); + if (hasRateTs !== hasRateRecordKey) { + return new Error('TNK rate timestamp and record key must be paired.'); + } + if (hasRateTs && + (!Number.isSafeInteger(intent.rate_ts) || intent.rate_ts < 0)) { + return new Error('Invalid TNK rate timestamp.'); + } + if (hasRateRecordKey) { + const prefix = `rate/tnk/${intent.rate_ts}/`; + if (typeof intent.rate_record_key !== 'string' || + !intent.rate_record_key.startsWith(prefix) || + !this.isHexBytes(intent.rate_record_key.slice(prefix.length), 32)) { + return new Error('Invalid TNK rate record key.'); + } + } + const tnkE18 = this.parseTnkE18(intent.tnk_e18); + if (tnkE18 instanceof Error) return tnkE18; + return null; + } + + guardianValidateBalanceRecord(record, user = null, rail = null) { + if (!record || typeof record !== 'object' || Array.isArray(record)) { + return new Error('Guardian non-negative balance invariant failed.'); + } + if (rail !== null && record.rail !== rail) { + return new Error('Guardian balance rail invariant failed.'); + } + if (record.denom !== PRICE_DENOMINATION) { + return new Error('Guardian balance denomination invariant failed.'); + } + if (user !== null && record.user !== user) { + return new Error('Guardian balance owner invariant failed.'); + } + if (this.normalizeAu(record.au, 'balance au') instanceof Error) { + return new Error('Guardian non-negative balance invariant failed.'); + } + if (!Number.isSafeInteger(record.updated_epoch) || record.updated_epoch < 0) { + return new Error('Guardian balance epoch invariant failed.'); + } + if (record.rail === 'tap') { + const scopeError = this.guardianValidateTapScope(record, ['au'], 'balance'); + if (scopeError) return scopeError; + } + return null; + } + + guardianValidateEarningRecord(record, provider = null, rail = null) { + if (!record || typeof record !== 'object' || Array.isArray(record)) { + return new Error('Guardian non-negative earnings invariant failed.'); + } + if (rail !== null && record.rail !== rail) { + return new Error('Guardian earnings rail invariant failed.'); + } + if (record.denom !== PRICE_DENOMINATION) { + return new Error('Guardian earnings denomination invariant failed.'); + } + if (provider !== null && record.provider !== provider) { + return new Error('Guardian earnings owner invariant failed.'); + } + for (const key of ['total_au', 'held_au', 'paid_cum_au']) { + if (this.normalizeAu(record[key], `earning ${key}`) instanceof Error) { + return new Error('Guardian non-negative earnings invariant failed.'); + } + } + if (!Number.isSafeInteger(record.updated_epoch) || record.updated_epoch < 0) { + return new Error('Guardian earnings epoch invariant failed.'); + } + const payableAu = this.safeSubAu(record.total_au, record.held_au); + if (payableAu instanceof Error) return payableAu; + if (this.compareAu(record.held_au, record.total_au) > 0 || this.compareAu(record.paid_cum_au, payableAu) > 0) { + return new Error('Guardian earnings conservation invariant failed.'); + } + if (record.holdbacks !== undefined) { + const holdbacks = this.normalizeHoldbackBuckets(record); + if (holdbacks instanceof Error) return holdbacks; + const heldAu = this.holdbackBucketTotal(holdbacks); + if (heldAu instanceof Error) return heldAu; + if (this.compareAu(heldAu, record.held_au) !== 0) { + return new Error('Guardian earnings conservation invariant failed.'); + } + } + if (record.rail === 'tap') { + const scopeError = this.guardianValidateTapScope( + record, + ['total_au', 'held_au', 'paid_cum_au'], + 'earning' + ); + if (scopeError) return scopeError; + } + return null; + } + + guardianValidatePayoutLiabilityRecord( + record, + provider = null, + rail = null, + revision = null + ) { + if (!record || + typeof record !== 'object' || + Array.isArray(record) || + record.type !== 'provider_payout_liability') { + return new Error('Guardian payout liability shape invariant failed.'); + } + if (!PROVIDER_PAYOUT_BINDING_RAILS.has(record.rail) || + (rail !== null && record.rail !== rail)) { + return new Error('Guardian payout liability rail invariant failed.'); + } + if (!this.isHexBytes(record.provider, 32) || + (provider !== null && record.provider !== provider)) { + return new Error('Guardian payout liability owner invariant failed.'); + } + if (!this.isHexBytes(record.revision, 32) || + (revision !== null && record.revision !== revision)) { + return new Error('Guardian payout liability revision invariant failed.'); + } + if (!this.isSafeKeyPart(record.target)) { + return new Error('Guardian payout liability target invariant failed.'); + } + if (record.rail === 'fiat') { + if (!this.isSafeKeyPart(record.currency) || record.chain_id !== null) { + return new Error('Guardian payout liability fiat scope invariant failed.'); + } + } else if (record.rail === 'tap') { + if (record.currency !== null || + !Number.isSafeInteger(record.chain_id) || + record.chain_id < 1 || + !this.isEthHexBytes(record.target, 20)) { + return new Error('Guardian payout liability TAP scope invariant failed.'); + } + } else if (record.currency !== null || record.chain_id !== null) { + return new Error('Guardian payout liability TNK scope invariant failed.'); + } + for (const key of ['total_au', 'held_au', 'paid_cum_au']) { + if (this.normalizeAu(record[key], `payout liability ${key}`) instanceof Error) { + return new Error('Guardian non-negative payout liability invariant failed.'); + } + } + if (!Number.isSafeInteger(record.updated_epoch) || record.updated_epoch < 0) { + return new Error('Guardian payout liability epoch invariant failed.'); + } + const payableAu = this.safeSubAu(record.total_au, record.held_au); + if (payableAu instanceof Error || + this.compareAu(record.held_au, record.total_au) > 0 || + this.compareAu(record.paid_cum_au, payableAu) > 0) { + return new Error('Guardian payout liability conservation invariant failed.'); + } + const holdbacks = this.normalizeHoldbackBuckets(record); + if (holdbacks instanceof Error) return holdbacks; + const heldAu = this.holdbackBucketTotal(holdbacks); + if (heldAu instanceof Error || + this.compareAu(heldAu, record.held_au) !== 0) { + return new Error('Guardian payout liability conservation invariant failed.'); + } + return null; + } + + guardianValidateFeeRecord(record, rail = null) { + if (!record || typeof record !== 'object' || Array.isArray(record)) { + return new Error('Guardian fee conservation invariant failed.'); + } + if (rail !== null && record.rail !== rail) { + return new Error('Guardian fee rail invariant failed.'); + } + if (record.denom !== PRICE_DENOMINATION) { + return new Error('Guardian fee denomination invariant failed.'); + } + for (const key of ['cum_au', 'swept_cum_au']) { + if (this.normalizeAu(record[key], `fee ${key}`) instanceof Error) { + return new Error('Guardian fee conservation invariant failed.'); + } + } + if (!Number.isSafeInteger(record.updated_epoch) || record.updated_epoch < 0) { + return new Error('Guardian monotonic epoch invariant failed.'); + } + if (this.compareAu(record.swept_cum_au, record.cum_au) > 0) { + return new Error('Guardian fee conservation invariant failed.'); + } + const settledCumAu = record.settled_cum_au ?? record.cum_au; + if ( + this.normalizeAu(settledCumAu, 'fee settled cumulative amount') instanceof Error || + this.compareAu(settledCumAu, record.cum_au) < 0 + ) { + return new Error('Guardian conservation invariant failed.'); + } + if (record.rail === 'tap') { + const scopeError = this.guardianValidateTapScope( + record, + ['cum_au', 'swept_cum_au', 'settled_cum_au'], + 'fee' + ); + if (scopeError) return scopeError; + } + return null; + } + + guardianValidateBurnRecord(record, rail = null) { + if (!record || typeof record !== 'object' || Array.isArray(record)) { + return new Error('Guardian burn conservation invariant failed.'); + } + if (rail !== null && record.rail !== rail) { + return new Error('Guardian burn rail invariant failed.'); + } + if (record.denom !== PRICE_DENOMINATION) { + return new Error('Guardian burn denomination invariant failed.'); + } + if (this.normalizeAu(record.cum_au, 'burn cumulative amount') instanceof Error) { + return new Error('Guardian burn conservation invariant failed.'); + } + if (!Number.isSafeInteger(record.updated_epoch) || record.updated_epoch < 0) { + return new Error('Guardian monotonic epoch invariant failed.'); + } + const expectedBps = record.rail === 'tap' ? TAP_BURN_BPS : 0; + if (record.burn_bps !== expectedBps) { + return new Error('Guardian burn policy invariant failed.'); + } + if (record.rail === 'tap') { + const scopeError = this.guardianValidateTapScope(record, ['cum_au'], 'burn'); + if (scopeError) return scopeError; + } + return null; + } + + guardianCheckEpochApply({ + epoch, + applyState, + feeRecords, + burnRecords, + debitTotal, + debitRailTotals, + feeDeltaByRail, + burnDeltaByRail, + providerDeltaTotal, + nextFeeRecords, + nextBurnRecords, + balances, + earnings, + }) { + if (!applyState || !Number.isSafeInteger(applyState.updated_epoch) || applyState.updated_epoch < 0) { + return new Error('Guardian monotonic epoch invariant failed.'); + } + if (epoch <= applyState.updated_epoch || epoch !== applyState.updated_epoch + 1) { + return new Error('Guardian monotonic epoch invariant failed.'); + } + let feeDeltaAu = ZERO_AU; + let burnDeltaAu = ZERO_AU; + for (const rail of PROVIDER_ACCEPTED_RAIL_ORDER) { + const fee = feeRecords.get(rail); + const feeError = this.guardianValidateFeeRecord(fee, rail); + if (feeError) return feeError; + const nextFee = nextFeeRecords.get(rail); + const nextFeeError = this.guardianValidateFeeRecord(nextFee, rail); + if (nextFeeError) return nextFeeError; + const railFeeDelta = feeDeltaByRail.get(rail) ?? ZERO_AU; + feeDeltaAu = this.safeAddAu(feeDeltaAu, railFeeDelta); + if (feeDeltaAu instanceof Error) return feeDeltaAu; + const expectedFeeCumAu = this.safeAddAu(fee.cum_au, railFeeDelta); + if (expectedFeeCumAu instanceof Error) return expectedFeeCumAu; + if (this.compareAu(nextFee.cum_au, expectedFeeCumAu) !== 0) { + return new Error('Guardian fee conservation invariant failed.'); + } + const burn = burnRecords.get(rail); + const burnError = this.guardianValidateBurnRecord(burn, rail); + if (burnError) return burnError; + const nextBurn = nextBurnRecords.get(rail); + const nextBurnError = this.guardianValidateBurnRecord(nextBurn, rail); + if (nextBurnError) return nextBurnError; + const railBurnDelta = burnDeltaByRail.get(rail) ?? ZERO_AU; + burnDeltaAu = this.safeAddAu(burnDeltaAu, railBurnDelta); + if (burnDeltaAu instanceof Error) return burnDeltaAu; + const expectedBurnCumAu = this.safeAddAu(burn.cum_au, railBurnDelta); + if (expectedBurnCumAu instanceof Error) return expectedBurnCumAu; + if (this.compareAu(nextBurn.cum_au, expectedBurnCumAu) !== 0) { + return new Error('Guardian burn conservation invariant failed.'); + } + } + const providerAndFeeAu = this.safeAddAu(providerDeltaTotal, feeDeltaAu); + if (providerAndFeeAu instanceof Error) return providerAndFeeAu; + const grossDeltaTotal = this.safeAddAu(providerAndFeeAu, burnDeltaAu); + if (grossDeltaTotal instanceof Error) return grossDeltaTotal; + if (this.compareAu(grossDeltaTotal, debitTotal) !== 0) { + return new Error('Guardian conservation invariant failed.'); + } + + for (const balance of balances.values()) { + const balanceError = this.guardianValidateBalanceRecord(balance, balance.user, balance.rail); + if (balanceError) return balanceError; + } + for (const earning of earnings.values()) { + const earningError = this.guardianValidateEarningRecord(earning, earning.provider, earning.rail); + if (earningError) return earningError; + } + + const nextSettledCumByRail = new Map(); + for (const rail of PROVIDER_ACCEPTED_RAIL_ORDER) { + const fee = feeRecords.get(rail); + const nextFee = nextFeeRecords.get(rail); + const priorSettledCumAu = fee.settled_cum_au ?? fee.cum_au; + const nextSettledCumAu = this.safeAddAu(priorSettledCumAu, debitRailTotals.get(rail) ?? ZERO_AU); + if (nextSettledCumAu instanceof Error) return nextSettledCumAu; + if (this.compareAu(nextFee.cum_au, nextSettledCumAu) > 0) { + return new Error('Guardian conservation invariant failed.'); + } + nextSettledCumByRail.set(rail, nextSettledCumAu); + } + return { ok: true, next_settled_cum_by_rail: nextSettledCumByRail }; + } + + lockedEarningEpochs(params) { + return Math.max(params.holdback_epochs ?? 0, params.challenge_epochs ?? 0); + } + + providerLockedEarningEpochs(provider, params) { + const normalHoldback = params.holdback_epochs ?? 0; + const newProviderHoldback = params.new_provider_holdback_epochs ?? normalHoldback; + const threshold = params.probation_successful_sessions ?? 0; + const successfulSessions = provider?.probation?.successful_sessions ?? 0; + if ( + !Number.isSafeInteger(normalHoldback) || + !Number.isSafeInteger(newProviderHoldback) || + !Number.isSafeInteger(threshold) || + !Number.isSafeInteger(successfulSessions) || + normalHoldback < 0 || + newProviderHoldback < 0 || + threshold < 0 || + successfulSessions < 0 + ) { + return new Error('Invalid provider holdback probation state.'); + } + const providerHoldback = successfulSessions < threshold + ? Math.max(normalHoldback, newProviderHoldback) + : normalHoldback; + return Math.max(providerHoldback, params.challenge_epochs ?? 0); + } + + async recordCanaryProbePass(value, auditor) { + const key = `probe/pass/${value.provider}/${value.epoch}`; + const current = await this.get(key); + const auditors = current?.auditors ?? []; + const probes = current?.probes ?? []; + if (!Array.isArray(auditors) || !Array.isArray(probes) || auditors.length !== probes.length) { + return new Error('Invalid canary pass record.'); + } + if (auditors.includes(auditor)) { + return new Error('Auditor already supplied a canary pass for this provider epoch.'); + } + const next = [...probes, { + auditor, + probe_id: value.probe_id, + evidence_hash: value.evidence_hash, + challenge_seed: value.challenge_seed, + }].sort((left, right) => compareCodepoint(left.auditor, right.auditor)); + const record = { + provider: value.provider, + epoch: value.epoch, + pass_count: next.length, + auditors: next.map((entry) => entry.auditor), + probes: next, + last_probe_id: value.probe_id, + last_evidence_hash: value.evidence_hash ?? null, + updated_at: this.tx, + }; + await this.put(key, record); + return record; + } + + async probeGateForEarning(provider, earning, params) { + const holdbackBps = params.canary_probe_holdback_bps ?? 0; + const requiredPasses = params.canary_probe_release_min_passes ?? 0; + if (holdbackBps <= 0 || requiredPasses <= 0) return null; + if (!Number.isSafeInteger(holdbackBps) || holdbackBps < 0 || holdbackBps > 10_000) { + return new Error('Invalid canary probe holdback bps.'); + } + if (!Number.isSafeInteger(requiredPasses) || requiredPasses < 2) { + return new Error('Invalid canary probe release threshold.'); + } + const holdbacks = this.normalizeHoldbackBuckets(earning); + if (holdbacks instanceof Error) return holdbacks; + const passedEpochs = new Set(); + for (const epoch of [...new Set(holdbacks.map((bucket) => bucket.epoch))]) { + const passRecord = await this.get(`probe/pass/${provider}/${epoch}`); + const distinctAuditors = new Set(passRecord?.auditors ?? []); + let activeAuditors = 0; + for (const auditor of distinctAuditors) { + if ((await this.get(`auditor/${auditor}`))?.status === 'active') activeAuditors += 1; + } + if ( + distinctAuditors.size === (passRecord?.pass_count ?? 0) && + activeAuditors >= requiredPasses + ) { + passedEpochs.add(epoch); + } + } + return { + holdback_bps: holdbackBps, + required_passes: requiredPasses, + passed_epochs: passedEpochs, + }; + } + + normalizeHoldbackBuckets(record) { + if (!Array.isArray(record.holdbacks)) { + const heldAu = this.normalizeAu(record.held_au, 'earning held amount'); + if (heldAu instanceof Error) { + return new Error('Guardian non-negative earnings invariant failed.'); + } + if (this.isZeroAu(heldAu)) return []; + return [{ + epoch: Number.isSafeInteger(record.updated_epoch) ? record.updated_epoch : 0, + au: heldAu, + }]; + } + + const byEpoch = new Map(); + for (const bucket of record.holdbacks) { + if (!bucket || typeof bucket !== 'object' || Array.isArray(bucket)) { + return new Error('Guardian earnings conservation invariant failed.'); + } + if (!Number.isSafeInteger(bucket.epoch) || bucket.epoch < 0) { + return new Error('Guardian monotonic epoch invariant failed.'); + } + const bucketAu = this.normalizeAu(bucket.au, 'holdback bucket amount', { allowZero: false }); + if (bucketAu instanceof Error) { + return new Error('Guardian non-negative earnings invariant failed.'); + } + const lockedEpochs = hasOwn(bucket, 'locked_epochs') ? bucket.locked_epochs : null; + if (lockedEpochs !== null && (!Number.isSafeInteger(lockedEpochs) || lockedEpochs < 0)) { + return new Error('Guardian monotonic epoch invariant failed.'); + } + const key = `${bucket.epoch}:${bucket.probe_gate === true ? 'probe' : 'time'}:${lockedEpochs ?? 'default'}`; + const next = this.safeAddAu(byEpoch.get(key)?.au ?? ZERO_AU, bucketAu); + if (next instanceof Error) return next; + byEpoch.set(key, { + epoch: bucket.epoch, + au: next, + probe_gate: bucket.probe_gate === true, + ...(lockedEpochs !== null ? { locked_epochs: lockedEpochs } : {}), + }); + } + return Array.from(byEpoch.values()) + .sort((a, b) => ( + a.epoch - b.epoch || + (a.locked_epochs ?? -1) - (b.locked_epochs ?? -1) || + Number(a.probe_gate) - Number(b.probe_gate) + )) + .map((bucket) => ( + bucket.probe_gate + ? { + epoch: bucket.epoch, + au: bucket.au, + probe_gate: true, + ...(hasOwn(bucket, 'locked_epochs') ? { locked_epochs: bucket.locked_epochs } : {}), + } + : { + epoch: bucket.epoch, + au: bucket.au, + ...(hasOwn(bucket, 'locked_epochs') ? { locked_epochs: bucket.locked_epochs } : {}), + } + )); + } + + holdbackBucketTotal(holdbacks) { + let total = ZERO_AU; + for (const bucket of holdbacks) { + const next = this.safeAddAu(total, bucket.au); + if (next instanceof Error) return next; + total = next; + } + return total; + } + + refreshEarningHoldback(record, currentEpoch, lockedEpochs, probeGate = null, disputeGate = false) { + if (!Number.isSafeInteger(currentEpoch) || currentEpoch < 0) { + return new Error('Guardian monotonic epoch invariant failed.'); + } + if (!Number.isSafeInteger(lockedEpochs) || lockedEpochs < 0) { + return new Error('Guardian monotonic epoch invariant failed.'); + } + const holdbacks = this.normalizeHoldbackBuckets(record); + if (holdbacks instanceof Error) return holdbacks; + const kept = []; + for (const bucket of holdbacks) { + const bucketLockedEpochs = hasOwn(bucket, 'locked_epochs') ? bucket.locked_epochs : lockedEpochs; + if (!Number.isSafeInteger(bucketLockedEpochs) || bucketLockedEpochs < 0) { + return new Error('Guardian monotonic epoch invariant failed.'); + } + if (disputeGate || bucket.epoch + bucketLockedEpochs > currentEpoch) { + kept.push(bucket); + continue; + } + if (!probeGate) continue; + if (probeGate.passed_epochs.has(bucket.epoch)) continue; + if (bucket.probe_gate === true) { + kept.push(bucket); + continue; + } + const gatedAu = this.safeMulDivAu(bucket.au, probeGate.holdback_bps, 10_000); + if (gatedAu instanceof Error) return gatedAu; + if (this.compareAu(gatedAu, ZERO_AU) > 0) { + kept.push({ + epoch: bucket.epoch, + au: gatedAu, + probe_gate: true, + ...(hasOwn(bucket, 'locked_epochs') ? { locked_epochs: bucket.locked_epochs } : {}), + }); + } + } + const heldAu = this.holdbackBucketTotal(kept); + if (heldAu instanceof Error) return heldAu; + return { + ...record, + held_au: heldAu, + holdbacks: kept, + last_holdback_release_epoch: currentEpoch, + }; + } + + slashHoldbackBuckets(holdbacks, slashAu) { + const slashAmountAu = this.normalizeAu(slashAu, 'slash amount'); + if (slashAmountAu instanceof Error) { + return new Error('Guardian non-negative earnings invariant failed.'); + } + if (this.isZeroAu(slashAmountAu)) return holdbacks; + + let remaining = slashAmountAu; + const kept = []; + for (let idx = holdbacks.length - 1; idx >= 0; idx -= 1) { + const bucket = holdbacks[idx]; + if (this.isZeroAu(remaining)) { + kept.push(bucket); + continue; + } + if (this.compareAu(bucket.au, remaining) <= 0) { + remaining = this.safeSubAu(remaining, bucket.au); + if (remaining instanceof Error) return remaining; + continue; + } + const reducedAu = this.safeSubAu(bucket.au, remaining); + if (reducedAu instanceof Error) return reducedAu; + kept.push({ + ...bucket, + epoch: bucket.epoch, + au: reducedAu, + }); + remaining = ZERO_AU; + } + if (!this.isZeroAu(remaining)) return new Error('Guardian earnings conservation invariant failed.'); + return kept.reverse(); + } + + slashAmount(heldAu, slashBps) { + if (this.normalizeAu(heldAu, 'held amount') instanceof Error) { + return new Error('Guardian non-negative earnings invariant failed.'); + } + if (!Number.isSafeInteger(slashBps) || slashBps < 0 || slashBps > 10_000) { + return new Error('Invalid slash bps.'); + } + return this.safeMulDivAu(heldAu, slashBps, 10_000); + } + + providerActiveEnclaves(provider) { + if (!provider || !Array.isArray(provider.enclaves)) return []; + return [...new Set(provider.enclaves.filter((enclaveId) => this.isSafeKeyPart(enclaveId)))].sort(); + } + + providerEnclavesWith(provider, enclaveId) { + const enclaves = this.providerActiveEnclaves(provider); + if (!enclaves.includes(enclaveId)) enclaves.push(enclaveId); + return enclaves.sort(); + } + + providerEnclavesWithout(provider, enclaveId) { + return this.providerActiveEnclaves(provider).filter((activeEnclaveId) => activeEnclaveId !== enclaveId); + } + + enclaveActiveProviders(enclave) { + if (!enclave || !Array.isArray(enclave.providers)) return []; + return [...new Set(enclave.providers.filter((providerId) => this.isSafeKeyPart(providerId)))].sort(); + } + + enclaveProvidersWith(enclave, providerId) { + const providers = this.enclaveActiveProviders(enclave); + if (!providers.includes(providerId)) providers.push(providerId); + return providers.sort(); + } + + enclaveProvidersWithout(enclave, providerId) { + return this.enclaveActiveProviders(enclave).filter((activeProviderId) => activeProviderId !== providerId); + } + + roomServingEntries(room) { + if (!room || !Array.isArray(room.serves)) return []; + const entries = new Map(); + for (const entry of room.serves) { + if (!entry || typeof entry !== 'object' || Array.isArray(entry)) continue; + if (!this.isSafeKeyPart(entry.provider) || !this.isSafeKeyPart(entry.enclave_id)) continue; + entries.set(JSON.stringify([entry.provider, entry.enclave_id]), { + provider: entry.provider, + enclave_id: entry.enclave_id, + }); + } + return Array.from(entries.values()).sort((a, b) => ( + compareCodepoint(a.provider, b.provider) || compareCodepoint(a.enclave_id, b.enclave_id) + )); + } + + roomServesWith(room, providerId, enclaveId) { + const entries = this.roomServingEntries(room); + if (!entries.some((entry) => entry.provider === providerId && entry.enclave_id === enclaveId)) { + entries.push({ provider: providerId, enclave_id: enclaveId }); + } + return entries.sort((a, b) => ( + compareCodepoint(a.provider, b.provider) || compareCodepoint(a.enclave_id, b.enclave_id) + )); + } + + roomServesWithout(room, providerId, enclaveId = null) { + return this.roomServingEntries(room).filter((entry) => ( + entry.provider !== providerId || (enclaveId !== null && entry.enclave_id !== enclaveId) + )); + } + + async tombstoneRoomServes(roomId, entries, evidenceHash) { + if (!this.isSafeKeyPart(roomId)) return new Error('Invalid room id.'); + const tombstones = []; + for (const entry of entries) { + const tombstone = await this.tombstoneRoomServing( + roomId, + entry.provider, + entry.enclave_id, + evidenceHash + ); + if (tombstone instanceof Error) return tombstone; + tombstones.push(tombstone); + } + return tombstones; + } + + async tombstoneRoomServing(roomId, providerId, enclaveId, evidenceHash) { + if (!this.isSafeKeyPart(roomId)) return new Error('Invalid room id.'); + if (!this.isSafeKeyPart(providerId)) return new Error('Invalid provider id.'); + if (!this.isSafeKeyPart(enclaveId)) return new Error('Invalid enclave id.'); + + const roomServeKey = `roomserve/${roomId}/${providerId}/${enclaveId}`; + const roomServing = await this.get(roomServeKey); + const roomKey = `room/${roomId}`; + const room = await this.get(roomKey); + const servingKey = `serve/${providerId}/${enclaveId}`; + const serving = await this.get(servingKey); + + if (room) { + await this.put(roomKey, { + ...room, + serves: this.roomServesWithout(room, providerId, enclaveId), + serves_updated_at: this.tx, + }); + } + if (serving) { + await this.put(servingKey, { + ...serving, + rooms: Array.isArray(serving.rooms) + ? serving.rooms.filter((activeRoomId) => activeRoomId !== roomId) + : [], + updated_at: this.tx, + }); + } + if (!roomServing || roomServing.status !== 'active') { + return { + room_id: roomId, + provider: providerId, + enclave_id: enclaveId, + roomserve_tombstoned: false, + }; + } + + await this.put(roomServeKey, { + ...roomServing, + status: 'tombstoned', + updated_at: this.tx, + tombstoned_at: this.tx, + tombstone_reason_hash: evidenceHash, + }); + return { + room_id: roomId, + provider: providerId, + enclave_id: enclaveId, + roomserve_tombstoned: true, + }; + } + + async tombstoneEnclaveProviders(enclaveId, providerIds, evidenceHash) { + if (!this.isSafeKeyPart(enclaveId)) return new Error('Invalid enclave id.'); + const tombstones = []; + for (const providerId of [...new Set(providerIds)].sort()) { + const tombstone = await this.tombstoneProviderEnclave(providerId, enclaveId, evidenceHash); + if (tombstone instanceof Error) return tombstone; + tombstones.push(tombstone); + } + return tombstones; + } + + async tombstoneProviderEnclaves(providerId, enclaveIds, evidenceHash) { + if (!this.isSafeKeyPart(providerId)) return new Error('Invalid provider id.'); + const tombstones = []; + for (const enclaveId of [...new Set(enclaveIds)].sort()) { + const tombstone = await this.tombstoneProviderEnclave(providerId, enclaveId, evidenceHash); + if (tombstone instanceof Error) return tombstone; + tombstones.push(tombstone); + } + return tombstones; + } + + async tombstoneProviderEnclave(providerId, enclaveId, evidenceHash) { + if (!this.isSafeKeyPart(providerId)) return new Error('Invalid provider id.'); + if (!enclaveId) { + return { + provider: providerId, + enclave_id: null, + serve_tombstoned: false, + rooms_tombstoned: [], + }; + } + if (!this.isSafeKeyPart(enclaveId)) return new Error('Invalid enclave id.'); + + const serveKey = `serve/${providerId}/${enclaveId}`; + const serving = await this.get(serveKey); + if (!serving) { + return { + provider: providerId, + enclave_id: enclaveId, + serve_tombstoned: false, + rooms_tombstoned: [], + }; + } + + const rooms = Array.isArray(serving.rooms) ? serving.rooms.slice().sort() : []; + const tombstonedRooms = []; + for (const roomId of rooms) { + const tombstone = await this.tombstoneRoomServing(roomId, providerId, enclaveId, evidenceHash); + if (tombstone instanceof Error) return tombstone; + if (tombstone.roomserve_tombstoned) tombstonedRooms.push(roomId); + } + + await this.put(serveKey, { + ...serving, + status: 'tombstoned', + rooms: [], + updated_at: this.tx, + tombstoned_at: this.tx, + tombstone_reason_hash: evidenceHash, + }); + const provider = await this.get(`prov/${providerId}`); + if (provider) { + await this.put(`prov/${providerId}`, { + ...provider, + enclaves: this.providerEnclavesWithout(provider, enclaveId), + updated_at: this.tx, + }); + } + const enclave = await this.get(`enclave/${enclaveId}`); + if (enclave) { + await this.put(`enclave/${enclaveId}`, { + ...enclave, + providers: this.enclaveProvidersWithout(enclave, providerId), + updated_at: this.tx, + }); + } + return { + provider: providerId, + enclave_id: enclaveId, + serve_tombstoned: true, + rooms_tombstoned: tombstonedRooms, + }; + } + + async applyProviderSlash({ + providerId, + source, + reason, + evidenceHash, + epoch, + at, + slashBps, + beneficiary = null, + enclaveId = null, + probeId = null, + eventId = null, + banProvider = false, + tombstoneEnclave = false, + }) { + if (!this.isSafeKeyPart(providerId)) return new Error('Invalid provider id.'); + if (beneficiary !== null && !this.isSafeKeyPart(beneficiary)) { + return new Error('Invalid slash beneficiary.'); + } + + const providerKey = `prov/${providerId}`; + const provider = await this.get(providerKey); + if (!provider) return new Error('Provider not found.'); + + const updatedEarnings = new Map(); + const beneficiaryBalances = new Map(); + const updatedFees = new Map(); + const railSlashRecords = []; + let heldBeforeAu = ZERO_AU; + let heldAfterAu = ZERO_AU; + let forfeitedAu = ZERO_AU; + let reporterAu = ZERO_AU; + let treasuryAu = ZERO_AU; + + for (const rail of PROVIDER_ACCEPTED_RAIL_ORDER) { + const earning = await this.earningRecord(providerId, rail); + if (earning instanceof Error) return earning; + const earningError = this.guardianValidateEarningRecord(earning, providerId, rail); + if (earningError) return earningError; + const holdbacks = this.normalizeHoldbackBuckets(earning); + if (holdbacks instanceof Error) return holdbacks; + + const railForfeitedAu = this.slashAmount(earning.held_au, slashBps); + if (railForfeitedAu instanceof Error) return railForfeitedAu; + const railReporterAu = beneficiary === null ? ZERO_AU : this.safeMulDivAu(railForfeitedAu, 1, 2); + if (railReporterAu instanceof Error) return railReporterAu; + const railTreasuryAu = this.safeSubAu(railForfeitedAu, railReporterAu); + if (railTreasuryAu instanceof Error) return railTreasuryAu; + const remainingHoldbacks = this.slashHoldbackBuckets(holdbacks, railForfeitedAu); + if (remainingHoldbacks instanceof Error) return remainingHoldbacks; + const railHeldAfterAu = this.safeSubAu(earning.held_au, railForfeitedAu); + if (railHeldAfterAu instanceof Error) return railHeldAfterAu; + const railTotalAu = this.safeSubAu(earning.total_au, railForfeitedAu); + if (railTotalAu instanceof Error) return railTotalAu; + const slashedCumAu = this.safeAddAu(earning.slashed_cum_au ?? ZERO_AU, railForfeitedAu); + if (slashedCumAu instanceof Error) return slashedCumAu; + + heldBeforeAu = this.safeAddAu(heldBeforeAu, earning.held_au); + if (heldBeforeAu instanceof Error) return heldBeforeAu; + heldAfterAu = this.safeAddAu(heldAfterAu, railHeldAfterAu); + if (heldAfterAu instanceof Error) return heldAfterAu; + forfeitedAu = this.safeAddAu(forfeitedAu, railForfeitedAu); + if (forfeitedAu instanceof Error) return forfeitedAu; + reporterAu = this.safeAddAu(reporterAu, railReporterAu); + if (reporterAu instanceof Error) return reporterAu; + treasuryAu = this.safeAddAu(treasuryAu, railTreasuryAu); + if (treasuryAu instanceof Error) return treasuryAu; + + if (this.compareAu(earning.total_au, ZERO_AU) > 0 || this.compareAu(railForfeitedAu, ZERO_AU) > 0) { + const updatedEarning = { + ...earning, + rail, + total_au: railTotalAu, + held_au: railHeldAfterAu, + holdbacks: remainingHoldbacks, + slashed_cum_au: slashedCumAu, + last_slash_at: this.tx, + updated_at: this.tx, + }; + const updatedEarningError = this.guardianValidateEarningRecord(updatedEarning, providerId, rail); + if (updatedEarningError) return updatedEarningError; + updatedEarnings.set(rail, updatedEarning); + } + + if (this.compareAu(railReporterAu, ZERO_AU) > 0) { + const currentBalance = await this.balanceRecord(beneficiary, rail); + if (currentBalance instanceof Error) return currentBalance; + const balanceError = this.guardianValidateBalanceRecord(currentBalance, beneficiary, rail); + if (balanceError) return balanceError; + const nextAu = this.safeAddAu(currentBalance.au, railReporterAu); + if (nextAu instanceof Error) return nextAu; + beneficiaryBalances.set(rail, { + ...currentBalance, + rail, + au: nextAu, + updated_epoch: Math.max(currentBalance.updated_epoch, epoch), + updated_at: this.tx, + }); + } + + if (this.compareAu(railTreasuryAu, ZERO_AU) > 0) { + const fee = await this.feeCumRecord(rail); + if (fee instanceof Error) return fee; + const feeError = this.guardianValidateFeeRecord(fee, rail); + if (feeError) return feeError; + const cumAu = this.safeAddAu(fee.cum_au, railTreasuryAu); + if (cumAu instanceof Error) return cumAu; + const settledCumAu = this.safeAddAu(fee.settled_cum_au ?? fee.cum_au, railTreasuryAu); + if (settledCumAu instanceof Error) return settledCumAu; + const updatedFee = { + ...fee, + rail, + cum_au: cumAu, + settled_cum_au: settledCumAu, + updated_epoch: Math.max(fee.updated_epoch, epoch), + updated_at: this.tx, + last_slash_at: this.tx, + }; + const updatedFeeError = this.guardianValidateFeeRecord(updatedFee, rail); + if (updatedFeeError) return updatedFeeError; + updatedFees.set(rail, updatedFee); + } + + if (this.compareAu(earning.held_au, ZERO_AU) > 0 || this.compareAu(railForfeitedAu, ZERO_AU) > 0) { + railSlashRecords.push({ + rail, + held_before_au: earning.held_au, + held_after_au: railHeldAfterAu, + forfeited_au: railForfeitedAu, + beneficiary_au: railReporterAu, + treasury_au: railTreasuryAu, + }); + } + } + + const tombstone = tombstoneEnclave + ? await this.tombstoneProviderEnclave(providerId, enclaveId, evidenceHash) + : { + enclave_id: enclaveId, + serve_tombstoned: false, + rooms_tombstoned: [], + }; + if (tombstone instanceof Error) return tombstone; + const banTombstones = banProvider + ? await this.tombstoneProviderEnclaves( + providerId, + this.providerActiveEnclaves(provider).filter((activeEnclaveId) => activeEnclaveId !== enclaveId), + evidenceHash + ) + : []; + if (banTombstones instanceof Error) return banTombstones; + + const slash = { + type: 'slash', + provider: providerId, + source, + reason, + evidence_hash: evidenceHash, + epoch, + at, + tx: this.tx, + slashed_by: this.address, + beneficiary, + enclave_id: enclaveId, + probe_id: probeId, + event_id: eventId, + slash_bps: slashBps, + held_before_au: heldBeforeAu, + held_after_au: heldAfterAu, + forfeited_au: forfeitedAu, + beneficiary_au: reporterAu, + treasury_au: treasuryAu, + rails: railSlashRecords, + tombstone, + ban_tombstones: banTombstones, + provider_banned: banProvider, + }; + slash.slash_hash = await this.opaqueHash('mayhem-slash-v1', slash); + + const updatedProvider = banProvider + ? { + ...provider, + status: 'banned', + enclaves: [], + tombstoned_enclaves: [tombstone, ...banTombstones] + .filter((entry) => entry.enclave_id) + .map((entry) => entry.enclave_id), + banned_at: provider.banned_at ?? this.tx, + banned_by: provider.banned_by ?? this.address, + ban_reason_hash: provider.ban_reason_hash ?? evidenceHash, + updated_at: this.tx, + } + : { + ...provider, + enclaves: tombstone.serve_tombstoned + ? this.providerEnclavesWithout(provider, tombstone.enclave_id) + : this.providerActiveEnclaves(provider), + updated_at: this.tx, + }; + + for (const [rail, updatedEarning] of updatedEarnings) { + await this.put(this.earningKey(providerId, rail), updatedEarning); + } + for (const [rail, beneficiaryBalance] of beneficiaryBalances) { + await this.put(this.balanceKey(beneficiary, rail), beneficiaryBalance); + } + for (const [rail, updatedFee] of updatedFees) { + await this.put(this.feeCumKey(rail), updatedFee); + } + await this.put(providerKey, updatedProvider); + await this.put(`ev/slash/${providerId}/${this.tx}`, slash); + return slash; + } + + appendHoldbackBucket(holdbacks, epoch, au, lockedEpochs = null) { + if (!Number.isSafeInteger(epoch) || epoch < 0) { + return new Error('Guardian monotonic epoch invariant failed.'); + } + const amount = this.normalizeAu(au, 'holdback amount', { allowZero: false }); + if (amount instanceof Error) { + return new Error('Guardian non-negative earnings invariant failed.'); + } + if (lockedEpochs !== null && (!Number.isSafeInteger(lockedEpochs) || lockedEpochs < 0)) { + return new Error('Guardian monotonic epoch invariant failed.'); + } + const normalized = this.normalizeHoldbackBuckets({ holdbacks }); + if (normalized instanceof Error) return normalized; + const gated = normalized.filter((bucket) => bucket.probe_gate === true); + const byEpoch = new Map( + normalized + .filter((bucket) => bucket.probe_gate !== true) + .map((bucket) => [ + `${bucket.epoch}:${hasOwn(bucket, 'locked_epochs') ? bucket.locked_epochs : 'default'}`, + bucket, + ]) + ); + const key = `${epoch}:${lockedEpochs ?? 'default'}`; + const current = byEpoch.get(key); + const next = this.safeAddAu(current?.au ?? ZERO_AU, amount); + if (next instanceof Error) return next; + byEpoch.set(key, { + epoch, + au: next, + ...(lockedEpochs !== null ? { locked_epochs: lockedEpochs } : {}), + }); + return [ + ...Array.from(byEpoch.values()) + .map((bucket) => ({ + epoch: bucket.epoch, + au: bucket.au, + ...(hasOwn(bucket, 'locked_epochs') ? { locked_epochs: bucket.locked_epochs } : {}), + })), + ...gated, + ] + .sort((a, b) => ( + a.epoch - b.epoch || + (a.locked_epochs ?? -1) - (b.locked_epochs ?? -1) || + Number(a.probe_gate === true) - Number(b.probe_gate === true) + )); + } + + normalizeEpochRoots(value) { + if (!value || typeof value !== 'object' || Array.isArray(value)) { + return new Error('Epoch roots must be an object.'); + } + const keys = Object.keys(value).sort(); + if ( + keys.length !== EPOCH_ROOT_KEYS.length || + keys.some((key, idx) => key !== EPOCH_ROOT_KEYS.slice().sort()[idx]) + ) { + return new Error('Epoch roots must include dep, use, earn, fee, and price.'); + } + const roots = {}; + for (const key of EPOCH_ROOT_KEYS) { + const root = value[key]; + if (typeof root !== 'string' || !/^[0-9a-fA-F]{64}$/.test(root)) { + return new Error(`Invalid epoch ${key} root.`); + } + roots[key] = root.toLowerCase(); + } + return roots; + } + + normalizeEpochTotals(value) { + if (!value || typeof value !== 'object' || Array.isArray(value)) { + return new Error('Epoch totals must be an object.'); + } + const expected = EPOCH_TOTAL_KEYS.slice().sort(); + const keys = Object.keys(value).sort(); + if (keys.length !== expected.length || keys.some((key, idx) => key !== expected[idx])) { + return new Error('Epoch totals have an invalid shape.'); + } + const totals = {}; + for (const key of EPOCH_TOTAL_KEYS) { + const total = value[key]; + if (EPOCH_TOTAL_MONEY_KEYS.has(key)) { + const au = this.normalizeAu(total, `epoch total ${key}`); + if (au instanceof Error) { + return new Error(`Invalid epoch total ${key}.`); + } + totals[key] = au; + continue; + } + if (!Number.isSafeInteger(total) || total < 0) { + return new Error(`Invalid epoch total ${key}.`); + } + totals[key] = total; + } + return totals; + } + + canonicalUsageUnit(unit) { + switch (unit) { + case 'in': + case 'in_tokens': + case 'input': + case 'input_tokens': + case 'prompt_tokens': + case 'input_token': + return 'input_token'; + case 'cached_input': + case 'cached_inputs': + case 'cached_input_tokens': + case 'cached_prompt_tokens': + case 'cached_tokens': + case 'cached_input_token': + return 'cached_input_token'; + case 'out': + case 'out_tokens': + case 'output': + case 'output_tokens': + case 'completion_tokens': + case 'output_token': + return 'output_token'; + case 'images': + case 'image': + return 'image'; + case 'steps': + case 'step': + return 'step'; + default: + return unit; + } + } + + normalizeReceiptUsage(usageSource) { + if (!usageSource || typeof usageSource !== 'object' || Array.isArray(usageSource)) { + return new Error('Fraud proof receipt usage must be an object.'); + } + const usage = {}; + for (const [rawUnit, count] of Object.entries(usageSource)) { + if (typeof rawUnit !== 'string' || rawUnit.length === 0 || rawUnit.length > 64) { + return new Error('Invalid receipt usage unit.'); + } + if (!Number.isSafeInteger(count) || count < 0) { + return new Error('Invalid receipt usage count.'); + } + if (count === 0) continue; + const unit = this.canonicalUsageUnit(rawUnit); + if (!this.isSafeKeyPart(unit)) return new Error('Invalid receipt usage unit.'); + const next = this.safeAddCount(usage[unit] ?? 0, count, 'receipt usage count'); + if (next instanceof Error) return next; + usage[unit] = next; + } + return Object.fromEntries(Object.entries(usage).sort(([left], [right]) => compareCodepoint(left, right))); + } + + normalizeWorkflowBinding(source, label, rateMap = null) { + const shapeError = this.validateExactObjectKeys( + source, + ['endpoint_family', 'graph_hash', 'runtime_id', 'outcome_class', 'quoted_usage'], + label + ); + if (shapeError) return shapeError; + if (!this.isSafeKeyPart(source.endpoint_family)) { + return new Error(`${label} endpoint_family is invalid.`); + } + if (!this.isHexBytes(source.graph_hash, 32)) { + return new Error(`${label} graph_hash is invalid.`); + } + if (!this.isSafeKeyPart(source.runtime_id)) { + return new Error(`${label} runtime_id is invalid.`); + } + if (!this.isSafeKeyPart(source.outcome_class)) { + return new Error(`${label} outcome_class is invalid.`); + } + const quotedUsage = this.normalizeReceiptUsage(source.quoted_usage); + if (quotedUsage instanceof Error || Object.keys(quotedUsage).length === 0) { + return new Error(`${label} quoted_usage is invalid.`); + } + if (rateMap !== null) { + const quotedAu = this.usageAuForRateMap(rateMap, quotedUsage); + if (quotedAu instanceof Error) return new Error(`${label} quoted_usage is not priced by the locked rate_map.`); + } + return { + endpoint_family: source.endpoint_family, + graph_hash: source.graph_hash.toLowerCase(), + runtime_id: source.runtime_id, + outcome_class: source.outcome_class, + quoted_usage: quotedUsage, + }; + } + + normalizeWorkflowOutputBinding(source, label) { + const shapeError = this.validateExactObjectKeys( + source, + ['output_modalities', 'metrics'], + label + ); + if (shapeError) return shapeError; + const modalityError = this.validateModalitySet(source.output_modalities, `${label} output_modalities`); + if (modalityError) return modalityError; + if (!source.metrics || typeof source.metrics !== 'object' || Array.isArray(source.metrics)) { + return new Error(`${label} metrics must be an object.`); + } + const metricEntries = Object.entries(source.metrics); + if (metricEntries.length === 0 || metricEntries.length > 32) { + return new Error(`${label} metrics must contain between 1 and 32 entries.`); + } + const metrics = {}; + for (const [key, count] of metricEntries) { + if (!this.isSafeKeyPart(key)) return new Error(`${label} metric key is invalid.`); + if (!Number.isSafeInteger(count) || count <= 0) { + return new Error(`${label} metric count is invalid.`); + } + metrics[key] = count; + } + return { + output_modalities: source.output_modalities.slice(), + metrics: Object.fromEntries( + Object.entries(metrics).sort(([left], [right]) => compareCodepoint(left, right)) + ), + }; + } + + normalizeReceiptUsageAttribution(source) { + if (source === undefined || source === null) return {}; + if (!source || typeof source !== 'object' || Array.isArray(source)) { + return new Error('Receipt usage attribution must be an object.'); + } + const allowed = new Set([ + 'context_input_tokens', + 'reasoning_output_tokens', + 'vision_input_tokens', + 'audio_input_tokens', + ]); + const normalized = {}; + for (const [axis, count] of Object.entries(source)) { + if (!allowed.has(axis)) return new Error(`Unsupported receipt usage attribution ${axis}.`); + if (!Number.isSafeInteger(count) || count < 0) { + return new Error('Invalid receipt usage attribution count.'); + } + if (count > 0) normalized[axis] = count; + } + return Object.fromEntries( + Object.entries(normalized).sort(([left], [right]) => compareCodepoint(left, right)) + ); + } + + async normalizeReceiptEnvelope(value, options = {}) { + const targetSchemaVersion = options.targetSchemaVersion ?? SESSION_RECEIPT_SCHEMA_VERSION; + if (!value || typeof value !== 'object' || Array.isArray(value)) { + return new Error('Fraud proof receipt must be an object.'); + } + const receipt = value.receipt ?? value; + if (!receipt || typeof receipt !== 'object' || Array.isArray(receipt)) { + return new Error('Fraud proof receipt must be an object.'); + } + const bodySource = receipt.body ?? receipt; + if (!bodySource || typeof bodySource !== 'object' || Array.isArray(bodySource)) { + return new Error('Fraud proof receipt body must be an object.'); + } + const body = { + schema_version: bodySource.schema_version, + session_id: bodySource.session_id, + billing_id: bodySource.billing_id, + billing_attempt: bodySource.billing_attempt, + billing_prior_usage: cloneValue(bodySource.billing_prior_usage), + billing_prior_au_owed_cum: bodySource.billing_prior_au_owed_cum, + billing_epoch: bodySource.billing_epoch, + reservation_id: bodySource.reservation_id, + reservation_expires_after_epoch: bodySource.reservation_expires_after_epoch, + reservation_receipt_grace_epochs: bodySource.reservation_receipt_grace_epochs, + payout_revision: bodySource.payout_revision, + seq: bodySource.seq, + final: bodySource.final, + rail: bodySource.rail, + user: bodySource.user, + provider: bodySource.provider, + enclave_id: bodySource.enclave_id, + model_id: bodySource.model_id, + price_ver: bodySource.price_ver, + locked_rate_map: cloneValue(bodySource.locked_rate_map), + rules_ver: bodySource.rules_ver, + usage: cloneValue(bodySource.usage), + au_owed_cum: bodySource.au_owed_cum, + prompt_hash: bodySource.prompt_hash, + ts: bodySource.ts, + }; + if (hasOwn(bodySource, 'usage_attribution')) { + body.usage_attribution = cloneValue(bodySource.usage_attribution); + } + if (hasOwn(bodySource, 'locked_per_req_au')) body.locked_per_req_au = bodySource.locked_per_req_au; + if (hasOwn(bodySource, 'locked_min_session_au')) body.locked_min_session_au = bodySource.locked_min_session_au; + if (hasOwn(bodySource, 'served_ctx')) body.served_ctx = bodySource.served_ctx; + if (hasOwn(bodySource, 'ctx_bracket')) body.ctx_bracket = bodySource.ctx_bracket; + if (hasOwn(bodySource, 'ctx_bracket_table_ver')) { + body.ctx_bracket_table_ver = bodySource.ctx_bracket_table_ver; + } + if (hasOwn(bodySource, 'workflow')) { + body.workflow = cloneValue(bodySource.workflow); + } + if (hasOwn(bodySource, 'workflow_output')) { + body.workflow_output = cloneValue(bodySource.workflow_output); + } + + if (body.schema_version !== targetSchemaVersion) { + return new Error('Unsupported receipt schema version.'); + } + + const bodyError = await this.validateReceiptBody(body, targetSchemaVersion); + if (bodyError) return bodyError; + + const envelope = { + body, + enclave_sig: receipt.enclave_sig ?? value.enclave_sig, + user_sig: receipt.user_sig ?? value.user_sig, + enclave_pubkey: receipt.enclave_pubkey ?? value.enclave_pubkey ?? bodySource.enclave_pubkey ?? null, + }; + if (!this.isHexBytes(envelope.enclave_sig, 64)) return new Error('Invalid enclave receipt signature.'); + if (!this.isHexBytes(envelope.user_sig, 64)) return new Error('Invalid user receipt signature.'); + if (!this.isHexBytes(envelope.enclave_pubkey, 32)) { + return new Error('Invalid enclave receipt public key.'); + } + envelope.enclave_pubkey = envelope.enclave_pubkey.toLowerCase(); + return envelope; + } + + async validateReceiptBody(body, expectedSchemaVersion = SESSION_RECEIPT_SCHEMA_VERSION) { + if (body.schema_version !== expectedSchemaVersion) { + return new Error('Unsupported receipt schema version.'); + } + for (const field of ['session_id', 'user', 'provider', 'enclave_id', 'model_id', 'prompt_hash']) { + if (typeof body[field] !== 'string' || body[field].length === 0 || body[field].length > 256) { + return new Error(`Invalid receipt ${field}.`); + } + } + if (!this.isHexBytes(body.billing_id, 32)) return new Error('Invalid receipt billing id.'); + if (!Number.isSafeInteger(body.billing_attempt) || body.billing_attempt < 0) { + return new Error('Invalid receipt billing attempt.'); + } + if (!Number.isSafeInteger(body.billing_epoch) || body.billing_epoch < 1) { + return new Error('Invalid receipt billing epoch.'); + } + if (!this.isHexBytes(body.reservation_id, 32)) { + return new Error('Invalid receipt reservation id.'); + } + if (!Number.isSafeInteger(body.reservation_expires_after_epoch) || + body.reservation_expires_after_epoch <= body.billing_epoch || + !Number.isSafeInteger(body.reservation_receipt_grace_epochs) || + body.reservation_receipt_grace_epochs < 0) { + return new Error('Invalid receipt reservation expiry policy.'); + } + if (!this.isHexBytes(body.payout_revision, 32)) { + return new Error('Invalid receipt payout revision.'); + } + const billingPriorUsage = this.normalizeReceiptUsage(body.billing_prior_usage); + if (billingPriorUsage instanceof Error) return billingPriorUsage; + if (stableJson(billingPriorUsage) !== stableJson(body.billing_prior_usage)) { + return new Error('Receipt billing prior usage must be canonical.'); + } + const billingPriorAuOwedCum = this.normalizeAu( + body.billing_prior_au_owed_cum, + 'receipt billing prior cumulative amount' + ); + if (billingPriorAuOwedCum instanceof Error) { + return new Error('Invalid receipt billing prior cumulative amount.'); + } + if ( + body.billing_attempt === 0 && + (Object.keys(billingPriorUsage).length > 0 || !this.isZeroAu(billingPriorAuOwedCum)) + ) { + return new Error('Initial receipt billing attempt must have an empty baseline.'); + } + if (this.isZeroAu(billingPriorAuOwedCum) && Object.keys(billingPriorUsage).length > 0) { + return new Error('Receipt billing prior usage requires a prior cumulative amount.'); + } + if (!this.isHexBytes(body.user, 32)) return new Error('Invalid receipt user public key.'); + if (!this.isHexBytes(body.provider, 32)) return new Error('Invalid receipt provider public key.'); + const rail = this.normalizeLedgerRail(body.rail, 'receipt rail'); + if (rail instanceof Error) return rail; + if (body.rail !== rail) return new Error('Receipt rail must be canonical.'); + if (!Number.isSafeInteger(body.seq) || body.seq < 0) return new Error('Invalid receipt sequence.'); + if (typeof body.final !== 'boolean') return new Error('Invalid receipt final flag.'); + if (!Number.isSafeInteger(body.price_ver) || body.price_ver < 1) { + return new Error('Invalid receipt price version.'); + } + const lockedRateMap = this.normalizeLockedRateMap(body.locked_rate_map, 'receipt locked_rate_map'); + if (lockedRateMap instanceof Error) return lockedRateMap; + if (stableJson(lockedRateMap) !== stableJson(body.locked_rate_map)) { + return new Error('Receipt locked_rate_map must be canonical.'); + } + const lockedPerReqAu = this.normalizeAu(body.locked_per_req_au, 'receipt locked per-request price'); + if (lockedPerReqAu instanceof Error) { + return new Error('Invalid receipt locked per-request price.'); + } + const lockedMinSessionAu = this.normalizeAu(body.locked_min_session_au, 'receipt locked minimum session price'); + if (lockedMinSessionAu instanceof Error) { + return new Error('Invalid receipt locked minimum session price.'); + } + if (!Number.isSafeInteger(body.served_ctx) || body.served_ctx < 0) { + return new Error('Invalid receipt served context.'); + } + const table = body.ctx_bracket_table_ver === null || body.ctx_bracket_table_ver === undefined + ? null + : await this.ctxBracketTableByVersion(body.ctx_bracket_table_ver); + if (table instanceof Error) return table; + const ctxMeta = await this.normalizeCtxBracketEvidenceForEnclave( + body.enclave_id, + body.served_ctx, + body.ctx_bracket, + body.ctx_bracket_table_ver, + table, + 'receipt' + ); + if (ctxMeta instanceof Error) return ctxMeta; + if (!Number.isSafeInteger(body.rules_ver) || body.rules_ver < 1) { + return new Error('Invalid receipt rules version.'); + } + let workflow = null; + if (hasOwn(body, 'workflow')) { + workflow = this.normalizeWorkflowBinding(body.workflow, 'receipt workflow', lockedRateMap); + if (workflow instanceof Error) return workflow; + if (stableJson(workflow) !== stableJson(body.workflow)) { + return new Error('Receipt workflow must be canonical.'); + } + } + if (hasOwn(body, 'workflow_output')) { + if (!workflow) return new Error('Receipt workflow_output requires workflow.'); + const workflowOutput = this.normalizeWorkflowOutputBinding( + body.workflow_output, + 'receipt workflow_output' + ); + if (workflowOutput instanceof Error) return workflowOutput; + if (stableJson(workflowOutput) !== stableJson(body.workflow_output)) { + return new Error('Receipt workflow_output must be canonical.'); + } + } else if (workflow) { + return new Error('Receipt workflow requires workflow_output.'); + } + const usage = this.normalizeReceiptUsage(body.usage); + if (usage instanceof Error) return usage; + if (stableJson(usage) !== stableJson(body.usage)) { + return new Error('Receipt usage must be canonical.'); + } + const usageAttribution = this.normalizeReceiptUsageAttribution(body.usage_attribution); + if (usageAttribution instanceof Error) return usageAttribution; + if (stableJson(usageAttribution) !== stableJson(body.usage_attribution ?? {})) { + return new Error('Receipt usage attribution must be canonical.'); + } + // Rendered context telemetry is not a billable usage axis. It may exceed + // canonical input units, but cannot exceed the signed served context. + if ((usageAttribution.context_input_tokens ?? 0) > body.served_ctx) { + return new Error('Receipt context attribution exceeds served context.'); + } + if ((usageAttribution.reasoning_output_tokens ?? 0) > (usage.output_token ?? 0)) { + return new Error('Receipt reasoning attribution exceeds billed output tokens.'); + } + if ((usageAttribution.vision_input_tokens ?? 0) > (usage.input_token ?? 0)) { + return new Error('Receipt vision attribution exceeds billed input tokens.'); + } + if ((usageAttribution.audio_input_tokens ?? 0) > (usage.input_token ?? 0)) { + return new Error('Receipt audio attribution exceeds billed input tokens.'); + } + const auOwedCum = this.normalizeAu(body.au_owed_cum, 'receipt cumulative amount'); + if (auOwedCum instanceof Error) { + return new Error('Invalid receipt cumulative amount.'); + } + const lockedAu = this.logicalCumulativeAuForLockedTerms( + body.locked_rate_map, + lockedPerReqAu, + lockedMinSessionAu, + billingPriorUsage, + billingPriorAuOwedCum, + usage + ); + if (lockedAu instanceof Error) return lockedAu; + if (this.compareAu(auOwedCum, lockedAu) !== 0) { + return new Error('Receipt cumulative amount does not match locked price terms.'); + } + if (!Number.isSafeInteger(body.ts) || body.ts < 0) return new Error('Invalid receipt timestamp.'); + return null; + } + + verifyReceiptEnvelope(envelope) { + const signedBody = envelope.body; + if (!signedBody || typeof signedBody !== 'object' || Array.isArray(signedBody)) return false; + const enclaveKey = envelope.enclave_pubkey ?? ( + this.isHexBytes(signedBody.enclave_id, 32) ? signedBody.enclave_id : null + ); + if (!enclaveKey) return false; + const message = receiptMessage(signedBody); + return ( + verifyEd25519Hex(envelope.enclave_sig, message, enclaveKey) && + verifyEd25519Hex(envelope.user_sig, message, signedBody.user) + ); + } + + receiptLeafEnvelope(envelope) { + return { + body: cloneValue(envelope.body), + enclave_sig: envelope.enclave_sig, + user_sig: envelope.user_sig, + }; + } + + async usageLeafHash(envelope) { + return await this.opaqueHash('mayhem-usage-leaf-v1', this.receiptLeafEnvelope(envelope)); + } + + async fraudProofHash(value) { + return await this.opaqueHash('mayhem-fraud-proof-v1', value); + } + + async validateOverCreditFraudProof(commit, receipt) { + if (commit.status === 'void') return new Error('Epoch commit is already void.'); + if (this.value.proof_epoch > commit.provisional_until_epoch) { + return new Error('Epoch commit challenge window has closed.'); + } + if (commit.totals.use_count !== 1) { + return new Error('Over-credit proof requires a single committed receipt.'); + } + + const previousAu = this.normalizeAu(this.value.previous_au_owed_cum ?? ZERO_AU, 'previous receipt amount'); + if (previousAu instanceof Error) { + return new Error('Invalid previous receipt amount.'); + } + const claimedCum = this.normalizeAu(this.value.claimed_au_owed_cum, 'claimed receipt amount'); + if (claimedCum instanceof Error) return new Error('Invalid claimed receipt amount.'); + if (this.compareAu(previousAu, receipt.body.au_owed_cum) > 0 || this.compareAu(previousAu, claimedCum) > 0) { + return new Error('Previous receipt amount exceeds cumulative amount.'); + } + const actualAu = this.safeSubAu(receipt.body.au_owed_cum, previousAu); + if (actualAu instanceof Error) return actualAu; + const claimedAu = this.safeSubAu(claimedCum, previousAu); + if (claimedAu instanceof Error) return claimedAu; + if (this.compareAu(claimedAu, actualAu) <= 0) return new Error('Receipt does not contradict committed usage.'); + if (this.compareAu(commit.totals.use_au, claimedAu) !== 0) { + return new Error('Fraud proof claimed amount does not match committed usage total.'); + } + + const claimedReceipt = { + ...receipt, + body: { + ...receipt.body, + au_owed_cum: claimedCum, + }, + }; + const claimedUseRoot = await this.usageLeafHash(claimedReceipt); + if (commit.roots.use !== claimedUseRoot) { + return new Error('Fraud proof does not match committed usage root.'); + } + + return { + actual_au: actualAu, + claimed_au: claimedAu, + receipt_hash: await this.usageLeafHash(receipt), + }; + } + + priceTermsSnapshot(record) { + return { + ver: record.ver, + ...(record.ctx_bracket ? { ctx_bracket: record.ctx_bracket } : {}), + ...(record.ctx_bracket_table_ver ? { ctx_bracket_table_ver: record.ctx_bracket_table_ver } : {}), + rate_map: cloneValue(record.rate_map), + per_req_au: record.per_req_au, + min_session_au: record.min_session_au, + }; + } + + priceDerivationLeafValue(derivation) { + const { + derivation_hash: _derivationHash, + price_root: _priceRoot, + updated_at: _updatedAt, + ...leaf + } = derivation; + return leaf; + } + + priceDerivationFromMarketUpdate(update, { epoch, at, epochSeconds = null, usageRoot = null } = {}) { + const record = update.record; + const market = record.market; + if (!record || !market || typeof market !== 'object') { + return new Error('Market price update is missing derivation data.'); + } + return { + type: 'price_derivation', + schema_version: 2, + epoch, + at, + epoch_seconds: epochSeconds, + enclave_id: record.enclave_id, + ...(record.ctx_bracket ? { + ctx_bracket: record.ctx_bracket, + ctx_bracket_table_ver: record.ctx_bracket_table_ver, + } : {}), + model_id: record.model_id, + denom: record.denom, + price_ver: record.ver, + price_source: record.price_source, + usage: { + usage_root: usageRoot, + settled_usage: cloneValue(market.settled_usage), + calibrated_work_ps: market.calibrated_work_ps, + active_demand_au: market.active_demand_au, + session_count: market.session_count, + ...(record.ctx_bracket ? { + ctx_bracket: record.ctx_bracket, + ctx_bracket_table_ver: record.ctx_bracket_table_ver, + } : {}), + }, + controller: { + source: market.source, + active_supply: market.active_supply, + activity_basis: market.activity_basis, + activity_vector: cloneValue(market.activity_vector), + previous_activity_vector: cloneValue(market.previous_activity_vector), + previous_activity_rate: market.previous_activity_rate, + previous_ema_activity_vector: cloneValue(market.previous_ema_activity_vector), + ema_activity_vector: cloneValue(market.ema_activity_vector), + momentum_bps: market.momentum_bps, + activity_rate: market.activity_rate, + previous_ema_activity_rate: market.previous_ema_activity_rate, + ema_activity_rate: market.ema_activity_rate, + activity_initialized: market.activity_initialized, + calibration_hash: market.calibration_hash, + calibration: cloneValue(market.calibration), + modelref_ver: market.modelref_ver, + multiplier_bps: market.multiplier_bps, + frozen: market.frozen, + frozen_reason: market.frozen_reason, + constants: cloneValue(market.constants), + }, + seed_price: this.priceTermsSnapshot(record.seed), + previous_price: { + ver: market.previous_price_ver, + rate_map: cloneValue(market.previous_rate_map), + per_req_au: market.previous_per_req_au, + min_session_au: market.previous_min_session_au, + }, + desired_price: { + rate_map: cloneValue(market.desired_rate_map), + per_req_au: market.desired_per_req_au, + min_session_au: market.desired_min_session_au, + }, + result_price: this.priceTermsSnapshot(record), + }; + } + + async priceDerivationsFromMarketUpdates(updates, context = {}) { + const derivations = []; + const sorted = updates.slice().sort((left, right) => ( + compareCodepoint(left.enclave_id, right.enclave_id) || + compareCodepoint(left.ctx_bracket ?? '', right.ctx_bracket ?? '') + )); + for (const update of sorted) { + const derivation = this.priceDerivationFromMarketUpdate(update, context); + if (derivation instanceof Error) return derivation; + const derivationHash = await this.priceDerivationLeafHash(derivation); + update.derivation_hash = derivationHash; + derivations.push({ + ...derivation, + derivation_hash: derivationHash, + }); + } + const priceRoot = await this.priceDerivationRoot(derivations); + for (const derivation of derivations) { + derivation.price_root = priceRoot; + } + return derivations; + } + + async priceDerivationLeafHash(derivation) { + return await this.opaqueHash('mayhem-price-derivation-leaf-v1', this.priceDerivationLeafValue(derivation)); + } + + async merkleRoot(kind, leaves) { + if (leaves.length === 0) return await this.opaqueHash(`mayhem-${kind}-empty-root-v1`, {}); + let level = leaves.slice().sort(); + while (level.length > 1) { + const next = []; + for (let idx = 0; idx < level.length; idx += 2) { + const left = level[idx]; + const right = idx + 1 < level.length ? level[idx + 1] : left; + next.push(await this.opaqueHash(`mayhem-${kind}-node-v1`, { left, right })); + } + level = next; + } + return level[0]; + } + + async priceDerivationRoot(derivations) { + const leaves = []; + for (const derivation of derivations) { + leaves.push(await this.priceDerivationLeafHash(derivation)); + } + return await this.merkleRoot('price', leaves); + } + + normalizePriceProofUsage(value) { + const usageMap = this.aggregateMarketUsageEntries([value]); + if (usageMap instanceof Error) return usageMap; + const entries = this.mapMarketUsageEntriesForHash(usageMap); + if (entries.length !== 1) return new Error('Price derivation proof requires one market usage entry.'); + return entries[0]; + } + + async prepareCommittedActivityEvidence({ epoch, at, epochSeconds, roots, totals }) { + if (totals.price_count === 0) return null; + if (totals.price_count !== 1) { + return new Error('Nonempty activity price commitments require one market; use bounded receipt pages for larger settlements.'); + } + const index = this.normalizeReceiptEpochIndexMetadata( + await this.get(this.receiptEpochIndexKey(epoch)), epoch); + if (index instanceof Error) return index; + if (index.count > 128 || index.count !== totals.use_count) { + return new Error('Activity price commitment requires at most 128 canonical receipts; use bounded receipt pages.'); + } + const freeze = await this.validateFrozenEpoch(epoch, at, index); + if (freeze) return freeze; + const markets = new Map(); const leaves = []; const seen = new Set(); + for (let page = 0; page < index.page_count; page++) { + const record = await this.get(this.receiptEpochPageKey(epoch, page)); + if (record?.type !== 'canonical_receipt_epoch_page' || record.epoch !== epoch || + record.page !== page || !Array.isArray(record.identities)) { + return new Error('Activity commitment receipt page is invalid.'); + } + for (const identity of record.identities) { + const identityKey = `${identity.billing_id}/${identity.billing_attempt}`; + if (seen.has(identityKey)) return new Error('Activity commitment duplicates a canonical receipt.'); + seen.add(identityKey); + const head = await this.get(this.receiptHeadKey(identity.billing_id, identity.billing_attempt)); + if (head?.type !== 'canonical_receipt_head' || head.epoch !== epoch || + head.settlement_epoch !== epoch || head.settlement_ready !== true || + head.billing_id !== identity.billing_id || head.billing_attempt !== identity.billing_attempt) { + return new Error('Activity commitment requires canonical final receipt heads.'); + } + const body = head.receipt.body; + const marketKey = this.priceMarketKey(body.enclave_id, body.ctx_bracket ?? null); + const row = markets.get(marketKey) ?? { + enclave_id: body.enclave_id, + ...(body.ctx_bracket ? { ctx_bracket: body.ctx_bracket, + ctx_bracket_table_ver: body.ctx_bracket_table_ver } : {}), + demand_au: '0', session_count: 0, providers: new Set(), settled_usage: {}, + }; + const increment = this.incrementalSettledUsage(body); + if (increment instanceof Error) return increment; + row.settled_usage = this.addSettledUsage(row.settled_usage, increment); + row.demand_au = this.safeAddAu(row.demand_au, head.incremental_au); + if (row.settled_usage instanceof Error || row.demand_au instanceof Error) { + return new Error('Activity commitment work overflow.'); + } + row.session_count++; row.providers.add(head.provider); + markets.set(marketKey, row); + leaves.push(await this.usageLeafHash(head.receipt)); + } + } + if (seen.size !== index.count || markets.size !== 1 || leaves.some((leaf) => leaf instanceof Error)) { + return new Error('Activity price commitment must cover exactly one complete canonical receipt market.'); + } + if (await this.merkleRoot('use', leaves) !== roots.use) { + return new Error('Activity price commitment usage root differs from canonical signed receipts.'); + } + const canonical = new Map(); const usage = new Map(); + for (const [key, row] of markets) { + const { providers, settled_usage, ...publicRow } = row; + publicRow.provider_count = providers.size; + if (publicRow.demand_au !== totals.use_au) return new Error('Activity commitment gross total mismatch.'); + usage.set(key, publicRow); + canonical.set(key, { ...publicRow, settled_usage }); + } + const updates = await this.computeMarketPriceUpdates(usage, { + epoch, at, epochSeconds, canonicalActivity: canonical, includeDormant: false, + }); + if (updates instanceof Error) return updates; + const derivations = await this.priceDerivationsFromMarketUpdates(updates, + { epoch, at, epochSeconds, usageRoot: roots.use }); + if (derivations instanceof Error) return derivations; + return { price_usage: this.mapMarketUsageEntriesForHash(usage)[0], + derivation: derivations[0], expected_price_root: await this.priceDerivationRoot(derivations) }; + } + + async validatePriceDerivationFraudProof(commit) { + if (commit.status === 'void') return new Error('Epoch commit is already void.'); + if (this.value.proof_epoch > commit.provisional_until_epoch) { + return new Error('Epoch commit challenge window has closed.'); + } + const evidence = commit.expected_activity_evidence; + if (commit.totals.price_count !== 1 || !evidence) { + return new Error('Price activity proof requires canonical work evidence pinned by its commitment.'); + } + const priceUsage = this.normalizePriceProofUsage(this.value.price_usage); + if (priceUsage instanceof Error) return priceUsage; + if (stableJson(priceUsage) !== stableJson(evidence.price_usage)) { + return new Error('Price activity proof usage differs from canonical commitment evidence.'); + } + const expected = await this.priceDerivationRoot([evidence.derivation]); + if (expected !== evidence.expected_price_root) return new Error('Pinned activity evidence is inconsistent.'); + if (expected === commit.roots.price) return new Error('Price activity proof does not contradict committed price root.'); + return { price_usage: priceUsage, enclave_id: priceUsage.enclave_id, + ...(priceUsage.ctx_bracket ? { ctx_bracket: priceUsage.ctx_bracket, + ctx_bracket_table_ver: priceUsage.ctx_bracket_table_ver } : {}), + expected_price_root: expected, committed_price_root: commit.roots.price, + price_derivation_hash: evidence.derivation.derivation_hash, + price_derivation: cloneValue(evidence.derivation) }; + } + + async validateEpochApplyTotals({ + epoch, + roots, + totals, + debitTotal, + feeDeltaAu, + nextFeeCum, + burnDeltaAu, + nextBurnCum, + providerCount, + earnCumTotal, + epochSeconds, + priceDerivations, + }) { + const commit = await this.get(`epoch/commit/${epoch}`); + if (!commit) return new Error('Epoch commit required before applying evidence roots.'); + if (commit.status === 'void') return new Error('Epoch commit is void.'); + if (commit.epoch_seconds !== epochSeconds) { + return new Error('Epoch apply epoch_seconds does not match committed epoch timing.'); + } + if ( + stableJson(commit.roots) !== stableJson(roots) || + stableJson(commit.totals) !== stableJson(totals) + ) { + return new Error('Epoch apply roots do not match committed roots.'); + } + if (this.compareAu(totals.use_au, debitTotal) !== 0) return new Error('Epoch usage total does not match debits.'); + if (this.compareAu(totals.earn_au, earnCumTotal) !== 0) { + return new Error('Epoch earn total does not match cumulative provider earnings.'); + } + if (this.compareAu(totals.fee_au, feeDeltaAu) !== 0) return new Error('Epoch fee total does not match computed fee.'); + if (this.compareAu(totals.fee_cum_au, nextFeeCum) !== 0) { + return new Error('Epoch cumulative fee total does not match fee state.'); + } + if (this.compareAu(totals.burn_au, burnDeltaAu) !== 0) { + return new Error('Epoch burn total does not match computed TAP burn.'); + } + if (this.compareAu(totals.burn_cum_au, nextBurnCum) !== 0) { + return new Error('Epoch cumulative burn total does not match burn state.'); + } + if (totals.provider_count !== providerCount) { + return new Error('Epoch provider count does not match earnings.'); + } + if (totals.price_count !== priceDerivations.length) { + return new Error('Epoch price derivation count does not match market updates.'); + } + const priceRoot = await this.priceDerivationRoot(priceDerivations); + if (roots.price !== priceRoot) { + return new Error('Epoch price root does not match recomputed price derivations.'); + } + + const depositRoot = await this.get(`ev/dep/${epoch}`); + if (depositRoot) { + if (depositRoot.type !== 'deposit_root') return new Error('Invalid deposit evidence root.'); + if ( + depositRoot.merkle_root !== roots.dep || + depositRoot.count !== totals.dep_count || + this.compareAu(depositRoot.au_total, totals.dep_au) !== 0 + ) { + return new Error('Committed deposit root does not match deposit evidence.'); + } + } + for (const key of ['use', 'earn', 'fee', 'price']) { + if ((await this.get(`ev/${key}/${epoch}`)) !== null) { + return new Error(`Epoch ${key} evidence root already exists.`); + } + } + return null; + } + + async validatePagedEpochCommitEvidence({ + commit, + feeCumAu, + burnCumAu, + priceDerivations, + }) { + if (commit.totals.price_count !== 0) { + return new Error('Paged receipt settlement cannot finalize market price derivations.'); + } + const emptyPriceRoot = await this.priceDerivationRoot([]); + if (commit.roots.price !== emptyPriceRoot) { + return new Error('Paged receipt settlement requires the canonical empty price root.'); + } + if (this.compareAu(commit.totals.fee_cum_au, feeCumAu) !== 0) { + return new Error('Epoch cumulative fee total does not match paged settlement state.'); + } + if (this.compareAu(commit.totals.burn_cum_au, burnCumAu) !== 0) { + return new Error('Epoch cumulative burn total does not match paged settlement state.'); + } + const depositRoot = await this.get(`ev/dep/${commit.epoch}`); + if (depositRoot && ( + depositRoot.type !== 'deposit_root' || + depositRoot.merkle_root !== commit.roots.dep || + depositRoot.count !== commit.totals.dep_count || + this.compareAu(depositRoot.au_total, commit.totals.dep_au) !== 0 + )) { + return new Error('Committed deposit root does not match deposit evidence.'); + } + for (const key of ['use', 'earn', 'fee', 'price']) { + if ((await this.get(`ev/${key}/${commit.epoch}`)) !== null) { + return new Error(`Epoch ${key} evidence root already exists.`); + } + } + if ((await this.get(`market/price/${commit.epoch}`)) !== null) { + return new Error('Bounded market price evidence already exists.'); + } + if (!Array.isArray(priceDerivations)) { + return new Error('Bounded market price derivations are invalid.'); + } + return null; + } + + async writeBoundedMarketPriceEvidence({ + epoch, + at, + epochSeconds, + usageRoot, + derivations, + }) { + const root = await this.priceDerivationRoot(derivations); + await this.put(`market/price/${epoch}`, { + type: 'bounded_market_price_root', + epoch, + epoch_seconds: epochSeconds, + usage_root: usageRoot, + price_root: root, + price_count: derivations.length, + ts: at, + updated_at: this.tx, + }); + for (const derivation of derivations) { + const suffix = derivation.ctx_bracket + ? `${derivation.enclave_id}/${derivation.ctx_bracket}` + : derivation.enclave_id; + await this.put(`market/price/${epoch}/${suffix}`, { + ...derivation, + price_root: root, + updated_at: this.tx, + }); + } + } + + async writePriceDerivationEvidence({ epoch, at, epoch_seconds, root, count, derivations }) { + await this.put(`ev/price/${epoch}`, { + type: 'price_root', + epoch, + epoch_seconds, + merkle_root: root, + price_count: count, + ts: at, + updated_at: this.tx, + }); + for (const derivation of derivations) { + const key = derivation.ctx_bracket + ? `ev/price/${epoch}/${derivation.enclave_id}/${derivation.ctx_bracket}` + : `ev/price/${epoch}/${derivation.enclave_id}`; + await this.put(key, { + ...derivation, + price_root: root, + updated_at: this.tx, + }); + } + } + + async writeEpochEvidenceRoots({ + epoch, + at, + epoch_seconds, + roots, + totals, + feeDeltaAu, + feeCumAu, + burnDeltaAu, + burnCumAu, + priceDerivations, + }) { + if ((await this.get(`ev/dep/${epoch}`)) === null) { + await this.put(`ev/dep/${epoch}`, { + type: 'deposit_root', + epoch, + epoch_seconds, + merkle_root: roots.dep, + count: totals.dep_count, + au_total: totals.dep_au, + ts: at, + updated_at: this.tx, + }); + } + await this.put(`ev/use/${epoch}`, { + type: 'usage_root', + epoch, + epoch_seconds, + merkle_root: roots.use, + sessions: totals.use_count, + au_total: totals.use_au, + providers: totals.provider_count, + ts: at, + updated_at: this.tx, + }); + await this.put(`ev/earn/${epoch}`, { + type: 'earn_root', + epoch, + epoch_seconds, + merkle_root: roots.earn, + provider_count: totals.provider_count, + au_cum_total: totals.earn_au, + ts: at, + updated_at: this.tx, + }); + await this.put(`ev/fee/${epoch}`, { + type: 'fee_root', + epoch, + epoch_seconds, + merkle_root: roots.fee, + au_fee_epoch: feeDeltaAu, + au_fee_cum: feeCumAu, + au_burn_epoch: burnDeltaAu, + au_burn_cum: burnCumAu, + tap_burn_bps: TAP_BURN_BPS, + sweep_msb_tx_hash: null, + ts: at, + updated_at: this.tx, + }); + await this.writePriceDerivationEvidence({ + epoch, + at, + epoch_seconds, + root: roots.price, + count: totals.price_count, + derivations: priceDerivations, + }); + } + + aggregateLedgerEntries(entries, idKey, amountKey, label) { + const out = new Map(); + for (const entry of entries) { + if (!entry || typeof entry !== 'object' || Array.isArray(entry)) { + return new Error(`Invalid ${label} entry.`); + } + const id = entry[idKey]; + const au = this.normalizeAu(entry[amountKey], `${label} amount`, { allowZero: false }); + if (au instanceof Error) return new Error(`Invalid ${label} amount.`); + if (!this.isSafeKeyPart(id)) return new Error(`Invalid ${label} ${idKey}.`); + const next = this.safeAddAu(out.get(id) ?? ZERO_AU, au); + if (next instanceof Error) return next; + out.set(id, next); + } + return out; + } + + aggregateRailLedgerEntries(entries, idKey, amountKey, label) { + const out = new Map(); + for (const entry of entries) { + if (!entry || typeof entry !== 'object' || Array.isArray(entry)) { + return new Error(`Invalid ${label} entry.`); + } + const rail = this.normalizeLedgerRail(entry.rail, `${label} rail`); + if (rail instanceof Error) return rail; + const id = entry[idKey]; + const au = this.normalizeAu(entry[amountKey], `${label} amount`, { allowZero: false }); + if (au instanceof Error) return new Error(`Invalid ${label} amount.`); + if (!this.isSafeKeyPart(id)) return new Error(`Invalid ${label} ${idKey}.`); + const key = stableJson([rail, id]); + const current = out.get(key) ?? { rail, [idKey]: id, [amountKey]: ZERO_AU }; + const next = this.safeAddAu(current[amountKey], au); + if (next instanceof Error) return next; + out.set(key, { ...current, [amountKey]: next }); + } + return out; + } + + aggregateMarketUsageEntries(entries) { + const out = new Map(); + for (const entry of entries) { + if (!entry || typeof entry !== 'object' || Array.isArray(entry)) { + return new Error('Invalid market usage entry.'); + } + const allowed = new Set([ + 'enclave_id', + 'ctx_bracket', + 'ctx_bracket_table_ver', + 'demand_au', + 'session_count', + 'provider_count', + ]); + const unknown = Object.keys(entry).filter((key) => !allowed.has(key)).sort(); + if (unknown.length > 0) { + return new Error(`market usage entry does not accept fields: ${unknown.join(', ')}.`); + } + for (const key of ['enclave_id', 'demand_au', 'session_count', 'provider_count']) { + if (!hasOwn(entry, key)) return new Error(`market usage entry is missing ${key}.`); + } + const enclaveId = entry.enclave_id; + if (!this.isSafeKeyPart(enclaveId)) return new Error('Invalid market usage enclave_id.'); + const ctxBracket = entry.ctx_bracket ?? null; + if (ctxBracket !== null && !this.isSafeKeyPart(ctxBracket)) { + return new Error('Invalid market usage context bracket.'); + } + if ( + hasOwn(entry, 'ctx_bracket_table_ver') && + (!Number.isSafeInteger(entry.ctx_bracket_table_ver) || entry.ctx_bracket_table_ver < 1) + ) { + return new Error('Invalid market usage context bracket table version.'); + } + const demandEntryAu = this.normalizeAu(entry.demand_au, 'market usage demand', { allowZero: false }); + if (demandEntryAu instanceof Error) { + return new Error('Invalid market usage demand.'); + } + if (!Number.isSafeInteger(entry.session_count) || entry.session_count <= 0) { + return new Error('Invalid market usage session_count.'); + } + if (!Number.isSafeInteger(entry.provider_count) || entry.provider_count <= 0) { + return new Error('Invalid market usage provider_count.'); + } + const key = this.priceMarketKey(enclaveId, ctxBracket); + const current = out.get(key) ?? { + enclave_id: enclaveId, + ...(ctxBracket ? { ctx_bracket: ctxBracket } : {}), + ...(entry.ctx_bracket_table_ver ? { ctx_bracket_table_ver: entry.ctx_bracket_table_ver } : {}), + demand_au: ZERO_AU, + session_count: 0, + provider_count: 0, + }; + if ((current.ctx_bracket_table_ver ?? null) !== (entry.ctx_bracket_table_ver ?? current.ctx_bracket_table_ver ?? null)) { + return new Error('Market usage context bracket table version mismatch.'); + } + const demandAu = this.safeAddAu(current.demand_au, demandEntryAu); + if (demandAu instanceof Error) return demandAu; + const sessionCount = this.safeAddCount(current.session_count, entry.session_count, 'market usage session_count'); + if (sessionCount instanceof Error) return sessionCount; + const providerCount = this.safeAddCount(current.provider_count, entry.provider_count, 'market usage provider_count'); + if (providerCount instanceof Error) return providerCount; + out.set(key, { + enclave_id: enclaveId, + ...(ctxBracket ? { ctx_bracket: ctxBracket } : {}), + ...(entry.ctx_bracket_table_ver ? { ctx_bracket_table_ver: entry.ctx_bracket_table_ver } : {}), + demand_au: demandAu, + session_count: sessionCount, + provider_count: providerCount, + }); + } + return out; + } + + sumAu(entries) { + let sum = ZERO_AU; + for (const [, au] of entries) { + const next = this.safeAddAu(sum, au); + if (next instanceof Error) return next; + sum = next; + } + return sum; + } + + sumRailAu(entries, amountKey) { + let sum = ZERO_AU; + for (const entry of entries.values()) { + const next = this.safeAddAu(sum, entry[amountKey]); + if (next instanceof Error) return next; + sum = next; + } + return sum; + } + + sumMarketDemandAu(entries) { + let sum = ZERO_AU; + for (const entry of entries.values()) { + const next = this.safeAddAu(sum, entry.demand_au); + if (next instanceof Error) return next; + sum = next; + } + return sum; + } + + usageAuForRateMap(rateMap, usage) { + const rates = this.rateMapByUnit(rateMap); + const priced = []; + for (const [unit, count] of Object.entries(usage ?? {})) { + if (!Number.isSafeInteger(count) || count < 0) return new Error('Invalid receipt usage count.'); + if (count === 0) continue; + const rate = rates.get(unit); + if (!rate) return new Error(`Receipt locked_rate_map missing usage unit ${unit}.`); + const perUnitAu = this.parseAu(rate.per_unit_au, 'locked rate per_unit_au', { allowZero: false }); + if (perUnitAu instanceof Error) { + return new Error('Invalid locked rate per_unit_au.'); + } + if (!Number.isSafeInteger(rate.granularity) || rate.granularity <= 0) { + return new Error('Invalid locked rate granularity.'); + } + priced.push({ + count: BigInt(count), + perUnitAu, + granularity: BigInt(rate.granularity), + }); + } + if (priced.length === 0) return ZERO_AU; + const sameGranularity = priced.every((entry) => entry.granularity === priced[0].granularity); + if (sameGranularity) { + const raw = priced.reduce((sum, entry) => sum + entry.count * entry.perUnitAu, 0n); + return this.canonicalAu(this.ceilDivBigInt(raw, priced[0].granularity)); + } + let total = 0n; + for (const entry of priced) { + total += this.ceilDivBigInt(entry.count * entry.perUnitAu, entry.granularity); + } + return this.canonicalAu(total); + } + + usageAuForLockedTerms(rateMap, perReqAu, minSessionAu, usage) { + const normalizedPerReqAu = this.normalizeAu(perReqAu, 'locked per-request price'); + if (normalizedPerReqAu instanceof Error) return new Error('Invalid locked per-request price.'); + const normalizedMinSessionAu = this.normalizeAu(minSessionAu, 'locked minimum session price'); + if (normalizedMinSessionAu instanceof Error) return new Error('Invalid locked minimum session price.'); + const usageAu = this.usageAuForRateMap(rateMap, usage); + if (usageAu instanceof Error) return usageAu; + const subtotal = this.safeAddAu(usageAu, normalizedPerReqAu); + if (subtotal instanceof Error) return subtotal; + return this.maxAu(subtotal, normalizedMinSessionAu); + } + + receiptUsageDelta(previous, current) { + const delta = {}; + for (const [unit, previousCount] of Object.entries(previous)) { + const currentCount = current[unit] ?? 0; + if (currentCount < previousCount) return new Error('Receipt cumulative usage regressed.'); + } + for (const [unit, currentCount] of Object.entries(current)) { + const count = currentCount - (previous[unit] ?? 0); + if (count > 0) delta[unit] = count; + } + return delta; + } + + logicalCumulativeAuForLockedTerms( + rateMap, + perReqAu, + minSessionAu, + priorUsage, + priorAuOwedCum, + currentUsage + ) { + const delta = this.receiptUsageDelta(priorUsage, currentUsage); + if (delta instanceof Error) return delta; + const increment = this.isZeroAu(priorAuOwedCum) + ? this.usageAuForLockedTerms(rateMap, perReqAu, minSessionAu, delta) + : this.usageAuForRateMap(rateMap, delta); + if (increment instanceof Error) return increment; + return this.safeAddAu(priorAuOwedCum, increment); + } + + ceilDivBigInt(value, divisor) { + if (value <= 0n) return 0n; + return (value + divisor - 1n) / divisor; + } + + railTotals(entries, amountKey) { + const out = new Map(PROVIDER_ACCEPTED_RAIL_ORDER.map((rail) => [rail, ZERO_AU])); + for (const entry of entries.values()) { + const next = this.safeAddAu(out.get(entry.rail) ?? ZERO_AU, entry[amountKey]); + if (next instanceof Error) return next; + out.set(entry.rail, next); + } + return out; + } + + assertMatchingRailTotals(left, right) { + for (const rail of PROVIDER_ACCEPTED_RAIL_ORDER) { + if (this.compareAu(left.get(rail) ?? ZERO_AU, right.get(rail) ?? ZERO_AU) !== 0) { + return new Error('Epoch debits must equal gross provider earnings per rail.'); + } + } + return null; + } + + safeAddAu(a, b) { + const left = this.parseAu(a, 'au value'); + if (left instanceof Error) return left; + const right = this.parseAu(b, 'au value'); + if (right instanceof Error) return right; + return this.canonicalAu(left + right); + } + + safeSubAu(a, b) { + const left = this.parseAu(a, 'au value'); + if (left instanceof Error) return left; + const right = this.parseAu(b, 'au value'); + if (right instanceof Error) return right; + if (right > left) return new Error('au value underflow.'); + return this.canonicalAu(left - right); + } + + safeMulDivAu(a, b, divisor) { + const amount = this.parseAu(a, 'au value'); + if (amount instanceof Error) return amount; + if ( + !Number.isSafeInteger(b) || + !Number.isSafeInteger(divisor) || + b < 0 || + divisor <= 0 + ) { + return new Error('Invalid au multiplier.'); + } + return this.canonicalAu((amount * BigInt(b)) / BigInt(divisor)); + } + + providerSettlementPageDelta({ grossAu, priorGrossAu, rail, feeBps }) { + const nextGrossAu = this.safeAddAu(priorGrossAu, grossAu); + if (nextGrossAu instanceof Error) return nextGrossAu; + const priorFeeAu = this.safeMulDivAu(priorGrossAu, feeBps, 10_000); + const nextFeeAu = this.safeMulDivAu(nextGrossAu, feeBps, 10_000); + if (priorFeeAu instanceof Error || nextFeeAu instanceof Error) { + return new Error('Provider settlement fee overflow.'); + } + const feeAu = this.safeSubAu(nextFeeAu, priorFeeAu); + if (feeAu instanceof Error) return feeAu; + const priorBurnAu = rail === 'tap' + ? this.safeMulDivAu(priorGrossAu, TAP_BURN_BPS, 10_000) + : ZERO_AU; + const nextBurnAu = rail === 'tap' + ? this.safeMulDivAu(nextGrossAu, TAP_BURN_BPS, 10_000) + : ZERO_AU; + if (priorBurnAu instanceof Error || nextBurnAu instanceof Error) { + return new Error('Provider settlement burn overflow.'); + } + const burnAu = this.safeSubAu(nextBurnAu, priorBurnAu); + if (burnAu instanceof Error) return burnAu; + const providerAu = this.safeSubAu( + this.safeSubAu(grossAu, feeAu), + burnAu + ); + if (providerAu instanceof Error) return providerAu; + return { + fee_au: feeAu, + burn_au: burnAu, + provider_au: providerAu, + }; + } + + safeAddCount(a, b, label = 'count') { + if (!Number.isSafeInteger(a) || !Number.isSafeInteger(b) || a < 0 || b < 0) { + return new Error(`Invalid ${label}.`); + } + const next = a + b; + if (!Number.isSafeInteger(next)) return new Error(`${label} overflow.`); + return next; + } + + parseAu(value, label = 'au value', { allowZero = true } = {}) { + if (typeof value === 'bigint') { + if (value < 0n || (!allowZero && value === 0n)) return new Error(`${label} must be positive.`); + return value; + } + if (typeof value !== 'string' || !/^(0|[1-9][0-9]*)$/.test(value)) { + return new Error(`${label} must be a canonical decimal string.`); + } + const parsed = BigInt(value); + if (!allowZero && parsed === 0n) return new Error(`${label} must be positive.`); + return parsed; + } + + normalizeAu(value, label = 'au value', options = {}) { + const parsed = this.parseAu(value, label, options); + if (parsed instanceof Error) return parsed; + return this.canonicalAu(parsed); + } + + canonicalAu(value) { + if (typeof value !== 'bigint' || value < 0n) return new Error('Invalid au value.'); + return value.toString(); + } + + compareAu(a, b) { + const left = this.parseAu(a, 'au value'); + if (left instanceof Error) return NaN; + const right = this.parseAu(b, 'au value'); + if (right instanceof Error) return NaN; + return left === right ? 0 : (left < right ? -1 : 1); + } + + maxAu(a, b) { + return this.compareAu(a, b) >= 0 ? this.normalizeAu(a) : this.normalizeAu(b); + } + + isZeroAu(value) { + return this.compareAu(value, ZERO_AU) === 0; + } + + sortedMapEntries(map) { + return Array.from(map.entries()).sort(([a], [b]) => compareCodepoint(a, b)); + } + + sortedRailRecords(map, idKey) { + return Array.from(map.values()).sort((a, b) => { + const railOrder = + PROVIDER_ACCEPTED_RAIL_ORDER.indexOf(a.rail) - PROVIDER_ACCEPTED_RAIL_ORDER.indexOf(b.rail); + if (railOrder !== 0) return railOrder; + return compareCodepoint(a[idKey], b[idKey]); + }); + } + + mapEntriesForHash(map, idKey, amountKey) { + return this.sortedMapEntries(map).map(([id, au]) => ({ + [idKey]: id, + [amountKey]: au, + })); + } + + mapRailEntriesForHash(map, idKey, amountKey) { + return this.sortedRailRecords(map, idKey).map((entry) => ({ + rail: entry.rail, + [idKey]: entry[idKey], + [amountKey]: entry[amountKey], + })); + } + + mapMarketUsageEntriesForHash(map) { + return Array.from(map.values()) + .sort((left, right) => ( + compareCodepoint(left.enclave_id, right.enclave_id) || + compareCodepoint(left.ctx_bracket ?? '', right.ctx_bracket ?? '') + )) + .map((entry) => ({ + enclave_id: entry.enclave_id, + ...(entry.ctx_bracket ? { ctx_bracket: entry.ctx_bracket } : {}), + ...(entry.ctx_bracket_table_ver ? { ctx_bracket_table_ver: entry.ctx_bracket_table_ver } : {}), + demand_au: entry.demand_au, + session_count: entry.session_count, + provider_count: entry.provider_count, + })); + } + + async balanceRecord(user, rail) { + const normalizedRail = this.normalizeLedgerRail(rail, 'balance rail'); + if (normalizedRail instanceof Error) return normalizedRail; + const tap = normalizedRail === 'tap' + ? await this.canonicalTapPaymentConfig({ optional: true }) + : null; + if (tap instanceof Error) return tap; + const current = await this.get(this.balanceKey(user, normalizedRail)); + const scoped = tap === null || ( + current?.chain_id === tap.chain_id && + current?.pool_address === tap.pool_address + ) ? current : null; + return scoped ?? { + user, + rail: normalizedRail, + denom: PRICE_DENOMINATION, + au: ZERO_AU, + updated_epoch: 0, + updated_at: null, + ...(tap ?? {}), + }; + } + + async earningRecord(provider, rail) { + const normalizedRail = this.normalizeLedgerRail(rail, 'earning rail'); + if (normalizedRail instanceof Error) return normalizedRail; + const tap = normalizedRail === 'tap' + ? await this.canonicalTapPaymentConfig({ optional: true }) + : null; + if (tap instanceof Error) return tap; + const current = await this.get(this.earningKey(provider, normalizedRail)); + const scoped = tap === null || ( + current?.chain_id === tap.chain_id && + current?.pool_address === tap.pool_address + ) ? current : null; + return scoped ?? { + provider, + rail: normalizedRail, + denom: PRICE_DENOMINATION, + total_au: ZERO_AU, + held_au: ZERO_AU, + paid_cum_au: ZERO_AU, + updated_epoch: 0, + updated_at: null, + ...(tap ?? {}), + }; + } + + async feeCumRecord(rail) { + const normalizedRail = this.normalizeLedgerRail(rail, 'fee rail'); + if (normalizedRail instanceof Error) return normalizedRail; + const tap = normalizedRail === 'tap' + ? await this.canonicalTapPaymentConfig({ optional: true }) + : null; + if (tap instanceof Error) return tap; + const current = await this.get(this.feeCumKey(normalizedRail)); + const scoped = tap === null || ( + current?.chain_id === tap.chain_id && + current?.pool_address === tap.pool_address + ) ? current : null; + return scoped ?? { + rail: normalizedRail, + denom: PRICE_DENOMINATION, + cum_au: ZERO_AU, + swept_cum_au: ZERO_AU, + updated_epoch: 0, + updated_at: null, + last_apply_hash: null, + last_fee_bps: null, + ...(tap ?? {}), + }; + } + + async burnCumRecord(rail) { + const normalizedRail = this.normalizeLedgerRail(rail, 'burn rail'); + if (normalizedRail instanceof Error) return normalizedRail; + const tap = normalizedRail === 'tap' + ? await this.canonicalTapPaymentConfig({ optional: true }) + : null; + if (tap instanceof Error) return tap; + const current = await this.get(this.burnCumKey(normalizedRail)); + const scoped = tap === null || ( + current?.chain_id === tap.chain_id && + current?.pool_address === tap.pool_address + ) ? current : null; + return scoped ?? { + rail: normalizedRail, + denom: PRICE_DENOMINATION, + cum_au: ZERO_AU, + updated_epoch: 0, + updated_at: null, + last_apply_hash: null, + burn_bps: normalizedRail === 'tap' ? TAP_BURN_BPS : 0, + ...(tap ?? {}), + }; + } + + async epochApplyStateRecord() { + return (await this.get('epoch/apply/state')) ?? { + updated_epoch: 0, + updated_at: null, + last_apply_hash: null, + last_settlement_unix: null, + }; + } + + epochApplyAnchorKey(epoch) { + return `epoch/apply-anchor/${epoch}`; + } + + async prepareEpochApplyAnchor(state) { + if ( + !state || + !Number.isSafeInteger(state.updated_epoch) || + state.updated_epoch < 1 || + !this.isHexBytes(state.last_apply_hash, 32) || + !Number.isSafeInteger(state.last_settlement_unix) || + state.last_settlement_unix < 0 || + (state.pending_epoch ?? null) !== null + ) { + return new Error('Cannot anchor an incomplete epoch apply.'); + } + const key = this.epochApplyAnchorKey(state.updated_epoch); + const record = { + type: 'epoch_apply_anchor', + epoch: state.updated_epoch, + apply_hash: state.last_apply_hash.toLowerCase(), + settlement_unix: state.last_settlement_unix, + applied_at: state.updated_at, + }; + const existing = await this.get(key); + if (existing !== null) { + return stableJson(existing) === stableJson(record) + ? null + : new Error('Epoch apply anchor conflict.'); + } + return { key, record, write: true }; + } + + async rememberEpochApplyAnchor(state) { + const prepared = await this.prepareEpochApplyAnchor(state); + if (prepared instanceof Error) return prepared; + await this.writePreparedAnchor(prepared); + return null; + } + + async epochApplyAnchor(epoch) { + if (!Number.isSafeInteger(epoch) || epoch < 1) { + return new Error('Invalid epoch apply anchor epoch.'); + } + const historical = await this.get(this.epochApplyAnchorKey(epoch)); + if (historical !== null) { + if ( + historical.type !== 'epoch_apply_anchor' || + historical.epoch !== epoch || + !this.isHexBytes(historical.apply_hash, 32) || + !Number.isSafeInteger(historical.settlement_unix) || + historical.settlement_unix < 0 || + typeof historical.applied_at !== 'string' || + historical.applied_at.length === 0 + ) { + return new Error('Epoch apply anchor is invalid.'); + } + return historical; + } + const current = await this.epochApplyStateRecord(); + if ( + current.updated_epoch === epoch && + this.isHexBytes(current.last_apply_hash, 32) && + (current.pending_epoch ?? null) === null + ) { + const settlementUnix = await this.priorEpochSettlementUnix(current); + if (settlementUnix instanceof Error) return settlementUnix; + return { + type: 'epoch_apply_anchor', + epoch, + apply_hash: current.last_apply_hash.toLowerCase(), + settlement_unix: settlementUnix, + applied_at: current.updated_at, + }; + } + return null; + } + + async requireEpochApplyAnchor(epoch, applyHash, label) { + const anchor = await this.epochApplyAnchor(epoch); + if (anchor instanceof Error) return anchor; + if (!anchor || anchor.apply_hash !== applyHash) { + return new Error(`${label} apply hash mismatch.`); + } + return anchor; + } + + async prepareCanaryChallengeAnchor(state) { + if ( + !state || + !Number.isSafeInteger(state.updated_epoch) || + state.updated_epoch < 1 || + !this.isHexBytes(state.last_apply_hash, 32) || + (state.pending_epoch ?? null) !== null + ) { + return null; + } + const key = `epoch/challenge/${state.updated_epoch}`; + const record = { + epoch: state.updated_epoch, + apply_hash: state.last_apply_hash.toLowerCase(), + recorded_at: this.tx, + }; + const existing = await this.get(key); + if (existing !== null) { + if (existing.epoch !== record.epoch || existing.apply_hash !== record.apply_hash) { + return new Error('Canary challenge anchor conflict.'); + } + return { key, record, write: false }; + } + return { key, record, write: true }; + } + + async writePreparedAnchor(prepared) { + if (prepared?.write === true) { + await this.put(prepared.key, prepared.record); + } + } + + async rememberCanaryChallengeAnchor(state) { + const prepared = await this.prepareCanaryChallengeAnchor(state); + if (prepared instanceof Error) return prepared; + await this.writePreparedAnchor(prepared); + return null; + } + + async canaryChallengeAnchor(epoch) { + const historical = await this.get(`epoch/challenge/${epoch}`); + if (historical) return historical; + const current = await this.epochApplyStateRecord(); + if ( + current.updated_epoch === epoch && + this.isHexBytes(current.last_apply_hash, 32) && + (current.pending_epoch ?? null) === null + ) { + return { + epoch, + apply_hash: current.last_apply_hash.toLowerCase(), + recorded_at: current.updated_at, + }; + } + return null; + } + + parseTnkE18(value) { + if (typeof value !== 'string' || !/^[0-9]+$/.test(value)) { + return new Error('tnk_e18 must be a decimal integer string.'); + } + const parsed = BigInt(value); + if (parsed <= 0n) return new Error('tnk_e18 must be positive.'); + return parsed; + } + + parseTapWei(value, { allowZero = false } = {}) { + if (typeof value !== 'string' || !/^[0-9]+$/.test(value)) { + return new Error('tap_wei must be a decimal integer string.'); + } + const parsed = BigInt(value); + if (parsed < 0n || (!allowZero && parsed === 0n)) { + return new Error('tap_wei must be positive.'); + } + return parsed; + } + + normalizeFiatCurrency(value) { + if (typeof value !== 'string') return new Error('Invalid fiat currency.'); + const currency = value.trim().toLowerCase(); + if (!/^[a-z]{3}$/.test(currency)) return new Error('Unsupported fiat currency.'); + return currency; + } + + fiatEvidenceFields() { + if (this.value.fiat_currency === undefined || this.value.fiat_amount_minor === undefined) { + return new Error('Fiat evidence requires fiat_currency and fiat_amount_minor.'); + } + const currency = this.normalizeFiatCurrency(this.value.fiat_currency); + if (currency instanceof Error) return currency; + if ( + !Number.isSafeInteger(this.value.fiat_amount_minor) || + this.value.fiat_amount_minor <= 0 + ) { + return new Error('Invalid fiat amount.'); + } + return { + fiat_currency: currency, + fiat_amount_minor: this.value.fiat_amount_minor, + }; + } + + tnkE18ToAu(tnkE18, tnkUsdAu) { + const rate = this.parseAu(tnkUsdAu, 'TNK/USD atto-rate', { allowZero: false }); + if (rate instanceof Error) return new Error('Invalid TNK/USD atto-rate.'); + // Deposit conversion rounds down so credited AU never exceeds received TNK value. + return this.canonicalAu((tnkE18 * rate) / TNK_E18); + } + + tapWeiToAu(tapWei, tapUsdAu) { + const rate = this.parseAu(tapUsdAu, 'TAP/USD policy rate', { allowZero: false }); + if (rate instanceof Error) return new Error('Invalid TAP/USD policy rate.'); + // Deposit conversion rounds down so credited AU never exceeds received TAP value. + return this.canonicalAu((tapWei * rate) / TAP_WEI); + } + + async requireFreshRate(at) { + const rate = await this.get('rate/latest'); + if (!rate) return new Error('Fresh rate oracle required.'); + const admin = await this.get('admin'); + if (admin === null) return new Error('Fresh rate oracle requires a current admin key.'); + if (rate.posted_by !== admin || rate.posted_by_role !== 'admin') { + return new Error('Fresh rate oracle must be admin-posted.'); + } + if (rate.ts > at) return new Error('Rate oracle timestamp is in the future.'); + const params = await this.activeParamsAt(at, ['rate_staleness_seconds']); + if (at - rate.ts > params.rate_staleness_seconds) { + return new Error('Rate oracle is stale.'); + } + return rate; + } + + validateTnkRateRecord( + rate, + { + tnkUsdAu = null, + source = null, + ts = null, + updatedAt = null, + admin = null, + label = 'TNK rate', + } = {} + ) { + if (!rate || typeof rate !== 'object' || Array.isArray(rate)) { + return new Error(`${label} is missing.`); + } + const normalized = this.normalizeAu( + rate.tnk_usd_au, + `${label} TNK/USD atto-rate`, + { allowZero: false } + ); + if (normalized instanceof Error || normalized !== rate.tnk_usd_au || + rate.denom !== 'tnk_usd_au' || + typeof rate.source !== 'string' || + rate.source.length < 1 || + rate.source.length > 64 || + !Number.isSafeInteger(rate.ts) || + rate.ts < 0 || + typeof rate.updated_at !== 'string' || + !rate.updated_at.startsWith(`rate/tnk/${rate.ts}/`) || + !this.isHexBytes(rate.updated_at.slice(`rate/tnk/${rate.ts}/`.length), 32) || + !this.isHexBytes(rate.posted_by, 32) || + rate.posted_by !== rate.posted_by.toLowerCase() || + rate.posted_by_role !== 'admin') { + return new Error(`${label} is invalid.`); + } + if (admin !== null && rate.posted_by !== admin) { + return new Error(`${label} is not admin-posted.`); + } + if (tnkUsdAu !== null && this.compareAu(rate.tnk_usd_au, tnkUsdAu) !== 0) { + return new Error(`${label} amount mismatch.`); + } + if (source !== null && rate.source !== source) { + return new Error(`${label} source mismatch.`); + } + if (ts !== null && rate.ts !== ts) { + return new Error(`${label} timestamp mismatch.`); + } + if (updatedAt !== null && rate.updated_at !== updatedAt) { + return new Error(`${label} record key mismatch.`); + } + return null; + } + + async currentTnkRateRecord(label = 'Current TNK rate') { + const rate = await this.get('rate/latest'); + const admin = await this.get('admin'); + if (!rate || admin === null) return new Error(`${label} is missing.`); + const rateError = this.validateTnkRateRecord(rate, { admin, label }); + if (rateError) return rateError; + return rate; + } + + async guardianAcceptTnkDepositIntentRate(intent) { + const rate = await this.currentTnkRateRecord('TNK deposit rate'); + if (rate instanceof Error) return rate; + const exactError = this.validateTnkRateRecord(rate, { + tnkUsdAu: intent.rate_tnk_usd_au, + source: intent.rate_source, + label: 'TNK deposit rate', + }); + if (exactError) { + return new Error('TNK deposit rate does not match current oracle.'); + } + if ( + (hasOwn(intent, 'rate_ts') && intent.rate_ts !== rate.ts) || + (hasOwn(intent, 'rate_record_key') && intent.rate_record_key !== rate.updated_at) + ) { + return new Error('TNK deposit rate record is not current.'); + } + return rate; + } + + legacyTnkDepositRateCloseToCurrent(lockedRate, currentRate) { + const locked = this.parseAu(lockedRate, 'legacy TNK deposit locked rate', { allowZero: false }); + const current = this.parseAu(currentRate, 'current TNK deposit rate', { allowZero: false }); + if (locked instanceof Error || current instanceof Error) return false; + const diff = locked > current ? locked - current : current - locked; + const ceiling = locked > current ? locked : current; + return diff * 10_000n <= ceiling * LEGACY_TNK_DEPOSIT_RATE_DRIFT_BPS; + } + + async guardianRequireHistoricalTnkDepositRate(pending, at) { + const oracleValue = { + op: 'rate_oracle', + tnk_usd_au: pending.rate_tnk_usd_au, + source: pending.rate_source, + ts: pending.rate_ts, + }; + const key = await this.rateFeatureKey(oracleValue); + if (key instanceof Error) { + return new Error(`Guardian TNK deposit rate invariant failed: ${key.message}`); + } + if (key !== pending.rate_record_key) { + return new Error('Guardian TNK deposit rate invariant failed: rate record key mismatch.'); + } + const rate = await this.get(key); + const admin = await this.get('admin'); + if (!rate || admin === null) { + return new Error('Guardian TNK deposit rate invariant failed: exact admin-posted oracle history required.'); + } + const rateError = this.validateTnkRateRecord(rate, { + tnkUsdAu: pending.rate_tnk_usd_au, + source: pending.rate_source, + ts: pending.rate_ts, + updatedAt: pending.rate_record_key, + admin, + label: 'Guardian TNK deposit rate', + }); + if (rateError) { + return new Error(`Guardian TNK deposit rate invariant failed: ${rateError.message}`); + } + if (rate.ts > at) { + return new Error('Guardian TNK deposit rate invariant failed: oracle timestamp is in the future.'); + } + return rate; + } + + async guardianRequireTnkDepositRateLock(pending, at) { + const pendingRate = this.normalizeAu( + pending?.rate_tnk_usd_au, + 'pending TNK deposit rate', + { allowZero: false } + ); + if (pendingRate instanceof Error || pendingRate !== pending.rate_tnk_usd_au || + !this.isSafeKeyPart(pending.rate_source)) { + return new Error('Guardian TNK deposit rate invariant failed: pending rate is invalid.'); + } + const hasRateTs = hasOwn(pending, 'rate_ts'); + const hasRateRecordKey = hasOwn(pending, 'rate_record_key'); + if (hasRateTs || hasRateRecordKey) { + if (!hasRateTs || !hasRateRecordKey || + !Number.isSafeInteger(pending.rate_ts) || + pending.rate_ts < 0 || + typeof pending.rate_record_key !== 'string') { + return new Error('Guardian TNK deposit rate invariant failed: pending rate lock is invalid.'); + } + return await this.guardianRequireHistoricalTnkDepositRate(pending, at); + } + + const current = await this.guardianRequireFreshRate(at); + if (current instanceof Error) return current; + if (current.source !== pending.rate_source || + !this.legacyTnkDepositRateCloseToCurrent(pending.rate_tnk_usd_au, current.tnk_usd_au)) { + return new Error('TNK deposit rate does not match pending intent.'); + } + return { + ...current, + tnk_usd_au: pending.rate_tnk_usd_au, + source: pending.rate_source, + legacy_rate_lock: true, + }; + } + + async requireFreshTapRate(at) { + const rate = await this.get('tap/rate/latest'); + if (!rate) return new Error('Fresh TAP rate oracle required.'); + const admin = await this.get('admin'); + if (admin === null) return new Error('Fresh TAP rate oracle requires a current admin key.'); + if (rate.posted_by !== admin || rate.posted_by_role !== 'admin') { + return new Error('Fresh TAP rate oracle must be admin-posted.'); + } + if (rate.ts > at) return new Error('TAP rate oracle timestamp is in the future.'); + const params = await this.activeParamsAt(at, ['rate_staleness_seconds']); + if (at - rate.ts > params.rate_staleness_seconds) { + return new Error('TAP rate oracle is stale.'); + } + return rate; + } + + async guardianRequireFreshRate(at) { + const rate = await this.requireFreshRate(at); + if (rate instanceof Error) { + return new Error(`Guardian rate freshness invariant failed: ${rate.message}`); + } + return rate; + } + + async guardianRequireHistoricalTnkRate(settlement, at) { + const oracleValue = { + op: 'rate_oracle', + tnk_usd_au: settlement.rate_tnk_usd_au, + source: settlement.rate_source, + ts: settlement.rate_ts, + }; + const key = await this.rateFeatureKey(oracleValue); + if (key instanceof Error) { + return new Error(`Guardian TNK settlement rate invariant failed: ${key.message}`); + } + const rate = await this.get(key); + if (!rate) { + return new Error('Guardian TNK settlement rate invariant failed: exact admin-posted oracle history required.'); + } + if ( + rate.denom !== 'tnk_usd_au' || + this.compareAu(rate.tnk_usd_au, settlement.rate_tnk_usd_au) !== 0 || + rate.source !== settlement.rate_source || + rate.ts !== settlement.rate_ts || + rate.updated_at !== key || + rate.posted_by_role !== 'admin' || + !this.isHexBytes(rate.posted_by, 32) + ) { + return new Error('Guardian TNK settlement rate invariant failed: oracle history is invalid.'); + } + if (rate.ts > at) { + return new Error('Guardian TNK settlement rate invariant failed: oracle timestamp is in the future.'); + } + const params = await this.activeParamsAt(at, ['rate_staleness_seconds']); + if (at - rate.ts > params.rate_staleness_seconds) { + return new Error('Guardian TNK settlement rate invariant failed: oracle is stale.'); + } + return rate; + } + + async guardianRequireHistoricalTapRateLock(lock, at) { + const oracleValue = { + op: 'tap_rate_oracle', + tap_usd_au: lock.tap_usd_au, + source: lock.source, + ts: lock.rate_ts, + }; + const key = await this.rateFeatureKey(oracleValue); + if (key instanceof Error) { + return new Error(`Guardian TAP settlement rate invariant failed: ${key.message}`); + } + if (key !== lock.rate_record_key) { + return new Error('Guardian TAP settlement rate invariant failed: rate record key mismatch.'); + } + const rate = await this.get(key); + const admin = await this.get('admin'); + if (!rate || admin === null) { + return new Error('Guardian TAP settlement rate invariant failed: exact admin-posted oracle history required.'); + } + if ( + rate.denom !== 'tap_usd_au' || + this.compareAu(rate.tap_usd_au, lock.tap_usd_au) !== 0 || + rate.source !== lock.source || + rate.ts !== lock.rate_ts || + rate.updated_at !== key || + rate.posted_by !== admin || + rate.posted_by !== lock.posted_by || + rate.posted_by_role !== 'admin' + ) { + return new Error('Guardian TAP settlement rate invariant failed: oracle history is invalid.'); + } + if (rate.ts > at) { + return new Error('Guardian TAP settlement rate invariant failed: oracle timestamp is in the future.'); + } + const params = await this.activeParamsAt(at, ['rate_staleness_seconds']); + if (at - rate.ts > params.rate_staleness_seconds) { + return new Error('Guardian TAP settlement rate invariant failed: oracle is stale.'); + } + return rate; + } + + async guardianRequireFreshTapRate(at) { + const rate = await this.requireFreshTapRate(at); + if (rate instanceof Error) { + return new Error(`Guardian TAP rate freshness invariant failed: ${rate.message}`); + } + return rate; + } + + async opaqueHash(domain, value) { + const digest = await blake3(b4a.from(stableJson({ domain, value }))); + return b4a.toString(digest, 'hex'); + } + + async depositLeafHash(value) { + return await this.opaqueHash('mayhem-deposit-leaf-v1', value); + } + + async nextDepositRoot({ epoch, leaf, au, at }) { + const current = await this.get(`ev/dep/${epoch}`); + if (current && current.type !== 'deposit_root') { + return new Error('Invalid deposit evidence root.'); + } + const count = (current?.count ?? 0) + 1; + const auTotal = this.safeAddAu(current?.au_total ?? ZERO_AU, au); + if (auTotal instanceof Error) return auTotal; + const merkleRoot = current + ? await this.opaqueHash('mayhem-deposit-root-v1', { + previous_root: current.merkle_root, + leaf, + count, + }) + : leaf; + return { + type: 'deposit_root', + epoch, + merkle_root: merkleRoot, + count, + au_total: auTotal, + ts: at, + updated_at: this.tx, + }; + } + + async nextDepositReversalRoot({ epoch, leaf, disputedAu, clawbackAu, absorbedAu, at }) { + const current = await this.get(`ev/dep/${epoch}`); + if (current && current.type !== 'deposit_root') { + return new Error('Invalid deposit evidence root.'); + } + const count = (current?.count ?? 0) + 1; + const reversedAuTotal = this.safeAddAu(current?.reversed_au_total ?? ZERO_AU, disputedAu); + if (reversedAuTotal instanceof Error) return reversedAuTotal; + const clawbackAuTotal = this.safeAddAu(current?.clawback_au_total ?? ZERO_AU, clawbackAu); + if (clawbackAuTotal instanceof Error) return clawbackAuTotal; + const networkAbsorbedAuTotal = this.safeAddAu(current?.network_absorbed_au_total ?? ZERO_AU, absorbedAu); + if (networkAbsorbedAuTotal instanceof Error) return networkAbsorbedAuTotal; + const merkleRoot = current + ? await this.opaqueHash('mayhem-deposit-root-v1', { + previous_root: current.merkle_root, + leaf, + count, + }) + : leaf; + return { + ...(current ?? { + type: 'deposit_root', + epoch, + au_total: ZERO_AU, + }), + type: 'deposit_root', + epoch, + merkle_root: merkleRoot, + count, + reversed: true, + reversal_count: (current?.reversal_count ?? 0) + 1, + reversed_au_total: reversedAuTotal, + clawback_au_total: clawbackAuTotal, + network_absorbed_au_total: networkAbsorbedAuTotal, + ts: at, + updated_at: this.tx, + }; + } + + async epochApplyHash(value) { + const digest = await blake3(b4a.from(stableJson({ + domain: 'mayhem-epoch-apply-v1', + value, + }))); + return b4a.toString(digest, 'hex'); + } + + epochEmptySealHashValue(value) { + return { + type: value.type, + epoch: value.epoch, + at: value.at, + epoch_seconds: value.epoch_seconds, + previous_apply_hash: value.previous_apply_hash ?? null, + reason_hash: value.reason_hash, + sealed_by: value.sealed_by, + sealed_by_role: value.sealed_by_role, + totals: value.totals, + ...(value.market_price_root !== undefined ? { + market_price_root: value.market_price_root, + market_price_count: value.market_price_count, + } : {}), + }; + } + + async epochEmptySealHash(value) { + return await this.opaqueHash('mayhem-epoch-empty-seal-v1', this.epochEmptySealHashValue(value)); + } + + async epochApplyFeatureKey(value) { + const shapeError = this.validateEpochApplyFeatureValue(value); + if (shapeError) return shapeError; + const digest = await blake3(b4a.from(stableJson({ + domain: 'mayhem-epoch-apply-feature-v1', + value, + }))); + return `epoch/apply/${value.epoch}/${b4a.toString(digest, 'hex')}`; + } + + async tapAccountBindingFeatureKey(value) { + const normalized = this.normalizeTapAccountBinding(value); + if (normalized instanceof Error) return normalized; + const digest = await blake3(b4a.from(tapAccountBindingMessage(normalized))); + return `tap_account/${normalized.user}/${b4a.toString(digest, 'hex')}`; + } + + tapAccountBindingKey(user, chainId, poolAddress) { + return `tap/account/${chainId}/${poolAddress.toLowerCase()}/${user}`; + } + + tapAccountAddressKey(ethereumAddress, chainId, poolAddress) { + return `tap/account-by-address/${chainId}/${poolAddress.toLowerCase()}/${ethereumAddress.toLowerCase()}`; + } + + tapDepositIdentity(value) { + return [ + value.chain_id, + value.pool_address.toLowerCase(), + value.eth_tx_hash.toLowerCase(), + value.log_index, + value.block_hash.toLowerCase(), + ].join('/'); + } + + validateTapDepositIdentity(value) { + for (const key of ['chain_id', 'pool_address', 'eth_tx_hash', 'log_index', 'block_hash']) { + if (!hasOwn(value, key)) return new Error(`TAP deposit is missing ${key}.`); + } + if (!Number.isSafeInteger(value.chain_id) || value.chain_id < 1) { + return new Error('Invalid TAP chain id.'); + } + if (!this.isEthHexBytes(value.pool_address, 20)) return new Error('Invalid TAP pool address.'); + if (!this.isEthHexBytes(value.eth_tx_hash, 32)) return new Error('Invalid Ethereum tx hash.'); + if (!Number.isSafeInteger(value.log_index) || value.log_index < 0) { + return new Error('Invalid TAP deposit log index.'); + } + if (!this.isEthHexBytes(value.block_hash, 32)) return new Error('Invalid TAP deposit block hash.'); + return null; + } + + async depositFeatureKey(value) { + if (!value || typeof value !== 'object' || Array.isArray(value)) { + return new Error('Deposit feature value must be an object.'); + } + const digest = b4a.toString( + await blake3(b4a.from(stableJson({ + domain: 'mayhem-deposit-feature-v1', + value, + }))), + 'hex' + ); + if (value.op === 'deposit_tnk' && value.intent) { + const intentError = this.validateDepositTnkIntent(value.intent); + if (intentError) return intentError; + return `dep/tnk-intent/${value.intent.memo_hash}/${digest}`; + } + if (value.op === 'tnk_deposit') { + if (!this.isSafeKeyPart(value.memo_hash)) return new Error('Invalid deposit memo hash.'); + return `dep/tnk/${value.memo_hash}/${digest}`; + } + if (value.op === 'tap_deposit') { + const validationError = this.validateTapDepositIdentity(value); + if (validationError) return validationError; + return `dep/tap/${this.tapDepositIdentity(value)}`; + } + if (value.op === 'tap_deposit_reversal') { + const validationError = this.validateTapDepositReversalValue(value); + if (validationError) return validationError; + return `dep/tap/${this.tapDepositIdentity(value)}/reversal`; + } + if (value.op === 'fiat_deposit') { + if (!this.isSafeKeyPart(value.ext_ref_hash)) return new Error('Invalid external reference hash.'); + return `dep/fiat/${value.ext_ref_hash}`; + } + if (value.op === 'fiat_chargeback') { + if (!this.isSafeKeyPart(value.ext_ref_hash)) return new Error('Invalid external reference hash.'); + if (!this.isSafeKeyPart(value.dispute_ref_hash)) return new Error('Invalid dispute reference hash.'); + return `dep/fiat/${value.ext_ref_hash}/chargeback/${value.dispute_ref_hash}`; + } + return new Error('Unsupported deposit feature op.'); + } + + async rateFeatureKey(value) { + if (!value || typeof value !== 'object' || Array.isArray(value)) { + return new Error('Rate feature value must be an object.'); + } + let kind; + if (value.op === 'rate_oracle') { + const shapeError = this.validateRateOracleValue(value); + if (shapeError) return shapeError; + kind = 'tnk'; + } else if (value.op === 'tap_rate_oracle') { + const shapeError = this.validateTapRateOracleValue(value); + if (shapeError) return shapeError; + kind = 'tap'; + } else { + return new Error('Unsupported rate feature op.'); + } + this._mayhemApplyStage = 'rate:key:hash'; + const digest = b4a.toString( + await blake3(b4a.from(stableJson({ + domain: 'mayhem-rate-feature-v1', + value, + }))), + 'hex' + ); + this._mayhemApplyStage = 'rate:key:hashed'; + return `rate/${kind}/${value.ts}/${digest}`; + } + + async targetedPayoutPreparationFeatureKey(value) { + const normalized = await this.normalizeTargetedPayoutPreparationValue(value); + if (normalized instanceof Error) return normalized; + const digest = b4a.toString( + await blake3(b4a.from(stableJson({ + domain: 'mayhem-targeted-payout-preparation-feature-v1', + value: normalized, + }))), + 'hex' + ); + return ( + `payout/preparation-submit/${normalized.rail}/` + + `${normalized.economic_op_id}/${digest}` + ); + } + + async targetedPayoutEpochFeatureKey(value) { + const normalized = await this.normalizeTargetedPayoutEpochValue(value); + if (normalized instanceof Error) return normalized; + const digest = b4a.toString( + await blake3(b4a.from(stableJson({ + domain: 'mayhem-targeted-payout-epoch-feature-v1', + value: normalized, + }))), + 'hex' + ); + return `payout/epoch-plan-submit/${normalized.rail}/${normalized.epoch}/${digest}`; + } + + async targetedFiatAttemptFeatureKey(value) { + const normalized = await this.normalizePrepareTargetedFiatAttemptValue(value); + if (normalized instanceof Error) return normalized; + const digest = b4a.toString( + await blake3(b4a.from(stableJson({ + domain: 'mayhem-targeted-fiat-attempt-feature-v1', + value: normalized, + }))), + 'hex' + ); + return ( + `payout/attempt-submit/fiat/${normalized.economic_op_id}/` + + `${normalized.attempt_id}/${digest}` + ); + } + + async finalizeTargetedFiatAttemptFeatureKey(value) { + const normalized = this.normalizeFinalizeTargetedFiatAttemptValue(value); + if (normalized instanceof Error) return normalized; + const digest = b4a.toString( + await blake3(b4a.from(stableJson({ + domain: 'mayhem-finalize-targeted-fiat-attempt-feature-v1', + value: normalized, + }))), + 'hex' + ); + return ( + `payout/attempt-finalize/fiat/${normalized.economic_op_id}/` + + `${normalized.attempt_id}/${digest}` + ); + } + + async targetedTnkOutputFeatureKey(value) { + const normalized = this.normalizeTargetedTnkOutputSettlementValue(value); + if (normalized instanceof Error) return normalized; + const digest = b4a.toString( + await blake3(b4a.from(stableJson({ + domain: 'mayhem-targeted-tnk-output-feature-v1', + value: normalized, + }))), + 'hex' + ); + return ( + `settle/targeted/tnk/${normalized.epoch}/output/` + + `${normalized.economic_op_id}/${digest}` + ); + } + + async targetedFiatOutputFeatureKey(value) { + const normalized = this.normalizeTargetedFiatOutputSettlementValue(value); + if (normalized instanceof Error) return normalized; + const digest = b4a.toString( + await blake3(b4a.from(stableJson({ + domain: 'mayhem-targeted-fiat-output-feature-v1', + value: normalized, + }))), + 'hex' + ); + return ( + `settle/targeted/fiat/${normalized.epoch}/output/` + + `${normalized.economic_op_id}/${digest}` + ); + } + + async closeTargetedPayoutEpochFeatureKey(value) { + const normalized = this.normalizeCloseTargetedPayoutEpochValue(value); + if (normalized instanceof Error) return normalized; + const digest = b4a.toString( + await blake3(b4a.from(stableJson({ + domain: 'mayhem-close-targeted-payout-epoch-feature-v1', + value: normalized, + }))), + 'hex' + ); + return `settle/targeted/${normalized.rail}/${normalized.epoch}/close/${digest}`; + } + + async targetedTnkSettlementFeatureKey(value) { + const normalized = this.normalizeTargetedTnkSettlementValue(value); + if (normalized instanceof Error) return normalized; + const digest = b4a.toString( + await blake3(b4a.from(stableJson({ + domain: 'mayhem-targeted-tnk-settlement-feature-v1', + value, + }))), + 'hex' + ); + return `settle/targeted/tnk/${value.epoch}/${digest}`; + } + + async targetedTapSettlementFeatureKey(value) { + const normalized = this.normalizeTargetedTapSettlementValue(value); + if (normalized instanceof Error) return normalized; + const digest = b4a.toString( + await blake3(b4a.from(stableJson({ + domain: 'mayhem-targeted-tap-settlement-feature-v1', + value, + }))), + 'hex' + ); + return `settle/targeted/tap/${value.epoch}/${digest}`; + } + + async targetedFiatSettlementFeatureKey(value) { + const normalized = this.normalizeTargetedFiatSettlementValue(value); + if (normalized instanceof Error) return normalized; + const digest = b4a.toString( + await blake3(b4a.from(stableJson({ + domain: 'mayhem-targeted-fiat-settlement-feature-v2', + value, + }))), + 'hex' + ); + return `settle/targeted/fiat/${value.epoch}/${digest}`; + } + + async fiatDustSweepFeatureKey(value) { + const validationError = this.validateFiatDustSweepValue(value); + if (validationError) return validationError; + const digest = b4a.toString( + await blake3(b4a.from(stableJson({ + domain: 'mayhem-fiat-dust-sweep-feature-v1', + value, + }))), + 'hex' + ); + return `settle/fiat-dust/${value.provider}/${value.epoch}/${digest}`; + } + + async reputationAnchorFeatureKey(value) { + const validationError = this.validateReputationAnchor(value); + if (validationError) return validationError; + const digest = b4a.toString( + await blake3(b4a.from(stableJson({ + domain: 'mayhem-reputation-anchor-feature-v1', + value, + }))), + 'hex' + ); + return `rep/${value.provider}/${value.epoch}/${digest}`; + } + + async tier3MeasurementFeatureKey(value) { + const validationError = this.validateTier3MeasurementBlessValue(value); + if (validationError) return validationError; + const digest = b4a.toString( + await blake3(b4a.from(stableJson({ + domain: 'mayhem-tier3-measurement-feature-v1', + value, + }))), + 'hex' + ); + return `tier3/measurement/${value.platform}/${digest}`; + } + + async epochCommitHash(value) { + const digest = await blake3(b4a.from(stableJson({ + domain: 'mayhem-epoch-commit-v1', + value, + }))); + return b4a.toString(digest, 'hex'); + } + + probePass(value, params) { + if (value.probe_kind === 'uptime_tick') return true; + return value.match_bps >= params.canary_match_min_bps; + } + + async requireAuditorEligibility(auditor, atSeconds) { + const rep = await this.get(`rep/${auditor}`); + if (!rep) return new Error('Auditor reputation snapshot required.'); + const params = await this.activeParamsAt(atSeconds, [ + 'auditor_min_reputation_bps', + 'auditor_min_age_seconds', + ]); + if (rep.provenance_violation === true) return new Error('Auditor has a provenance violation.'); + if ((rep.r_bps ?? 0) < params.auditor_min_reputation_bps) { + return new Error('Auditor reputation too low.'); + } + const sinceSeconds = rep.probation?.since_seconds ?? 0; + if (atSeconds - sinceSeconds < params.auditor_min_age_seconds) { + return new Error('Auditor account age too low.'); + } + return null; + } + + async appendReputationEvent(event) { + if (!this.isSafeKeyPart(event.event_id)) return new Error('Invalid reputation event id.'); + const key = `ev/rep/${event.provider}/${event.event_id}`; + if ((await this.get(key)) !== null) return new Error('Reputation event already recorded.'); + + const headKey = `ev/rep/head/${event.provider}`; + const currentHead = await this.get(headKey); + const body = { + ...event, + paid_au: event.paid_au !== null && event.paid_au !== undefined + ? this.normalizeAu(event.paid_au, 'reputation paid amount') + : null, + max_spend_au: event.max_spend_au !== null && event.max_spend_au !== undefined + ? this.normalizeAu(event.max_spend_au, 'reputation max spend') + : null, + evidence_hash: event.evidence_hash ?? null, + recorded_at: this.tx, + recorded_by: this.address, + }; + const head = await this.reputationEventHead(currentHead?.head ?? null, body); + const foldKey = `ev/rep/fold/${event.provider}`; + const fold = this.advanceReputationFold(await this.get(foldKey), body, head); + if (fold instanceof Error) return fold; + const record = { + ...body, + head, + }; + const headRecord = { + provider: event.provider, + head, + count: (currentHead?.count ?? 0) + 1, + updated_at: this.tx, + }; + + await this.put(key, record); + await this.put(headKey, headRecord); + await this.put(foldKey, fold); + return record; + } + + parseSignedDecimal(value, label) { + if (typeof value !== 'string' || !/^(0|-?[1-9][0-9]*)$/.test(value)) { + return new Error(`${label} must be a canonical signed decimal string.`); + } + return BigInt(value); + } + + roundSignedRatio(value, divisor) { + if (typeof value !== 'bigint' || typeof divisor !== 'bigint' || divisor <= 0n) { + return new Error('Invalid signed ratio.'); + } + const negative = value < 0n; + const absolute = negative ? -value : value; + const rounded = (absolute + divisor / 2n) / divisor; + return negative ? -rounded : rounded; + } + + quantizePositiveReputation(value, scale, label) { + const scaled = value * Number(scale); + if (!Number.isFinite(scaled) || scaled < 0 || !Number.isSafeInteger(Math.floor(scaled + 0.5))) { + return new Error(`Invalid ${label}.`); + } + return BigInt(Math.floor(scaled + 0.5)); + } + + decayReputationRawNano(rawNano, fromSeconds, toSeconds) { + if ( + typeof rawNano !== 'bigint' || + !Number.isSafeInteger(fromSeconds) || + !Number.isSafeInteger(toSeconds) || + fromSeconds < 0 || + toSeconds < fromSeconds + ) { + return new Error('Invalid reputation decay range.'); + } + if (fromSeconds === toSeconds || rawNano === 0n) return rawNano; + const decay = 2 ** (-(toSeconds - fromSeconds) / REPUTATION_HALF_LIFE_SECONDS); + const decayPico = this.quantizePositiveReputation( + decay, + REPUTATION_DECAY_PICO_SCALE, + 'reputation decay' + ); + if (decayPico instanceof Error) return decayPico; + return this.roundSignedRatio( + rawNano * decayPico, + REPUTATION_DECAY_PICO_SCALE + ); + } + + reputationEventRawNano(event) { + let scoreQuarters = null; + let weightedAu = null; + if (event.kind === 'session_ok') { + scoreQuarters = 4n; + weightedAu = event.paid_au; + } else if (event.kind === 'session_partial') { + scoreQuarters = 1n; + weightedAu = event.paid_au; + } else if (event.kind === 'session_fail') { + scoreQuarters = -16n; + weightedAu = event.max_spend_au; + } + if (scoreQuarters !== null) { + const amount = this.parseAu(weightedAu, 'reputation weighted amount'); + if (amount instanceof Error) return amount; + const weight = Math.log10(1 + Number(amount)); + const weightNano = this.quantizePositiveReputation( + weight, + REPUTATION_RAW_NANO_SCALE, + 'reputation paid weight' + ); + if (weightNano instanceof Error) return weightNano; + return this.roundSignedRatio(weightNano * scoreQuarters, 4n); + } + const fixedScores = { + probe_ok: 500_000_000n, + probe_fail: -6_000_000_000n, + uptime_tick: 100_000_000n, + underdelivery: -6_000_000_000n, + dispute_lost: -20_000_000_000n, + provenance_violation: 0n, + }; + return fixedScores[event.kind] ?? new Error('Unsupported reputation event kind.'); + } + + advanceReputationFold(current, event, eventsHead) { + if (!Number.isSafeInteger(event.at) || event.at < 0) { + return new Error('Invalid reputation event time.'); + } + if (!Number.isSafeInteger(event.epoch) || event.epoch < 0) { + return new Error('Invalid reputation event epoch.'); + } + let rawNano = 0n; + let foldAt = event.at; + let successfulSessions = 0; + let provenanceViolation = false; + let eventCount = 0; + let maxEpoch = 0; + if (current !== null) { + rawNano = this.parseSignedDecimal(current.raw_nano, 'reputation raw_nano'); + if (rawNano instanceof Error) return rawNano; + if ( + !Number.isSafeInteger(current.at) || current.at < 0 || + !Number.isSafeInteger(current.successful_sessions) || current.successful_sessions < 0 || + !Number.isSafeInteger(current.event_count) || current.event_count < 0 || + !Number.isSafeInteger(current.max_epoch) || current.max_epoch < 0 || + typeof current.provenance_violation !== 'boolean' + ) { + return new Error('Invalid reputation fold state.'); + } + foldAt = Math.max(current.at, event.at); + rawNano = this.decayReputationRawNano(rawNano, current.at, foldAt); + if (rawNano instanceof Error) return rawNano; + successfulSessions = current.successful_sessions; + provenanceViolation = current.provenance_violation; + eventCount = current.event_count; + maxEpoch = current.max_epoch; + } + let contribution = this.reputationEventRawNano(event); + if (contribution instanceof Error) return contribution; + contribution = this.decayReputationRawNano(contribution, event.at, foldAt); + if (contribution instanceof Error) return contribution; + const nextSuccessfulSessions = this.safeAddCount( + successfulSessions, + event.kind === 'session_ok' ? 1 : 0, + 'successful reputation session count' + ); + if (nextSuccessfulSessions instanceof Error) return nextSuccessfulSessions; + const nextEventCount = this.safeAddCount(eventCount, 1, 'reputation event count'); + if (nextEventCount instanceof Error) return nextEventCount; + return { + provider: event.provider, + raw_nano: (rawNano + contribution).toString(), + at: foldAt, + max_epoch: Math.max(maxEpoch, event.epoch), + successful_sessions: nextSuccessfulSessions, + provenance_violation: provenanceViolation || event.kind === 'provenance_violation', + event_count: nextEventCount, + events_head: eventsHead, + updated_at: this.tx, + }; + } + + reputationFoldAt(fold, foldedAt) { + if (!Number.isSafeInteger(foldedAt) || foldedAt < fold.at) { + return new Error('Reputation folded_at precedes the latest event.'); + } + const rawNano = this.parseSignedDecimal(fold.raw_nano, 'reputation raw_nano'); + if (rawNano instanceof Error) return rawNano; + const decayed = this.decayReputationRawNano(rawNano, fold.at, foldedAt); + if (decayed instanceof Error) return decayed; + const rawMilliValue = this.roundSignedRatio(decayed, REPUTATION_RAW_NANO_PER_MILLI); + if (rawMilliValue instanceof Error) return rawMilliValue; + const rawMilli = Number(rawMilliValue); + if (!Number.isSafeInteger(rawMilli)) return new Error('Reputation raw_milli overflow.'); + const raw = rawMilli / 1_000; + const r = 1 / (1 + Math.exp(-raw / REPUTATION_KAPPA)); + const rBps = Math.floor(r * 10_000 + 0.5); + if (!Number.isSafeInteger(rBps) || rBps < 0 || rBps > 10_000) { + return new Error('Invalid folded reputation score.'); + } + return { + raw_milli: rawMilli, + r_bps: rBps, + successful_sessions: fold.successful_sessions, + provenance_violation: fold.provenance_violation, + }; + } + + isSafeKeyPart(value) { + return typeof value === 'string' && /^[a-zA-Z0-9._:-]{1,128}$/.test(value); + } + + isSafeModelId(value) { + return typeof value === 'string' && + /^[a-zA-Z0-9._:@/+~-]{1,256}$/.test(value) && + !value.startsWith('/') && + !value.endsWith('/') && + !value.includes('//'); + } + + isSafeHuggingFaceRepo(value) { + if (typeof value !== 'string') return false; + const parts = value.split('/'); + return parts.length === 2 && + parts.every((part) => this.isSafeHuggingFaceComponent(part)); + } + + isSafeHuggingFaceComponent(value) { + return typeof value === 'string' && + /^[A-Za-z0-9][A-Za-z0-9._-]{0,95}$/.test(value) && + !value.endsWith('.') && + !value.endsWith('-') && + !value.includes('..') && + !value.includes('--'); + } + + isSafeHuggingFacePath(value) { + return typeof value === 'string' && + value.length > 0 && + !value.startsWith('/') && + !value.startsWith('\\') && + !value.includes('\\') && + !value.includes('?') && + !value.includes('#') && + !value.includes('%') && + !/[\x00-\x1f\x7f]/.test(value) && + value.split('/').every((part) => this.isSafeHuggingFacePathSegment(part)); + } + + isSafeHuggingFacePathSegment(value) { + return typeof value === 'string' && + value.length > 0 && + value !== '.' && + value !== '..' && + /^[A-Za-z0-9._+-]+$/.test(value); + } + + isHttpsUrl(value) { + if (typeof value !== 'string' || value.length === 0 || value.length > 512) return false; + try { + const parsed = new URL(value); + return parsed.protocol === 'https:' && !!parsed.hostname; + } catch { + return false; + } + } + + isPinnedHuggingFaceResolveUrl(value) { + return this.pinnedHuggingFaceResolveRevision(value) !== null; + } + + pinnedHuggingFaceResolveRevision(value) { + if (!this.isHttpsUrl(value)) return null; + const parsed = new URL(value); + if (parsed.hostname !== 'huggingface.co') return null; + const parts = parsed.pathname.split('/').filter(Boolean); + const resolveIndex = parts.indexOf('resolve'); + if (resolveIndex < 0 || resolveIndex + 2 >= parts.length) return null; + return this.isHexBytes(parts[resolveIndex + 1], 20) ? parts[resolveIndex + 1] : null; + } + + isSafeExternalRef(value) { + return typeof value === 'string' && /^[a-zA-Z0-9._:-]{1,256}$/.test(value); + } + + normalizeProviderKybValue(value) { + if (!this.isHexBytes(value.provider, 32)) return new Error('Invalid provider id.'); + const legalName = value.legal_name.trim(); + if (!legalName || /[\x00-\x1f\x7f]/.test(legalName)) { + return new Error('Invalid provider KYB legal name.'); + } + const jurisdiction = value.jurisdiction.trim().toUpperCase(); + if (!/^[A-Z0-9._:-]{1,64}$/.test(jurisdiction)) { + return new Error('Invalid provider KYB jurisdiction.'); + } + const proofHash = value.proof_hash.toLowerCase(); + if (!this.isHexBytes(proofHash, 32)) return new Error('Invalid provider KYB proof hash.'); + const kybRef = value.kyb_ref.trim(); + if (!this.isSafeExternalRef(kybRef)) return new Error('Invalid provider KYB reference.'); + const schemaVersion = value.schema_version ?? 1; + if (!Number.isInteger(schemaVersion) || schemaVersion < 1) { + return new Error('Invalid provider KYB schema version.'); + } + const adminSig = value.admin_sig.toLowerCase(); + if (!this.isHexBytes(adminSig, 64)) return new Error('Invalid provider KYB admin signature.'); + return { + provider: value.provider.toLowerCase(), + legal_name: legalName, + jurisdiction, + proof_hash: proofHash, + kyb_ref: kybRef, + verified_at: value.verified_at, + schema_version: schemaVersion, + admin_sig: adminSig, + }; + } + + isHexBytes(value, bytes) { + return typeof value === 'string' && + value.length === bytes * 2 && + /^[0-9a-fA-F]+$/.test(value); + } + + isEthHexBytes(value, bytes) { + return typeof value === 'string' && + value.length === bytes * 2 + 2 && + /^0x[0-9a-fA-F]+$/.test(value); + } + + async reputationEventHead(previousHead, event) { + const payload = stableJson({ + domain: 'mayhem-reputation-event-v1', + previous_head: previousHead, + event, + }); + const digest = await blake3(b4a.from(payload)); + return b4a.toString(digest, 'hex'); + } + + verifyConsentSignature(sender, ver, hash, sig) { + const verify = this.protocol?.peer?.wallet?.verify; + if (typeof verify !== 'function') return false; + return verify.call(this.protocol.peer.wallet, sig, consentMessage(ver, hash), sender) === true; + } + + verifyProviderLifecycleSignature(provider, intent, sig) { + const verify = this.protocol?.peer?.wallet?.verify; + if (typeof verify !== 'function') return false; + return verify.call(this.protocol.peer.wallet, sig, providerLifecycleIntentMessage(intent), provider) === true; + } + + verifyProviderPayoutBindingSignature(provider, intent, signature) { + const verify = this.protocol?.peer?.wallet?.verify; + if (typeof verify !== 'function') return false; + return verify.call( + this.protocol.peer.wallet, + signature, + providerPayoutBindingMessage(intent), + provider + ) === true; + } + + verifyProviderPayoutTargetBindingSignature(intent) { + if (intent.rail === 'fiat') return intent.target_signature === null; + if (intent.rail === 'tnk') { + const verify = this.protocol?.peer?.wallet?.verify; + if (typeof verify !== 'function') return false; + return verify.call( + this.protocol.peer.wallet, + intent.target_signature, + providerPayoutTargetBindingMessage(intent), + intent.target_wallet + ) === true; + } + if (intent.rail !== 'tap') return false; + try { + const bytes = b4a.from(intent.target_signature.slice(2), 'hex'); + let recovery = bytes[64]; + if (recovery === 27 || recovery === 28) recovery -= 27; + if (recovery !== 0 && recovery !== 1) return false; + const signature = secp256k1.Signature + .fromCompact(bytes.subarray(0, 64)) + .addRecoveryBit(recovery); + if (signature.hasHighS()) return false; + const publicKey = signature + .recoverPublicKey( + ethereumPersonalMessageHash(providerPayoutTargetBindingMessage(intent)) + ) + .toRawBytes(false); + return ethereumAddressFromPublicKey(publicKey) === intent.target; + } catch { + return false; + } + } + + verifyDepositTnkSignature(sender, intent, sig) { + const verify = this.protocol?.peer?.wallet?.verify; + if (typeof verify !== 'function') return false; + return verify.call(this.protocol.peer.wallet, sig, depositTnkIntentMessage(intent), sender) === true; + } + + verifyTapAccountUserSignature(value) { + const verify = this.protocol?.peer?.wallet?.verify; + if (typeof verify !== 'function') return false; + return verify.call( + this.protocol.peer.wallet, + value.user_sig, + tapAccountBindingMessage(value), + value.user + ) === true; + } + + verifyTapAccountEthereumSignature(value) { + try { + const bytes = b4a.from(value.ethereum_sig.slice(2), 'hex'); + let recovery = bytes[64]; + if (recovery === 27 || recovery === 28) recovery -= 27; + if (recovery !== 0 && recovery !== 1) return false; + const signature = secp256k1.Signature + .fromCompact(bytes.subarray(0, 64)) + .addRecoveryBit(recovery); + if (signature.hasHighS()) return false; + const publicKey = signature + .recoverPublicKey(ethereumPersonalMessageHash(tapAccountBindingMessage(value))) + .toRawBytes(false); + return ethereumAddressFromPublicKey(publicKey) === value.ethereum_address.toLowerCase(); + } catch { + return false; + } + } + + verifySpendVoucherSignature(user, body, sig) { + const verify = this.protocol?.peer?.wallet?.verify; + if (typeof verify !== 'function') return false; + return verify.call(this.protocol.peer.wallet, sig, spendVoucherMessage(body), user) === true; + } + + verifySpendReservationSignature(provider, value) { + const verify = this.protocol?.peer?.wallet?.verify; + if (typeof verify !== 'function') return false; + return verify.call(this.protocol.peer.wallet, value.provider_sig, spendReservationMessage(value), provider) === true; + } + + verifyProbeResultSignature(auditor, value) { + const verify = this.protocol?.peer?.wallet?.verify; + if (typeof verify !== 'function') return false; + return verify.call(this.protocol.peer.wallet, value.auditor_sig, probeResultMessage(value, auditor), auditor) === true; + } + + async verifyProviderKybSignature(value) { + const admin = await this.get('admin'); + const verify = this.protocol?.peer?.wallet?.verify; + if (typeof admin !== 'string' || typeof verify !== 'function') return false; + return verify.call(this.protocol.peer.wallet, value.admin_sig, providerKybMessage(value), admin) === true; + } +} + +export default MayhemContract; diff --git a/intercom/contract/history/v26.js b/intercom/contract/history/v26.js new file mode 100644 index 00000000..9cca535a --- /dev/null +++ b/intercom/contract/history/v26.js @@ -0,0 +1,25422 @@ +import b4a from 'b4a'; +import { blake3 } from '@tracsystems/blake3'; +import { keccak256 } from 'ethereum-cryptography/keccak'; +import { secp256k1 } from 'ethereum-cryptography/secp256k1'; +import { Contract } from 'trac-peer'; +import { consumeCanonicalReplayContext } from 'trac-peer/src/base/canonical-replay.js'; +import PeerWallet from 'trac-wallet'; +import ContractV23 from './v23.js'; +import ContractV24 from './v24.js'; +import ContractV25 from './v25.js'; + +export const CONTRACT_VERSION = 26; +// Recovery is limited to unchanged schema-11 receipt evidence already signed by +// v23, v24, or v25 participants. New prior-version operations are not admitted; +// separately authenticated canonical replay does not constitute new admission. +const RECOVERABLE_RECEIPT_CONTRACT_VERSIONS = new Set([23, 24, 25]); +const SIGNING_MESSAGE_VERSION = 2; +const CURRENT_RULES_KEY = 'rules/current'; +const PROVIDER_ACCEPTED_RAILS = new Set(['fiat', 'tap', 'tnk']); +const PROVIDER_ACCEPTED_RAIL_ORDER = Object.freeze(['fiat', 'tap', 'tnk']); +const PROVIDER_RAIL_SCHEMA_VERSION = 1; +const PROVIDER_PAYOUT_BINDING_RAILS = new Set(['fiat', 'tap', 'tnk']); +const PROVIDER_PAYOUT_BINDING_RAIL_ORDER = Object.freeze(['fiat', 'tap', 'tnk']); +const PROVIDER_PAYOUT_BINDING_CONTEXT_CURRENT_KEY = 'payout/context/current'; +export const PAYOUT_INTENT_MAX_EXPIRY_EPOCHS_DEFAULT = 10_000; +const PAYOUT_PARAM_DEFINITIONS = Object.freeze({ + payout_intent_max_expiry_epochs: { + default: PAYOUT_INTENT_MAX_EXPIRY_EPOCHS_DEFAULT, + min: 1, + max: 1_000_000, + }, +}); +const FIAT_DEPOSIT_RAILS = new Set(['stripe']); +const REQUIRED_FIAT_PAYOUT_CURRENCIES = Object.freeze(['eur', 'gbp', 'usd']); +const PRICE_DENOMINATION = 'au_usd'; +const RATE_SOURCES = new Set(['gate-spot', 'mexc-spot']); +const LEGACY_TNK_DEPOSIT_RATE_DRIFT_BPS = 100n; +const CATALOG_SOURCE_KINDS = new Set(['https', 'huggingface']); +const CATALOG_RUNTIME_STATUSES = new Set(['blessed', 'deprecated', 'revoked']); +const CATALOG_OUTCOME_CLASS_STATUSES = new Set(['active', 'deprecated', 'revoked']); +const PROVIDER_LIFECYCLE_OPS = new Set([ + 'register_provider', + 'join_enclave', + 'leave_enclave', + 'join_room', + 'leave_room', + 'set_provider_rails', +]); +const DAY_SECONDS = 24 * 60 * 60; +const DEFAULT_PRICE_RATE_LIMIT_SECONDS = 6 * 60 * 60; +const DEFAULT_MARKET_PRICE_TARGET_UTILIZATION_BPS = 8_500; +const DEFAULT_MARKET_PRICE_EMA_ALPHA_BPS = 2_500; +const DEFAULT_MARKET_PRICE_GAIN_BPS = 5_000; +const DEFAULT_MARKET_PRICE_MAX_STEP_BPS = 1_000; +const DEFAULT_MARKET_PRICE_COLD_START_MIN_PROVIDERS = 2; +const ZERO_AU = '0'; +const ONE_USD_AU = '1000000000000000000'; +const FIVE_MILLI_USD_AU = '5000000000000000'; +const DEFAULT_MARKET_PRICE_PROVIDER_EPOCH_TARGET_AU = ONE_USD_AU; +const DEFAULT_MARKET_PRICE_MAX_UTILIZATION_BPS = 50_000; +const DEFAULT_MARKET_PRICE_BELOW_TARGET_DISCOUNT_BPS = 2_500; +const DEFAULT_MARKET_PRICE_ABOVE_TARGET_SLOPE_BPS = 15_000; +const DEFAULT_PARAM_ACTIVATION_DELAY_SECONDS = DAY_SECONDS; +const PROBATION_SECONDS = 7 * DAY_SECONDS; +const DEFAULT_FRAUD_SLASH_BPS = 10_000; +const DEFAULT_DISPUTE_LOST_SLASH_BPS = 2_000; +const MAX_OPERATOR_FEE_BPS = 1_500; +const MAX_LAUNCH_ENCLAVE_ATTESTATION_TIER = 3; +const DEFAULT_DISPUTE_DEPOSIT_AU = ONE_USD_AU; +const DEFAULT_DISPUTE_TIMEOUT_EPOCHS = 168; +const DEFAULT_MAX_OPEN_DISPUTES_PER_OPENER = 8; +const DEFAULT_DISPUTE_OPENER_FAULT_FORFEIT_BPS = 2_500; +const DEFAULT_MAX_APPLY_BATCH = 2_000; +const DEFAULT_MAX_MARKET_USAGE_ENTRIES = 5_000; +const DEFAULT_MAX_TAP_SETTLEMENT_OUTPUTS = 5_000; +const DEFAULT_MAX_TNK_SETTLEMENT_OUTPUTS = 5_000; +const DEFAULT_MAX_FIAT_SETTLEMENT_OUTPUTS = 5_000; +const MIN_TAP_CONFIRMATION_DEPTH = 12; +const TAP_OPERATOR_BPS = 1_500; +const TAP_BURN_BPS = 1_000; +const DISPUTE_EVIDENCE_MAX_BYTES = 4_096; +const FRAUD_PROOF_MAX_BYTES = 4_096; +export const SESSION_RECEIPT_SCHEMA_VERSION = 11; +export const SPEND_VOUCHER_SCHEMA_VERSION = 11; +const RECEIPT_EPOCH_INDEX_PAGE_SIZE = 128; +const CTX_BRACKET_TABLE_VERSION = 1; +const CTX_BRACKETS = Object.freeze([ + { id: 'le8k', max_ctx: 8_192 }, + { id: 'le32k', max_ctx: 32_768 }, + { id: 'le128k', max_ctx: 131_072 }, + { id: 'le256k', max_ctx: 262_144 }, + { id: 'gt256k', max_ctx: null }, +]); +const TNK_E18 = 1_000_000_000_000_000_000n; +const TAP_WEI = 1_000_000_000_000_000_000n; +const USD_AU = 1_000_000_000_000_000_000n; +const USD_CENT_AU = 10_000_000_000_000_000n; +const TAP_DEPOSIT_EVENT_SIGNATURE = '0xe1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d3c460751c2402c5c5cc9109c'; +const TAP_DEPOSIT_WATCHER_ID = 'tap-deposit-watcher-v1'; +const MSB_TRANSFER_EVIDENCE_VERSION = 1; +const STRIPE_TRANSFER_EVIDENCE_VERSION = 2; +const REPUTATION_HALF_LIFE_SECONDS = 14 * DAY_SECONDS; +const REPUTATION_KAPPA = 25; +const REPUTATION_RAW_NANO_SCALE = 1_000_000_000n; +const REPUTATION_DECAY_PICO_SCALE = 1_000_000_000_000n; +const REPUTATION_RAW_NANO_PER_MILLI = 1_000_000n; +const PARAM_DEFINITIONS = Object.freeze({ + probation_successful_sessions: { default: 50, min: 0, max: 1_000_000 }, + probation_seconds: { default: PROBATION_SECONDS, min: 0, max: 365 * 24 * 60 * 60 }, + probation_max_concurrent_sessions_per_user: { default: 2, min: 1, max: 1_000_000 }, + probation_price_max_bps: { default: 10_000, min: 0, max: 1_000_000 }, + probation_weight_bps: { default: 5_000, min: 0, max: 10_000 }, + auditor_min_reputation_bps: { default: 8_000, min: 0, max: 10_000 }, + auditor_min_age_seconds: { default: 30 * DAY_SECONDS, min: 0, max: 10 * 365 * DAY_SECONDS }, + canary_match_min_bps: { default: 9_000, min: 0, max: 10_000 }, + canary_probe_holdback_bps: { default: 0, min: 0, max: 10_000 }, + canary_probe_release_min_passes: { default: 2, min: 0, max: 1_000_000 }, + probe_reward_au: { default: FIVE_MILLI_USD_AU, min: ZERO_AU, money: true }, + uptime_tick_seconds: { default: 6 * 60 * 60, min: 60, max: 30 * DAY_SECONDS }, + fraud_slash_bps: { default: DEFAULT_FRAUD_SLASH_BPS, min: 0, max: 10_000 }, + dispute_lost_slash_bps: { default: DEFAULT_DISPUTE_LOST_SLASH_BPS, min: 0, max: 10_000 }, + new_provider_holdback_epochs: { default: 168, min: 0, max: 1_000_000 }, + holdback_epochs: { default: 24, min: 0, max: 1_000_000 }, + min_tier_notice_epochs: { default: 24, min: 1, max: 1_000_000 }, + fee_bps: { default: 1_500, min: 0, max: MAX_OPERATOR_FEE_BPS }, + dispute_deposit_au: { default: DEFAULT_DISPUTE_DEPOSIT_AU, min: '1', money: true }, + dispute_timeout_epochs: { default: DEFAULT_DISPUTE_TIMEOUT_EPOCHS, min: 1, max: 1_000_000 }, + max_open_disputes_per_opener: { default: DEFAULT_MAX_OPEN_DISPUTES_PER_OPENER, min: 1, max: 1_000 }, + dispute_opener_fault_forfeit_bps: { default: DEFAULT_DISPUTE_OPENER_FAULT_FORFEIT_BPS, min: 1, max: 9_999 }, + payout_min_au: { default: ONE_USD_AU, min: ZERO_AU, money: true }, + price_min_bps: { default: 2_500, min: 2_500, max: 40_000 }, + price_max_bps: { default: 40_000, min: 2_500, max: 40_000 }, + price_rate_limit_seconds: { default: DEFAULT_PRICE_RATE_LIMIT_SECONDS, min: 0, max: 365 * DAY_SECONDS }, + market_target_utilization_bps: { default: DEFAULT_MARKET_PRICE_TARGET_UTILIZATION_BPS, min: 1, max: 9_999, deprecated: true }, + market_ema_alpha_bps: { default: DEFAULT_MARKET_PRICE_EMA_ALPHA_BPS, min: 1, max: 10_000 }, + market_gain_bps: { default: DEFAULT_MARKET_PRICE_GAIN_BPS, min: 1, max: 10_000 }, + market_max_step_bps: { default: DEFAULT_MARKET_PRICE_MAX_STEP_BPS, min: 1, max: 10_000 }, + market_cold_start_min_providers: { default: DEFAULT_MARKET_PRICE_COLD_START_MIN_PROVIDERS, min: 0, max: 1_000_000, deprecated: true }, + market_provider_epoch_target_au: { default: DEFAULT_MARKET_PRICE_PROVIDER_EPOCH_TARGET_AU, min: '1', money: true, deprecated: true }, + market_max_utilization_bps: { default: DEFAULT_MARKET_PRICE_MAX_UTILIZATION_BPS, min: 1, max: 1_000_000, deprecated: true }, + market_below_target_discount_bps: { default: DEFAULT_MARKET_PRICE_BELOW_TARGET_DISCOUNT_BPS, min: 0, max: 10_000, deprecated: true }, + market_above_target_slope_bps: { default: DEFAULT_MARKET_PRICE_ABOVE_TARGET_SLOPE_BPS, min: 0, max: 1_000_000, deprecated: true }, + epoch_seconds: { default: 3_600, min: 60, max: 86_400 }, + reservation_max_lifetime_epochs: { default: 24, min: 1, max: 1_000_000 }, + reservation_receipt_grace_epochs: { default: 6, min: 0, max: 1_000_000 }, + rate_staleness_seconds: { default: 45 * 60, min: 60, max: 86_400 }, + rules_grace_seconds: { default: 14 * 24 * 60 * 60, min: 0, max: 365 * 24 * 60 * 60 }, + challenge_epochs: { default: 6, min: 0, max: 1_000_000 }, + max_apply_batch: { default: DEFAULT_MAX_APPLY_BATCH, min: 1, max: Number.MAX_SAFE_INTEGER }, + max_market_usage_entries: { default: DEFAULT_MAX_MARKET_USAGE_ENTRIES, min: 0, max: Number.MAX_SAFE_INTEGER }, + max_tap_settlement_outputs: { default: DEFAULT_MAX_TAP_SETTLEMENT_OUTPUTS, min: 1, max: Number.MAX_SAFE_INTEGER }, + max_tnk_settlement_outputs: { default: DEFAULT_MAX_TNK_SETTLEMENT_OUTPUTS, min: 1, max: Number.MAX_SAFE_INTEGER }, + max_fiat_settlement_outputs: { default: DEFAULT_MAX_FIAT_SETTLEMENT_OUTPUTS, min: 1, max: Number.MAX_SAFE_INTEGER }, + param_activation_delay_seconds: { default: DEFAULT_PARAM_ACTIVATION_DELAY_SECONDS, min: 0, max: 30 * DAY_SECONDS }, +}); +const EPOCH_ADMIN_PARAM_KEYS = Object.freeze([ + 'probation_successful_sessions', + 'probation_seconds', + 'probation_max_concurrent_sessions_per_user', + 'probation_price_max_bps', + 'probation_weight_bps', + 'auditor_min_reputation_bps', + 'auditor_min_age_seconds', + 'canary_match_min_bps', + 'canary_probe_holdback_bps', + 'canary_probe_release_min_passes', + 'probe_reward_au', + 'uptime_tick_seconds', + 'fraud_slash_bps', + 'dispute_lost_slash_bps', + 'new_provider_holdback_epochs', + 'holdback_epochs', + 'min_tier_notice_epochs', + 'fee_bps', + 'dispute_deposit_au', + 'dispute_timeout_epochs', + 'max_open_disputes_per_opener', + 'dispute_opener_fault_forfeit_bps', + 'payout_min_au', + 'price_min_bps', + 'price_max_bps', + 'price_rate_limit_seconds', + 'market_ema_alpha_bps', + 'market_gain_bps', + 'market_max_step_bps', + 'epoch_seconds', + 'reservation_max_lifetime_epochs', + 'reservation_receipt_grace_epochs', + 'rate_staleness_seconds', + 'rules_grace_seconds', + 'challenge_epochs', + 'max_apply_batch', + 'max_market_usage_entries', + 'max_tap_settlement_outputs', + 'max_tnk_settlement_outputs', + 'max_fiat_settlement_outputs', + 'param_activation_delay_seconds', +]); +const REPUTATION_EVENT_KINDS = new Set([ + 'session_ok', + 'session_partial', + 'session_fail', + 'probe_ok', + 'probe_fail', + 'uptime_tick', + 'underdelivery', + 'dispute_lost', + 'provenance_violation', +]); +const PROBE_KINDS = new Set(['canary', 'uptime_tick']); +const PROBE_VERIFICATION_METHODS = new Set([ + 'token_fingerprint', + 'context_needle', + 'seed_perceptual_hash', + 'embedding_cosine', + 'transcript_match', + 'audio_fingerprint', + 'attestation_of_compute', +]); +const AUDITOR_SLASH_REASONS = new Set(['collusion', 'false_report']); +const BAN_TARGET_TYPES = new Set(['provider', 'device', 'fingerprint', 'committer', 'kyb']); +const FRAUD_PROOF_REASONS = new Set(['over_credit', 'price_derivation']); +const DISPUTE_OUTCOMES = new Set(['provider_fault', 'opener_fault', 'no_fault']); +const DISPUTE_DEPOSIT_ACTIONS = new Set(['refund', 'forfeit', 'partial_forfeit']); +const EPOCH_ROOT_KEYS = ['dep', 'use', 'earn', 'fee', 'price']; +const EPOCH_TOTAL_KEYS = [ + 'dep_count', + 'dep_au', + 'use_count', + 'use_au', + 'provider_count', + 'earn_au', + 'fee_au', + 'fee_cum_au', + 'burn_au', + 'burn_cum_au', + 'price_count', +]; +const EPOCH_TOTAL_MONEY_KEYS = new Set([ + 'dep_au', + 'use_au', + 'earn_au', + 'fee_au', + 'fee_cum_au', + 'burn_au', + 'burn_cum_au', +]); +const ENCLAVE_UPDATE_FIELDS = [ + 'att_tier', + 'binary_hash', + 'approved_binary_hashes', + 'launch_measurements', + 'caps', +]; +const ENCLAVE_ARTIFACT_SIDECARS_MAX = 64; +const ENCLAVE_APPROVED_BINARY_HASHES_MAX = 64; +const TIER3_MEASUREMENT_MAX_NAMES = 32; +const TIER3_MEASUREMENT_MAX_VALUES = 128; +const ENCLAVE_BACKEND_PATTERN = /^[a-z][a-z0-9]*(?:[.-][a-z0-9]+)*$/; +const ENCLAVE_BACKEND_MAX_LENGTH = 64; +const ENCLAVE_QUANT_BUCKETS = new Set([ + 'unknown', + 'fp32', + 'fp16', + 'bf16', + 'fp8', + 'nvfp4', + 'int8', + 'int4', + 'int2', + 'int1', + 'fp64', + 'tf32', + 'mxfp8', + 'mxfp6', + 'mxfp4', + 'fp6', + 'fp4', + 'nf4', +]); +const ENCLAVE_QUANT_BUCKET_PATTERN = /^(?:unknown|binary|ternary|tf32|(?:mxfp|nvfp|uint|int|fp|bf|nf)[1-9][0-9]?(?:-[a-z0-9]+)*)$/; +const ENCLAVE_QUANT_BUCKET_MAX_LENGTH = 32; +const DEFAULT_MODEL_CLASS = 'text-generation'; +const MODEL_CLASSES = new Set([ + DEFAULT_MODEL_CLASS, + 'embedding', + 'image-generation', + 'video-generation', + 'tts', + 'stt', + 'audio-generation', + 'music-generation', + 'workflow', +]); +const RATE_MAP_MAX_ENTRIES = 16; +const MODEL_CLASS_RATE_UNITS = Object.freeze({ + [DEFAULT_MODEL_CLASS]: new Set([ + 'input_token', + 'cached_input_token', + 'output_token', + ]), + embedding: new Set(['input_token', 'embedding']), + 'image-generation': new Set(['image', 'step']), + 'video-generation': new Set(['video_second', 'frame']), + tts: new Set(['input_character', 'audio_second']), + stt: new Set(['audio_second']), + 'audio-generation': new Set(['input_character', 'audio_second']), + 'music-generation': new Set(['input_character', 'audio_second']), + workflow: new Set([ + 'megapixel_step', + 'megapixel', + // Exact integer pixel-frames (width * height * frames * artifact_count). + // Video workflow classes price in this unit; `megapixel_step` rounds each + // frame up to a whole megapixel and is image-only. + 'pixel_frame', + 'compute_second', + 'audio_second', + 'input_character', + 'frame', + 'image', + 'step', + 'video_second', + ]), +}); +const CAP_OUTPUT_MODALITIES = new Set(['text', 'embedding', 'image', 'video', 'audio']); +const ENCLAVE_MODALITIES = new Set(['text', 'embedding', 'image', 'video', 'audio']); +const MODEL_CLASS_OUTPUT_MODALITIES = Object.freeze({ + [DEFAULT_MODEL_CLASS]: new Set(['text']), + embedding: new Set(['embedding']), + 'image-generation': new Set(['image']), + 'video-generation': new Set(['video', 'audio']), + tts: new Set(['audio']), + stt: new Set(['text']), + 'audio-generation': new Set(['audio']), + 'music-generation': new Set(['audio']), + workflow: new Set(['image', 'video', 'audio']), +}); +const ENCLAVE_ARTIFACT_ROOT_KIND = 'blake3_merkle_v1'; +const ENCLAVE_CAP_BOOLEAN_FIELDS = [ + 'chat', + 'tools', + 'json', + 'embeddings', + 'vision', + 'image', + 'video', + 'audio', +]; +const ENCLAVE_CAP_INTEGER_FIELDS = [ + 'ctx', + 'ctx_max', + 'tp_degree', + 'max_batch_size', + 'max_num_tokens', + 'kv_bytes_per_token', + 'vllm_gpu_memory_utilization_pct', + 'max_image_width', + 'max_image_height', + 'max_image_steps', + 'max_video_width', + 'max_video_height', + 'max_video_frames', + 'max_video_seconds', + 'max_audio_seconds', + 'sample_rate_hz', +]; +const ENCLAVE_CAP_STRING_FIELDS = [ + 'vllm_dtype', +]; +const ENCLAVE_CAP_FIELDS = new Set([ + ...ENCLAVE_CAP_BOOLEAN_FIELDS, + ...ENCLAVE_CAP_INTEGER_FIELDS, + ...ENCLAVE_CAP_STRING_FIELDS, + 'output_modality', + 'output_modalities', + 'modality_set', + 'speciality_levels', +]); +const ROOM_POLICY_FIELDS = new Set([ + 'region_hint', + 'canary_set', + 'min_reputation', + 'max_price_mult', +]); + +export const signingMessageVersions = () => [SIGNING_MESSAGE_VERSION]; +export const consentMessage = (ver, hash, signingVersion = SIGNING_MESSAGE_VERSION) => { + if (signingVersion !== SIGNING_MESSAGE_VERSION) { + throw new Error(`Unsupported signing message version: ${signingVersion}`); + } + return JSON.stringify({ + domain: 'mayhem-consent', + signing_version: SIGNING_MESSAGE_VERSION, + rules_ver: ver, + rules_hash: hash, + }); +}; +export const providerLifecycleIntentMessage = (intent, signingVersion = SIGNING_MESSAGE_VERSION) => { + if (signingVersion !== SIGNING_MESSAGE_VERSION) { + throw new Error(`Unsupported signing message version: ${signingVersion}`); + } + return JSON.stringify({ + domain: 'mayhem-provider-lifecycle', + signing_version: SIGNING_MESSAGE_VERSION, + intent: stableValue(intent), + }); +}; +export const providerPayoutTargetBindingEvidence = (intent) => ({ + admin: intent.admin, + bootstrap: intent.bootstrap, + chain_id: intent.chain_id, + context_revision: intent.context_revision, + currency: intent.currency, + expires_after_epoch: intent.expires_after_epoch, + network: intent.network, + nonce: intent.nonce, + payment_config_version: intent.payment_config_version, + previous_revision: intent.previous_revision, + provider: intent.provider, + rail: intent.rail, + target: intent.target, + target_wallet: intent.target_wallet, +}); +export const providerPayoutTargetBindingMessage = (intent) => + `mayhem-provider-payout-target-binding-v1${stableJson( + providerPayoutTargetBindingEvidence(intent) + )}`; +export const providerPayoutBindingMessage = (intent) => + `mayhem-provider-payout-binding-v1${stableJson(intent)}`; +export const stripePayoutProcessorEvidence = (value) => ({ + account_id: value.account_id, + account_type: value.account_type, + country: value.country, + currency: value.currency, + mode: value.mode, + provider: value.provider, +}); +export const stripePayoutProcessorRevision = async (value) => { + const digest = await blake3(b4a.from(stableJson({ + domain: 'mayhem-stripe-payout-processor-evidence-v1', + evidence: stripePayoutProcessorEvidence(value), + }))); + return b4a.toString(digest, 'hex'); +}; +export const stripePayoutVerificationFeatureKey = async (value) => { + const digest = await blake3(b4a.from(stableJson({ + domain: 'mayhem-stripe-payout-verification-feature-v1', + value, + }))); + return `payout/stripe-verified/${value.provider}/${b4a.toString(digest, 'hex')}`; +}; +export const depositTnkIntentMessage = (intent) => + `mayhem-deposit-tnk-intent-v1${stableJson(intent)}`; +export const tapAccountBindingEvidence = (value) => ({ + user: value.user, + ethereum_address: value.ethereum_address.toLowerCase(), + chain_id: value.chain_id, + pool_address: value.pool_address.toLowerCase(), +}); +export const tapAccountBindingMessage = (value) => + `mayhem-tap-account-bind-v1${stableJson(tapAccountBindingEvidence(value))}`; +const canonicalSpendVoucherBody = (body) => { + if (!body || typeof body !== 'object' || Array.isArray(body)) return body; + const canonical = { + schema_version: body.schema_version, + session_id: body.session_id, + billing_id: body.billing_id, + billing_attempt: body.billing_attempt, + billing_prior_usage: body.billing_prior_usage, + billing_prior_au_owed_cum: body.billing_prior_au_owed_cum, + billing_epoch: body.billing_epoch, + reservation_id: body.reservation_id, + reservation_expires_after_epoch: body.reservation_expires_after_epoch, + reservation_receipt_grace_epochs: body.reservation_receipt_grace_epochs, + user: body.user, + provider: body.provider, + payout_revision: body.payout_revision, + rail: body.rail, + enclave_id: body.enclave_id, + model_id: body.model_id, + price_ver: body.price_ver, + locked_rate_map: body.locked_rate_map, + locked_per_req_au: body.locked_per_req_au, + locked_min_session_au: body.locked_min_session_au, + served_ctx: body.served_ctx, + }; + if (Array.isArray(body.required_modalities) && body.required_modalities.length > 0) { + canonical.required_modalities = body.required_modalities; + } + if (body.required_specialities && typeof body.required_specialities === 'object' && + !Array.isArray(body.required_specialities) && Object.keys(body.required_specialities).length > 0) { + canonical.required_specialities = body.required_specialities; + } + if (hasOwn(body, 'workflow')) canonical.workflow = canonicalWorkflowBinding(body.workflow); + if (hasOwn(body, 'ctx_bracket')) canonical.ctx_bracket = body.ctx_bracket; + if (hasOwn(body, 'ctx_bracket_table_ver')) canonical.ctx_bracket_table_ver = body.ctx_bracket_table_ver; + canonical.rules_ver = body.rules_ver; + canonical.max_spend_au = body.max_spend_au; + canonical.checkpoint_every = body.checkpoint_every; + return canonical; +}; +const canonicalReceiptBody = (body) => { + if (!body || typeof body !== 'object' || Array.isArray(body)) return body; + const canonical = { + schema_version: body.schema_version, + session_id: body.session_id, + billing_id: body.billing_id, + billing_attempt: body.billing_attempt, + billing_prior_usage: body.billing_prior_usage, + billing_prior_au_owed_cum: body.billing_prior_au_owed_cum, + billing_epoch: body.billing_epoch, + reservation_id: body.reservation_id, + reservation_expires_after_epoch: body.reservation_expires_after_epoch, + reservation_receipt_grace_epochs: body.reservation_receipt_grace_epochs, + payout_revision: body.payout_revision, + seq: body.seq, + final: body.final, + rail: body.rail, + user: body.user, + provider: body.provider, + enclave_id: body.enclave_id, + model_id: body.model_id, + price_ver: body.price_ver, + locked_rate_map: body.locked_rate_map, + locked_per_req_au: body.locked_per_req_au, + locked_min_session_au: body.locked_min_session_au, + served_ctx: body.served_ctx, + }; + if (hasOwn(body, 'ctx_bracket')) canonical.ctx_bracket = body.ctx_bracket; + if (hasOwn(body, 'ctx_bracket_table_ver')) canonical.ctx_bracket_table_ver = body.ctx_bracket_table_ver; + canonical.rules_ver = body.rules_ver; + if (hasOwn(body, 'workflow')) canonical.workflow = canonicalWorkflowBinding(body.workflow); + if (hasOwn(body, 'workflow_output')) canonical.workflow_output = canonicalWorkflowOutput(body.workflow_output); + canonical.usage = body.usage; + if (body.usage_attribution && typeof body.usage_attribution === 'object' && + !Array.isArray(body.usage_attribution) && Object.keys(body.usage_attribution).length > 0) { + canonical.usage_attribution = body.usage_attribution; + } + canonical.au_owed_cum = body.au_owed_cum; + canonical.prompt_hash = body.prompt_hash; + canonical.ts = body.ts; + return canonical; +}; +const canonicalWorkflowBinding = (workflow) => { + if (!workflow || typeof workflow !== 'object' || Array.isArray(workflow)) return workflow; + return { + endpoint_family: workflow.endpoint_family, + graph_hash: workflow.graph_hash, + runtime_id: workflow.runtime_id, + outcome_class: workflow.outcome_class, + quoted_usage: stableValue(workflow.quoted_usage), + }; +}; +const canonicalWorkflowOutput = (output) => { + if (!output || typeof output !== 'object' || Array.isArray(output)) return output; + return { + output_modalities: output.output_modalities, + metrics: stableValue(output.metrics), + }; +}; +export const receiptMessage = (body, signingVersion = SIGNING_MESSAGE_VERSION) => { + if (signingVersion !== SIGNING_MESSAGE_VERSION) { + throw new Error(`Unsupported signing message version: ${signingVersion}`); + } + return JSON.stringify({ + domain: 'mayhem-session-receipt', + signing_version: SIGNING_MESSAGE_VERSION, + body: canonicalReceiptBody(body), + }); +}; +export const spendVoucherMessage = (body, signingVersion = SIGNING_MESSAGE_VERSION) => { + if (signingVersion !== SIGNING_MESSAGE_VERSION) { + throw new Error(`Unsupported signing message version: ${signingVersion}`); + } + return JSON.stringify({ + domain: 'mayhem-spend-voucher', + signing_version: SIGNING_MESSAGE_VERSION, + body: canonicalSpendVoucherBody(body), + }); +}; +export const spendReservationEvidence = (value) => { + const voucher = stableValue(value.voucher); + if (voucher?.required_specialities && typeof voucher.required_specialities === 'object' && + !Array.isArray(voucher.required_specialities) && Object.keys(voucher.required_specialities).length === 0) { + delete voucher.required_specialities; + } + const evidence = { + contract_version: value.contract_version, + session_id: value.session_id, + reservation_id: value.reservation_id, + reservation_expires_after_epoch: value.reservation_expires_after_epoch, + reservation_receipt_grace_epochs: value.reservation_receipt_grace_epochs, + epoch: value.epoch, + at: value.at, + rail: value.rail, + user: value.user, + provider: value.provider, + enclave_id: value.enclave_id, + enclave_pubkey: value.enclave_pubkey, + model_id: value.model_id, + price_ver: value.price_ver, + rules_ver: value.rules_ver, + served_ctx: value.served_ctx, + required_modalities: value.required_modalities, + ctx_bracket: value.ctx_bracket, + ctx_bracket_table_ver: value.ctx_bracket_table_ver, + max_spend_au: value.max_spend_au, + voucher, + }; + if (value.required_specialities && typeof value.required_specialities === 'object' && + !Array.isArray(value.required_specialities) && Object.keys(value.required_specialities).length > 0) { + evidence.required_specialities = value.required_specialities; + } + if (value.workflow && typeof value.workflow === 'object' && !Array.isArray(value.workflow)) { + evidence.workflow = canonicalWorkflowBinding(value.workflow); + } + return evidence; +}; +export const spendReservationMessage = (value) => + `mayhem-spend-reservation-v1${stableJson(spendReservationEvidence(value))}`; +export const targetedSpendReservationEvidence = (value) => ({ + payout_revision: value.payout_revision, + reservation: spendReservationEvidence(value), +}); +export const targetedSpendReservationMessage = (value) => + `mayhem-targeted-spend-reservation-v1${stableJson( + targetedSpendReservationEvidence(value) + )}`; +export const recordUsageReceiptEvidence = (value) => ({ + contract_version: value.contract_version, + epoch: value.epoch, + payout_revision: value.payout_revision, + receipt: stableValue(value.receipt), +}); +export const recordUsageReceiptMessage = (value) => + `mayhem-record-usage-receipt-v1${stableJson(recordUsageReceiptEvidence(value))}`; +export const closeUsageReservationEvidence = (value) => ({ + op: value.op, + contract_version: value.contract_version, + billing_epoch: value.billing_epoch, + reservation_id: value.reservation_id, + reservation_expires_after_epoch: value.reservation_expires_after_epoch, + reservation_receipt_grace_epochs: value.reservation_receipt_grace_epochs, + billing_id: value.billing_id, + billing_attempt: value.billing_attempt, + session_id: value.session_id, + user: value.user, + rail: value.rail, + provider: value.provider, + payout_revision: value.payout_revision, + latest_receipt_seq: value.latest_receipt_seq, + latest_receipt_hash: value.latest_receipt_hash, + at: value.at, + reason: value.reason, + actor: value.actor, + actor_role: value.actor_role, +}); +export const closeUsageReservationMessage = (value) => + `mayhem-close-usage-reservation-v1${stableJson(closeUsageReservationEvidence(value))}`; +export const expireUsageReservationMessage = (value) => + `mayhem-expire-usage-reservation-v1${stableJson(closeUsageReservationEvidence(value))}`; +export const payoutPreparationEvidence = (value) => ({ + op: value.op, + contract_version: value.contract_version, + economic_op_id: value.economic_op_id, + rail: value.rail, + epoch: value.epoch, + epoch_apply_hash: value.epoch_apply_hash, + prepared_at: value.prepared_at, + kind: value.kind, + output_index: value.output_index, + payload_hash: value.payload_hash, + payload: stableValue(value.payload), + liability: stableValue(value.liability), + external_effect_ids: value.external_effect_ids, + admin: value.admin, +}); +export const payoutPreparationMessage = (value) => + `mayhem-targeted-payout-preparation-v1${stableJson(payoutPreparationEvidence(value))}`; +export const targetedPayoutControlEvidence = (value) => { + const evidence = { ...value }; + delete evidence.admin_sig; + return stableValue(evidence); +}; +export const targetedPayoutControlMessage = (value) => + `mayhem-targeted-payout-control-v1${stableJson(targetedPayoutControlEvidence(value))}`; +export const probeResultEvidence = (value, auditor) => ({ + auditor, + probe_id: value.probe_id, + probe_kind: value.probe_kind, + provider: value.provider, + enclave_id: value.enclave_id, + binary_hash: value.binary_hash, + canary_set: value.canary_set, + canary_prompt_id: value.canary_prompt_id, + challenge_epoch: value.challenge_epoch, + challenge_apply_hash: value.challenge_apply_hash, + challenge_seed: value.challenge_seed, + verification_method: value.verification_method, + session_receipt_hash: value.session_receipt_hash, + evidence_hash: value.evidence_hash, + match_bps: value.match_bps, + pass: value.pass, + epoch: value.epoch, + at: value.at, +}); +export const probeResultMessage = (value, auditor) => + `mayhem-probe-result-v1${stableJson(probeResultEvidence(value, auditor))}`; +export const providerKybEvidence = (value) => ({ + provider: value.provider, + legal_name: value.legal_name, + jurisdiction: value.jurisdiction, + proof_hash: value.proof_hash, + kyb_ref: value.kyb_ref, + verified_at: value.verified_at, + schema_version: value.schema_version, +}); +export const providerKybMessage = (value) => + `mayhem-provider-kyb-v1${stableJson(providerKybEvidence(value))}`; +export const roomSidechannelName = (roomId) => `mx/room/${roomId}`; +export const deriveRoomId = async (modelId, creator, nonce) => { + const digest = await blake3(b4a.from(`${modelId}${creator}${nonce}`)); + return b4a.toString(digest, 'hex').slice(0, 32); +}; + +const cloneValue = (value) => (value === undefined ? undefined : JSON.parse(JSON.stringify(value))); +const hasOwn = (value, key) => Object.prototype.hasOwnProperty.call(value, key); + +const versionedMayhemOperation = (op) => { + const dispatch = op?.value?.dispatch; + if (!dispatch || typeof dispatch !== 'object' || Array.isArray(dispatch)) { + return { present: false, version: null, operation: op }; + } + + if (op.type === 'feature' && dispatch.type === 'mayhem_feature' && + hasOwn(dispatch, 'contract_version')) { + const normalizedDispatch = { ...dispatch }; + const version = normalizedDispatch.contract_version; + delete normalizedDispatch.contract_version; + return { + present: true, + version, + operation: { + ...op, + value: { ...op.value, dispatch: normalizedDispatch }, + }, + }; + } + + if (op.type === 'tx' && dispatch.value && typeof dispatch.value === 'object' && + !Array.isArray(dispatch.value) && hasOwn(dispatch.value, 'contract_version')) { + const normalizedValue = { ...dispatch.value }; + const version = normalizedValue.contract_version; + delete normalizedValue.contract_version; + return { + present: true, + version, + operation: { + ...op, + value: { + ...op.value, + dispatch: { ...dispatch, value: normalizedValue }, + }, + }, + }; + } + + return { present: false, version: null, operation: op }; +}; + +export const validateMayhemOperationContractVersion = ( + op, + expectedVersion = CONTRACT_VERSION +) => { + if (!Number.isSafeInteger(expectedVersion) || expectedVersion < 1) { + throw new Error('Expected contract version must be a positive safe integer.'); + } + const versioned = versionedMayhemOperation(op); + if (versioned.present && + (!Number.isSafeInteger(versioned.version) || versioned.version !== expectedVersion)) { + const actual = Number.isSafeInteger(versioned.version) + ? versioned.version + : 'invalid'; + throw new Error( + `Contract upgrade required: expected CONTRACT_VERSION ${expectedVersion}, got ${actual}.` + ); + } + return versioned.operation; +}; +const compareCodepoint = (left, right) => { + const a = String(left); + const b = String(right); + if (a < b) return -1; + if (a > b) return 1; + return 0; +}; +const stableValue = (value) => { + if (Array.isArray(value)) return value.map((item) => stableValue(item)); + if (value && typeof value === 'object') { + return Object.fromEntries( + Object.keys(value) + .sort() + .map((key) => [key, stableValue(value[key])]) + ); + } + return value; +}; +const stableJson = (value) => JSON.stringify(stableValue(value)); + +const verifyEd25519Hex = (signature, message, publicKey) => { + const signatureHex = String(signature ?? '').toLowerCase(); + const publicKeyHex = String(publicKey ?? '').toLowerCase(); + const messageBytes = b4a.isBuffer(message) ? message : b4a.from(String(message)); + if (!/^[0-9a-f]{128}$/.test(signatureHex) || + !/^[0-9a-f]{64}$/.test(publicKeyHex) || + messageBytes.length === 0) { + return false; + } + const signatureBytes = b4a.from(signatureHex, 'hex'); + const publicKeyBytes = b4a.from(publicKeyHex, 'hex'); + try { + return PeerWallet.verify(signatureBytes, messageBytes, publicKeyBytes) === true; + } catch (_error) { + return false; + } +}; + +export const adminContractTxSigningValue = ({ + address, + context, + nonce, + prepared_command: preparedCommand, + sim, +}) => stableValue({ + address: String(address ?? '').trim().toLowerCase(), + context: { + contract_version: context?.contract_version, + msb_bootstrap: String(context?.msb_bootstrap ?? '').trim().toLowerCase(), + network_id: context?.network_id, + subnet_bootstrap: String(context?.subnet_bootstrap ?? '').trim().toLowerCase(), + }, + domain: 'mayhem-admin-contract-tx-v1', + nonce: String(nonce ?? '').trim().toLowerCase(), + prepared_command: preparedCommand, + sim: sim === true, +}); + +export const adminContractTxDigest = async (value) => b4a.toString( + await blake3(b4a.from(stableJson(adminContractTxSigningValue(value)), 'utf8')), + 'hex' +); + +const serializableFeatureResult = (value) => { + if (value === undefined) return null; + if (value instanceof Error) { + return { + name: value.name, + message: value.message, + }; + } + try { + return JSON.parse(JSON.stringify(value)); + } catch { + return String(value); + } +}; + +const ethereumPersonalMessageHash = (message) => { + const body = b4a.from(message, 'utf8'); + const prefix = b4a.from(`\x19Ethereum Signed Message:\n${body.length}`, 'utf8'); + return keccak256(b4a.concat([prefix, body])); +}; +const ethereumAddressFromPublicKey = (publicKey) => + `0x${b4a.toString(keccak256(publicKey.subarray(1)).subarray(12), 'hex')}`; + +export const contractParamDefinitions = () => cloneValue(PARAM_DEFINITIONS); +export const contractEpochAdminParamKeys = () => [...EPOCH_ADMIN_PARAM_KEYS]; +export const contractEpochAdminParamDefinitions = () => Object.fromEntries( + EPOCH_ADMIN_PARAM_KEYS.map((key) => [key, cloneValue(PARAM_DEFINITIONS[key])]) +); +export const contractCtxBracketTable = () => ({ + ver: CTX_BRACKET_TABLE_VERSION, + brackets: cloneValue(CTX_BRACKETS), +}); + +const ctxBracketForTokens = (tokens, table = CTX_BRACKETS) => { + if (!Number.isSafeInteger(tokens) || tokens < 0) return null; + const bracket = table.find((entry) => entry.max_ctx === null || tokens <= entry.max_ctx); + return bracket?.id ?? null; +}; + +class MayhemContract extends Contract { + // The base class runs one execution at a time and calls executeQueued() from + // inside that queue, so the per-operation fields set here belong to the call + // that is running and cannot be overwritten by an overlapping one. + async executeQueued(op, storage, consensusContext = null) { + // Admin Feature envelopes temporarily impersonate a command execution. Keep + // the actual consensus operation kind separately for paid-only operations. + this._mayhemExecutionType = op?.type; + try { + const versioned = versionedMayhemOperation(op); + const canonicalReplay = consumeCanonicalReplayContext(consensusContext, op, storage); + const historical = versioned.present && ( + ([23, 24, 25].includes(versioned.version) && canonicalReplay) || + ([24, 25].includes(versioned.version) && + await this.isPreparedCheckpointReplay(op, storage)) + ); + if (historical) { + // Replaying with today's pricing/receipt methods would produce a + // different signed view. Retained implementations preserve the exact + // historical transition, and never participate in new admission. + const Implementation = versioned.version === 23 + ? ContractV23 + : versioned.version === 24 + ? ContractV24 + : ContractV25; + this._historicalContracts ??= new Map(); + if (!this._historicalContracts.has(versioned.version)) { + this._historicalContracts.set(versioned.version, new Implementation(this.protocol, this.config)); + } + const previous = this._mayhemReplayStatus; + this._mayhemReplayStatus = { active: true, completed: previous?.completed ?? 0, + contractVersion: versioned.version, canonicalSignedLength: canonicalReplay?.signedLength ?? null }; + try { + const result = await this._historicalContracts.get(versioned.version).execute(op, storage); + this._mayhemReplayStatus.completed++; + return result; + } finally { this._mayhemReplayStatus.active = false; } + } + return await super.executeQueued(validateMayhemOperationContractVersion(op), storage); + } finally { + this._mayhemExecutionType = null; + } + } + + // Compatibility is attached to canonical preparation evidence, never a + // caller-supplied replay flag. TxOperation verifies the original MSB payment + // and exact dispatch hash before entering consensus execution here. + async isPreparedCheckpointReplay(op, storage) { + const dispatch = op?.value?.dispatch; + const value = dispatch?.value; + if (op?.type !== 'tx' || dispatch?.type !== 'stateCheckpoint' || + value?.op !== 'state_checkpoint' || value.contract_version !== 24 || + !Number.isSafeInteger(value.slot) || value.slot < 1 || + !this.isHexBytes(op.key, 32) || !this.isHexBytes(value.snapshot_hash, 32)) return false; + const read = async (key) => (await storage.get(key))?.value ?? null; + const admin = await read('admin'); + const snapshot = await read(`checkpoint/prepared/${value.slot}`); + if (op.value.ipk !== admin || !snapshot || + snapshot.type !== 'state_checkpoint_snapshot' || snapshot.schema_version !== 1 || + snapshot.slot !== value.slot || snapshot.prepared_by !== admin || + snapshot.state?.contract_version !== 24 || snapshot.snapshot_hash !== value.snapshot_hash) return false; + const { snapshot_hash: snapshotHash, ...body } = snapshot; + if (await this.opaqueHash('mayhem-checkpoint-state-v1', snapshot.state) !== snapshot.state_hash || + await this.opaqueHash('mayhem-checkpoint-snapshot-v1', body) !== snapshotHash) return false; + const existing = await read(`checkpoint/slot/${value.slot}`); + if (existing) return existing.tx === op.key && existing.snapshot_hash === snapshotHash && + existing.paid_by === admin; + const preparing = await read('checkpoint/preparing'); + return preparing?.slot === value.slot && preparing.snapshot_hash === snapshotHash; + } + + constructor(protocol, options = {}) { + super(protocol, options); + const self = this; + this._mayhemApplyStage = null; + + this.addFeature('mayhem_feature', async function () { + const result = await self.mayhemFeature(); + await self.recordMayhemFeatureResult(result); + return result; + }); + + this.addSchema('noop', { + value: { + $$strict: true, + $$type: 'object', + op: { type: 'string', min: 1, max: 32 }, + }, + }); + + this.addSchema('gatedNoop', { + value: { + $$strict: true, + $$type: 'object', + op: { type: 'string', min: 1, max: 32 }, + }, + }); + + this.addSchema('readKey', { + value: { + $$strict: true, + $$type: 'object', + op: { type: 'string', min: 1, max: 64 }, + key: { type: 'string', min: 1, max: 256 }, + }, + }); + + this.addSchema('setRules', { + value: { + $$strict: true, + $$type: 'object', + op: { type: 'string', min: 1, max: 64 }, + ver: { type: 'number', integer: true, min: 1 }, + hash: { type: 'string', min: 1, max: 128 }, + }, + }); + + this.addSchema('setParams', { + value: { + $$strict: true, + $$type: 'object', + op: { type: 'string', min: 1, max: 64 }, + submitted_at: { type: 'number', integer: true, min: 0 }, + effective_at: { type: 'number', integer: true, min: 0 }, + values: { type: 'any' }, + }, + }); + + this.addSchema('setPayments', { + value: { + $$strict: true, + $$type: 'object', + op: { type: 'string', min: 1, max: 64 }, + ver: { type: 'number', integer: true, min: 1 }, + fiat: { type: 'any' }, + tap: { type: 'any' }, + tnk: { type: 'any' }, + }, + }); + + this.addSchema('readParams', { + value: { + $$strict: true, + $$type: 'object', + op: { type: 'string', min: 1, max: 64 }, + at: { type: 'number', integer: true, min: 0 }, + keys: { + type: 'array', + max: 64, + items: { type: 'string', min: 1, max: 64 }, + optional: true, + }, + }, + }); + + this.addSchema('setCtxBrackets', { + value: { + $$strict: true, + $$type: 'object', + op: { type: 'string', min: 1, max: 64 }, + submitted_at: { type: 'number', integer: true, min: 0 }, + effective_at: { type: 'number', integer: true, min: 0 }, + brackets: { type: 'array', min: 1, max: 32, items: { type: 'any' } }, + }, + }); + + this.addSchema('readCtxBrackets', { + value: { + $$strict: true, + $$type: 'object', + op: { type: 'string', min: 1, max: 64 }, + at: { type: 'number', integer: true, min: 0, optional: true }, + ver: { type: 'number', integer: true, min: 1, optional: true }, + }, + }); + + this.addSchema('consent', { + value: { + $$strict: true, + $$type: 'object', + op: { type: 'string', min: 1, max: 64 }, + ver: { type: 'number', integer: true, min: 1 }, + hash: { type: 'string', min: 1, max: 128 }, + sig: { type: 'string', min: 1, max: 256 }, + }, + }); + + this.addFunction('registerProvider'); + + this.addSchema('setProviderRails', { + value: { + $$strict: true, + $$type: 'object', + op: { type: 'string', min: 1, max: 64 }, + rails: { + type: 'array', + min: 1, + max: 3, + items: { type: 'string', min: 1, max: 16 }, + }, + }, + }); + + this.addSchema('setProviderKyb', { + value: { + $$strict: true, + $$type: 'object', + op: { type: 'string', min: 1, max: 64 }, + provider: { type: 'string', min: 1, max: 128 }, + legal_name: { type: 'string', min: 1, max: 160 }, + jurisdiction: { type: 'string', min: 1, max: 64 }, + proof_hash: { type: 'string', min: 1, max: 128 }, + kyb_ref: { type: 'string', min: 1, max: 128 }, + verified_at: { type: 'number', integer: true, min: 0 }, + schema_version: { type: 'number', integer: true, min: 1, optional: true }, + admin_sig: { type: 'string', min: 1, max: 256 }, + }, + }); + + this.addSchema('revokeProviderKyb', { + value: { + $$strict: true, + $$type: 'object', + op: { type: 'string', min: 1, max: 64 }, + provider: { type: 'string', min: 1, max: 128 }, + reason_hash: { type: 'string', min: 1, max: 128, optional: true }, + }, + }); + + this.addSchema('banProvider', { + value: { + $$strict: true, + $$type: 'object', + op: { type: 'string', min: 1, max: 64 }, + provider: { type: 'string', min: 1, max: 128 }, + reason_hash: { type: 'string', min: 1, max: 128, optional: true }, + device_key: { type: 'string', min: 1, max: 128, optional: true }, + hardware_fingerprint: { type: 'string', min: 1, max: 128, optional: true }, + }, + }); + + this.addSchema('unban', { + value: { + $$strict: true, + $$type: 'object', + op: { type: 'string', min: 1, max: 64 }, + target_type: { type: 'string', min: 1, max: 32 }, + target: { type: 'string', min: 1, max: 128 }, + reason_hash: { type: 'string', min: 1, max: 128 }, + }, + }); + + this.addSchema('deviceRebind', { + value: { + $$strict: true, + $$type: 'object', + op: { type: 'string', min: 1, max: 64 }, + device_key: { type: 'string', min: 1, max: 128 }, + provider: { type: 'string', min: 1, max: 128 }, + reason_hash: { type: 'string', min: 1, max: 128 }, + }, + }); + + this.addSchema('migrateMarketPricing', { + value: { + $$strict: true, + $$type: 'object', + op: { type: 'string', min: 1, max: 64 }, + at: { type: 'number', integer: true, min: 0 }, + markets: { type: 'array', min: 0, max: 128, items: { type: 'any' } }, + }, + }); + + this.addSchema('setModelRef', { + value: { + $$strict: true, + $$type: 'object', + op: { type: 'string', min: 1, max: 64 }, + model_id: { type: 'string', min: 1, max: 256 }, + model_class: { type: 'string', min: 1, max: 64 }, + rate_map: { type: 'array', min: 1, max: RATE_MAP_MAX_ENTRIES, items: { type: 'any' } }, + source_hash: { type: 'string', min: 1, max: 128, optional: true }, + activity_calibration: { type: 'any', optional: true }, + }, + }); + + this.addSchema('publishCatalog', { + value: { + $$strict: true, + $$type: 'object', + op: { type: 'string', min: 1, max: 64 }, + catalog_id: { type: 'string', min: 1, max: 128 }, + source_kind: { type: 'string', min: 1, max: 32 }, + catalog_url: { type: 'string', min: 1, max: 512 }, + signature_url: { type: 'string', min: 1, max: 512 }, + catalog_hash: { type: 'string', min: 1, max: 128 }, + signature_hash: { type: 'string', min: 1, max: 128 }, + key_id: { type: 'string', min: 1, max: 128 }, + public_key: { type: 'string', min: 1, max: 128 }, + model_count: { type: 'number', integer: true, min: 1 }, + artifact_count: { type: 'number', integer: true, min: 1 }, + canaries: { type: 'array', max: 64, items: { type: 'any' } }, + parts_anchor: { type: 'any', optional: true }, + blessed_runtimes: { type: 'array', max: 32, optional: true, items: { type: 'any' } }, + outcome_classes: { type: 'array', max: 64, optional: true, items: { type: 'any' } }, + }, + }); + + this.addSchema('registerEnclave', { + value: { + $$strict: true, + $$type: 'object', + op: { type: 'string', min: 1, max: 64 }, + enclave_id: { type: 'string', min: 1, max: 128 }, + model_id: { type: 'string', min: 1, max: 256 }, + model_class: { type: 'string', min: 1, max: 64 }, + backend: { type: 'string', min: 1, max: 64 }, + artifact_root: { type: 'string', min: 1, max: 256 }, + artifact_root_kind: { type: 'string', min: 1, max: 64 }, + artifact_source: { type: 'any' }, + artifact_sidecars: { type: 'any', optional: true }, + source_sha256: { type: 'string', min: 1, max: 128, optional: true }, + manifest_hash: { type: 'string', min: 1, max: 128 }, + att_tier: { type: 'number', integer: true, min: 1, max: 4 }, + quant: { type: 'string', min: 1, max: 32, optional: true }, + binary_hash: { type: 'string', min: 1, max: 128 }, + launch_measurements: { type: 'any', optional: true }, + caps: { type: 'any' }, + }, + }); + + this.addSchema('updateEnclave', { + value: { + $$strict: true, + $$type: 'object', + op: { type: 'string', min: 1, max: 64 }, + enclave_id: { type: 'string', min: 1, max: 128 }, + model_class: { type: 'string', min: 1, max: 64, optional: true }, + backend: { type: 'string', min: 1, max: 64, optional: true }, + artifact_root: { type: 'string', min: 1, max: 256, optional: true }, + artifact_root_kind: { type: 'string', min: 1, max: 64, optional: true }, + artifact_source: { type: 'any', optional: true }, + artifact_sidecars: { type: 'any', optional: true }, + source_sha256: { type: 'string', min: 1, max: 128, optional: true }, + manifest_hash: { type: 'string', min: 1, max: 128, optional: true }, + att_tier: { type: 'number', integer: true, min: 1, max: 4, optional: true }, + quant: { type: 'string', min: 1, max: 32, optional: true }, + binary_hash: { type: 'string', min: 1, max: 128, optional: true }, + launch_measurements: { type: 'any', optional: true }, + caps: { type: 'any', optional: true }, + }, + }); + + this.addSchema('setEnclaveMinTier', { + value: { + $$strict: true, + $$type: 'object', + op: { type: 'string', min: 1, max: 64 }, + enclave_id: { type: 'string', min: 1, max: 128 }, + min_att_tier: { type: 'number', integer: true, min: 1, max: 4 }, + submitted_epoch: { type: 'number', integer: true, min: 0 }, + effective_epoch: { type: 'number', integer: true, min: 0 }, + submitted_at: { type: 'number', integer: true, min: 0 }, + reason_hash: { type: 'string', min: 1, max: 128 }, + }, + }); + + this.addSchema('joinEnclave', { + value: { + $$strict: true, + $$type: 'object', + op: { type: 'string', min: 1, max: 64 }, + enclave_id: { type: 'string', min: 1, max: 128 }, + att_tier: { type: 'number', integer: true, min: 1, max: 3 }, + attestation_head: { type: 'string', min: 64, max: 64 }, + hardware_fingerprint: { type: 'string', min: 1, max: 128, optional: true }, + device_key: { type: 'string', min: 1, max: 128, optional: true }, + }, + }); + + this.addSchema('leaveEnclave', { + value: { + $$strict: true, + $$type: 'object', + op: { type: 'string', min: 1, max: 64 }, + enclave_id: { type: 'string', min: 1, max: 128 }, + }, + }); + + this.addSchema('joinRoom', { + value: { + $$strict: true, + $$type: 'object', + op: { type: 'string', min: 1, max: 64 }, + room_id: { type: 'string', min: 1, max: 128 }, + enclave_id: { type: 'string', min: 1, max: 128 }, + }, + }); + + this.addSchema('leaveRoom', { + value: { + $$strict: true, + $$type: 'object', + op: { type: 'string', min: 1, max: 64 }, + room_id: { type: 'string', min: 1, max: 128 }, + enclave_id: { type: 'string', min: 1, max: 128 }, + }, + }); + + this.addSchema('retireEnclave', { + value: { + $$strict: true, + $$type: 'object', + op: { type: 'string', min: 1, max: 64 }, + enclave_id: { type: 'string', min: 1, max: 128 }, + }, + }); + + this.addSchema('openRoom', { + value: { + $$strict: true, + $$type: 'object', + op: { type: 'string', min: 1, max: 64 }, + enclave_id: { type: 'string', min: 1, max: 128 }, + model_id: { type: 'string', min: 1, max: 256, optional: true }, + nonce: { type: 'string', min: 1, max: 128 }, + label: { type: 'string', min: 1, max: 64 }, + policy: { type: 'any' }, + }, + }); + + this.addSchema('closeRoom', { + value: { + $$strict: true, + $$type: 'object', + op: { type: 'string', min: 1, max: 64 }, + room_id: { type: 'string', min: 1, max: 128 }, + }, + }); + + this.addSchema('setPrice', { + value: { + $$strict: true, + $$type: 'object', + op: { type: 'string', min: 1, max: 64 }, + enclave_id: { type: 'string', min: 1, max: 128 }, + rate_map: { type: 'array', min: 1, max: RATE_MAP_MAX_ENTRIES, items: { type: 'any' } }, + per_req_au: { type: 'string', min: 1, max: 80 }, + min_session_au: { type: 'string', min: 1, max: 80 }, + effective_at: { type: 'number', integer: true, min: 0 }, + ctx_bracket: { type: 'string', min: 1, max: 64, optional: true }, + }, + }); + + this.addSchema('readPrice', { + value: { + $$strict: true, + $$type: 'object', + op: { type: 'string', min: 1, max: 64 }, + enclave_id: { type: 'string', min: 1, max: 128 }, + at: { type: 'number', integer: true, min: 0 }, + ctx_bracket: { type: 'string', min: 1, max: 64, optional: true }, + }, + }); + + this.addSchema('recordReputationEvent', { + value: { + $$strict: true, + $$type: 'object', + op: { type: 'string', min: 1, max: 64 }, + provider: { type: 'string', min: 1, max: 128 }, + event_id: { type: 'string', min: 1, max: 128 }, + kind: { type: 'string', min: 1, max: 64 }, + epoch: { type: 'number', integer: true, min: 0 }, + at: { type: 'number', integer: true, min: 0 }, + paid_au: { type: 'string', min: 1, max: 80, optional: true }, + max_spend_au: { type: 'string', min: 1, max: 80, optional: true }, + evidence_hash: { type: 'string', min: 1, max: 128, optional: true }, + beneficiary: { type: 'string', min: 1, max: 128, optional: true }, + enclave_id: { type: 'string', min: 1, max: 128, optional: true }, + }, + }); + + this.addSchema('anchorReputation', { + value: { + $$strict: true, + $$type: 'object', + op: { type: 'string', min: 1, max: 64 }, + provider: { type: 'string', min: 1, max: 128 }, + epoch: { type: 'number', integer: true, min: 0 }, + folded_at: { type: 'number', integer: true, min: 0 }, + events_head: { type: 'string', min: 1, max: 128 }, + r_bps: { type: 'number', integer: true, min: 0, max: 10_000 }, + raw_milli: { type: 'number', integer: true }, + successful_sessions: { type: 'number', integer: true, min: 0 }, + provenance_violation: { type: 'boolean', optional: true }, + }, + }); + + this.addSchema('auditorRegister', { + value: { + $$strict: true, + $$type: 'object', + op: { type: 'string', min: 1, max: 64 }, + auditor: { type: 'string', min: 1, max: 128, optional: true }, + registered_at_seconds: { type: 'number', integer: true, min: 0, optional: true }, + }, + }); + + this.addSchema('auditorSlash', { + value: { + $$strict: true, + $$type: 'object', + op: { type: 'string', min: 1, max: 64 }, + auditor: { type: 'string', min: 64, max: 64 }, + provider: { type: 'string', min: 64, max: 64 }, + probe_id: { type: 'string', min: 1, max: 128 }, + epoch: { type: 'number', integer: true, min: 0 }, + reason: { type: 'string', min: 1, max: 64 }, + evidence_hash: { type: 'string', min: 64, max: 64 }, + at: { type: 'number', integer: true, min: 0 }, + }, + }); + + this.addSchema('probeResult', { + value: { + $$strict: true, + $$type: 'object', + op: { type: 'string', min: 1, max: 64 }, + probe_id: { type: 'string', min: 1, max: 128 }, + probe_kind: { type: 'string', min: 1, max: 64 }, + provider: { type: 'string', min: 1, max: 128 }, + enclave_id: { type: 'string', min: 1, max: 128, optional: true }, + binary_hash: { type: 'string', min: 1, max: 128, optional: true }, + epoch: { type: 'number', integer: true, min: 0 }, + at: { type: 'number', integer: true, min: 0 }, + canary_set: { type: 'string', min: 1, max: 128, optional: true }, + canary_prompt_id: { type: 'string', min: 1, max: 128, optional: true }, + challenge_epoch: { type: 'number', integer: true, min: 0, optional: true }, + challenge_apply_hash: { type: 'string', min: 64, max: 64, optional: true }, + challenge_seed: { type: 'string', min: 64, max: 64, optional: true }, + verification_method: { type: 'string', min: 1, max: 64, optional: true }, + match_bps: { type: 'number', integer: true, min: 0, max: 10_000, optional: true }, + pass: { type: 'boolean', optional: true }, + session_receipt_hash: { type: 'string', min: 1, max: 128, optional: true }, + evidence_hash: { type: 'string', min: 1, max: 128, optional: true }, + auditor_sig: { type: 'string', min: 1, max: 128, optional: true }, + }, + }); + + this.addSchema('prepareStateCheckpoint', { + value: { + $$strict: true, $$type: 'object', + op: { type: 'string', min: 1, max: 64 }, + slot: { type: 'number', integer: true, min: 1 }, + observed_at: { type: 'number', integer: true, min: 0 }, + contract_code_sha256: { type: 'string', min: 64, max: 64 }, + }, + }); + this.addSchema('stateCheckpoint', { + value: { + $$strict: true, $$type: 'object', + op: { type: 'string', min: 1, max: 64 }, + slot: { type: 'number', integer: true, min: 1 }, + snapshot_hash: { type: 'string', min: 64, max: 64 }, + }, + }); + + this.addSchema('epochFreeze', { + value: { + $$strict: true, + $$type: 'object', + op: { type: 'string', min: 1, max: 64 }, + epoch: { type: 'number', integer: true, min: 1 }, + at: { type: 'number', integer: true, min: 0 }, + }, + }); + + this.addSchema('epochCommit', { + value: { + $$strict: true, + $$type: 'object', + op: { type: 'string', min: 1, max: 64 }, + epoch: { type: 'number', integer: true, min: 1 }, + at: { type: 'number', integer: true, min: 0 }, + roots: { type: 'any' }, + totals: { type: 'any' }, + }, + }); + + this.addSchema('epochSealEmpty', { + value: { + $$strict: true, + $$type: 'object', + op: { type: 'string', min: 1, max: 64 }, + epoch: { type: 'number', integer: true, min: 1 }, + at: { type: 'number', integer: true, min: 0 }, + reason_hash: { type: 'string', min: 64, max: 64 }, + }, + }); + + this.addSchema('fraudProof', { + value: { + $$strict: true, + $$type: 'object', + op: { type: 'string', min: 1, max: 64 }, + epoch: { type: 'number', integer: true, min: 1 }, + proof_epoch: { type: 'number', integer: true, min: 1 }, + at: { type: 'number', integer: true, min: 0 }, + reason: { type: 'string', min: 1, max: 64 }, + receipt: { type: 'any', optional: true }, + claimed_au_owed_cum: { type: 'string', min: 1, max: 80, optional: true }, + previous_au_owed_cum: { type: 'string', min: 1, max: 80, optional: true }, + price_usage: { type: 'any', optional: true }, + }, + }); + + this.addSchema('dispute', { + value: { + $$strict: true, + $$type: 'object', + op: { type: 'string', min: 1, max: 64 }, + rail: { type: 'string', min: 1, max: 16 }, + session_id: { type: 'string', min: 64, max: 64 }, + reason: { type: 'string', min: 1, max: 64 }, + provider: { type: 'string', min: 64, max: 64 }, + counterparty: { type: 'string', min: 1, max: 128, optional: true }, + enclave_id: { type: 'string', min: 64, max: 64 }, + epoch: { type: 'number', integer: true, min: 0, optional: true }, + at: { type: 'number', integer: true, min: 0 }, + evidence_hash: { type: 'string', min: 1, max: 128, optional: true }, + evidence: { type: 'any', optional: true }, + }, + }); + + this.addSchema('disputeResolve', { + value: { + $$strict: true, + $$type: 'object', + op: { type: 'string', min: 1, max: 64 }, + dispute_id: { type: 'number', integer: true, min: 1 }, + outcome: { type: 'string', min: 1, max: 64 }, + deposit_action: { type: 'string', min: 1, max: 64 }, + rationale_hash: { type: 'string', min: 1, max: 128 }, + at: { type: 'number', integer: true, min: 0 }, + slash: { type: 'boolean', optional: true }, + beneficiary: { type: 'string', min: 1, max: 128, optional: true }, + }, + }); + + this.addSchema('disputeExpire', { + value: { + $$strict: true, + $$type: 'object', + op: { type: 'string', min: 1, max: 64 }, + dispute_id: { type: 'number', integer: true, min: 1 }, + at: { type: 'number', integer: true, min: 0 }, + }, + }); + + this.addSchema('fiatDeposit', { + value: { + $$strict: true, + $$type: 'object', + op: { type: 'string', min: 1, max: 64 }, + rail: { type: 'string', min: 1, max: 64 }, + who: { type: 'string', min: 1, max: 128 }, + au: { type: 'string', min: 1, max: 80 }, + ext_ref_hash: { type: 'string', min: 1, max: 128 }, + fiat_currency: { type: 'string', min: 3, max: 3 }, + fiat_amount_minor: { type: 'number', integer: true, min: 1, max: Number.MAX_SAFE_INTEGER }, + epoch: { type: 'number', integer: true, min: 1 }, + at: { type: 'number', integer: true, min: 0 }, + }, + }); + + this.addSchema('fiatChargeback', { + value: { + $$strict: true, + $$type: 'object', + op: { type: 'string', min: 1, max: 64 }, + rail: { type: 'string', min: 1, max: 64 }, + who: { type: 'string', min: 1, max: 128 }, + au: { type: 'string', min: 1, max: 80 }, + ext_ref_hash: { type: 'string', min: 1, max: 128 }, + dispute_ref_hash: { type: 'string', min: 1, max: 128 }, + fiat_currency: { type: 'string', min: 3, max: 3 }, + fiat_amount_minor: { type: 'number', integer: true, min: 1, max: Number.MAX_SAFE_INTEGER }, + epoch: { type: 'number', integer: true, min: 1 }, + at: { type: 'number', integer: true, min: 0 }, + }, + }); + + } + + async noop() { + const result = { + ok: true, + op: 'noop', + contract: 'mayhem', + version: CONTRACT_VERSION, + address: this.address, + }; + console.log('mayhem noop', result); + return result; + } + + async gatedNoop() { + const consentError = await this.requireConsent(); + if (consentError) return consentError; + + const result = { + ok: true, + op: 'gatedNoop', + contract: 'mayhem', + version: CONTRACT_VERSION, + address: this.address, + }; + console.log('mayhem gatedNoop', result); + return result; + } + + async readKey() { + const key = this.value?.key; + const value = typeof key === 'string' ? await this.get(key) : null; + console.log('mayhem readKey', key, '=>', value); + return value; + } + + async mayhemFeature() { + this._mayhemLastFeatureResult = undefined; + const rawKey = this.op?.key; + const key = typeof rawKey === 'string' && rawKey.startsWith('mayhem_') + ? rawKey.slice('mayhem_'.length) + : rawKey; + const value = this.value; + if (typeof key !== 'string' || !value || typeof value !== 'object' || Array.isArray(value)) { + return; + } + if (value.op === 'deposit_tnk') { + const result = await this.applyDepositTnkFeature(key, value); + this._mayhemLastFeatureResult = result; + return result; + } + if (value.op === 'consent') { + const result = await this.applyConsentFeature(key, value); + this._mayhemLastFeatureResult = result; + return result; + } + if (value.op === 'provider_lifecycle') { + const result = await this.applyProviderLifecycleFeature(key, value); + this._mayhemLastFeatureResult = result; + return result; + } + if (value.op === 'bind_provider_payout') { + const result = await this.applyProviderPayoutBindingFeature(key, value); + this._mayhemLastFeatureResult = result; + return result; + } + if (value.op === 'spend_reserve_targeted') { + const result = await this.applyTargetedSpendReserveFeature(key, value); + this._mayhemLastFeatureResult = result; + return result; + } + if (value.op === 'record_usage_receipt') { + const result = await this.applyRecordUsageReceiptFeature(key, value); + this._mayhemLastFeatureResult = result; + return result; + } + if (value.op === 'close_usage_reservation') { + const result = await this.applyCloseUsageReservationFeature(key, value); + this._mayhemLastFeatureResult = result; + return result; + } + if (value.op === 'expire_usage_reservation') { + const result = await this.applyCloseUsageReservationFeature(key, value, { + expiry: true, + }); + this._mayhemLastFeatureResult = result; + return result; + } + if (value.op === 'tap_account_bind') { + const result = await this.applyTapAccountBindingFeature(key, value); + this._mayhemLastFeatureResult = result; + return result; + } + const isRateFeature = value.op === 'rate_oracle' || value.op === 'tap_rate_oracle'; + if (isRateFeature) this._mayhemApplyStage = 'rate:require-admin'; + const adminError = await this.requireAdmin(this.address); + if (adminError) { + if (isRateFeature) this._mayhemApplyStage = null; + this._mayhemLastFeatureResult = adminError; + return adminError; + } + if (isRateFeature) this._mayhemApplyStage = 'rate:admin-verified'; + if (value.op === 'admin_contract_tx') { + const result = await this.applyAdminContractTxFeature(key, value); + this._mayhemLastFeatureResult = result; + return result; + } + if (value.op === 'publish_payout_context') { + const result = await this.applyPublishPayoutContextFeature(key, value); + this._mayhemLastFeatureResult = result; + return result; + } + if (value.op === 'schedule_payout_parameter') { + const result = await this.applySchedulePayoutParameterFeature(key, value); + this._mayhemLastFeatureResult = result; + return result; + } + if (value.op === 'verify_stripe_payout') { + const result = await this.applyVerifyStripePayoutFeature(key, value); + this._mayhemLastFeatureResult = result; + return result; + } + if (value.op === 'apply_targeted_epoch') { + const result = await this.applyTargetedEpochFeature(key, value); + this._mayhemLastFeatureResult = result; + return result; + } + if (value.op === 'commit_apply_targeted_epoch_page0') { + const result = await this.applyCommitTargetedEpochPageZeroFeature(key, value); + this._mayhemLastFeatureResult = result; + return result; + } + if (['tnk_deposit', 'tap_deposit', 'tap_deposit_reversal', 'fiat_deposit', 'fiat_chargeback'].includes(value.op)) { + const result = await this.applyDepositCreditFeature(key, value); + this._mayhemLastFeatureResult = result; + return result; + } + if (value.op === 'rate_oracle' || value.op === 'tap_rate_oracle') { + const result = await this.applyRateOracleFeature(key, value); + this._mayhemApplyStage = null; + this._mayhemLastFeatureResult = result; + return result; + } + if (value.op === 'prepare_targeted_payout') { + const result = await this.applyTargetedPayoutPreparationFeature(key, value); + this._mayhemLastFeatureResult = result; + return result; + } + if (value.op === 'prepare_targeted_payout_epoch') { + const result = await this.applyTargetedPayoutEpochFeature(key, value); + this._mayhemLastFeatureResult = result; + return result; + } + if (value.op === 'prepare_targeted_fiat_attempt') { + const result = await this.applyTargetedFiatAttemptFeature(key, value); + this._mayhemLastFeatureResult = result; + return result; + } + if (value.op === 'finalize_targeted_fiat_attempt') { + const result = await this.applyFinalizeTargetedFiatAttemptFeature(key, value); + this._mayhemLastFeatureResult = result; + return result; + } + if (value.op === 'settle_targeted_tap') { + const result = await this.applyTargetedTapSettlementFeature(key, value); + this._mayhemLastFeatureResult = result; + return result; + } + if (value.op === 'settle_targeted_tnk_output') { + const result = await this.applyTargetedTnkOutputFeature(key, value); + this._mayhemLastFeatureResult = result; + return result; + } + if (value.op === 'settle_targeted_fiat_output') { + const result = await this.applyTargetedFiatOutputFeature(key, value); + this._mayhemLastFeatureResult = result; + return result; + } + if (value.op === 'close_targeted_payout_epoch') { + const result = await this.applyCloseTargetedPayoutEpochFeature(key, value); + this._mayhemLastFeatureResult = result; + return result; + } + if (value.op === 'fiat_dust_sweep') { + const result = await this.applyFiatDustSweepFeature(key, value); + this._mayhemLastFeatureResult = result; + return result; + } + if (value.op === 'anchor_reputation') { + const result = await this.applyReputationAnchorFeature(key, value); + this._mayhemLastFeatureResult = result; + return result; + } + if (value.op === 'tier3_bless_measurement') { + const result = await this.applyTier3MeasurementFeature(key, value); + this._mayhemLastFeatureResult = result; + return result; + } + } + + async recordMayhemFeatureResult(result) { + const hash = String(this.op?.hash ?? '').toLowerCase(); + if (!/^[0-9a-f]+$/.test(hash)) return; + const error = result instanceof Error + ? result + : result === undefined + ? new Error('Feature returned no result.') + : result?.ok === false + ? new Error(String(result?.error?.message ?? result?.message ?? 'Feature rejected.')) + : null; + const ok = error === null; + await this.put(`fr/${hash}`, { + type: 'feature_result', + feature_key: this.op?.key ?? null, + hash, + address: this.address ?? null, + status: ok ? 'applied' : 'rejected', + ok, + result: ok ? serializableFeatureResult(result) : null, + error: ok + ? null + : { + name: error.name || 'FeatureRejected', + message: error.message || 'Feature rejected.', + }, + }); + } + + async applyAdminContractTxFeature(key, value) { + const shapeError = this.validateExactObjectKeys( + value, + ['op', 'tx', 'prepared_command', 'address', 'signature', 'nonce', 'sim', 'context'], + 'admin contract transaction feature' + ); + if (shapeError) return shapeError; + if ( + value.op !== 'admin_contract_tx' || + !this.isHexBytes(value.tx, 32) || + !this.isHexBytes(value.address, 32) || + !this.isHexBytes(value.signature, 64) || + !this.isHexBytes(value.nonce, 32) || + typeof value.sim !== 'boolean' || + !value.prepared_command || + typeof value.prepared_command !== 'object' || + Array.isArray(value.prepared_command) || + !value.context || + typeof value.context !== 'object' || + Array.isArray(value.context) + ) { + return new Error('Invalid admin contract transaction feature.'); + } + if (value.sim) { + return new Error('Simulated admin transactions must not be appended.'); + } + if (key !== `admin/contract-tx/${value.tx}`) { + return new Error('Admin contract transaction key does not match its digest.'); + } + const commandShapeError = this.validateExactObjectKeys( + value.prepared_command, + ['type', 'value'], + 'prepared admin command' + ); + if (commandShapeError) return commandShapeError; + const contextShapeError = this.validateExactObjectKeys( + value.context, + ['contract_version', 'msb_bootstrap', 'network_id', 'subnet_bootstrap'], + 'admin contract transaction context' + ); + if (contextShapeError) return contextShapeError; + const peer = this.protocol?.peer; + const configuredBootstrap = peer?.config?.bootstrap; + const subnetBootstrap = b4a.isBuffer(configuredBootstrap) + ? b4a.toString(configuredBootstrap, 'hex') + : typeof configuredBootstrap === 'string' && configuredBootstrap + ? configuredBootstrap.toLowerCase() + : b4a.isBuffer(peer?.base?.key) + ? b4a.toString(peer.base.key, 'hex') + : ''; + const runtimeContext = { + contract_version: CONTRACT_VERSION, + msb_bootstrap: String(peer?.msbClient?.bootstrapHex ?? '').toLowerCase(), + network_id: peer?.msbClient?.networkId, + subnet_bootstrap: subnetBootstrap, + }; + if ( + !Number.isSafeInteger(value.context.contract_version) || + !Number.isSafeInteger(value.context.network_id) || + !this.isHexBytes(value.context.msb_bootstrap, 32) || + !this.isHexBytes(value.context.subnet_bootstrap, 32) || + stableJson(value.context) !== stableJson(runtimeContext) + ) { + return new Error('Admin contract transaction context does not match this contract network.'); + } + const adminError = await this.requireAdmin(value.address); + if (adminError) return adminError; + const expectedTx = await adminContractTxDigest(value); + if (expectedTx !== value.tx) { + return new Error('Invalid admin contract transaction digest.'); + } + const verify = this.protocol?.peer?.wallet?.verify; + if ( + typeof verify !== 'function' || + verify.call( + this.protocol.peer.wallet, + value.signature, + b4a.from(value.tx, 'hex'), + value.address + ) !== true + ) { + return new Error('Invalid admin contract transaction signature.'); + } + + const existing = await this.get(key); + if (existing !== null) return existing; + + const type = value.prepared_command.type; + if ( + typeof type !== 'string' || + (!hasOwn(this.metadata.schemas, type) && !hasOwn(this.metadata.functions, type)) || + typeof this[type] !== 'function' + ) { + return new Error('Admin contract transaction type is not a registered Mayhem command.'); + } + if ( + hasOwn(this.metadata.schemas, type) && + this.check.validateSchema(type, value.prepared_command) !== true + ) { + return new Error('Invalid prepared admin command schema.'); + } + + const applyingRecord = { + ok: false, + op: 'admin_contract_tx', + status: 'applying', + tx: value.tx, + type, + result: null, + error: null, + }; + await this.put(key, applyingRecord); + + const context = { + address: this.address, + isFeature: this.is_feature, + op: this.op, + tx: this.tx, + value: this.value, + }; + let commandResult; + try { + this.address = value.address; + this.is_feature = false; + this.op = value.prepared_command; + this.tx = value.tx; + this.value = value.prepared_command.value; + commandResult = await this[type](); + } catch (error) { + commandResult = error instanceof Error ? error : new Error(String(error)); + } finally { + this.address = context.address; + this.is_feature = context.isFeature; + this.op = context.op; + this.tx = context.tx; + this.value = context.value; + } + const commandError = commandResult === undefined + ? new Error('Admin contract transaction returned no result.') + : commandResult instanceof Error + ? commandResult + : commandResult?.ok === false + ? new Error( + String( + commandResult?.error?.message ?? + commandResult?.message ?? + 'Admin command rejected.' + ) + ) + : null; + if (commandError) { + await this.put(key, { + ...applyingRecord, + status: 'rejected', + error: serializableFeatureResult(commandError), + }); + return commandError; + } + + const record = { + ok: true, + op: 'admin_contract_tx', + status: 'applied', + tx: value.tx, + type, + result: serializableFeatureResult(commandResult), + error: null, + }; + await this.put(key, record); + return record; + } + + async applyConsentFeature(key, value) { + const shapeError = this.validateExactObjectKeys( + value, + ['op', 'sender', 'ver', 'hash', 'sig'], + 'consent feature' + ); + if (shapeError) return; + if (!this.isHexBytes(value.sender, 32)) return; + if (!this.isHexBytes(value.sig, 64)) return; + const rules = await this.currentRules(); + if (!rules || value.ver !== rules.ver || value.hash !== rules.hash) return; + if (key !== `consent/${value.sender}/${value.ver}/${value.hash}`) return; + if (!this.verifyConsentSignature(value.sender, value.ver, value.hash, value.sig)) return; + + const record = { + ver: value.ver, + hash: value.hash, + at: key, + via: 'feature', + }; + await this.put(`consent/${value.sender}`, record); + console.log('mayhem consent feature', { address: value.sender, ...record }); + return { ok: true, op: 'consentFeature', address: value.sender, ...record }; + } + + async applyTapAccountBindingFeature(key, value) { + const normalized = this.normalizeTapAccountBinding(value); + if (normalized instanceof Error) return normalized; + const expectedKey = await this.tapAccountBindingFeatureKey(normalized); + if (expectedKey instanceof Error) return expectedKey; + if (key !== expectedKey) return new Error('Invalid TAP account binding key.'); + + const consentError = await this.requireConsent(normalized.user); + if (consentError) return consentError; + if (!this.verifyTapAccountUserSignature(normalized)) { + return new Error('Invalid TAP account user signature.'); + } + if (!this.verifyTapAccountEthereumSignature(normalized)) { + return new Error('Invalid TAP account Ethereum signature.'); + } + const poolError = await this.requireCanonicalTapPool( + normalized.chain_id, + normalized.pool_address + ); + if (poolError) return poolError; + + const bindingKey = this.tapAccountBindingKey( + normalized.user, + normalized.chain_id, + normalized.pool_address + ); + const addressKey = this.tapAccountAddressKey( + normalized.ethereum_address, + normalized.chain_id, + normalized.pool_address + ); + const existingBinding = await this.get(bindingKey); + if (existingBinding && existingBinding.ethereum_address !== normalized.ethereum_address) { + return new Error('Mayhem wallet is already bound to a different TAP account.'); + } + const existingAddress = await this.get(addressKey); + if (existingAddress && existingAddress.user !== normalized.user) { + return new Error('TAP account is already bound to a different Mayhem wallet.'); + } + + const source = await this.balanceRecord(normalized.ethereum_address, 'tap'); + if (source instanceof Error) return source; + const sourceError = this.guardianValidateBalanceRecord( + source, + normalized.ethereum_address, + 'tap' + ); + if (sourceError) return sourceError; + const target = await this.balanceRecord(normalized.user, 'tap'); + if (target instanceof Error) return target; + const targetError = this.guardianValidateBalanceRecord(target, normalized.user, 'tap'); + if (targetError) return targetError; + const nextAu = this.safeAddAu(target.au, source.au); + if (nextAu instanceof Error) return nextAu; + + const record = { + type: 'tap_account_binding', + user: normalized.user, + ethereum_address: normalized.ethereum_address, + chain_id: normalized.chain_id, + pool_address: normalized.pool_address, + user_sig: normalized.user_sig, + ethereum_sig: normalized.ethereum_sig, + status: 'active', + bound_at: key, + }; + await this.put(bindingKey, record); + await this.put(addressKey, record); + + await this.put(this.balanceKey(normalized.user, 'tap'), { + ...target, + user: normalized.user, + rail: 'tap', + au: nextAu, + updated_epoch: Math.max(target.updated_epoch, source.updated_epoch), + updated_at: key, + ...(!this.isZeroAu(source.au) ? { + last_tap_account_claim_au: source.au, + last_tap_account_claim_from: normalized.ethereum_address, + } : {}), + }); + await this.put(this.balanceKey(normalized.ethereum_address, 'tap'), { + ...source, + au: ZERO_AU, + updated_at: key, + tap_account_bound_to: normalized.user, + tap_account_binding_key: bindingKey, + }); + + return { + ok: true, + op: 'tapAccountBind', + user: normalized.user, + ethereum_address: normalized.ethereum_address, + chain_id: normalized.chain_id, + pool_address: normalized.pool_address, + claimed_au: source.au, + balance_au: nextAu, + idempotent: existingBinding !== null && existingAddress !== null && this.isZeroAu(source.au), + }; + } + + async applyProviderLifecycleFeature(key, value) { + const shapeError = this.validateExactObjectKeys( + value, + ['op', 'intent', 'sig'], + 'provider lifecycle feature' + ); + if (shapeError) return; + const intent = value.intent; + if (!intent || typeof intent !== 'object' || Array.isArray(intent)) return; + const intentError = this.validateProviderLifecycleIntent(intent); + if (intentError) return; + if (!this.isHexBytes(value.sig, 64)) return; + if (!(await this.providerLifecycleFeatureKeys(intent)).includes(key)) return; + if (!this.verifyProviderLifecycleSignature(intent.provider, intent, value.sig)) return; + + switch (intent.op) { + case 'register_provider': + return await this.applyRegisterProvider(intent.provider, key); + case 'join_enclave': + return await this.applyJoinEnclave( + intent.provider, + intent.enclave_id, + key, + intent.att_tier, + intent.attestation_head, + intent.hardware_fingerprint ?? null, + intent.device_key ?? null, + { + served_ctx: intent.served_ctx, + served_modalities: intent.served_modalities, + served_specialities: intent.served_specialities, + ctx_bracket: intent.ctx_bracket, + ctx_bracket_table_ver: intent.ctx_bracket_table_ver, + } + ); + case 'leave_enclave': + return await this.applyLeaveEnclave(intent.provider, intent.enclave_id, key); + case 'join_room': + return await this.applyJoinRoom(intent.provider, intent.room_id, intent.enclave_id, key); + case 'leave_room': + return await this.applyLeaveRoom(intent.provider, intent.room_id, intent.enclave_id, key); + case 'set_provider_rails': + return await this.applySetProviderRails(intent.provider, intent.rails, key); + default: + return; + } + } + + async applyProviderPayoutBindingFeature(key, value) { + const shapeError = this.validateExactObjectKeys( + value, + ['op', 'intent', 'provider_signature'], + 'provider payout binding feature' + ); + if (shapeError) return shapeError; + if (value.op !== 'bind_provider_payout') { + return new Error('Invalid provider payout binding feature op.'); + } + const intentError = await this.validateProviderPayoutBindingIntent( + value.intent, + { currentState: false } + ); + if (intentError) return intentError; + if (!this.isHexBytes(value.provider_signature, 64)) { + return new Error('Invalid provider payout binding signature.'); + } + + const revision = await this.providerPayoutBindingRevision(value.intent); + const expectedKey = this.providerPayoutBindingFeatureKey( + value.intent.rail, + value.intent.provider, + revision + ); + if (key !== expectedKey) return new Error('Invalid provider payout binding key.'); + if (!this.verifyProviderPayoutBindingSignature( + value.intent.provider, + value.intent, + value.provider_signature + )) { + return new Error('Invalid provider payout binding signature.'); + } + if (!this.verifyProviderPayoutTargetBindingSignature(value.intent)) { + return new Error('Invalid provider payout target ownership signature.'); + } + const adminError = await this.requireAdmin(this.address); + if (adminError) return adminError; + + const nonceKey = this.providerPayoutBindingNonceKey( + value.intent.provider, + value.intent.nonce + ); + const nonceRecord = await this.get(nonceKey); + if (nonceRecord) { + if (nonceRecord.revision !== revision) { + return new Error('Provider payout binding nonce already consumed.'); + } + const existing = await this.get(expectedKey); + if (!existing || + existing.type !== 'provider_payout_binding' || + existing.revision !== revision || + existing.provider !== value.intent.provider || + existing.rail !== value.intent.rail || + existing.nonce !== value.intent.nonce || + existing.provider_signature !== value.provider_signature || + existing.target_signature !== value.intent.target_signature) { + return new Error('Provider payout binding nonce record is inconsistent.'); + } + return { + ok: true, + op: 'bindProviderPayout', + provider: value.intent.provider, + rail: value.intent.rail, + revision, + activation_epoch: existing.activation_epoch, + idempotent: true, + }; + } + + const currentIntentError = await this.validateProviderPayoutBindingIntent(value.intent); + if (currentIntentError) return currentIntentError; + const provider = await this.get(`prov/${value.intent.provider}`); + if (!provider || provider.status !== 'active') { + return new Error('Active provider registration required.'); + } + if (!Array.isArray(provider.accepted_rails) || + !provider.accepted_rails.includes(value.intent.rail)) { + return new Error('Provider does not accept payout rail.'); + } + + const applyState = await this.epochApplyStateRecord(); + if ((applyState.pending_epoch ?? null) !== null) { + return new Error('Provider payout binding cannot rotate during a paged epoch apply.'); + } + if (value.intent.expires_after_epoch <= applyState.updated_epoch) { + return new Error('Provider payout binding intent expired.'); + } + const payoutParams = await this.activePayoutParamsAtEpoch(applyState.updated_epoch); + if (payoutParams instanceof Error) return payoutParams; + if (value.intent.expires_after_epoch - applyState.updated_epoch > + payoutParams.payout_intent_max_expiry_epochs) { + return new Error('Provider payout binding expiry is too far in the future.'); + } + + const context = await this.providerPayoutBindingContext(value.intent); + if (context instanceof Error) return context; + const currentContext = await this.get(PROVIDER_PAYOUT_BINDING_CONTEXT_CURRENT_KEY); + if (!currentContext) { + return new Error('Canonical provider payout context is not published.'); + } + const immutableContext = await this.get( + this.providerPayoutContextRecordKey( + value.intent.payment_config_version, + value.intent.context_revision + ) + ); + if (!immutableContext || immutableContext.revision !== value.intent.context_revision) { + return new Error('Referenced immutable provider payout context is not published.'); + } + if ( + currentContext.revision !== context.context_revision || + immutableContext.network !== context.network || + immutableContext.admin !== context.admin || + immutableContext.bootstrap !== context.bootstrap || + immutableContext.payment_config_version !== context.payment_config_version + ) { + return new Error('Provider payout binding canonical context mismatch.'); + } + + const pointerKey = this.providerPayoutBindingPointerKey( + value.intent.provider, + value.intent.rail + ); + const storedPointer = await this.get(pointerKey); + const activeBillingEpoch = applyState.updated_epoch + 1; + const pointer = storedPointer?.pending_revision !== null && + storedPointer?.pending_revision !== undefined && + storedPointer.pending_activation_epoch <= activeBillingEpoch + ? { + ...storedPointer, + current_revision: storedPointer.pending_revision, + pending_revision: null, + pending_activation_epoch: null, + } + : storedPointer; + const latestRevision = pointer?.latest_revision ?? null; + if (value.intent.previous_revision !== latestRevision) { + return new Error('Provider payout binding revision is stale.'); + } + if ((await this.get(expectedKey)) !== null) { + return new Error('Provider payout binding revision already exists.'); + } + + let stripeVerification = null; + if (value.intent.rail === 'fiat') { + stripeVerification = await this.providerStripePayoutVerificationForTarget( + value.intent.provider, + value.intent.target + ); + if (!stripeVerification || + stripeVerification.target !== value.intent.target || + stripeVerification.currency !== value.intent.currency || + stripeVerification.ready !== true) { + return new Error('Current ready provider-scoped Stripe verification required.'); + } + } + + const firstBinding = latestRevision === null; + const activationEpoch = applyState.updated_epoch + (firstBinding ? 1 : 2); + const binding = { + type: 'provider_payout_binding', + revision, + provider: value.intent.provider, + rail: value.intent.rail, + target: value.intent.target, + target_wallet: value.intent.target_wallet, + currency: value.intent.currency, + chain_id: value.intent.chain_id, + stripe_processor_revision: stripeVerification?.processor_revision ?? null, + stripe_verification_revision: stripeVerification?.revision ?? null, + network: value.intent.network, + admin: value.intent.admin, + bootstrap: value.intent.bootstrap, + context_revision: value.intent.context_revision, + payment_config_version: value.intent.payment_config_version, + previous_revision: value.intent.previous_revision, + nonce: value.intent.nonce, + expires_after_epoch: value.intent.expires_after_epoch, + activation_epoch: activationEpoch, + target_signature: value.intent.target_signature, + provider_signature: value.provider_signature, + verified: true, + bound_at: key, + bound_by: this.address, + bound_by_role: 'admin', + }; + const nextPointer = { + provider: value.intent.provider, + rail: value.intent.rail, + latest_revision: revision, + current_revision: firstBinding ? revision : pointer.current_revision, + pending_revision: firstBinding ? null : revision, + pending_activation_epoch: firstBinding ? null : activationEpoch, + updated_at: key, + }; + await this.put(expectedKey, binding); + await this.put(pointerKey, nextPointer); + await this.put(nonceKey, { + provider: value.intent.provider, + rail: value.intent.rail, + nonce: value.intent.nonce, + revision, + expires_after_epoch: value.intent.expires_after_epoch, + consumed_at: key, + }); + return { + ok: true, + op: 'bindProviderPayout', + provider: value.intent.provider, + rail: value.intent.rail, + revision, + activation_epoch: activationEpoch, + idempotent: false, + }; + } + + async applyPublishPayoutContextFeature(key, value) { + const shapeError = this.validateExactObjectKeys( + value, + [ + 'op', + 'network', + 'admin', + 'bootstrap', + 'payment_config_version', + 'payment_config_hash', + ], + 'provider payout context feature' + ); + if (shapeError) return shapeError; + if (value.op !== 'publish_payout_context') { + return new Error('Invalid provider payout context feature op.'); + } + if (!this.isSafeKeyPart(value.network) || + !this.isHexBytes(value.admin, 32) || + value.admin !== value.admin.toLowerCase() || + !this.isHexBytes(value.bootstrap, 32) || + value.bootstrap !== value.bootstrap.toLowerCase() || + !Number.isSafeInteger(value.payment_config_version) || + value.payment_config_version < 1 || + !this.isHexBytes(value.payment_config_hash, 32) || + value.payment_config_hash !== value.payment_config_hash.toLowerCase()) { + return new Error('Invalid provider payout context.'); + } + const admin = await this.get('admin'); + if (value.admin !== admin || this.address !== admin) { + return new Error('Provider payout context requires canonical admin authority.'); + } + const payments = await this.get('payments/current'); + if (!payments || payments.set_by !== admin || payments.set_by_role !== 'admin') { + return new Error('Canonical payment configuration required.'); + } + if (value.network !== payments.tnk?.network || + value.payment_config_version !== payments.ver) { + return new Error('Provider payout context does not match canonical payment configuration.'); + } + const paymentConfigHash = await this.providerPayoutPaymentConfigHash(payments); + if (value.payment_config_hash !== paymentConfigHash) { + return new Error('Provider payout context payment configuration hash mismatch.'); + } + const expectedKey = await this.providerPayoutContextFeatureKey(value); + if (key !== expectedKey) return new Error('Invalid provider payout context feature key.'); + + const record = { + type: 'provider_payout_context', + revision: await this.providerPayoutContextRevision(value), + network: value.network, + admin: value.admin, + bootstrap: value.bootstrap, + payment_config_version: value.payment_config_version, + payment_config_hash: value.payment_config_hash, + published_at: key, + published_by: this.address, + published_by_role: 'admin', + }; + const recordKey = this.providerPayoutContextRecordKey( + value.payment_config_version, + record.revision + ); + if (key !== recordKey) return new Error('Invalid provider payout context record key.'); + const existing = await this.get(recordKey); + if (existing) { + if (stableJson(existing) === stableJson(record)) { + const current = await this.get(PROVIDER_PAYOUT_BINDING_CONTEXT_CURRENT_KEY); + if (current?.revision !== record.revision) { + return new Error('Immutable provider payout context is not current.'); + } + return { + ok: true, + op: 'publishPayoutContext', + context: existing, + idempotent: true, + }; + } + return new Error('Immutable provider payout context record already exists.'); + } + const current = await this.get(PROVIDER_PAYOUT_BINDING_CONTEXT_CURRENT_KEY); + if (current && current.payment_config_version >= value.payment_config_version) { + return new Error('Provider payout context payment config version must increase.'); + } + await this.put(recordKey, record); + await this.put(PROVIDER_PAYOUT_BINDING_CONTEXT_CURRENT_KEY, { + type: 'provider_payout_context_pointer', + payment_config_version: value.payment_config_version, + revision: record.revision, + record_key: recordKey, + updated_at: key, + updated_by: this.address, + updated_by_role: 'admin', + }); + return { + ok: true, + op: 'publishPayoutContext', + context: record, + idempotent: false, + }; + } + + async applySchedulePayoutParameterFeature(key, value) { + const shapeError = this.validateExactObjectKeys( + value, + ['op', 'key', 'value', 'effective_epoch'], + 'payout parameter feature' + ); + if (shapeError) return shapeError; + if (value.op !== 'schedule_payout_parameter' || + !Object.hasOwn(PAYOUT_PARAM_DEFINITIONS, value.key)) { + return new Error('Invalid payout parameter feature.'); + } + const definition = PAYOUT_PARAM_DEFINITIONS[value.key]; + if (!Number.isSafeInteger(value.value) || + value.value < definition.min || + value.value > definition.max || + !Number.isSafeInteger(value.effective_epoch) || + value.effective_epoch < 1) { + return new Error('Invalid payout parameter value or activation epoch.'); + } + const adminError = await this.requireAdmin(this.address); + if (adminError) return adminError; + const applyState = await this.epochApplyStateRecord(); + if (value.effective_epoch <= applyState.updated_epoch) { + return new Error('Payout parameter activation epoch must be in the future.'); + } + const expectedKey = await this.payoutParameterFeatureKey(value); + if (key !== expectedKey) return new Error('Invalid payout parameter feature key.'); + + const recordKey = this.payoutParameterKey(value.key); + const schedule = await this.payoutParameterRecord(value.key); + const current = schedule.pending && + schedule.pending.effective_epoch <= applyState.updated_epoch + ? schedule.pending + : schedule.current; + const pending = schedule.pending && + schedule.pending.effective_epoch > applyState.updated_epoch + ? schedule.pending + : null; + const nextEntry = { + key: value.key, + value: value.value, + effective_epoch: value.effective_epoch, + scheduled_at: key, + scheduled_by: this.address, + scheduled_by_role: 'admin', + }; + if (pending) { + if (stableJson(pending) === stableJson(nextEntry)) { + return { + ok: true, + op: 'schedulePayoutParameter', + key: value.key, + value: value.value, + effective_epoch: value.effective_epoch, + idempotent: true, + }; + } + return new Error('A payout parameter update is already pending.'); + } + await this.put(recordKey, { key: value.key, current, pending: nextEntry }); + return { + ok: true, + op: 'schedulePayoutParameter', + key: value.key, + value: value.value, + effective_epoch: value.effective_epoch, + idempotent: false, + }; + } + + async applyVerifyStripePayoutFeature(key, value) { + const shapeError = this.validateExactObjectKeys( + value, + [ + 'op', + 'provider', + 'account_id', + 'account_type', + 'country', + 'currency', + 'mode', + 'verification_kind', + 'source_provider', + 'processor_revision', + 'previous_verification', + 'details_submitted', + 'payouts_enabled', + 'transfers_enabled', + 'network', + 'admin', + 'bootstrap', + 'context_revision', + 'payment_config_version', + 'request_nonce', + ], + 'Stripe payout verification feature' + ); + if (shapeError) return shapeError; + if (value.op !== 'verify_stripe_payout' || + !this.isHexBytes(value.provider, 32) || + value.provider !== value.provider.toLowerCase() || + typeof value.account_id !== 'string' || + !/^acct_[A-Za-z0-9._-]+$/.test(value.account_id) || + !['express', 'standard', 'custom'].includes(value.account_type) || + typeof value.country !== 'string' || + !/^[A-Z]{2}$/.test(value.country) || + !['adopt', 'onboard', 'status', 'relink'].includes(value.verification_kind) || + (value.verification_kind === 'adopt' && value.account_type !== 'standard') || + (value.source_provider !== null && + (!this.isHexBytes(value.source_provider, 32) || + value.source_provider !== value.source_provider.toLowerCase())) || + (value.verification_kind === 'relink' && + (value.source_provider === null || value.source_provider === value.provider)) || + (value.verification_kind !== 'relink' && value.source_provider !== null) || + !this.isHexBytes(value.processor_revision, 32) || + value.processor_revision !== value.processor_revision.toLowerCase() || + (value.previous_verification !== null && + (!this.isHexBytes(value.previous_verification, 32) || + value.previous_verification !== value.previous_verification.toLowerCase())) || + !['live', 'test'].includes(value.mode) || + typeof value.details_submitted !== 'boolean' || + typeof value.payouts_enabled !== 'boolean' || + typeof value.transfers_enabled !== 'boolean' || + !this.isHexBytes(value.admin, 32) || + value.admin !== value.admin.toLowerCase() || + !this.isHexBytes(value.bootstrap, 32) || + value.bootstrap !== value.bootstrap.toLowerCase() || + !this.isHexBytes(value.context_revision, 32) || + value.context_revision !== value.context_revision.toLowerCase() || + !Number.isSafeInteger(value.payment_config_version) || + value.payment_config_version < 1 || + !this.isHexBytes(value.request_nonce, 32) || + value.request_nonce !== value.request_nonce.toLowerCase()) { + return new Error('Invalid Stripe payout verification.'); + } + const processorRevision = await stripePayoutProcessorRevision(value); + if (value.processor_revision !== processorRevision) { + return new Error('Stripe payout processor evidence revision mismatch.'); + } + const adminError = await this.requireAdmin(this.address); + if (adminError) return adminError; + const admin = await this.get('admin'); + if (value.admin !== admin) { + return new Error('Stripe payout verification admin is not canonical.'); + } + const expectedKey = await stripePayoutVerificationFeatureKey(value); + if (key !== expectedKey) return new Error('Invalid Stripe payout verification key.'); + const record = { + type: 'stripe_payout_verification', + revision: key.split('/').at(-1), + provider: value.provider, + target: value.account_id, + account_type: value.account_type, + country: value.country, + currency: value.currency, + mode: value.mode, + verification_kind: value.verification_kind, + source_provider: value.source_provider, + processor_revision: value.processor_revision, + previous_verification: value.previous_verification, + details_submitted: value.details_submitted, + payouts_enabled: value.payouts_enabled, + transfers_enabled: value.transfers_enabled, + ready: value.details_submitted && + value.payouts_enabled && + value.transfers_enabled, + network: value.network, + admin: value.admin, + bootstrap: value.bootstrap, + context_revision: value.context_revision, + payment_config_version: value.payment_config_version, + request_nonce: value.request_nonce, + verified_at: key, + verified_by: this.address, + verified_by_role: 'admin', + }; + const existing = await this.get(key); + if (existing) { + if (stableJson(existing) !== stableJson(record)) { + return new Error('Stripe payout verification record already exists.'); + } + return { + ok: true, + op: 'verifyStripePayout', + provider: value.provider, + revision: record.revision, + idempotent: true, + }; + } + const nonceKey = `payout/stripe-verified/nonce/${value.provider}/${value.request_nonce}`; + const nonceRecord = await this.get(nonceKey); + if (nonceRecord) { + return new Error('Stripe payout verification request nonce already consumed.'); + } + const currentPointer = await this.get( + `payout/stripe-verified/current/${value.provider}` + ); + if ((currentPointer?.revision ?? null) !== value.previous_verification) { + return new Error('Stripe payout verification revision is stale.'); + } + + const contextPointer = await this.get(PROVIDER_PAYOUT_BINDING_CONTEXT_CURRENT_KEY); + const context = await this.get( + this.providerPayoutContextRecordKey( + value.payment_config_version, + value.context_revision + ) + ); + if (!contextPointer || + contextPointer.revision !== value.context_revision || + !context || + context.network !== value.network || + context.admin !== value.admin || + context.bootstrap !== value.bootstrap || + context.payment_config_version !== value.payment_config_version) { + return new Error('Stripe payout verification context is not current.'); + } + if ((value.network === 'mainnet' && value.mode !== 'live') || + (value.network !== 'mainnet' && value.mode !== 'test')) { + return new Error('Stripe payout verification mode does not match canonical network.'); + } + const payments = await this.get('payments/current'); + if (!payments || + payments.ver !== value.payment_config_version || + payments.set_by !== admin || + payments.set_by_role !== 'admin' || + payments.fiat?.processor !== 'stripe' || + !Array.isArray(payments.fiat.payout_currencies) || + !payments.fiat.payout_currencies.includes(value.currency)) { + return new Error('Stripe payout verification currency is not canonical.'); + } + const provider = await this.get(`prov/${value.provider}`); + if (!provider || provider.status !== 'active' || + !Array.isArray(provider.accepted_rails) || + !provider.accepted_rails.includes('fiat')) { + return new Error('Stripe payout verification requires an active fiat provider.'); + } + await this.put(key, record); + await this.put(nonceKey, { + provider: value.provider, + request_nonce: value.request_nonce, + processor_revision: value.processor_revision, + revision: record.revision, + record_key: key, + consumed_at: key, + }); + const verificationPointer = { + provider: value.provider, + revision: record.revision, + record_key: key, + target: value.account_id, + currency: value.currency, + processor_revision: value.processor_revision, + ready: record.ready, + details_submitted: value.details_submitted, + payouts_enabled: value.payouts_enabled, + transfers_enabled: value.transfers_enabled, + context_revision: value.context_revision, + payment_config_version: value.payment_config_version, + updated_at: key, + updated_by: this.address, + updated_by_role: 'admin', + }; + await this.put(`payout/stripe-verified/current/${value.provider}`, verificationPointer); + await this.put( + this.providerStripePayoutVerificationTargetKey(value.provider, value.account_id), + verificationPointer + ); + return { + ok: true, + op: 'verifyStripePayout', + provider: value.provider, + revision: record.revision, + idempotent: false, + }; + } + + async applySpendReserveFeature(key, value) { + const normalized = await this.normalizeSpendReserveValue(value); + if (normalized instanceof Error) return normalized; + normalized.voucher_hash = await this.opaqueHash('mayhem-spend-voucher-record-v1', normalized.voucher_body); + const expectedKey = await this.spendReservationFeatureKey(normalized); + if (expectedKey instanceof Error) return expectedKey; + if (key !== expectedKey) return; + if (!this.verifySpendVoucherSignature(normalized.user, normalized.voucher_body, normalized.voucher.user_sig)) { + return new Error('Invalid spend voucher signature.'); + } + if (!this.verifySpendReservationSignature(normalized.provider, normalized)) { + return new Error('Invalid spend reservation provider signature.'); + } + + const applyState = await this.epochApplyStateRecord(); + const activeEpoch = (applyState.pending_epoch ?? applyState.updated_epoch) + 1; + if (normalized.epoch !== activeEpoch) { + return new Error('Spend reservation epoch is not the active billing epoch.'); + } + + const provider = await this.get(`prov/${normalized.provider}`); + if (!provider || provider.status !== 'active') return new Error('Provider registration required.'); + if (!Array.isArray(provider.accepted_rails) || !provider.accepted_rails.includes(normalized.rail)) { + return new Error('Provider does not accept payment rail.'); + } + const enclave = await this.get(`enclave/${normalized.enclave_id}`); + if (!enclave || enclave.status !== 'active') return new Error('Admin enclave is not active.'); + const enclaveError = this.requireAdminCreatedEnclave(enclave); + if (enclaveError) return enclaveError; + const serve = await this.get(`serve/${normalized.provider}/${normalized.enclave_id}`); + if (!serve || serve.status !== 'active') { + return new Error('Provider is not actively serving this admin enclave.'); + } + const serveTermsError = this.validateCommittedServeTerms(serve, normalized); + if (serveTermsError) return serveTermsError; + const priceCtxBracket = normalized.ctx_bracket ?? null; + const priceError = await this.requireCurrentAdminPrice(normalized.enclave_id, priceCtxBracket); + if (priceError) return priceError; + const lockedPrice = await this.get( + this.priceRecordKey(normalized.enclave_id, normalized.price_ver, priceCtxBracket) + ); + if (!lockedPrice || lockedPrice.denom !== PRICE_DENOMINATION) { + return new Error('Spend reservation locked price version is not known.'); + } + if (lockedPrice.set_by_role !== 'admin') { + return new Error('Spend reservation locked price is not admin-set.'); + } + if ((lockedPrice.ctx_bracket ?? null) !== priceCtxBracket) { + return new Error('Spend reservation locked price context bracket mismatch.'); + } + if ((lockedPrice.ctx_bracket_table_ver ?? null) !== (normalized.ctx_bracket_table_ver ?? null)) { + return new Error('Spend reservation locked price context bracket table mismatch.'); + } + if (stableJson(normalized.locked_rate_map) !== stableJson(lockedPrice.rate_map)) { + return new Error('Spend reservation locked rate_map does not match price version.'); + } + if (this.compareAu(normalized.locked_per_req_au, lockedPrice.per_req_au ?? ZERO_AU) !== 0) { + return new Error('Spend reservation locked per_req_au does not match price version.'); + } + if (this.compareAu(normalized.locked_min_session_au, lockedPrice.min_session_au ?? ZERO_AU) !== 0) { + return new Error('Spend reservation locked min_session_au does not match price version.'); + } + const rules = await this.currentRules(); + if (!rules || rules.ver !== normalized.rules_ver) { + return new Error('Spend reservation rules version is not current.'); + } + + const balance = await this.balanceRecord(normalized.user, normalized.rail); + if (balance instanceof Error) return balance; + const balanceError = this.guardianValidateBalanceRecord(balance, normalized.user, normalized.rail); + if (balanceError) return balanceError; + + const holdKey = this.spendHoldKey(normalized.user, normalized.rail, normalized.epoch); + const hold = await this.normalizeSpendHoldRecord( + (await this.get(holdKey)) ?? null, + normalized.user, + normalized.rail, + normalized.epoch + ); + if (hold instanceof Error) return hold; + const existing = hold.sessions.find((session) => session.session_id === normalized.session_id); + if (existing) { + if ( + existing.provider !== normalized.provider || + existing.enclave_id !== normalized.enclave_id || + existing.price_ver !== normalized.price_ver || + stableJson(existing.locked_rate_map) !== stableJson(normalized.locked_rate_map) || + this.compareAu(existing.locked_per_req_au, normalized.locked_per_req_au) !== 0 || + this.compareAu(existing.locked_min_session_au, normalized.locked_min_session_au) !== 0 || + existing.served_ctx !== normalized.served_ctx || + stableJson(existing.required_modalities) !== stableJson(normalized.required_modalities) || + stableJson(existing.required_specialities) !== stableJson(normalized.required_specialities) || + stableJson(existing.workflow ?? null) !== stableJson(normalized.workflow ?? null) || + existing.ctx_bracket !== normalized.ctx_bracket || + existing.ctx_bracket_table_ver !== normalized.ctx_bracket_table_ver || + this.compareAu(existing.max_spend_au, normalized.max_spend_au) !== 0 || + existing.voucher_hash !== normalized.voucher_hash + ) { + return new Error('Spend reservation session already exists with different terms.'); + } + const availableAu = this.compareAu(hold.reserved_au, balance.au) >= 0 + ? ZERO_AU + : this.safeSubAu(balance.au, hold.reserved_au); + if (availableAu instanceof Error) return availableAu; + return { + ok: true, + op: 'spendReserve', + session_id: normalized.session_id, + epoch: normalized.epoch, + rail: normalized.rail, + user: normalized.user, + provider: normalized.provider, + reserved_au: hold.reserved_au, + available_au: availableAu, + idempotent: true, + }; + } + + const nextReservedAu = this.safeAddAu(hold.reserved_au, normalized.max_spend_au); + if (nextReservedAu instanceof Error) return nextReservedAu; + if (this.compareAu(nextReservedAu, balance.au) > 0) { + return new Error('Insufficient unreserved credit balance.'); + } + const session = { + session_id: normalized.session_id, + provider: normalized.provider, + enclave_id: normalized.enclave_id, + price_ver: normalized.price_ver, + locked_rate_map: normalized.locked_rate_map, + locked_per_req_au: normalized.locked_per_req_au, + locked_min_session_au: normalized.locked_min_session_au, + served_ctx: normalized.served_ctx, + required_modalities: normalized.required_modalities.slice(), + required_specialities: cloneValue(normalized.required_specialities), + ...(normalized.workflow ? { workflow: cloneValue(normalized.workflow) } : {}), + ctx_bracket: normalized.ctx_bracket, + ctx_bracket_table_ver: normalized.ctx_bracket_table_ver, + rules_ver: normalized.rules_ver, + max_spend_au: normalized.max_spend_au, + voucher_hash: normalized.voucher_hash, + feature_key: key, + reserved_at: normalized.at, + recorded_at: this.tx, + }; + const nextHold = { + ...hold, + balance_au_at_last_reserve: balance.au, + reserved_au: nextReservedAu, + sessions: [...hold.sessions, session].sort((a, b) => compareCodepoint(a.session_id, b.session_id)), + updated_at: this.tx, + }; + await this.put(holdKey, nextHold); + const availableAu = this.safeSubAu(balance.au, nextHold.reserved_au); + if (availableAu instanceof Error) return availableAu; + return { + ok: true, + op: 'spendReserve', + session_id: normalized.session_id, + epoch: normalized.epoch, + rail: normalized.rail, + user: normalized.user, + provider: normalized.provider, + reserved_au: nextHold.reserved_au, + available_au: availableAu, + idempotent: false, + }; + } + + async applyTargetedSpendReserveFeature(key, value) { + const normalized = await this.normalizeTargetedSpendReserveValue(value); + if (normalized instanceof Error) return normalized; + normalized.voucher_hash = await this.opaqueHash( + 'mayhem-spend-voucher-record-v1', + normalized.voucher_body + ); + const binding = await this.providerPayoutBindingForEpoch( + normalized.provider, + normalized.rail, + value.payout_revision, + normalized.epoch, + { requireCurrentReadiness: true } + ); + if (binding instanceof Error) return binding; + const expectedKey = await this.targetedSpendReservationFeatureKey(value); + if (expectedKey instanceof Error) return expectedKey; + if (key !== expectedKey) return new Error('Invalid targeted spend reservation key.'); + if (!this.verifySpendVoucherSignature( + normalized.user, + normalized.voucher_body, + normalized.voucher.user_sig + )) { + return new Error('Invalid spend voucher signature.'); + } + const verify = this.protocol?.peer?.wallet?.verify; + if (typeof verify !== 'function' || + verify.call( + this.protocol.peer.wallet, + normalized.provider_sig, + targetedSpendReservationMessage({ + ...normalized, + payout_revision: value.payout_revision, + }), + normalized.provider + ) !== true) { + return new Error('Invalid targeted spend reservation provider signature.'); + } + + const applyState = await this.epochApplyStateRecord(); + const activeEpoch = (applyState.pending_epoch ?? applyState.updated_epoch) + 1; + if (normalized.epoch !== activeEpoch) { + return new Error('Spend reservation epoch is not the active billing epoch.'); + } + + const provider = await this.get(`prov/${normalized.provider}`); + if (!provider || provider.status !== 'active') return new Error('Provider registration required.'); + if (!Array.isArray(provider.accepted_rails) || !provider.accepted_rails.includes(normalized.rail)) { + return new Error('Provider does not accept payment rail.'); + } + const enclave = await this.get(`enclave/${normalized.enclave_id}`); + if (!enclave || enclave.status !== 'active') return new Error('Admin enclave is not active.'); + const enclaveError = this.requireAdminCreatedEnclave(enclave); + if (enclaveError) return enclaveError; + if (enclave.model_id !== normalized.model_id) { + return new Error('Spend reservation model does not match admin enclave.'); + } + const serve = await this.get(`serve/${normalized.provider}/${normalized.enclave_id}`); + if (!serve || serve.status !== 'active') { + return new Error('Provider is not actively serving this admin enclave.'); + } + const serveTermsError = this.validateCommittedServeTerms(serve, normalized); + if (serveTermsError) return serveTermsError; + const priceCtxBracket = normalized.ctx_bracket ?? null; + const priceError = await this.requireCurrentAdminPrice(normalized.enclave_id, priceCtxBracket); + if (priceError) return priceError; + const lockedPrice = await this.get( + this.priceRecordKey(normalized.enclave_id, normalized.price_ver, priceCtxBracket) + ); + if (!lockedPrice || lockedPrice.denom !== PRICE_DENOMINATION) { + return new Error('Spend reservation locked price version is not known.'); + } + if (lockedPrice.set_by_role !== 'admin') { + return new Error('Spend reservation locked price is not admin-set.'); + } + if ((lockedPrice.ctx_bracket ?? null) !== priceCtxBracket) { + return new Error('Spend reservation locked price context bracket mismatch.'); + } + if ((lockedPrice.ctx_bracket_table_ver ?? null) !== (normalized.ctx_bracket_table_ver ?? null)) { + return new Error('Spend reservation locked price context bracket table mismatch.'); + } + if (stableJson(normalized.locked_rate_map) !== stableJson(lockedPrice.rate_map)) { + return new Error('Spend reservation locked rate_map does not match price version.'); + } + if (this.compareAu(normalized.locked_per_req_au, lockedPrice.per_req_au ?? ZERO_AU) !== 0) { + return new Error('Spend reservation locked per_req_au does not match price version.'); + } + if (this.compareAu(normalized.locked_min_session_au, lockedPrice.min_session_au ?? ZERO_AU) !== 0) { + return new Error('Spend reservation locked min_session_au does not match price version.'); + } + const rules = await this.currentRules(); + if (!rules || rules.ver !== normalized.rules_ver) { + return new Error('Spend reservation rules version is not current.'); + } + + const balance = await this.balanceRecord(normalized.user, normalized.rail); + if (balance instanceof Error) return balance; + const balanceError = this.guardianValidateBalanceRecord(balance, normalized.user, normalized.rail); + if (balanceError) return balanceError; + + const accounting = await this.targetedSpendAccountingState( + normalized.user, + normalized.rail + ); + if (accounting instanceof Error) return accounting; + const reservationState = await this.targetedSpendReservationState( + normalized.user, + normalized.rail, + normalized.reservation_id, + normalized.session_id + ); + if (reservationState instanceof Error) return reservationState; + const legacySessionById = accounting.hold.sessions.find( + (session) => session.session_id === normalized.session_id + ); + const sessionIndexKey = this.targetedSpendSessionIndexKey( + normalized.user, + normalized.rail, + normalized.session_id + ); + const sessionIndex = this.normalizeTargetedSpendReservationIndexRecord( + await this.get(sessionIndexKey), + { + user: normalized.user, + rail: normalized.rail, + sessionId: normalized.session_id, + } + ); + if (sessionIndex instanceof Error) return sessionIndex; + const billingAttemptKey = this.targetedSpendBillingAttemptKey( + normalized.user, + normalized.rail, + normalized.voucher_body.billing_id, + normalized.voucher_body.billing_attempt + ); + const billingAttemptIndex = this.normalizeTargetedSpendReservationIndexRecord( + await this.get(billingAttemptKey), + { + user: normalized.user, + rail: normalized.rail, + billingId: normalized.voucher_body.billing_id, + billingAttempt: normalized.voucher_body.billing_attempt, + } + ); + if (billingAttemptIndex instanceof Error) return billingAttemptIndex; + if (sessionIndex !== null && sessionIndex.reservation_id !== normalized.reservation_id) { + return new Error('Targeted spend reservation session already exists with different terms.'); + } + if (billingAttemptIndex !== null && + billingAttemptIndex.reservation_id !== normalized.reservation_id) { + return new Error('Billing attempt already has an active reservation.'); + } + const existing = reservationState.kind !== 'missing' + ? reservationState.session + : legacySessionById ?? null; + if (existing) { + if ( + existing.billing_id !== normalized.voucher_body.billing_id || + existing.billing_attempt !== normalized.voucher_body.billing_attempt || + existing.billing_epoch !== normalized.epoch || + existing.reservation_id !== normalized.voucher_body.reservation_id || + existing.reservation_expires_after_epoch !== normalized.reservation_expires_after_epoch || + existing.reservation_receipt_grace_epochs !== normalized.reservation_receipt_grace_epochs || + existing.user !== normalized.user || + existing.rail !== normalized.rail || + existing.provider !== normalized.provider || + existing.payout_revision !== value.payout_revision || + existing.enclave_id !== normalized.enclave_id || + existing.enclave_pubkey !== normalized.enclave_pubkey || + existing.model_id !== normalized.model_id || + existing.price_ver !== normalized.price_ver || + stableJson(existing.billing_prior_usage) !== + stableJson(normalized.voucher_body.billing_prior_usage) || + this.compareAu( + existing.billing_prior_au_owed_cum, + normalized.voucher_body.billing_prior_au_owed_cum + ) !== 0 || + stableJson(existing.locked_rate_map) !== stableJson(normalized.locked_rate_map) || + this.compareAu(existing.locked_per_req_au, normalized.locked_per_req_au) !== 0 || + this.compareAu(existing.locked_min_session_au, normalized.locked_min_session_au) !== 0 || + existing.served_ctx !== normalized.served_ctx || + stableJson(existing.required_modalities) !== stableJson(normalized.required_modalities) || + stableJson(existing.required_specialities) !== stableJson(normalized.required_specialities) || + stableJson(existing.workflow ?? null) !== stableJson(normalized.workflow ?? null) || + existing.ctx_bracket !== normalized.ctx_bracket || + existing.ctx_bracket_table_ver !== normalized.ctx_bracket_table_ver || + existing.rules_ver !== normalized.rules_ver || + this.compareAu(existing.max_spend_au, normalized.max_spend_au) !== 0 || + existing.voucher_hash !== normalized.voucher_hash || + existing.payout_revision !== value.payout_revision + ) { + return new Error('Targeted spend reservation session already exists with different terms.'); + } + const billingError = await this.validateExistingBillingReservation(normalized); + if (billingError) return billingError; + const availableAu = this.compareAu(accounting.total_reserved_au, balance.au) >= 0 + ? ZERO_AU + : this.safeSubAu(balance.au, accounting.total_reserved_au); + if (availableAu instanceof Error) return availableAu; + return { + ok: true, + op: 'spendReserveTargeted', + session_id: normalized.session_id, + epoch: normalized.epoch, + rail: normalized.rail, + user: normalized.user, + provider: normalized.provider, + payout_revision: value.payout_revision, + reserved_au: accounting.total_reserved_au, + available_au: availableAu, + idempotent: true, + }; + } + + const billingReservation = await this.prepareBillingReservation(normalized, key); + if (billingReservation instanceof Error) return billingReservation; + const nextSummaryReservedAu = this.safeAddAu( + accounting.summary.reserved_au, + normalized.max_spend_au + ); + if (nextSummaryReservedAu instanceof Error) return nextSummaryReservedAu; + const nextTotalReservedAu = this.safeAddAu( + accounting.legacy_reserved_au, + nextSummaryReservedAu + ); + if (nextTotalReservedAu instanceof Error) return nextTotalReservedAu; + if (this.compareAu(nextTotalReservedAu, balance.au) > 0) { + return new Error('Insufficient unreserved credit balance.'); + } + const session = { + session_id: normalized.session_id, + billing_id: normalized.voucher_body.billing_id, + billing_attempt: normalized.voucher_body.billing_attempt, + billing_prior_usage: cloneValue(normalized.voucher_body.billing_prior_usage), + billing_prior_au_owed_cum: normalized.voucher_body.billing_prior_au_owed_cum, + billing_epoch: normalized.epoch, + reservation_id: normalized.voucher_body.reservation_id, + reservation_expires_after_epoch: normalized.reservation_expires_after_epoch, + reservation_receipt_grace_epochs: normalized.reservation_receipt_grace_epochs, + user: normalized.user, + rail: normalized.rail, + provider: normalized.provider, + payout_revision: value.payout_revision, + enclave_id: normalized.enclave_id, + enclave_pubkey: normalized.enclave_pubkey, + model_id: normalized.model_id, + price_ver: normalized.price_ver, + locked_rate_map: normalized.locked_rate_map, + locked_per_req_au: normalized.locked_per_req_au, + locked_min_session_au: normalized.locked_min_session_au, + served_ctx: normalized.served_ctx, + required_modalities: normalized.required_modalities.slice(), + required_specialities: cloneValue(normalized.required_specialities), + ...(normalized.workflow ? { workflow: cloneValue(normalized.workflow) } : {}), + ctx_bracket: normalized.ctx_bracket, + ctx_bracket_table_ver: normalized.ctx_bracket_table_ver, + rules_ver: normalized.rules_ver, + max_spend_au: normalized.max_spend_au, + voucher_hash: normalized.voucher_hash, + feature_key: key, + reserved_at: normalized.at, + recorded_at: this.tx, + }; + const sessionKey = this.targetedSpendSessionKey( + normalized.user, + normalized.rail, + normalized.reservation_id + ); + const nextSummary = { + ...accounting.summary, + balance_au_at_last_reserve: balance.au, + reserved_au: nextSummaryReservedAu, + updated_at: this.tx, + }; + const indexRecord = this.targetedSpendReservationIndexRecord( + session, + sessionKey, + this.tx + ); + await this.put( + this.targetedSpendSummaryKey(normalized.user, normalized.rail), + nextSummary + ); + await this.put(sessionKey, this.targetedSpendSessionRecord(session, this.tx)); + await this.put(sessionIndexKey, indexRecord); + await this.put(billingAttemptKey, indexRecord); + await this.put(billingReservation.anchor_key, billingReservation.anchor); + await this.put(billingReservation.reservation_key, billingReservation.reservation); + const availableAu = this.safeSubAu(balance.au, nextTotalReservedAu); + if (availableAu instanceof Error) return availableAu; + return { + ok: true, + op: 'spendReserveTargeted', + session_id: normalized.session_id, + epoch: normalized.epoch, + rail: normalized.rail, + user: normalized.user, + provider: normalized.provider, + payout_revision: value.payout_revision, + reserved_au: nextTotalReservedAu, + available_au: availableAu, + idempotent: false, + }; + } + + receiptAttemptTerms(body) { + return { + schema_version: body.schema_version, + session_id: body.session_id, + billing_id: body.billing_id, + billing_attempt: body.billing_attempt, + billing_prior_usage: body.billing_prior_usage, + billing_prior_au_owed_cum: body.billing_prior_au_owed_cum, + billing_epoch: body.billing_epoch, + reservation_id: body.reservation_id, + reservation_expires_after_epoch: body.reservation_expires_after_epoch, + reservation_receipt_grace_epochs: body.reservation_receipt_grace_epochs, + payout_revision: body.payout_revision, + rail: body.rail, + user: body.user, + provider: body.provider, + enclave_id: body.enclave_id, + model_id: body.model_id, + price_ver: body.price_ver, + locked_rate_map: body.locked_rate_map, + locked_per_req_au: body.locked_per_req_au, + locked_min_session_au: body.locked_min_session_au, + served_ctx: body.served_ctx, + ctx_bracket: body.ctx_bracket, + ctx_bracket_table_ver: body.ctx_bracket_table_ver, + rules_ver: body.rules_ver, + workflow: body.workflow ?? null, + prompt_hash: body.prompt_hash, + }; + } + + receiptUsageIsMonotonic(previous, next) { + const units = new Set([...Object.keys(previous), ...Object.keys(next)]); + for (const unit of units) { + const before = previous[unit] ?? 0; + const after = next[unit] ?? 0; + if (!Number.isSafeInteger(before) || + !Number.isSafeInteger(after) || + after < before) { + return false; + } + } + return true; + } + + async normalizeRecordUsageReceiptValue(value) { + const shapeError = this.validateExactObjectKeys( + value, + ['op', 'contract_version', 'epoch', 'payout_revision', 'receipt', 'provider_sig'], + 'record usage receipt feature' + ); + if (shapeError) return shapeError; + if (value.op !== 'record_usage_receipt') { + return new Error('Invalid record usage receipt op.'); + } + if (value.contract_version !== CONTRACT_VERSION && + !RECOVERABLE_RECEIPT_CONTRACT_VERSIONS.has(value.contract_version)) { + return new Error('Invalid record usage receipt contract version.'); + } + if (!Number.isSafeInteger(value.epoch) || value.epoch < 1) { + return new Error('Invalid record usage receipt epoch.'); + } + if (!this.isHexBytes(value.payout_revision, 32) || + value.payout_revision !== value.payout_revision.toLowerCase()) { + return new Error('Invalid record usage receipt payout revision.'); + } + if (!this.isHexBytes(value.provider_sig, 64)) { + return new Error('Invalid record usage receipt provider signature.'); + } + const receiptShapeError = this.validateExactObjectKeys( + value.receipt, + ['body', 'enclave_sig', 'enclave_pubkey', 'user_sig'], + 'record usage receipt envelope' + ); + if (receiptShapeError) return receiptShapeError; + const receipt = await this.normalizeReceiptEnvelope(value.receipt, { + targetSchemaVersion: SESSION_RECEIPT_SCHEMA_VERSION, + }); + if (receipt instanceof Error) return receipt; + const canonicalReceipt = { + body: canonicalReceiptBody(receipt.body), + enclave_sig: receipt.enclave_sig.toLowerCase(), + enclave_pubkey: receipt.enclave_pubkey.toLowerCase(), + user_sig: receipt.user_sig.toLowerCase(), + }; + if (stableJson(value.receipt) !== stableJson(canonicalReceipt)) { + return new Error('Record usage receipt envelope must be canonical.'); + } + if (receipt.body.billing_epoch !== value.epoch) { + return new Error('Record usage receipt outer epoch does not match signed receipt.'); + } + if (receipt.body.payout_revision !== value.payout_revision) { + return new Error('Record usage receipt outer payout revision does not match signed receipt.'); + } + return { + op: 'record_usage_receipt', + // The outer version participates in the provider signature and feature key. + // Never rewrite retained v23 evidence while executing under a v24 dispatch. + contract_version: value.contract_version, + epoch: value.epoch, + payout_revision: value.payout_revision, + receipt: canonicalReceipt, + provider_sig: value.provider_sig.toLowerCase(), + }; + } + + async recordUsageReceiptFeatureKey(value) { + const normalized = await this.normalizeRecordUsageReceiptValue(value); + if (normalized instanceof Error) return normalized; + const digest = await blake3(b4a.from(stableJson({ + domain: 'mayhem-record-usage-receipt-feature-v1', + evidence: recordUsageReceiptEvidence(normalized), + }))); + const body = normalized.receipt.body; + return ( + `receipt/submit/${body.billing_epoch}/${body.billing_id}/` + + `${body.billing_attempt}/${body.seq}/${b4a.toString(digest, 'hex')}` + ); + } + + normalizeCloseUsageReservationValue(value, { expiry = false } = {}) { + const shapeError = this.validateExactObjectKeys( + value, + [ + 'op', + 'contract_version', + 'billing_epoch', + 'reservation_id', + 'reservation_expires_after_epoch', + 'reservation_receipt_grace_epochs', + 'billing_id', + 'billing_attempt', + 'session_id', + 'user', + 'rail', + 'provider', + 'payout_revision', + 'latest_receipt_seq', + 'latest_receipt_hash', + 'at', + 'reason', + 'actor', + 'actor_role', + 'actor_sig', + ], + 'close usage reservation feature' + ); + if (shapeError) return shapeError; + const rail = this.normalizeLedgerRail(value.rail, 'close usage reservation rail'); + if (rail instanceof Error) return rail; + const expectedOp = expiry ? 'expire_usage_reservation' : 'close_usage_reservation'; + const expectedRole = expiry ? 'user' : 'provider'; + if (value.op !== expectedOp || + value.contract_version !== CONTRACT_VERSION || + !Number.isSafeInteger(value.billing_epoch) || + value.billing_epoch < 1 || + !this.isHexBytes(value.reservation_id, 32) || + !Number.isSafeInteger(value.reservation_expires_after_epoch) || + value.reservation_expires_after_epoch <= value.billing_epoch || + !Number.isSafeInteger(value.reservation_receipt_grace_epochs) || + value.reservation_receipt_grace_epochs < 0 || + !this.isHexBytes(value.billing_id, 32) || + !Number.isSafeInteger(value.billing_attempt) || + value.billing_attempt < 0 || + !this.isHexBytes(value.session_id, 32) || + !this.isHexBytes(value.user, 32) || + !this.isHexBytes(value.provider, 32) || + !this.isHexBytes(value.payout_revision, 32) || + !Number.isSafeInteger(value.at) || + value.at < 0 || + !this.isSafeKeyPart(value.reason) || + !this.isHexBytes(value.actor, 32) || + value.actor_role !== expectedRole || + !this.isHexBytes(value.actor_sig, 64)) { + return new Error('Invalid close usage reservation feature.'); + } + const expectedActor = expiry ? value.user : value.provider; + if (value.actor !== expectedActor) { + return new Error('Close usage reservation actor does not match its role.'); + } + const hasReceipt = value.latest_receipt_seq !== null || + value.latest_receipt_hash !== null; + if ( + hasReceipt + ? (!Number.isSafeInteger(value.latest_receipt_seq) || + value.latest_receipt_seq < 0 || + !this.isHexBytes(value.latest_receipt_hash, 32)) + : value.latest_receipt_seq !== null || value.latest_receipt_hash !== null + ) { + return new Error('Invalid close usage reservation receipt head.'); + } + const normalized = { + op: expectedOp, + contract_version: CONTRACT_VERSION, + billing_epoch: value.billing_epoch, + reservation_id: value.reservation_id.toLowerCase(), + reservation_expires_after_epoch: value.reservation_expires_after_epoch, + reservation_receipt_grace_epochs: value.reservation_receipt_grace_epochs, + billing_id: value.billing_id.toLowerCase(), + billing_attempt: value.billing_attempt, + session_id: value.session_id.toLowerCase(), + user: value.user.toLowerCase(), + rail, + provider: value.provider.toLowerCase(), + payout_revision: value.payout_revision.toLowerCase(), + latest_receipt_seq: value.latest_receipt_seq, + latest_receipt_hash: value.latest_receipt_hash?.toLowerCase() ?? null, + at: value.at, + reason: value.reason, + actor: value.actor.toLowerCase(), + actor_role: value.actor_role, + actor_sig: value.actor_sig.toLowerCase(), + }; + if (stableJson(normalized) !== stableJson(value)) { + return new Error('Close usage reservation feature must be canonical.'); + } + return normalized; + } + + async closeUsageReservationFeatureKey(value, { expiry = false } = {}) { + const normalized = this.normalizeCloseUsageReservationValue(value, { expiry }); + if (normalized instanceof Error) return normalized; + const digest = await blake3(b4a.from(stableJson({ + domain: expiry + ? 'mayhem-expire-usage-reservation-feature-v1' + : 'mayhem-close-usage-reservation-feature-v1', + evidence: closeUsageReservationEvidence(normalized), + actor_sig: normalized.actor_sig, + }))); + return ( + `receipt/${expiry ? 'expire' : 'close'}/${normalized.billing_epoch}/` + + `${normalized.reservation_id}/` + + `${b4a.toString(digest, 'hex')}` + ); + } + + async nextReceiptEpochIndex(epoch, billingId, billingAttempt) { + if (await this.get(`epoch/freeze/${epoch}`)) { + return new Error('Cannot append receipts to a frozen settlement epoch.'); + } + const indexKey = this.receiptEpochIndexKey(epoch); + const existingIndex = await this.get(indexKey); + const index = existingIndex ?? { + type: 'canonical_receipt_epoch_index', + epoch, + count: 0, + page_size: RECEIPT_EPOCH_INDEX_PAGE_SIZE, + page_count: 0, + revision: 0, + updated_at: null, + }; + const normalizedIndex = this.normalizeReceiptEpochIndexMetadata(index, epoch, { + allowEmpty: true, + }); + if (normalizedIndex instanceof Error) return normalizedIndex; + if (index.count >= Number.MAX_SAFE_INTEGER) { + return new Error('Canonical receipt epoch count overflow.'); + } + const page = Math.floor(index.count / RECEIPT_EPOCH_INDEX_PAGE_SIZE); + const pageKey = this.receiptEpochPageKey(epoch, page); + const existingPage = await this.get(pageKey); + const pageRecord = existingPage ?? { + type: 'canonical_receipt_epoch_page', + epoch, + page, + identities: [], + }; + if (pageRecord.type !== 'canonical_receipt_epoch_page' || + pageRecord.epoch !== epoch || + pageRecord.page !== page || + !Array.isArray(pageRecord.identities) || + Object.keys(pageRecord).sort().join(',') !== 'epoch,identities,page,type' || + pageRecord.identities.length !== index.count % RECEIPT_EPOCH_INDEX_PAGE_SIZE || + pageRecord.identities.length >= RECEIPT_EPOCH_INDEX_PAGE_SIZE) { + return new Error('Canonical receipt epoch page is invalid or full.'); + } + const identities = pageRecord.identities; + if ( + identities.length >= RECEIPT_EPOCH_INDEX_PAGE_SIZE) { + return new Error('Canonical receipt epoch page is invalid or full.'); + } + const identity = { billing_id: billingId, billing_attempt: billingAttempt }; + if (identities.some((entry) => + entry?.billing_id === billingId && entry?.billing_attempt === billingAttempt + )) { + return new Error('Canonical receipt billing attempt is already indexed.'); + } + return { + index_key: indexKey, + index: { + ...index, + count: index.count + 1, + page_count: Math.max(index.page_count, page + 1), + }, + page_key: pageKey, + page: { + ...pageRecord, + identities: [...identities, identity], + }, + position: index.count, + }; + } + + normalizeReceiptEpochIndexMetadata(value, epoch, { allowEmpty = false } = {}) { + const shapeError = this.validateExactObjectKeys( + value, + ['type', 'epoch', 'count', 'page_size', 'page_count', 'revision', 'updated_at'], + 'canonical receipt epoch metadata' + ); + if (shapeError) return shapeError; + if (value.type !== 'canonical_receipt_epoch_index' || value.epoch !== epoch) { + return new Error('Canonical receipt epoch metadata identity is invalid.'); + } + if (!Number.isSafeInteger(value.count) || value.count < 0 || + value.page_size !== RECEIPT_EPOCH_INDEX_PAGE_SIZE || + !Number.isSafeInteger(value.page_count) || value.page_count < 0 || + !Number.isSafeInteger(value.revision) || value.revision < value.count) { + return new Error('Canonical receipt epoch metadata counters are invalid.'); + } + const expectedPageCount = value.count === 0 + ? 0 + : Math.ceil(value.count / RECEIPT_EPOCH_INDEX_PAGE_SIZE); + if (value.page_count !== expectedPageCount) { + return new Error('Canonical receipt epoch metadata page count is invalid.'); + } + if (value.count === 0) { + if (!allowEmpty || value.revision !== 0 || value.updated_at !== null) { + return new Error('Canonical receipt epoch metadata cannot be empty.'); + } + } else if (typeof value.updated_at !== 'string' || value.updated_at.length === 0) { + return new Error('Canonical receipt epoch metadata updated_at is invalid.'); + } + return { + type: value.type, + epoch: value.epoch, + count: value.count, + page_size: value.page_size, + page_count: value.page_count, + revision: value.revision, + updated_at: value.updated_at, + }; + } + + async receiptSettlementEpoch(applyState) { + const base = applyState.pending_epoch ?? applyState.updated_epoch; + if (!Number.isSafeInteger(base) || base < 0 || base >= Number.MAX_SAFE_INTEGER) { + return new Error('Receipt settlement epoch overflow.'); + } + const cursor = await this.get('receipt/ingress'); + if (cursor === null) return base + 1; + if (cursor.type !== 'receipt_ingress' || + !Number.isSafeInteger(cursor.next_epoch) || cursor.next_epoch < 1 || + !Number.isSafeInteger(cursor.activated_epoch) || cursor.activated_epoch < 1 || + cursor.activated_epoch >= cursor.next_epoch || + cursor.next_epoch > base + 2) { + return new Error('Canonical receipt ingress cursor is invalid.'); + } + return Math.max(base + 1, cursor.next_epoch); + } + + async prepareStateCheckpoint() { + const adminError = await this.requireAdmin(); + if (adminError) return adminError; + const shapeError = this.validateExactCommandValue( + ['op', 'slot', 'observed_at', 'contract_code_sha256'], 'prepare_state_checkpoint' + ); + if (shapeError) return shapeError; + const { slot, observed_at: observedAt, contract_code_sha256: codeHash } = this.value; + if (!Number.isSafeInteger(slot) || slot < 1 || !Number.isSafeInteger(slot * 30) || + !Number.isSafeInteger(observedAt) || observedAt < slot * 30 || + !this.isHexBytes(codeHash, 32) || codeHash !== codeHash.toLowerCase()) { + return new Error('Invalid checkpoint slot, observation time or release hash.'); + } + const key = `checkpoint/prepared/${slot}`; + const existing = await this.get(key); + if (existing !== null) { + return { ok: true, op: 'prepareStateCheckpoint', idempotent: true, snapshot: existing }; + } + const previous = await this.get('checkpoint/current'); + const preparing = await this.get('checkpoint/preparing'); + if (preparing && preparing.slot !== slot && preparing.slot > (previous?.slot ?? 0)) { + return new Error('An earlier checkpoint preparation is still awaiting its paid transaction.'); + } + if (previous && (slot !== previous.slot + 1 || observedAt < previous.observed_at)) { + return new Error('Checkpoint preparation must follow the last paid slot and observation time.'); + } + const applyState = await this.epochApplyStateRecord(); + const ingress = await this.receiptSettlementEpoch(applyState); + if (ingress instanceof Error) return ingress; + const completed = applyState.updated_epoch > 0 + ? await this.epochApplyAnchor(applyState.updated_epoch) : null; + if (completed instanceof Error) return completed; + if (applyState.updated_epoch > 0 && completed === null) { + return new Error('Completed settlement anchor is unavailable for checkpoint.'); + } + const catalog = await this.get('catalog/current'); + const state = { + completed_settlement: completed, + pending_apply: applyState.pending_epoch == null ? null : { + epoch: applyState.pending_epoch, next_page: applyState.pending_next_page, + apply_hash: applyState.last_apply_hash, + }, + receipt_ingress_epoch: ingress, + open_receipt_index: await this.get(this.receiptEpochIndexKey(ingress)), + frozen_receipts: await this.get(`epoch/freeze/${applyState.updated_epoch + 1}`), + catalog_hash: catalog?.catalog_hash ?? null, + catalog_version: catalog?.ver ?? catalog?.version ?? null, + contract_version: CONTRACT_VERSION, + // The writer supplies its startup-verified release manifest digest. This + // attests the publisher's code identity; canonical state is read here. + contract_code_sha256: codeHash, + }; + const stateHash = await this.opaqueHash('mayhem-checkpoint-state-v1', state); + const body = { + type: 'state_checkpoint_snapshot', schema_version: 1, slot, + scheduled_at: slot * 30, observed_at: observedAt, + previous_tx: previous?.tx ?? null, + state, state_hash: stateHash, + no_change: previous?.state_hash === stateHash, + prepared_by: this.address, + }; + const snapshot = { + ...body, snapshot_hash: await this.opaqueHash('mayhem-checkpoint-snapshot-v1', body), + }; + await this.put(key, snapshot); + await this.put('checkpoint/preparing', { slot, snapshot_hash: snapshot.snapshot_hash }); + return { ok: true, op: 'prepareStateCheckpoint', idempotent: false, snapshot }; + } + + async stateCheckpoint() { + if (this._mayhemExecutionType !== 'tx' || this.isFeature() || !this.isHexBytes(this.tx, 32)) { + return new Error('State checkpoints require a paid MSB transaction; free admin Features are forbidden.'); + } + const adminError = await this.requireAdmin(); + if (adminError) return adminError; + const shapeError = this.validateExactCommandValue( + ['op', 'slot', 'snapshot_hash'], 'state_checkpoint' + ); + if (shapeError) return shapeError; + const { slot, snapshot_hash: snapshotHash } = this.value; + if (!Number.isSafeInteger(slot) || slot < 1 || !this.isHexBytes(snapshotHash, 32)) { + return new Error('Invalid paid checkpoint identity.'); + } + const key = `checkpoint/slot/${slot}`; + const existing = await this.get(key); + if (existing !== null) { + return existing.tx === this.tx && existing.snapshot_hash === snapshotHash + ? { ok: true, op: 'stateCheckpoint', idempotent: true, checkpoint: existing } + : new Error('Checkpoint slot already has a paid transaction.'); + } + const snapshot = await this.get(`checkpoint/prepared/${slot}`); + if (!snapshot || snapshot.type !== 'state_checkpoint_snapshot' || + snapshot.slot !== slot || snapshot.snapshot_hash !== snapshotHash || + snapshot.prepared_by !== this.address) { + return new Error('Matching canonical checkpoint snapshot required.'); + } + const previous = await this.get('checkpoint/current'); + if (snapshot.previous_tx !== (previous?.tx ?? null) || + (previous && slot !== previous.slot + 1)) { + return new Error('Paid checkpoint predecessor does not match canonical history.'); + } + const checkpoint = { + type: 'paid_state_checkpoint', schema_version: 1, slot, + scheduled_at: snapshot.scheduled_at, observed_at: snapshot.observed_at, + snapshot_hash: snapshotHash, state_hash: snapshot.state_hash, + no_change: snapshot.no_change, previous_tx: snapshot.previous_tx, + tx: this.tx, paid_by: this.address, + }; + await this.put(key, checkpoint); + await this.put('checkpoint/current', checkpoint); + return { ok: true, op: 'stateCheckpoint', idempotent: false, checkpoint }; + } + + async epochFreeze() { + const adminError = await this.requireAdmin(); + if (adminError) return adminError; + const shapeError = this.validateExactCommandValue(['op', 'epoch', 'at'], 'epoch_freeze'); + if (shapeError) return shapeError; + const { epoch, at } = this.value; + if (!Number.isSafeInteger(epoch) || epoch < 1 || epoch >= Number.MAX_SAFE_INTEGER || + !Number.isSafeInteger(at) || at < 0) { + return new Error('Invalid epoch freeze identity or timestamp.'); + } + const key = `epoch/freeze/${epoch}`; + const existing = await this.get(key); + if (existing !== null) { + return { ok: true, op: 'epochFreeze', idempotent: true, freeze: existing }; + } + const applyState = await this.epochApplyStateRecord(); + const orderError = this.validateEpochApplyPageOrder(applyState, epoch, 0); + if (orderError) return orderError; + const params = await this.activeParamsAt(at, ['epoch_seconds']); + const cadenceError = await this.validateEpochCadenceTime( + applyState, epoch, 0, at, params.epoch_seconds + ); + if (cadenceError) return cadenceError; + if (at < epoch * params.epoch_seconds) { + return new Error('Epoch freeze is not active until the epoch window ends.'); + } + const ingress = await this.receiptSettlementEpoch(applyState); + if (ingress instanceof Error) return ingress; + if (ingress !== epoch) return new Error('Epoch freeze must close the open receipt batch.'); + const metadata = this.normalizeReceiptEpochIndexMetadata( + (await this.get(this.receiptEpochIndexKey(epoch))) ?? { + type: 'canonical_receipt_epoch_index', epoch, count: 0, + page_size: RECEIPT_EPOCH_INDEX_PAGE_SIZE, page_count: 0, + revision: 0, updated_at: null, + }, epoch, { allowEmpty: true } + ); + if (metadata instanceof Error) return metadata; + const body = { + type: 'epoch_receipt_freeze', epoch, at, epoch_seconds: params.epoch_seconds, + previous_apply_hash: applyState.last_apply_hash ?? null, + receipt_index: metadata, frozen_by: this.address, + }; + const freeze = { + ...body, freeze_hash: await this.opaqueHash('mayhem-epoch-receipt-freeze-v1', body), + frozen_at: this.tx, + }; + const cursor = await this.get('receipt/ingress'); + // Both writes are in the same consensus apply. No external observation or + // receipt append can interleave with this cutoff. + await this.put(key, freeze); + await this.put('receipt/ingress', { + type: 'receipt_ingress', next_epoch: epoch + 1, + activated_epoch: cursor?.activated_epoch ?? epoch, + freeze_hash: freeze.freeze_hash, updated_at: this.tx, + }); + return { ok: true, op: 'epochFreeze', idempotent: false, freeze }; + } + + async validateFrozenEpoch(epoch, at, receiptIndex) { + const cursor = await this.get('receipt/ingress'); + const freeze = await this.get(`epoch/freeze/${epoch}`); + // Legacy already-open/partially-applied epochs remain recoverable. Once + // ingress is activated, every subsequent epoch requires a canonical cutoff. + if (freeze === null) { + return cursor && epoch >= cursor.activated_epoch + ? new Error('Canonical epoch freeze required before settlement.') : null; + } + if (freeze.type !== 'epoch_receipt_freeze' || freeze.epoch !== epoch || + freeze.at !== at || + stableJson(freeze.receipt_index) !== stableJson(receiptIndex)) { + return new Error('Settlement does not match its canonical epoch freeze.'); + } + return null; + } + + async normalizeTargetedSpendSummaryRecord(record, user, rail) { + if (!record) { + return { + type: 'targeted_spend_summary', + user, + rail, + denom: PRICE_DENOMINATION, + reserved_au: ZERO_AU, + balance_au_at_last_reserve: null, + updated_at: null, + }; + } + if (!record || typeof record !== 'object' || Array.isArray(record) || + record.type !== 'targeted_spend_summary' || + record.user !== user || + record.rail !== rail || + record.denom !== PRICE_DENOMINATION) { + return new Error('Targeted spend summary key mismatch.'); + } + const reservedAu = this.normalizeAu(record.reserved_au, 'targeted spend summary reserved amount'); + if (reservedAu instanceof Error) { + return new Error('Invalid targeted spend summary reserved amount.'); + } + let balanceAu = null; + if (record.balance_au_at_last_reserve !== null) { + balanceAu = this.normalizeAu( + record.balance_au_at_last_reserve, + 'targeted spend summary balance amount' + ); + if (balanceAu instanceof Error) { + return new Error('Invalid targeted spend summary balance amount.'); + } + } + if (record.updated_at !== null && + (typeof record.updated_at !== 'string' || record.updated_at.length === 0)) { + return new Error('Invalid targeted spend summary update pointer.'); + } + return { + type: 'targeted_spend_summary', + user, + rail, + denom: PRICE_DENOMINATION, + reserved_au: reservedAu, + balance_au_at_last_reserve: balanceAu, + updated_at: record.updated_at, + }; + } + + async normalizeTargetedSpendLegacyReleaseSummaryRecord(record, user, rail) { + if (!record) { + return { + type: 'targeted_spend_legacy_release_summary', + user, + rail, + denom: PRICE_DENOMINATION, + released_au: ZERO_AU, + updated_at: null, + }; + } + if (!record || typeof record !== 'object' || Array.isArray(record) || + record.type !== 'targeted_spend_legacy_release_summary' || + record.user !== user || + record.rail !== rail || + record.denom !== PRICE_DENOMINATION) { + return new Error('Targeted spend legacy release summary key mismatch.'); + } + const releasedAu = this.normalizeAu( + record.released_au, + 'targeted spend legacy release amount' + ); + if (releasedAu instanceof Error) { + return new Error('Invalid targeted spend legacy release amount.'); + } + if (record.updated_at !== null && + record.updated_at !== undefined && + (typeof record.updated_at !== 'string' || record.updated_at.length === 0)) { + return new Error('Invalid targeted spend legacy release update pointer.'); + } + return { + type: 'targeted_spend_legacy_release_summary', + user, + rail, + denom: PRICE_DENOMINATION, + released_au: releasedAu, + updated_at: record.updated_at ?? null, + }; + } + + async normalizeTargetedSpendSessionRecord(record, user, rail, reservationId = null) { + if (!record) return null; + if (!record || typeof record !== 'object' || Array.isArray(record) || + record.type !== 'targeted_spend_session') { + return new Error('Targeted spend session record must be a session object.'); + } + const session = { ...record }; + delete session.type; + delete session.updated_at; + const hold = await this.normalizeTargetedSpendHoldRecord({ + type: 'targeted_spend_hold', + user, + rail, + denom: PRICE_DENOMINATION, + reserved_au: session.max_spend_au, + balance_au_at_last_reserve: null, + sessions: [session], + updated_at: record.updated_at ?? null, + }, user, rail); + if (hold instanceof Error) return hold; + const normalized = hold.sessions[0]; + if (normalized.user !== user || + normalized.rail !== rail || + (reservationId !== null && normalized.reservation_id !== reservationId)) { + return new Error('Targeted spend session key mismatch.'); + } + if (record.updated_at !== null && + record.updated_at !== undefined && + (typeof record.updated_at !== 'string' || record.updated_at.length === 0)) { + return new Error('Invalid targeted spend session update pointer.'); + } + return { + type: 'targeted_spend_session', + ...normalized, + updated_at: record.updated_at ?? null, + }; + } + + targetedSpendSessionRecord(session, updatedAt) { + return { + type: 'targeted_spend_session', + ...session, + updated_at: updatedAt, + }; + } + + normalizeTargetedSpendReservationIndexRecord( + record, + { + user, + rail, + sessionId = null, + billingId = null, + billingAttempt = null, + } + ) { + if (!record) return null; + if (!record || typeof record !== 'object' || Array.isArray(record) || + record.type !== 'targeted_spend_reservation_index' || + record.user !== user || + record.rail !== rail || + (sessionId !== null && record.session_id !== sessionId) || + (billingId !== null && record.billing_id !== billingId) || + (billingAttempt !== null && record.billing_attempt !== billingAttempt) || + !this.isHexBytes(record.session_id, 32) || + !this.isHexBytes(record.billing_id, 32) || + !Number.isSafeInteger(record.billing_attempt) || + record.billing_attempt < 0 || + !this.isHexBytes(record.reservation_id, 32) || + typeof record.session_key !== 'string' || + record.session_key !== + this.targetedSpendSessionKey(user, rail, record.reservation_id) || + (record.updated_at !== null && + record.updated_at !== undefined && + (typeof record.updated_at !== 'string' || record.updated_at.length === 0))) { + return new Error('Targeted spend reservation index is invalid.'); + } + return { + type: 'targeted_spend_reservation_index', + user, + rail, + session_id: record.session_id, + billing_id: record.billing_id, + billing_attempt: record.billing_attempt, + reservation_id: record.reservation_id, + session_key: record.session_key, + updated_at: record.updated_at, + }; + } + + targetedSpendReservationIndexRecord(session, sessionKey, updatedAt) { + return { + type: 'targeted_spend_reservation_index', + user: session.user, + rail: session.rail, + session_id: session.session_id, + billing_id: session.billing_id, + billing_attempt: session.billing_attempt, + reservation_id: session.reservation_id, + session_key: sessionKey, + updated_at: updatedAt, + }; + } + + async targetedSpendAccountingState(user, rail) { + // MAYHEM PATCH: combine legacy aggregate holds with sharded reservation + // state so existing reservations survive the targeted-hold rollout. + const hold = await this.normalizeTargetedSpendHoldRecord( + (await this.get(this.targetedSpendHoldKey(user, rail))) ?? null, + user, + rail + ); + if (hold instanceof Error) return hold; + const summary = await this.normalizeTargetedSpendSummaryRecord( + await this.get(this.targetedSpendSummaryKey(user, rail)), + user, + rail + ); + if (summary instanceof Error) return summary; + const legacyRelease = await this.normalizeTargetedSpendLegacyReleaseSummaryRecord( + await this.get(this.targetedSpendLegacyReleaseSummaryKey(user, rail)), + user, + rail + ); + if (legacyRelease instanceof Error) return legacyRelease; + const legacyReservedAu = this.safeSubAu(hold.reserved_au, legacyRelease.released_au); + if (legacyReservedAu instanceof Error) { + return new Error('Targeted spend legacy release exceeds outstanding holds.'); + } + const totalReservedAu = this.safeAddAu(legacyReservedAu, summary.reserved_au); + if (totalReservedAu instanceof Error) return totalReservedAu; + return { + hold, + summary, + legacy_release: legacyRelease, + legacy_reserved_au: legacyReservedAu, + total_reserved_au: totalReservedAu, + }; + } + + async targetedSpendReservationState(user, rail, reservationId, sessionId) { + // MAYHEM PATCH: prefer sharded targeted reservation records and retain a + // legacy overlay path for reservations opened before the sharded layout. + const sessionKey = this.targetedSpendSessionKey(user, rail, reservationId); + const sessionRecord = await this.normalizeTargetedSpendSessionRecord( + await this.get(sessionKey), + user, + rail, + reservationId + ); + if (sessionRecord instanceof Error) return sessionRecord; + if (sessionRecord !== null) { + if (sessionRecord.session_id !== sessionId) { + return new Error('Targeted spend session id does not match reservation key.'); + } + const summary = await this.normalizeTargetedSpendSummaryRecord( + await this.get(this.targetedSpendSummaryKey(user, rail)), + user, + rail + ); + if (summary instanceof Error) return summary; + return { + kind: 'sharded', + sessionKey, + sessionIndexKey: this.targetedSpendSessionIndexKey(user, rail, sessionId), + billingAttemptKey: this.targetedSpendBillingAttemptKey( + user, + rail, + sessionRecord.billing_id, + sessionRecord.billing_attempt + ), + summary, + session: sessionRecord, + }; + } + const holdKey = this.targetedSpendHoldKey(user, rail); + const hold = await this.normalizeTargetedSpendHoldRecord( + (await this.get(holdKey)) ?? null, + user, + rail + ); + if (hold instanceof Error) return hold; + const session = hold.sessions.find((entry) => + entry.session_id === sessionId && + entry.reservation_id === reservationId + ); + if (!session) return { kind: 'missing', hold }; + const legacySessionKey = this.targetedSpendLegacySessionKey(user, rail, reservationId); + const legacySessionRecord = await this.normalizeTargetedSpendSessionRecord( + await this.get(legacySessionKey), + user, + rail, + reservationId + ); + if (legacySessionRecord instanceof Error) return legacySessionRecord; + const legacyRelease = await this.normalizeTargetedSpendLegacyReleaseSummaryRecord( + await this.get(this.targetedSpendLegacyReleaseSummaryKey(user, rail)), + user, + rail + ); + if (legacyRelease instanceof Error) return legacyRelease; + if (legacySessionRecord !== null) { + if (legacySessionRecord.session_id !== sessionId) { + return new Error('Targeted spend legacy session id does not match reservation key.'); + } + return { + kind: 'legacy_overlay', + holdKey, + hold, + legacySessionKey, + legacyRelease, + session: legacySessionRecord, + }; + } + return { kind: 'legacy', holdKey, hold, legacySessionKey, legacyRelease, session }; + } + + prepareTargetedReservationClosure({ + hold, + session, + reservation, + head, + closeRecordKey, + closedBy, + closedByRole, + at, + reason, + }) { + if (reservation.status !== 'active' || + reservation.closed_at !== null || + reservation.close_record_key !== null) { + return new Error('Targeted reservation is already closed.'); + } + const retainedAu = head?.incremental_au ?? ZERO_AU; + const releasedAu = this.safeSubAu(session.max_spend_au, retainedAu); + if (releasedAu instanceof Error) { + return new Error('Targeted reservation close exceeds its hold.'); + } + const reservedAu = this.safeSubAu(hold.reserved_au, releasedAu); + if (reservedAu instanceof Error) { + return new Error('Targeted reservation close exceeds outstanding holds.'); + } + const sessions = head + ? hold.sessions.map((entry) => ( + entry.reservation_id === session.reservation_id + ? { + ...entry, + max_spend_au: retainedAu, + settlement_ready: true, + closed_at: closeRecordKey, + } + : entry + )) + : hold.sessions.filter((entry) => entry.reservation_id !== session.reservation_id); + const closeRecord = { + type: 'targeted_reservation_close', + reservation_id: session.reservation_id, + billing_id: session.billing_id, + billing_attempt: session.billing_attempt, + billing_epoch: session.billing_epoch, + reservation_expires_after_epoch: session.reservation_expires_after_epoch, + reservation_receipt_grace_epochs: session.reservation_receipt_grace_epochs, + session_id: session.session_id, + user: session.user, + rail: session.rail, + provider: session.provider, + payout_revision: session.payout_revision, + latest_receipt_seq: head?.receipt_seq ?? null, + latest_receipt_hash: head?.receipt_hash ?? null, + retained_au: retainedAu, + released_au: releasedAu, + reason, + closed_by: closedBy, + closed_by_role: closedByRole, + at, + recorded_at: closeRecordKey, + }; + return { + hold: { + ...hold, + reserved_au: reservedAu, + sessions, + updated_at: closeRecordKey, + }, + reservation: { + ...reservation, + status: 'closed', + closed_at: closeRecordKey, + close_record_key: closeRecordKey, + }, + close_record: closeRecord, + }; + } + + prepareShardedTargetedReservationClosure({ + summary, + session, + reservation, + head, + closeRecordKey, + closedBy, + closedByRole, + at, + reason, + }) { + const closure = this.prepareTargetedReservationClosure({ + hold: { + type: 'targeted_spend_hold', + user: session.user, + rail: session.rail, + denom: PRICE_DENOMINATION, + reserved_au: session.max_spend_au, + balance_au_at_last_reserve: summary.balance_au_at_last_reserve, + sessions: [session], + updated_at: summary.updated_at, + }, + session, + reservation, + head, + closeRecordKey, + closedBy, + closedByRole, + at, + reason, + }); + if (closure instanceof Error) return closure; + const nextReservedAu = this.safeSubAu( + summary.reserved_au, + closure.close_record.released_au + ); + if (nextReservedAu instanceof Error) { + return new Error('Targeted reservation close exceeds outstanding sharded holds.'); + } + return { + summary: { + ...summary, + reserved_au: nextReservedAu, + updated_at: closeRecordKey, + }, + session: head ? this.targetedSpendSessionRecord(closure.hold.sessions[0], closeRecordKey) : null, + reservation: closure.reservation, + close_record: closure.close_record, + }; + } + + prepareLegacyTargetedReservationClosure({ + legacyRelease, + hold, + session, + reservation, + head, + closeRecordKey, + closedBy, + closedByRole, + at, + reason, + }) { + // MAYHEM PATCH: close or retain legacy targeted holds deterministically + // while payout epochs consume the new sharded reservation records. + const closure = this.prepareTargetedReservationClosure({ + hold, + session, + reservation, + head, + closeRecordKey, + closedBy, + closedByRole, + at, + reason, + }); + if (closure instanceof Error) return closure; + const nextReleasedAu = this.safeAddAu( + legacyRelease.released_au, + closure.close_record.released_au + ); + if (nextReleasedAu instanceof Error || + this.compareAu(nextReleasedAu, hold.reserved_au) > 0) { + return new Error('Targeted reservation close exceeds legacy outstanding holds.'); + } + const overlaySession = head + ? closure.hold.sessions.find( + (entry) => entry.reservation_id === session.reservation_id + ) + : null; + if (head && !overlaySession) { + return new Error('Targeted reservation close lost its retained legacy session.'); + } + return { + legacy_release: { + ...legacyRelease, + released_au: nextReleasedAu, + updated_at: closeRecordKey, + }, + session: overlaySession + ? this.targetedSpendSessionRecord(overlaySession, closeRecordKey) + : null, + reservation: closure.reservation, + close_record: closure.close_record, + }; + } + + async applyRecordUsageReceiptFeature(key, value) { + const normalized = await this.normalizeRecordUsageReceiptValue(value); + if (normalized instanceof Error) return normalized; + const expectedKey = await this.recordUsageReceiptFeatureKey(normalized); + if (expectedKey instanceof Error) return expectedKey; + if (key !== expectedKey) return new Error('Invalid record usage receipt feature key.'); + const body = normalized.receipt.body; + if (!verifyEd25519Hex( + normalized.provider_sig, + recordUsageReceiptMessage(normalized), + body.provider + )) { + return new Error('Invalid record usage receipt provider signature.'); + } + if (!this.verifyReceiptEnvelope(normalized.receipt)) { + return new Error('Invalid record usage receipt user or enclave signature.'); + } + + const receiptHash = await this.opaqueHash( + 'mayhem-canonical-receipt-v1', + normalized.receipt + ); + const headKey = this.receiptHeadKey(body.billing_id, body.billing_attempt); + const existingHead = await this.get(headKey); + if (existingHead) { + if (existingHead.type !== 'canonical_receipt_head') { + return new Error('Canonical receipt head is invalid.'); + } + if (existingHead.receipt_hash === receiptHash && + stableJson(existingHead.receipt) === stableJson(normalized.receipt)) { + return { + ok: true, + op: 'recordUsageReceipt', + epoch: existingHead.settlement_epoch ?? null, + billing_epoch: body.billing_epoch, + billing_id: body.billing_id, + billing_attempt: body.billing_attempt, + receipt_seq: body.seq, + receipt_hash: receiptHash, + idempotent: true, + }; + } + if ((await this.get(this.receiptConsumedKey(body.billing_id, body.billing_attempt))) !== null) { + return new Error('A consumed canonical receipt head cannot advance.'); + } + if (body.seq <= existingHead.receipt_seq) { + return new Error('Receipt sequence conflicts with the canonical high-water head.'); + } + if (existingHead.receipt.body.final === true || + existingHead.settlement_ready === true) { + return new Error('A finalized canonical receipt head cannot advance.'); + } + if (stableJson(this.receiptAttemptTerms(existingHead.receipt.body)) !== + stableJson(this.receiptAttemptTerms(body))) { + return new Error('Higher receipt sequence changed immutable attempt terms.'); + } + if (!this.receiptUsageIsMonotonic(existingHead.receipt.body.usage, body.usage) || + this.compareAu(body.au_owed_cum, existingHead.receipt.body.au_owed_cum) < 0) { + return new Error('Higher receipt sequence is not monotonic.'); + } + } + + const reservationState = await this.targetedSpendReservationState( + body.user, + body.rail, + body.reservation_id, + body.session_id + ); + if (reservationState instanceof Error) return reservationState; + if (reservationState.kind === 'missing') { + return new Error('Receipt does not match an exact targeted spend hold session.'); + } + const session = reservationState.session; + const sessionTermsMatch = + session.billing_id === body.billing_id && + session.billing_attempt === body.billing_attempt && + session.billing_epoch === body.billing_epoch && + session.reservation_id === body.reservation_id && + session.reservation_expires_after_epoch === body.reservation_expires_after_epoch && + session.reservation_receipt_grace_epochs === body.reservation_receipt_grace_epochs && + session.user === body.user && + session.rail === body.rail && + session.provider === body.provider && + session.payout_revision === body.payout_revision && + session.enclave_id === body.enclave_id && + session.enclave_pubkey === normalized.receipt.enclave_pubkey && + session.model_id === body.model_id && + session.price_ver === body.price_ver && + stableJson(session.billing_prior_usage) === stableJson(body.billing_prior_usage) && + this.compareAu( + session.billing_prior_au_owed_cum, + body.billing_prior_au_owed_cum + ) === 0 && + stableJson(session.locked_rate_map) === stableJson(body.locked_rate_map) && + this.compareAu(session.locked_per_req_au, body.locked_per_req_au) === 0 && + this.compareAu(session.locked_min_session_au, body.locked_min_session_au) === 0 && + session.served_ctx === body.served_ctx && + session.ctx_bracket === body.ctx_bracket && + session.ctx_bracket_table_ver === body.ctx_bracket_table_ver && + session.rules_ver === body.rules_ver && + stableJson(session.workflow ?? null) === stableJson(body.workflow ?? null); + if (!sessionTermsMatch) { + return new Error('Receipt terms do not match the targeted spend hold session.'); + } + + const incrementalAu = this.safeSubAu( + body.au_owed_cum, + body.billing_prior_au_owed_cum + ); + if (incrementalAu instanceof Error || + this.isZeroAu(incrementalAu) || + this.compareAu(incrementalAu, session.max_spend_au) > 0) { + return new Error('Receipt incremental amount is not positive or exceeds its session reservation.'); + } + + const billingAnchor = await this.get(this.receiptBillingKey(body.billing_id)); + if (!billingAnchor || + billingAnchor.type !== 'receipt_billing_anchor' || + billingAnchor.user !== body.user || + billingAnchor.rail !== body.rail || + billingAnchor.epoch !== body.billing_epoch || + billingAnchor.latest_attempt < body.billing_attempt) { + return new Error('Receipt billing anchor is missing or inconsistent.'); + } + if (billingAnchor.latest_attempt > body.billing_attempt) { + return new Error('An older billing attempt cannot advance after a higher attempt exists.'); + } + const reservationKey = this.receiptReservationKey(body.reservation_id); + const reservation = await this.get(reservationKey); + if (!reservation || + reservation.type !== 'receipt_reservation_identity' || + reservation.reservation_id !== body.reservation_id || + reservation.billing_id !== body.billing_id || + reservation.billing_attempt !== body.billing_attempt || + reservation.billing_epoch !== body.billing_epoch || + reservation.reservation_expires_after_epoch !== body.reservation_expires_after_epoch || + reservation.reservation_receipt_grace_epochs !== body.reservation_receipt_grace_epochs || + reservation.session_id !== body.session_id || + reservation.user !== body.user || + reservation.rail !== body.rail || + reservation.provider !== body.provider || + reservation.payout_revision !== body.payout_revision) { + return new Error('Receipt reservation identity is missing or inconsistent.'); + } + if (reservation.status !== 'active' || + reservation.closed_at !== null || + reservation.close_record_key !== null) { + return new Error('A closed targeted reservation cannot advance.'); + } + const applyState = await this.epochApplyStateRecord(); + const settlementEpoch = await this.receiptSettlementEpoch(applyState); + if (settlementEpoch instanceof Error) return settlementEpoch; + if (body.billing_epoch > settlementEpoch) { + return new Error('Receipt billing epoch is in the future.'); + } + const isFinal = body.final === true; + let epochIndex = null; + if (isFinal) { + if (existingHead?.index_position !== null && + existingHead?.index_position !== undefined) { + return new Error('Non-final canonical receipt head was unexpectedly indexed.'); + } + epochIndex = await this.nextReceiptEpochIndex( + settlementEpoch, + body.billing_id, + body.billing_attempt + ); + if (epochIndex instanceof Error) return epochIndex; + } + const head = { + type: 'canonical_receipt_head', + billing_id: body.billing_id, + billing_attempt: body.billing_attempt, + epoch: isFinal ? settlementEpoch : null, + billing_epoch: body.billing_epoch, + settlement_epoch: isFinal ? settlementEpoch : null, + index_position: isFinal ? epochIndex.position : null, + settlement_ready: isFinal, + user: body.user, + rail: body.rail, + provider: body.provider, + payout_revision: body.payout_revision, + session_id: body.session_id, + reservation_id: body.reservation_id, + receipt_seq: body.seq, + receipt_hash: receiptHash, + incremental_au: incrementalAu, + receipt: cloneValue(normalized.receipt), + feature_key: key, + updated_at: key, + }; + let closure = null; + let nextMetadata = null; + if (isFinal) { + const closeRecordKey = this.receiptReservationCloseKey(body.reservation_id); + if ((await this.get(closeRecordKey)) !== null) { + return new Error('Targeted reservation close record already exists.'); + } + closure = reservationState.kind === 'legacy' + ? this.prepareLegacyTargetedReservationClosure({ + legacyRelease: reservationState.legacyRelease, + hold: reservationState.hold, + session, + reservation, + head, + closeRecordKey, + closedBy: body.provider, + closedByRole: 'provider', + at: body.ts, + reason: 'final_receipt', + }) + : this.prepareShardedTargetedReservationClosure({ + summary: reservationState.summary, + session, + reservation, + head, + closeRecordKey, + closedBy: body.provider, + closedByRole: 'provider', + at: body.ts, + reason: 'final_receipt', + }); + if (closure instanceof Error) return closure; + if (epochIndex.index.revision >= Number.MAX_SAFE_INTEGER) { + return new Error('Canonical receipt epoch revision overflow.'); + } + nextMetadata = { + ...epochIndex.index, + revision: epochIndex.index.revision + 1, + updated_at: key, + }; + } + await this.put(headKey, head); + if (isFinal) { + await this.put(epochIndex.page_key, epochIndex.page); + await this.put(epochIndex.index_key, nextMetadata); + if (reservationState.kind === 'legacy') { + await this.put( + this.targetedSpendLegacyReleaseSummaryKey(body.user, body.rail), + closure.legacy_release + ); + await this.put(reservationState.legacySessionKey, closure.session); + } else { + await this.put( + this.targetedSpendSummaryKey(body.user, body.rail), + closure.summary + ); + await this.put(reservationState.sessionKey, closure.session); + } + await this.put(reservationKey, closure.reservation); + await this.put(this.receiptReservationCloseKey(body.reservation_id), closure.close_record); + } + return { + ok: true, + op: 'recordUsageReceipt', + epoch: isFinal ? settlementEpoch : null, + billing_epoch: body.billing_epoch, + billing_id: body.billing_id, + billing_attempt: body.billing_attempt, + receipt_seq: body.seq, + receipt_hash: receiptHash, + idempotent: false, + }; + } + + async applyCloseUsageReservationFeature(key, value, { expiry = false } = {}) { + const normalized = this.normalizeCloseUsageReservationValue(value, { expiry }); + if (normalized instanceof Error) return normalized; + const expectedKey = await this.closeUsageReservationFeatureKey(normalized, { expiry }); + if (expectedKey instanceof Error) return expectedKey; + if (key !== expectedKey) return new Error('Invalid close usage reservation feature key.'); + const verify = this.protocol?.peer?.wallet?.verify; + if (typeof verify !== 'function' || + verify.call( + this.protocol.peer.wallet, + normalized.actor_sig, + expiry + ? expireUsageReservationMessage(normalized) + : closeUsageReservationMessage(normalized), + normalized.actor + ) !== true) { + return new Error('Invalid close usage reservation signature.'); + } + + const closeRecordKey = this.receiptReservationCloseKey(normalized.reservation_id); + const existingClose = await this.get(closeRecordKey); + if (existingClose !== null) { + if (existingClose.feature_key === key) { + return { + ok: true, + op: expiry ? 'expireUsageReservation' : 'closeUsageReservation', + billing_epoch: normalized.billing_epoch, + reservation_id: normalized.reservation_id, + settlement_epoch: existingClose.settlement_epoch, + idempotent: true, + }; + } + return new Error('Targeted reservation close conflicts with its canonical close.'); + } + + const reservationKey = this.receiptReservationKey(normalized.reservation_id); + const reservation = await this.get(reservationKey); + if (!reservation || + reservation.type !== 'receipt_reservation_identity' || + reservation.status !== 'active' || + reservation.closed_at !== null || + reservation.close_record_key !== null || + reservation.reservation_id !== normalized.reservation_id || + reservation.billing_id !== normalized.billing_id || + reservation.billing_attempt !== normalized.billing_attempt || + reservation.billing_epoch !== normalized.billing_epoch || + reservation.reservation_expires_after_epoch !== + normalized.reservation_expires_after_epoch || + reservation.reservation_receipt_grace_epochs !== + normalized.reservation_receipt_grace_epochs || + reservation.session_id !== normalized.session_id || + reservation.user !== normalized.user || + reservation.rail !== normalized.rail || + reservation.provider !== normalized.provider || + reservation.payout_revision !== normalized.payout_revision) { + return new Error('Close usage reservation does not match an active reservation.'); + } + const reservationState = await this.targetedSpendReservationState( + normalized.user, + normalized.rail, + normalized.reservation_id, + normalized.session_id + ); + if (reservationState instanceof Error) return reservationState; + const session = reservationState.kind === 'missing' + ? null + : reservationState.session; + if (!session || + session.billing_id !== normalized.billing_id || + session.billing_attempt !== normalized.billing_attempt || + session.billing_epoch !== normalized.billing_epoch || + session.reservation_expires_after_epoch !== + normalized.reservation_expires_after_epoch || + session.reservation_receipt_grace_epochs !== + normalized.reservation_receipt_grace_epochs || + session.provider !== normalized.provider || + session.payout_revision !== normalized.payout_revision) { + return new Error('Close usage reservation does not match its outstanding hold.'); + } + + const headKey = this.receiptHeadKey( + normalized.billing_id, + normalized.billing_attempt + ); + const existingHead = await this.get(headKey); + if (existingHead === null) { + if (normalized.latest_receipt_seq !== null || + normalized.latest_receipt_hash !== null) { + return new Error('Close usage reservation receipt head does not exist.'); + } + } else { + if (existingHead.type !== 'canonical_receipt_head' || + existingHead.billing_epoch !== normalized.billing_epoch || + existingHead.reservation_id !== normalized.reservation_id || + existingHead.session_id !== normalized.session_id || + existingHead.user !== normalized.user || + existingHead.rail !== normalized.rail || + existingHead.provider !== normalized.provider || + existingHead.payout_revision !== normalized.payout_revision || + existingHead.receipt_seq !== normalized.latest_receipt_seq || + existingHead.receipt_hash !== normalized.latest_receipt_hash) { + return new Error('Close usage reservation does not match the canonical receipt head.'); + } + if (existingHead.settlement_ready === true || + existingHead.receipt?.body?.final === true || + (await this.get( + this.receiptConsumedKey(normalized.billing_id, normalized.billing_attempt) + )) !== null) { + return new Error('Finalized or consumed receipt evidence cannot be closed again.'); + } + } + + const applyState = await this.epochApplyStateRecord(); + const settlementEpoch = await this.receiptSettlementEpoch(applyState); + if (settlementEpoch instanceof Error) return settlementEpoch; + if (normalized.billing_epoch > settlementEpoch) { + return new Error('Close usage reservation billing epoch is in the future.'); + } + if (expiry) { + if ( + normalized.reservation_expires_after_epoch > + Number.MAX_SAFE_INTEGER - normalized.reservation_receipt_grace_epochs || + applyState.updated_epoch < + normalized.reservation_expires_after_epoch + + normalized.reservation_receipt_grace_epochs + ) { + return new Error( + 'Buyer reservation close is not yet past canonical expiry and receipt grace.' + ); + } + } + let head = null; + let epochIndex = null; + let nextMetadata = null; + if (existingHead !== null) { + epochIndex = await this.nextReceiptEpochIndex( + settlementEpoch, + normalized.billing_id, + normalized.billing_attempt + ); + if (epochIndex instanceof Error) return epochIndex; + if (epochIndex.index.revision >= Number.MAX_SAFE_INTEGER) { + return new Error('Canonical receipt epoch revision overflow.'); + } + head = { + ...existingHead, + epoch: settlementEpoch, + settlement_epoch: settlementEpoch, + index_position: epochIndex.position, + settlement_ready: true, + updated_at: key, + }; + nextMetadata = { + ...epochIndex.index, + revision: epochIndex.index.revision + 1, + updated_at: key, + }; + } + const closure = reservationState.kind === 'legacy' + ? this.prepareLegacyTargetedReservationClosure({ + legacyRelease: reservationState.legacyRelease, + hold: reservationState.hold, + session, + reservation, + head, + closeRecordKey, + closedBy: normalized.actor, + closedByRole: normalized.actor_role, + at: normalized.at, + reason: normalized.reason, + }) + : this.prepareShardedTargetedReservationClosure({ + summary: reservationState.summary, + session, + reservation, + head, + closeRecordKey, + closedBy: normalized.actor, + closedByRole: normalized.actor_role, + at: normalized.at, + reason: normalized.reason, + }); + if (closure instanceof Error) return closure; + const closeRecord = { + ...closure.close_record, + settlement_epoch: head?.settlement_epoch ?? null, + actor_sig: normalized.actor_sig, + feature_key: key, + signed_evidence: closeUsageReservationEvidence(normalized), + }; + + if (head !== null) { + await this.put(headKey, head); + await this.put(epochIndex.page_key, epochIndex.page); + await this.put(epochIndex.index_key, nextMetadata); + } + if (reservationState.kind === 'legacy') { + await this.put( + this.targetedSpendLegacyReleaseSummaryKey(normalized.user, normalized.rail), + closure.legacy_release + ); + if (closure.session) { + await this.put(reservationState.legacySessionKey, closure.session); + } else { + await this.del(reservationState.legacySessionKey); + } + } else { + await this.put( + this.targetedSpendSummaryKey(normalized.user, normalized.rail), + closure.summary + ); + if (closure.session) { + await this.put(reservationState.sessionKey, closure.session); + } else { + await this.del(reservationState.sessionKey); + await this.del(reservationState.sessionIndexKey); + await this.del(reservationState.billingAttemptKey); + } + } + await this.put(reservationKey, closure.reservation); + await this.put(closeRecordKey, closeRecord); + return { + ok: true, + op: expiry ? 'expireUsageReservation' : 'closeUsageReservation', + billing_epoch: normalized.billing_epoch, + reservation_id: normalized.reservation_id, + settlement_epoch: head?.settlement_epoch ?? null, + retained_au: closeRecord.retained_au, + released_au: closeRecord.released_au, + idempotent: false, + }; + } + + async applyEpochApplyFeature(key, value) { + const expectedKey = await this.epochApplyFeatureKey(value); + if (expectedKey instanceof Error) return expectedKey; + if (key !== expectedKey) return; + + const previousTx = this.tx; + this.tx = key; + try { + return await this.epochApply(); + } finally { + this.tx = previousTx; + } + } + + async applyTargetedEpochFeature(key, value, options = {}) { + const normalized = options.normalized ?? await this.normalizeTargetedEpochFeatureValue(value); + if (normalized instanceof Error) return normalized; + const expectedKey = options.expectedKey ?? await this.targetedEpochFeatureKey(value); + if (expectedKey instanceof Error) return expectedKey; + if (key !== expectedKey) return new Error('Invalid targeted epoch feature key.'); + + const applyState = await this.epochApplyStateRecord(); + const page = this.epochApplyPage(value); + if (page instanceof Error) return page; + const lastPage = this.epochApplyLastPage(value); + if (lastPage instanceof Error) return lastPage; + const replayPosition = this.isEpochApplyPagePositionReplay( + applyState, + value.epoch, + page, + lastPage + ); + const params = await this.activeParamsAt(value.at, [ + 'fee_bps', + 'probation_successful_sessions', + 'new_provider_holdback_epochs', + 'holdback_epochs', + 'challenge_epochs', + 'canary_probe_holdback_bps', + 'canary_probe_release_min_passes', + ]); + const reservationBindings = await this.validateTargetedEpochReservationBindings( + value, + normalized.targeted_earnings, + key + ); + if (reservationBindings instanceof Error) return reservationBindings; + const allocationUpdates = normalized.allocations.map((allocation) => ({ + key: `payout/allocation/${value.epoch}/${allocation.session_id}`, + value: { + type: 'provider_payout_session_allocation', + epoch: value.epoch, + page, + ...allocation, + feature_key: key, + }, + })); + const consumptionUpdates = normalized.allocations.map((allocation) => ({ + key: this.receiptConsumedKey(allocation.billing_id, allocation.billing_attempt), + value: this.receiptConsumptionRecord(value.epoch, allocation, key), + })); + let hasPreexistingFeatureArtifact = false; + for (const update of allocationUpdates) { + const existing = await this.get(update.key); + if (existing !== null && stableJson(existing) !== stableJson(update.value)) { + return new Error('Targeted epoch session allocation already exists.'); + } + hasPreexistingFeatureArtifact ||= existing !== null; + } + for (const update of consumptionUpdates) { + const existing = await this.get(update.key); + if (existing !== null && stableJson(existing) !== stableJson(update.value)) { + return new Error('Canonical receipt billing attempt is already consumed.'); + } + hasPreexistingFeatureArtifact ||= existing !== null; + } + const epochCommit = options.commitTransition?.record ?? + await this.get(`epoch/commit/${value.epoch}`); + const boundedReceiptSettlement = + epochCommit?.apply_mode === 'targeted_receipt_pages_v1'; + const providerSettlementDeltas = new Map(); + const liabilityUpdates = []; + for (const earning of normalized.targeted_earnings) { + const binding = await this.providerPayoutBindingForEpoch( + earning.provider, + earning.rail, + earning.payout_revision, + value.epoch + ); + if (binding instanceof Error) return binding; + const provider = await this.get(`prov/${earning.provider}`); + if (!provider || provider.status !== 'active') { + return new Error('Targeted epoch provider is not active.'); + } + let priorGrossAu = ZERO_AU; + if (boundedReceiptSettlement) { + const marker = await this.get( + `epoch/earning-provider/${value.epoch}/${earning.rail}/${earning.provider}` + ); + if (marker !== null) { + if (marker.type !== 'epoch_provider_earning' || + marker.epoch !== value.epoch || + marker.rail !== earning.rail || + marker.provider !== earning.provider || + !Number.isSafeInteger(marker.last_page) || + (replayPosition ? marker.last_page !== page : marker.last_page >= page)) { + return new Error('Bounded receipt provider earning marker is inconsistent.'); + } + priorGrossAu = replayPosition + ? this.safeSubAu(marker.gross_au, earning.gross_au) + : this.normalizeAu( + marker.gross_au, + 'bounded receipt prior provider gross earning', + { allowZero: true } + ); + if (priorGrossAu instanceof Error) return priorGrossAu; + } else if (replayPosition) { + return new Error('Bounded receipt provider earning marker is missing on replay.'); + } + } + const settlementDelta = this.providerSettlementPageDelta({ + grossAu: earning.gross_au, + priorGrossAu, + rail: earning.rail, + feeBps: params.fee_bps, + }); + if (settlementDelta instanceof Error) return settlementDelta; + const { + fee_au: feeAu, + burn_au: burnAu, + provider_au: providerAu, + } = settlementDelta; + providerSettlementDeltas.set( + stableJson([earning.rail, earning.provider]), + { + gross_au: earning.gross_au, + ...settlementDelta, + } + ); + + const liabilityKey = this.providerPayoutLiabilityKey( + earning.provider, + earning.rail, + earning.payout_revision + ); + const existing = (await this.get(liabilityKey)) ?? { + type: 'provider_payout_liability', + provider: earning.provider, + rail: earning.rail, + revision: earning.payout_revision, + target: binding.target, + currency: binding.currency, + chain_id: binding.chain_id, + total_au: ZERO_AU, + held_au: ZERO_AU, + paid_cum_au: ZERO_AU, + holdbacks: [], + updated_epoch: 0, + updated_at: null, + }; + if ( + existing.provider !== earning.provider || + existing.rail !== earning.rail || + existing.revision !== earning.payout_revision || + existing.target !== binding.target || + (existing.currency ?? null) !== binding.currency || + (existing.chain_id ?? null) !== binding.chain_id + ) { + return new Error('Provider payout liability binding mismatch.'); + } + const existingLiabilityError = this.guardianValidatePayoutLiabilityRecord( + existing, + earning.provider, + earning.rail, + earning.payout_revision + ); + if (existingLiabilityError) return existingLiabilityError; + const aggregate = await this.earningRecord(earning.provider, earning.rail); + if (aggregate instanceof Error) return aggregate; + const probeGate = await this.probeGateForEarning(earning.provider, aggregate, params); + if (probeGate instanceof Error) return probeGate; + const lockedEarningEpochs = this.providerLockedEarningEpochs(provider, params); + if (lockedEarningEpochs instanceof Error) return lockedEarningEpochs; + const disputeGate = await this.providerHasOpenDispute(earning.provider); + if (disputeGate instanceof Error) return disputeGate; + const refreshed = this.refreshEarningHoldback( + existing, + value.epoch, + lockedEarningEpochs, + probeGate, + disputeGate + ); + if (refreshed instanceof Error) return refreshed; + const totalAu = this.safeAddAu(refreshed.total_au, providerAu); + if (totalAu instanceof Error) return totalAu; + const heldAu = this.safeAddAu(refreshed.held_au, providerAu); + if (heldAu instanceof Error) return heldAu; + const holdbacks = this.appendHoldbackBucket( + refreshed.holdbacks, + value.epoch, + providerAu, + lockedEarningEpochs + ); + if (holdbacks instanceof Error) return holdbacks; + const nextLiability = { + ...refreshed, + total_au: totalAu, + held_au: heldAu, + holdbacks, + updated_epoch: value.epoch, + updated_at: key, + }; + const nextLiabilityError = this.guardianValidatePayoutLiabilityRecord( + nextLiability, + earning.provider, + earning.rail, + earning.payout_revision + ); + if (nextLiabilityError) return nextLiabilityError; + liabilityUpdates.push({ + key: liabilityKey, + value: nextLiability, + snapshot_key: this.providerPayoutEpochSnapshotKey( + value.epoch, + page, + earning.provider, + earning.rail + ), + snapshot: { + type: 'provider_payout_epoch_binding', + epoch: value.epoch, + page, + provider: earning.provider, + rail: earning.rail, + revision: earning.payout_revision, + target: binding.target, + currency: binding.currency, + chain_id: binding.chain_id, + earned_au: providerAu, + feature_key: key, + }, + }); + } + const liabilityIndexUpdates = await this.nextProviderPayoutLiabilityIndexes( + liabilityUpdates, + value.epoch, + key + ); + if (liabilityIndexUpdates instanceof Error) return liabilityIndexUpdates; + for (const update of liabilityUpdates) { + const existing = await this.get(update.snapshot_key); + if (existing !== null && stableJson(existing) !== stableJson(update.snapshot)) { + return new Error('Targeted epoch payout snapshot already exists.'); + } + hasPreexistingFeatureArtifact ||= existing !== null; + } + for (const update of liabilityIndexUpdates) { + const existing = await this.get(update.key); + hasPreexistingFeatureArtifact ||= + existing !== null && stableJson(existing) === stableJson(update.value); + } + if (hasPreexistingFeatureArtifact && !replayPosition) { + return new Error('Targeted epoch feature artifacts require an idempotent page replay.'); + } + + const previousTx = this.tx; + this.tx = key; + let result; + try { + result = await this.targetedEpochApply( + normalized.ledger_value, + normalized.revision_bindings, + normalized.allocations, + { + commitTransition: options.commitTransition ?? null, + providerSettlementDeltas, + canonicalMarketUsage: reservationBindings.market_usage, + } + ); + } finally { + this.tx = previousTx; + } + if (!result || result instanceof Error || result.ok !== true) return result; + if (result.idempotent === true) { + for (const update of allocationUpdates) { + const allocation = await this.get(update.key); + if (!allocation || stableJson(allocation) !== stableJson(update.value)) { + return new Error('Targeted epoch session allocation is missing.'); + } + } + for (const update of liabilityUpdates) { + const snapshot = await this.get(update.snapshot_key); + if (!snapshot || stableJson(snapshot) !== stableJson(update.snapshot)) { + return new Error('Targeted epoch liability snapshot is missing.'); + } + } + for (const update of liabilityIndexUpdates) { + const index = await this.get(update.key); + if (!index || stableJson(index) !== stableJson(update.value)) { + return new Error('Targeted epoch liability index is missing.'); + } + } + for (const update of consumptionUpdates) { + const consumption = await this.get(update.key); + if (!consumption || stableJson(consumption) !== stableJson(update.value)) { + return new Error('Canonical receipt consumption is missing.'); + } + } + return { + ...result, + op: options.commitTransition ? 'commitApplyTargetedEpochPage0' : 'applyTargetedEpoch', + ...(options.commitTransition ? { + commit_hash: options.commitTransition.record.commit_hash, + replaced_commit: false, + } : {}), + }; + } + + for (const update of reservationBindings.hold_updates) { + await this.put(update.key, update.value); + } + for (const update of reservationBindings.summary_updates) { + await this.put(update.key, update.value); + } + for (const update of reservationBindings.legacy_release_updates) { + await this.put(update.key, update.value); + } + for (const deleteKey of reservationBindings.session_deletes) { + await this.del(deleteKey); + } + for (const update of allocationUpdates) { + await this.put(update.key, update.value); + } + for (const update of consumptionUpdates) { + await this.put(update.key, update.value); + } + for (const update of liabilityUpdates) { + await this.put(update.key, update.value); + await this.put(update.snapshot_key, update.snapshot); + const pointerKey = this.providerPayoutBindingPointerKey( + update.value.provider, + update.value.rail + ); + const pointer = await this.get(pointerKey); + if (pointer?.pending_revision === update.value.revision && + pointer.pending_activation_epoch <= value.epoch) { + await this.put(pointerKey, { + ...pointer, + current_revision: update.value.revision, + pending_revision: null, + pending_activation_epoch: null, + updated_at: key, + }); + } + } + for (const update of liabilityIndexUpdates) { + await this.put(update.key, update.value); + } + return { + ...result, + op: options.commitTransition ? 'commitApplyTargetedEpochPage0' : 'applyTargetedEpoch', + ...(options.commitTransition ? { + commit_hash: options.commitTransition.record.commit_hash, + replaced_commit: options.commitTransition.archive !== null, + } : {}), + }; + } + + async applyCommitTargetedEpochPageZeroFeature(key, value) { + const expectedKey = await this.commitTargetedEpochPageZeroFeatureKey(value); + if (expectedKey instanceof Error) return expectedKey; + if (key !== expectedKey) return new Error('Invalid commit-plus-page-zero feature key.'); + const prepared = await this.normalizeCommitTargetedEpochPageZeroFeatureValue(value); + if (prepared instanceof Error) return prepared; + const applyState = await this.epochApplyStateRecord(); + const commitTransition = await this.prepareTargetedEpochCommitTransition( + prepared, + applyState, + key + ); + if (commitTransition instanceof Error) return commitTransition; + return await this.applyTargetedEpochFeature( + key, + prepared.targeted_value, + { + normalized: prepared.normalized, + expectedKey: key, + commitTransition, + } + ); + } + + async applyDepositTnkFeature(key, value) { + const shapeError = this.validateExactObjectKeys( + value, + ['op', 'sender', 'intent', 'sig'], + 'deposit TNK feature' + ); + if (shapeError) return shapeError; + if (value.op !== 'deposit_tnk') return new Error('Invalid deposit TNK feature op.'); + if (!this.isHexBytes(value.sender, 32)) return new Error('Invalid deposit TNK sender.'); + if (!this.isHexBytes(value.sig, 64)) return new Error('Invalid deposit TNK signature.'); + const intentError = this.validateDepositTnkIntent(value.intent); + if (intentError) return intentError; + if (!this.verifyDepositTnkSignature(value.sender, value.intent, value.sig)) { + return new Error('Invalid deposit TNK signature.'); + } + const expectedKey = await this.depositFeatureKey(value); + if (expectedKey instanceof Error) return expectedKey; + if (key !== expectedKey) return; + + const previousAddress = this.address; + const previousTx = this.tx; + const previousValue = this.value; + this.address = value.sender; + this.tx = key; + this.value = value.intent; + try { + return await this.depositTnk(); + } finally { + this.address = previousAddress; + this.tx = previousTx; + this.value = previousValue; + } + } + + async applyDepositCreditFeature(key, value) { + const expectedKey = await this.depositFeatureKey(value); + if (expectedKey instanceof Error) return expectedKey; + if (key !== expectedKey) return; + + const previousTx = this.tx; + this.tx = key; + try { + if (value.op === 'tnk_deposit') return await this.tnkDeposit(); + if (value.op === 'tap_deposit') return await this.tapDeposit(); + if (value.op === 'tap_deposit_reversal') return await this.tapDepositReversal(); + if (value.op === 'fiat_deposit') return await this.fiatDeposit(); + if (value.op === 'fiat_chargeback') return await this.fiatChargeback(); + return; + } finally { + this.tx = previousTx; + } + } + + async applyRateOracleFeature(key, value) { + this._mayhemApplyStage = 'rate:key'; + const expectedKey = await this.rateFeatureKey(value); + if (expectedKey instanceof Error) return expectedKey; + if (key !== expectedKey) return; + + const previousTx = this.tx; + this.tx = key; + try { + this._mayhemApplyStage = 'rate:dispatch'; + if (value.op === 'rate_oracle') return await this.rateOracle(); + if (value.op === 'tap_rate_oracle') return await this.tapRateOracle(); + return; + } finally { + this.tx = previousTx; + } + } + + async normalizeTargetedPayoutPreparationValue(value) { + const shapeError = this.validateExactObjectKeys( + value, + [ + 'op', + 'contract_version', + 'economic_op_id', + 'rail', + 'epoch', + 'epoch_apply_hash', + 'prepared_at', + 'kind', + 'output_index', + 'payload_hash', + 'payload', + 'liability', + 'external_effect_ids', + 'admin', + 'admin_sig', + ], + 'targeted payout preparation' + ); + if (shapeError) return shapeError; + const rail = this.normalizeLedgerRail(value.rail, 'targeted payout preparation rail'); + if (rail instanceof Error) return rail; + if (value.op !== 'prepare_targeted_payout' || + value.contract_version !== CONTRACT_VERSION || + !this.isHexBytes(value.economic_op_id, 32) || + value.economic_op_id !== value.economic_op_id.toLowerCase() || + !Number.isSafeInteger(value.epoch) || + value.epoch < 1 || + !this.isHexBytes(value.epoch_apply_hash, 32) || + value.epoch_apply_hash !== value.epoch_apply_hash.toLowerCase() || + !Number.isSafeInteger(value.prepared_at) || + value.prepared_at < 0 || + !['liability', 'fee', 'tap_root'].includes(value.kind) || + !Number.isSafeInteger(value.output_index) || + value.output_index < 0 || + !this.isHexBytes(value.payload_hash, 32) || + value.payload_hash !== value.payload_hash.toLowerCase() || + !value.payload || + typeof value.payload !== 'object' || + Array.isArray(value.payload) || + !Array.isArray(value.external_effect_ids) || + value.external_effect_ids.length > 2 || + !this.isHexBytes(value.admin, 32) || + value.admin !== value.admin.toLowerCase() || + !this.isHexBytes(value.admin_sig, 64)) { + return new Error('Invalid targeted payout preparation.'); + } + if (b4a.byteLength(stableJson(value.payload)) > 16_384) { + return new Error('Targeted payout preparation payload exceeds 16384 bytes.'); + } + const externalEffectIds = value.external_effect_ids.map((effectId) => + String(effectId).toLowerCase() + ); + if (externalEffectIds.some((effectId) => !this.isHexBytes(effectId, 32)) || + new Set(externalEffectIds).size !== externalEffectIds.length) { + return new Error('Invalid targeted payout preparation external effect ids.'); + } + if ((value.kind === 'tap_root' && rail !== 'tap') || + (value.kind === 'tap_root' && externalEffectIds.length !== 2) || + (value.kind === 'fee' && rail === 'tap') || + (value.kind === 'fee' && rail === 'tnk' && + externalEffectIds.length !== 1) || + (value.kind === 'fee' && rail === 'fiat' && + externalEffectIds.length !== 0) || + (value.kind === 'liability' && rail === 'tnk' && + externalEffectIds.length !== 1) || + (value.kind === 'liability' && ['tap', 'fiat'].includes(rail) && + externalEffectIds.length !== 0)) { + return new Error('Targeted payout preparation kind does not match rail effects.'); + } + let liability = null; + if (value.kind === 'liability') { + const liabilityShapeError = this.validateExactObjectKeys( + value.liability, + [ + 'provider', + 'payout_revision', + 'target', + 'currency', + 'chain_id', + 'paid_cum_au_before', + 'aggregate_paid_cum_au_before', + 'liability_au', + 'paid_au', + ], + 'targeted payout preparation liability' + ); + if (liabilityShapeError) return liabilityShapeError; + if (!this.isHexBytes(value.liability.provider, 32) || + value.liability.provider !== value.liability.provider.toLowerCase() || + !this.isHexBytes(value.liability.payout_revision, 32) || + value.liability.payout_revision !== value.liability.payout_revision.toLowerCase() || + !this.isSafeKeyPart(value.liability.target) || + (value.liability.currency !== null && + this.normalizeFiatCurrency(value.liability.currency) !== value.liability.currency) || + (value.liability.chain_id !== null && + (!Number.isSafeInteger(value.liability.chain_id) || + value.liability.chain_id < 1))) { + return new Error('Invalid targeted payout preparation liability identity.'); + } + const paidCumAuBefore = this.normalizeAu( + value.liability.paid_cum_au_before, + 'targeted payout preparation liability watermark', + { allowZero: true } + ); + const aggregatePaidCumAuBefore = this.normalizeAu( + value.liability.aggregate_paid_cum_au_before, + 'targeted payout preparation aggregate watermark', + { allowZero: true } + ); + const liabilityAu = this.normalizeAu( + value.liability.liability_au, + 'targeted payout preparation liability amount', + { allowZero: false } + ); + const paidAu = this.normalizeAu( + value.liability.paid_au, + 'targeted payout preparation paid amount', + { allowZero: false } + ); + if ([paidCumAuBefore, aggregatePaidCumAuBefore, liabilityAu, paidAu] + .some((entry) => entry instanceof Error) || + this.compareAu(paidAu, liabilityAu) > 0) { + return new Error('Invalid targeted payout preparation liability amount.'); + } + liability = { + provider: value.liability.provider, + payout_revision: value.liability.payout_revision, + target: value.liability.target, + currency: value.liability.currency, + chain_id: value.liability.chain_id, + paid_cum_au_before: paidCumAuBefore, + aggregate_paid_cum_au_before: aggregatePaidCumAuBefore, + liability_au: liabilityAu, + paid_au: paidAu, + }; + } else if (value.liability !== null) { + return new Error('Non-liability payout preparation cannot bind a liability.'); + } + let payload = stableValue(value.payload); + if (rail === 'fiat') { + payload = this.normalizeTargetedFiatPreparationPayload( + value, + payload, + liability + ); + if (payload instanceof Error) return payload; + } else if (rail === 'tnk') { + payload = await this.normalizeTargetedTnkPreparationPayload( + value, + payload, + liability, + externalEffectIds + ); + if (payload instanceof Error) return payload; + } + if (stableJson(payload) !== stableJson(value.payload)) { + return new Error('Targeted payout preparation payload must be canonical.'); + } + const payloadHash = await this.opaqueHash( + 'mayhem-targeted-payout-preparation-payload-v1', + { + economic_op_id: value.economic_op_id, + rail, + epoch: value.epoch, + epoch_apply_hash: value.epoch_apply_hash, + kind: value.kind, + output_index: value.output_index, + payload, + } + ); + if (payloadHash !== value.payload_hash) { + return new Error('Targeted payout preparation payload hash mismatch.'); + } + const normalized = { + op: 'prepare_targeted_payout', + contract_version: CONTRACT_VERSION, + economic_op_id: value.economic_op_id, + rail, + epoch: value.epoch, + epoch_apply_hash: value.epoch_apply_hash, + prepared_at: value.prepared_at, + kind: value.kind, + output_index: value.output_index, + payload_hash: value.payload_hash, + payload, + liability, + external_effect_ids: externalEffectIds, + admin: value.admin, + admin_sig: value.admin_sig.toLowerCase(), + }; + if (stableJson(normalized) !== stableJson(value)) { + return new Error('Targeted payout preparation must be canonical.'); + } + return normalized; + } + + payoutPreparationRecordKey(rail, economicOpId) { + return `payout/preparation/${rail}/${economicOpId}`; + } + + payoutPreparationLiabilityLockKey(rail, liability) { + return ( + `payout/preparation-lock/${rail}/${liability.provider}/` + + `${liability.payout_revision}/${liability.paid_cum_au_before}` + ); + } + + payoutPreparationAggregateTailKey(rail, provider) { + return `payout/preparation-tail/${rail}/${provider}`; + } + + payoutPreparationEffectLockKey(rail, effectId) { + return `payout/preparation-effect/${rail}/${effectId}`; + } + + async validatePayoutPreparationLiability(value) { + const liability = value.liability; + const binding = await this.get( + this.providerPayoutBindingFeatureKey( + value.rail, + liability.provider, + liability.payout_revision + ) + ); + if (!binding || + binding.verified !== true || + binding.provider !== liability.provider || + binding.rail !== value.rail || + binding.revision !== liability.payout_revision || + binding.activation_epoch > value.epoch || + binding.target !== liability.target || + (binding.currency ?? null) !== liability.currency || + (binding.chain_id ?? null) !== liability.chain_id) { + return new Error('Targeted payout preparation requires its immutable payout binding.'); + } + const liabilityKey = this.providerPayoutLiabilityKey( + liability.provider, + value.rail, + liability.payout_revision + ); + const current = await this.get(liabilityKey); + if (!current || + current.provider !== liability.provider || + current.rail !== value.rail || + current.revision !== liability.payout_revision || + current.target !== liability.target || + (current.currency ?? null) !== liability.currency || + (current.chain_id ?? null) !== liability.chain_id || + current.paid_cum_au !== liability.paid_cum_au_before) { + return new Error('Targeted payout preparation liability watermark mismatch.'); + } + const provider = await this.get(`prov/${liability.provider}`); + if (!provider || + (provider.status !== 'active' && provider.status !== 'banned')) { + return new Error('Targeted payout preparation provider status is not payable.'); + } + const params = await this.activeParamsAt(value.prepared_at, [ + 'holdback_epochs', + 'challenge_epochs', + 'canary_probe_holdback_bps', + 'canary_probe_release_min_passes', + ]); + if (params instanceof Error) return params; + const probeGate = await this.probeGateForEarning( + liability.provider, + current, + params + ); + if (probeGate instanceof Error) return probeGate; + const lockedEpochs = this.providerLockedEarningEpochs(provider, params); + if (lockedEpochs instanceof Error) return lockedEpochs; + const disputeGate = await this.providerHasOpenDispute(liability.provider); + if (disputeGate instanceof Error) return disputeGate; + const refreshed = this.refreshEarningHoldback( + current, + value.epoch, + lockedEpochs, + probeGate, + disputeGate + ); + if (refreshed instanceof Error) return refreshed; + const payable = this.safeSubAu( + this.safeSubAu(refreshed.total_au, refreshed.held_au), + refreshed.paid_cum_au + ); + if (payable instanceof Error) return payable; + if (this.compareAu(liability.liability_au, payable) > 0) { + return new Error('Targeted payout preparation exceeds revision liability.'); + } + const earning = await this.earningRecord(liability.provider, value.rail); + if (earning instanceof Error) return earning; + const tailKey = this.payoutPreparationAggregateTailKey(value.rail, liability.provider); + const tail = await this.get(tailKey); + if (tail && + typeof tail.consumed !== 'boolean') { + return new Error('Targeted payout preparation aggregate tail is invalid.'); + } + if (tail?.consumed === true && + tail.paid_cum_au_after !== earning.paid_cum_au) { + return new Error('Consumed payout preparation tail does not match aggregate earnings.'); + } + const expectedAggregateBefore = tail?.consumed === false + ? tail.paid_cum_au_after + : earning.paid_cum_au; + if (liability.aggregate_paid_cum_au_before !== expectedAggregateBefore) { + return new Error('Targeted payout preparation aggregate watermark mismatch.'); + } + const paidCumAuAfter = this.safeAddAu( + liability.aggregate_paid_cum_au_before, + liability.paid_au + ); + if (paidCumAuAfter instanceof Error) return paidCumAuAfter; + return { + liability_key: liabilityKey, + tail_key: tailKey, + paid_cum_au_after: paidCumAuAfter, + }; + } + + async applyTargetedPayoutPreparationFeature(key, value) { + const normalized = await this.normalizeTargetedPayoutPreparationValue(value); + if (normalized instanceof Error) return normalized; + const expectedKey = await this.targetedPayoutPreparationFeatureKey(normalized); + if (expectedKey instanceof Error) return expectedKey; + if (key !== expectedKey) return new Error('Invalid targeted payout preparation key.'); + const admin = await this.get('admin'); + if (normalized.admin !== admin || this.address !== admin) { + return new Error('Targeted payout preparation requires canonical admin authority.'); + } + const verify = this.protocol?.peer?.wallet?.verify; + if (typeof verify !== 'function' || + verify.call( + this.protocol.peer.wallet, + normalized.admin_sig, + payoutPreparationMessage(normalized), + admin + ) !== true) { + return new Error('Invalid targeted payout preparation admin signature.'); + } + const applyAnchor = await this.requireEpochApplyAnchor( + normalized.epoch, + normalized.epoch_apply_hash, + 'Targeted payout preparation' + ); + if (applyAnchor instanceof Error) return applyAnchor; + if (['fiat', 'tnk'].includes(normalized.rail)) { + const plan = await this.get( + this.targetedPayoutEpochPlanRecordKey(normalized.rail, normalized.epoch) + ); + const output = plan?.value?.outputs?.[normalized.output_index]; + if (!plan || + plan.plan_root !== normalized.payload.plan_root || + plan.value.epoch_apply_hash !== normalized.epoch_apply_hash || + output?.economic_op_id !== normalized.economic_op_id || + stableJson(output) !== stableJson(normalized.payload.output)) { + return new Error( + 'Targeted payout preparation does not match its canonical epoch plan.' + ); + } + } + if (normalized.rail === 'tnk') { + const payment = await this.canonicalTnkPaymentConfig(); + if (payment instanceof Error) return payment; + if (normalized.payload.network !== payment.network || + normalized.payload.treasury_from !== payment.treasury_address) { + return new Error( + 'Targeted TNK preparation source does not match payment config.' + ); + } + } + if (normalized.rail === 'tap') { + const params = await this.activeParamsAt(normalized.prepared_at, ['fee_bps']); + const split = this.targetedTapPayoutSplit(params.fee_bps); + if (split instanceof Error) return split; + if (normalized.payload.fee_bps !== split.fee_bps || + normalized.payload.tap_burn_bps !== split.tap_burn_bps || + normalized.payload.provider_share_bps !== split.provider_share_bps) { + return new Error( + 'Targeted TAP preparation does not match the fixed on-chain split.' + ); + } + } + const recordKey = this.payoutPreparationRecordKey( + normalized.rail, + normalized.economic_op_id + ); + const record = { + type: 'targeted_payout_preparation', + ...normalized, + consumed: false, + consumed_by: null, + prepared_at_tx: this.tx, + }; + const existing = await this.get(recordKey); + if (existing !== null) { + if (stableJson(existing) === stableJson(record)) { + return { + ok: true, + op: 'targetedPayoutPreparation', + rail: normalized.rail, + economic_op_id: normalized.economic_op_id, + idempotent: true, + }; + } + return new Error('Targeted payout preparation economic operation already exists.'); + } + let liabilityState = null; + if (normalized.liability !== null) { + liabilityState = await this.validatePayoutPreparationLiability(normalized); + if (liabilityState instanceof Error) return liabilityState; + const lockKey = this.payoutPreparationLiabilityLockKey( + normalized.rail, + normalized.liability + ); + if ((await this.get(lockKey)) !== null) { + return new Error('Targeted payout liability watermark already has a preparation.'); + } + } + for (const effectId of normalized.external_effect_ids) { + if ((await this.get( + this.payoutPreparationEffectLockKey(normalized.rail, effectId) + )) !== null) { + return new Error('Targeted payout external effect id already has a preparation.'); + } + } + if (normalized.liability !== null) { + await this.put( + this.payoutPreparationLiabilityLockKey(normalized.rail, normalized.liability), + { + economic_op_id: normalized.economic_op_id, + rail: normalized.rail, + provider: normalized.liability.provider, + payout_revision: normalized.liability.payout_revision, + paid_cum_au_before: normalized.liability.paid_cum_au_before, + prepared_at: this.tx, + } + ); + await this.put(liabilityState.tail_key, { + economic_op_id: normalized.economic_op_id, + paid_cum_au_before: normalized.liability.aggregate_paid_cum_au_before, + paid_cum_au_after: liabilityState.paid_cum_au_after, + consumed: false, + updated_at: this.tx, + }); + } + for (const effectId of normalized.external_effect_ids) { + await this.put(this.payoutPreparationEffectLockKey(normalized.rail, effectId), { + economic_op_id: normalized.economic_op_id, + effect_id: effectId, + consumed: false, + updated_at: this.tx, + }); + } + await this.put(recordKey, record); + return { + ok: true, + op: 'targetedPayoutPreparation', + rail: normalized.rail, + economic_op_id: normalized.economic_op_id, + idempotent: false, + }; + } + + async requireTargetedPayoutAdminSignature(value, label) { + const admin = await this.get('admin'); + if (value.admin !== admin || this.address !== admin) { + return new Error(`${label} requires canonical admin authority.`); + } + const verify = this.protocol?.peer?.wallet?.verify; + if (typeof verify !== 'function' || + verify.call( + this.protocol.peer.wallet, + value.admin_sig, + targetedPayoutControlMessage(value), + admin + ) !== true) { + return new Error(`Invalid ${label.toLowerCase()} admin signature.`); + } + return null; + } + + targetedPayoutEpochPlanRecordKey(rail, epoch) { + return `payout/epoch-plan/${rail}/${epoch}`; + } + + targetedPayoutOutputRecordKey(rail, epoch, economicOpId) { + return `settle/targeted/${rail}/${epoch}/output/${economicOpId}`; + } + + targetedPayoutEpochCloseRecordKey(rail, epoch) { + return `settle/targeted/${rail}/${epoch}`; + } + + targetedFiatAttemptRecordKey(economicOpId, attemptId) { + return `payout/attempt/fiat/${economicOpId}/${attemptId}`; + } + + targetedFiatAttemptTailKey(economicOpId) { + return `payout/attempt/fiat/${economicOpId}/latest`; + } + + targetedFiatAttemptEffectKey(effectId) { + return `payout/attempt-effect/fiat/${effectId}`; + } + + normalizeTargetedPayoutPlanOutput(rail, output, expectedIndex) { + if (!output || typeof output !== 'object' || Array.isArray(output) || + !this.isHexBytes(output.economic_op_id, 32) || + output.economic_op_id !== output.economic_op_id.toLowerCase() || + output.output_index !== expectedIndex) { + return new Error('Invalid targeted payout epoch output identity.'); + } + const { + economic_op_id: economicOpId, + output_index: outputIndex, + ...economicOutput + } = output; + let normalized; + if (rail === 'tnk') { + const allowed = economicOutput.role === 'provider' + ? [ + 'role', + 'provider', + 'payout_revision', + 'to', + 'paid_cum_au_before', + 'aggregate_paid_cum_au_before', + 'au', + 'tnk_e18', + ] + : ['role', 'to', 'au', 'tnk_e18']; + const shapeError = this.validateExactObjectKeys( + economicOutput, + allowed, + 'targeted TNK epoch output' + ); + if (shapeError) return shapeError; + if (economicOutput.role === 'provider') { + if (!this.isHexBytes(economicOutput.provider, 32) || + economicOutput.provider !== economicOutput.provider.toLowerCase() || + !this.isHexBytes(economicOutput.payout_revision, 32) || + economicOutput.payout_revision !== economicOutput.payout_revision.toLowerCase()) { + return new Error('Invalid targeted TNK epoch provider identity.'); + } + const paidCumAuBefore = this.normalizeAu( + economicOutput.paid_cum_au_before, + 'targeted TNK epoch liability watermark', + { allowZero: true } + ); + const aggregatePaidCumAuBefore = this.normalizeAu( + economicOutput.aggregate_paid_cum_au_before, + 'targeted TNK epoch aggregate watermark', + { allowZero: true } + ); + if (paidCumAuBefore instanceof Error || + aggregatePaidCumAuBefore instanceof Error) { + return new Error('Invalid targeted TNK epoch output watermark.'); + } + normalized = this.normalizeTargetedTnkSettlementOutput(economicOutput); + if (normalized instanceof Error) return normalized; + normalized = { + ...normalized, + payout_revision: economicOutput.payout_revision, + paid_cum_au_before: paidCumAuBefore, + aggregate_paid_cum_au_before: aggregatePaidCumAuBefore, + }; + } else if (economicOutput.role === 'operator_fee') { + normalized = this.normalizeTargetedTnkSettlementOutput(economicOutput); + if (normalized instanceof Error) return normalized; + } else { + return new Error('Invalid targeted TNK epoch output role.'); + } + } else if (rail === 'fiat') { + normalized = this.normalizeTargetedFiatPreparationOutput(economicOutput); + if (normalized instanceof Error) return normalized; + } else { + return new Error('Targeted payout epoch plans support only fiat and TNK.'); + } + const result = { + economic_op_id: economicOpId, + output_index: outputIndex, + ...normalized, + }; + return stableJson(result) === stableJson(output) + ? result + : new Error('Targeted payout epoch output must be canonical.'); + } + + normalizeTargetedPayoutCarry(value) { + const shapeError = this.validateExactObjectKeys( + value, + [ + 'provider', + 'payout_revision', + 'liability_au', + 'held_au', + 'payable_au', + 'payout_min_au', + 'reason', + ], + 'targeted payout epoch carry' + ); + if (shapeError) return shapeError; + if (!this.isHexBytes(value.provider, 32) || + value.provider !== value.provider.toLowerCase() || + !this.isHexBytes(value.payout_revision, 32) || + value.payout_revision !== value.payout_revision.toLowerCase() || + !['held', 'below_payout_minimum'].includes(value.reason)) { + return new Error('Invalid targeted payout epoch carry identity.'); + } + const liabilityAu = this.normalizeAu( + value.liability_au, + 'targeted payout carry liability', + { allowZero: false } + ); + const heldAu = this.normalizeAu( + value.held_au, + 'targeted payout carry held amount', + { allowZero: true } + ); + const payableAu = this.normalizeAu( + value.payable_au, + 'targeted payout carry payable amount', + { allowZero: true } + ); + const payoutMinAu = this.normalizeAu( + value.payout_min_au, + 'targeted payout carry minimum', + { allowZero: true } + ); + if ([liabilityAu, heldAu, payableAu, payoutMinAu] + .some((entry) => entry instanceof Error)) { + return new Error('Invalid targeted payout epoch carry amount.'); + } + const classified = this.safeAddAu(heldAu, payableAu); + if (classified instanceof Error || + classified !== liabilityAu || + (value.reason === 'held' && + (this.isZeroAu(heldAu) || !this.isZeroAu(payableAu))) || + (value.reason === 'below_payout_minimum' && + (this.isZeroAu(payableAu) || + this.compareAu(payableAu, payoutMinAu) >= 0))) { + return new Error('Targeted payout epoch carry classification is invalid.'); + } + return { + provider: value.provider, + payout_revision: value.payout_revision, + liability_au: liabilityAu, + held_au: heldAu, + payable_au: payableAu, + payout_min_au: payoutMinAu, + reason: value.reason, + }; + } + + async normalizeTargetedPayoutEpochValue(value) { + const shapeError = this.validateExactObjectKeys( + value, + [ + 'op', + 'contract_version', + 'rail', + 'epoch', + 'at', + 'epoch_apply_hash', + 'snapshot_signed_length', + 'outcome', + 'outputs', + 'carry', + 'outputs_root', + 'carry_root', + 'plan_root', + 'admin', + 'admin_sig', + ], + 'targeted payout epoch plan' + ); + if (shapeError) return shapeError; + if (value.op !== 'prepare_targeted_payout_epoch' || + value.contract_version !== CONTRACT_VERSION || + !['fiat', 'tnk'].includes(value.rail) || + !Number.isSafeInteger(value.epoch) || + value.epoch < 1 || + !Number.isSafeInteger(value.at) || + value.at < 0 || + !this.isHexBytes(value.epoch_apply_hash, 32) || + value.epoch_apply_hash !== value.epoch_apply_hash.toLowerCase() || + !Number.isSafeInteger(value.snapshot_signed_length) || + value.snapshot_signed_length < 1 || + !['payouts', 'carry', 'no_work'].includes(value.outcome) || + !Array.isArray(value.outputs) || + !Array.isArray(value.carry) || + !this.isHexBytes(value.outputs_root, 32) || + value.outputs_root !== value.outputs_root.toLowerCase() || + !this.isHexBytes(value.carry_root, 32) || + value.carry_root !== value.carry_root.toLowerCase() || + !this.isHexBytes(value.plan_root, 32) || + value.plan_root !== value.plan_root.toLowerCase() || + !this.isHexBytes(value.admin, 32) || + value.admin !== value.admin.toLowerCase() || + !this.isHexBytes(value.admin_sig, 64)) { + return new Error('Invalid targeted payout epoch plan.'); + } + const outputs = value.outputs.map((output, index) => + this.normalizeTargetedPayoutPlanOutput(value.rail, output, index) + ); + const outputError = outputs.find((output) => output instanceof Error); + if (outputError) return outputError; + const economicIds = new Set(outputs.map((output) => output.economic_op_id)); + if (economicIds.size !== outputs.length) { + return new Error('Targeted payout epoch output identities must be unique.'); + } + if (outputs.filter((output) => output.role === 'operator_fee').length > 1) { + return new Error('Targeted payout epoch may contain only one operator output.'); + } + for (let index = 1; index < outputs.length; index += 1) { + const left = outputs[index - 1]; + const right = outputs[index]; + const order = left.role === right.role + ? left.role === 'provider' + ? compareCodepoint(left.provider, right.provider) || + compareCodepoint(left.payout_revision, right.payout_revision) + : compareCodepoint(left.economic_op_id, right.economic_op_id) + : left.role === 'provider' ? -1 : 1; + if (order >= 0) { + return new Error('Targeted payout epoch outputs are not canonically ordered.'); + } + } + const carry = value.carry.map((entry) => this.normalizeTargetedPayoutCarry(entry)); + const carryError = carry.find((entry) => entry instanceof Error); + if (carryError) return carryError; + carry.sort((left, right) => + compareCodepoint(left.provider, right.provider) || + compareCodepoint(left.payout_revision, right.payout_revision) + ); + if (stableJson(carry) !== stableJson(value.carry) || + new Set(carry.map((entry) => + `${entry.provider}/${entry.payout_revision}` + )).size !== carry.length) { + return new Error('Targeted payout epoch carries must be canonical and unique.'); + } + const plannedLiabilities = new Set( + outputs + .filter((output) => output.role === 'provider') + .map((output) => `${output.provider}/${output.payout_revision}`) + ); + if (carry.some((entry) => + plannedLiabilities.has(`${entry.provider}/${entry.payout_revision}`) + )) { + return new Error('Targeted payout liability cannot be both payable and carried.'); + } + const expectedOutcome = outputs.length > 0 + ? 'payouts' + : carry.length > 0 ? 'carry' : 'no_work'; + if (value.outcome !== expectedOutcome) { + return new Error('Targeted payout epoch outcome does not match its work.'); + } + const outputsRoot = await this.opaqueHash( + 'mayhem-targeted-payout-epoch-outputs-v1', + { rail: value.rail, epoch: value.epoch, outputs } + ); + const carryRoot = await this.opaqueHash( + 'mayhem-targeted-payout-epoch-carry-v1', + { rail: value.rail, epoch: value.epoch, carry } + ); + const planRoot = await this.opaqueHash( + 'mayhem-targeted-payout-epoch-plan-v1', + { + rail: value.rail, + epoch: value.epoch, + at: value.at, + epoch_apply_hash: value.epoch_apply_hash, + snapshot_signed_length: value.snapshot_signed_length, + outcome: value.outcome, + outputs_root: outputsRoot, + carry_root: carryRoot, + } + ); + if (value.outputs_root !== outputsRoot || + value.carry_root !== carryRoot || + value.plan_root !== planRoot) { + return new Error('Targeted payout epoch root mismatch.'); + } + const normalized = { + op: 'prepare_targeted_payout_epoch', + contract_version: CONTRACT_VERSION, + rail: value.rail, + epoch: value.epoch, + at: value.at, + epoch_apply_hash: value.epoch_apply_hash, + snapshot_signed_length: value.snapshot_signed_length, + outcome: value.outcome, + outputs, + carry, + outputs_root: outputsRoot, + carry_root: carryRoot, + plan_root: planRoot, + admin: value.admin, + admin_sig: value.admin_sig.toLowerCase(), + }; + return stableJson(normalized) === stableJson(value) + ? normalized + : new Error('Targeted payout epoch plan must be canonical.'); + } + + async validateTargetedPayoutEpochCompleteness(value, params) { + const index = await this.providerPayoutLiabilityIndex(value.rail); + if (index instanceof Error) return index; + const providerOutputs = new Map( + value.outputs + .filter((output) => output.role === 'provider') + .map((output) => [ + `${output.provider}/${output.payout_revision}`, + output, + ]) + ); + const carries = new Map(value.carry.map((entry) => [ + `${entry.provider}/${entry.payout_revision}`, + entry, + ])); + const classified = new Set(); + const aggregateCursors = new Map(); + + for (const entry of index.entries) { + const identity = `${entry.provider}/${entry.payout_revision}`; + const liability = await this.get( + this.providerPayoutLiabilityKey( + entry.provider, + value.rail, + entry.payout_revision + ) + ); + if (!liability || + liability.type !== 'provider_payout_liability' || + liability.provider !== entry.provider || + liability.rail !== value.rail || + liability.revision !== entry.payout_revision || + liability.updated_epoch > value.epoch) { + return new Error('Targeted payout liability index does not match canonical state.'); + } + const liabilityError = this.guardianValidatePayoutLiabilityRecord( + liability, + entry.provider, + value.rail, + entry.payout_revision + ); + if (liabilityError) return liabilityError; + const provider = await this.get(`prov/${entry.provider}`); + if (!provider || + (provider.status !== 'active' && provider.status !== 'banned')) { + return new Error('Targeted payout indexed provider status is not payable.'); + } + const binding = await this.get( + this.providerPayoutBindingFeatureKey( + value.rail, + entry.provider, + entry.payout_revision + ) + ); + if (!binding || + binding.verified !== true || + binding.provider !== entry.provider || + binding.rail !== value.rail || + binding.revision !== entry.payout_revision || + binding.activation_epoch > value.epoch || + binding.target !== liability.target || + (binding.currency ?? null) !== (liability.currency ?? null) || + (binding.chain_id ?? null) !== (liability.chain_id ?? null)) { + return new Error('Targeted payout indexed liability binding mismatch.'); + } + const probeGate = await this.probeGateForEarning( + entry.provider, + liability, + params + ); + if (probeGate instanceof Error) return probeGate; + const lockedEpochs = this.providerLockedEarningEpochs(provider, params); + if (lockedEpochs instanceof Error) return lockedEpochs; + const disputeGate = await this.providerHasOpenDispute(entry.provider); + if (disputeGate instanceof Error) return disputeGate; + const refreshed = this.refreshEarningHoldback( + liability, + value.epoch, + lockedEpochs, + probeGate, + disputeGate + ); + if (refreshed instanceof Error) return refreshed; + const outstanding = this.safeSubAu( + refreshed.total_au, + refreshed.paid_cum_au + ); + const payable = this.safeSubAu(outstanding, refreshed.held_au); + if (outstanding instanceof Error || payable instanceof Error) { + return new Error('Targeted payout canonical liability is invalid.'); + } + const output = providerOutputs.get(identity) ?? null; + const carry = carries.get(identity) ?? null; + if (this.isZeroAu(outstanding)) { + if (output !== null || carry !== null) { + return new Error('Settled targeted payout liability must be omitted.'); + } + continue; + } + + const isPayable = + !this.isZeroAu(payable) && + this.compareAu(payable, params.payout_min_au) >= 0; + if (!isPayable) { + const expectedCarry = { + provider: entry.provider, + payout_revision: entry.payout_revision, + liability_au: outstanding, + held_au: refreshed.held_au, + payable_au: payable, + payout_min_au: params.payout_min_au, + reason: this.isZeroAu(payable) ? 'held' : 'below_payout_minimum', + }; + if (output !== null || + carry === null || + stableJson(carry) !== stableJson(expectedCarry)) { + return new Error( + 'Targeted payout plan must explicitly carry every held or below-minimum liability.' + ); + } + classified.add(identity); + continue; + } + + if (output === null || carry !== null || + output.to !== binding.target || + output.paid_cum_au_before !== refreshed.paid_cum_au || + (value.rail === 'tnk' && output.au !== payable) || + (value.rail === 'fiat' && + (output.liability_au !== payable || + output.destination_currency !== binding.currency))) { + return new Error( + 'Targeted payout plan must include every payable canonical liability exactly.' + ); + } + let aggregate = aggregateCursors.get(entry.provider); + if (!aggregate) { + const earning = await this.earningRecord(entry.provider, value.rail); + if (earning instanceof Error) return earning; + const earningError = this.guardianValidateEarningRecord( + earning, + entry.provider, + value.rail + ); + if (earningError) return earningError; + const aggregateProbeGate = await this.probeGateForEarning( + entry.provider, + earning, + params + ); + if (aggregateProbeGate instanceof Error) return aggregateProbeGate; + const refreshedEarning = this.refreshEarningHoldback( + earning, + value.epoch, + lockedEpochs, + aggregateProbeGate, + disputeGate + ); + if (refreshedEarning instanceof Error) return refreshedEarning; + aggregate = { paid_cum_au: refreshedEarning.paid_cum_au }; + } + if (output.aggregate_paid_cum_au_before !== aggregate.paid_cum_au) { + return new Error('Targeted payout aggregate paid watermark mismatch.'); + } + const settledAu = value.rail === 'fiat' ? output.paid_au : output.au; + const nextPaid = this.safeAddAu(aggregate.paid_cum_au, settledAu); + if (nextPaid instanceof Error) return nextPaid; + aggregateCursors.set(entry.provider, { paid_cum_au: nextPaid }); + classified.add(identity); + } + + if (classified.size !== carries.size + providerOutputs.size) { + return new Error( + 'Targeted payout plan contains a liability absent from the canonical index.' + ); + } + + const operatorOutputs = value.outputs.filter( + (output) => output.role === 'operator_fee' + ); + const fee = await this.feeCumRecord(value.rail); + if (fee instanceof Error) return fee; + const feeError = this.guardianValidateFeeRecord(fee, value.rail); + if (feeError) return feeError; + const payableFee = this.safeSubAu(fee.cum_au, fee.swept_cum_au); + if (payableFee instanceof Error) return payableFee; + if (this.isZeroAu(payableFee)) { + if (operatorOutputs.length !== 0) { + return new Error('Targeted payout plan includes a nonexistent operator fee.'); + } + } else if ( + operatorOutputs.length !== 1 || + (value.rail === 'tnk' && operatorOutputs[0].au !== payableFee) || + (value.rail === 'fiat' && + operatorOutputs[0].liability_au !== payableFee) + ) { + return new Error( + 'Targeted payout plan must include the complete canonical operator fee.' + ); + } + return null; + } + + async applyTargetedPayoutEpochFeature(key, value) { + const normalized = await this.normalizeTargetedPayoutEpochValue(value); + if (normalized instanceof Error) return normalized; + const expectedKey = await this.targetedPayoutEpochFeatureKey(normalized); + if (expectedKey instanceof Error) return expectedKey; + if (key !== expectedKey) return new Error('Invalid targeted payout epoch feature key.'); + const adminError = await this.requireTargetedPayoutAdminSignature( + normalized, + 'Targeted payout epoch plan' + ); + if (adminError) return adminError; + const applyAnchor = await this.requireEpochApplyAnchor( + normalized.epoch, + normalized.epoch_apply_hash, + 'Targeted payout epoch plan' + ); + if (applyAnchor instanceof Error) return applyAnchor; + const recordKey = this.targetedPayoutEpochPlanRecordKey( + normalized.rail, + normalized.epoch + ); + const existing = await this.get(recordKey); + if (existing !== null) { + if (existing.plan_root === normalized.plan_root && + stableJson(existing.value) === stableJson(normalized)) { + return { + ok: true, + op: 'targetedPayoutEpochPrepared', + rail: normalized.rail, + epoch: normalized.epoch, + plan_root: normalized.plan_root, + idempotent: true, + }; + } + return new Error('Targeted payout epoch plan already exists.'); + } + const applyState = await this.epochApplyStateRecord(); + if (applyState.updated_epoch !== normalized.epoch || + applyState.last_apply_hash !== normalized.epoch_apply_hash || + (applyState.pending_epoch ?? null) !== null) { + return new Error( + 'Targeted payout epoch plan must freeze the latest completed canonical apply.' + ); + } + const params = await this.activeParamsAt( + normalized.at, + [ + normalized.rail === 'tnk' + ? 'max_tnk_settlement_outputs' + : 'max_fiat_settlement_outputs', + 'payout_min_au', + 'holdback_epochs', + 'challenge_epochs', + 'canary_probe_holdback_bps', + 'canary_probe_release_min_passes', + ] + ); + const maxOutputs = normalized.rail === 'tnk' + ? params.max_tnk_settlement_outputs + : params.max_fiat_settlement_outputs; + if (normalized.outputs.length > maxOutputs) { + return new Error('Targeted payout epoch output count exceeds limit.'); + } + const completenessError = await this.validateTargetedPayoutEpochCompleteness( + normalized, + params + ); + if (completenessError) return completenessError; + await this.put(recordKey, { + type: 'targeted_payout_epoch_plan', + rail: normalized.rail, + epoch: normalized.epoch, + plan_root: normalized.plan_root, + value: normalized, + prepared_at: this.tx, + }); + return { + ok: true, + op: 'targetedPayoutEpochPrepared', + rail: normalized.rail, + epoch: normalized.epoch, + plan_root: normalized.plan_root, + idempotent: false, + }; + } + + normalizeTargetedFiatAttemptRequest(request) { + const shapeError = this.validateExactObjectKeys( + request, + [ + 'processor', + 'kind', + 'destination', + 'source_currency', + 'source_amount_minor', + 'destination_currency', + 'destination_amount_min_minor', + 'destination_amount_max_minor', + 'fx_quote_id', + 'fx_quote_hash', + 'transfer_group', + 'metadata_hash', + ], + 'targeted fiat attempt request' + ); + if (shapeError) return shapeError; + const sourceCurrency = this.normalizeFiatCurrency(request.source_currency); + const sourceAmountMinor = this.normalizeFiatMinor(request.source_amount_minor); + if (request.processor !== 'stripe' || + !['stripe_transfer', 'platform_balance'].includes(request.kind) || + !this.isSafeKeyPart(request.destination) || + sourceCurrency instanceof Error || + sourceCurrency !== request.source_currency || + sourceAmountMinor instanceof Error || + sourceAmountMinor !== request.source_amount_minor || + !this.isHexBytes(request.metadata_hash, 32) || + request.metadata_hash !== request.metadata_hash.toLowerCase()) { + return new Error('Invalid targeted fiat attempt request.'); + } + if (request.kind === 'platform_balance') { + if (request.destination_currency !== null || + request.destination_amount_min_minor !== null || + request.destination_amount_max_minor !== null || + request.fx_quote_id !== null || + request.fx_quote_hash !== null || + request.transfer_group !== null) { + return new Error('Platform-balance fiat attempt must not contain Stripe transfer terms.'); + } + return { + processor: 'stripe', + kind: 'platform_balance', + destination: request.destination, + source_currency: sourceCurrency, + source_amount_minor: sourceAmountMinor, + destination_currency: null, + destination_amount_min_minor: null, + destination_amount_max_minor: null, + fx_quote_id: null, + fx_quote_hash: null, + transfer_group: null, + metadata_hash: request.metadata_hash, + }; + } + const destinationCurrency = this.normalizeFiatCurrency(request.destination_currency); + const destinationMin = this.normalizeFiatMinor(request.destination_amount_min_minor); + const destinationMax = this.normalizeFiatMinor(request.destination_amount_max_minor); + const expectedGroup = /^mayhem_fiat_epoch_[1-9][0-9]*_[0-9a-f]{16}$/; + if (destinationCurrency instanceof Error || + destinationCurrency !== request.destination_currency || + destinationMin instanceof Error || + destinationMax instanceof Error || + BigInt(destinationMax) < BigInt(destinationMin) || + !expectedGroup.test(String(request.transfer_group || ''))) { + return new Error('Invalid targeted fiat attempt destination terms.'); + } + const requiresQuote = sourceCurrency !== 'usd' || destinationCurrency !== 'usd'; + if (requiresQuote) { + if (!/^fxq_[A-Za-z0-9._-]+$/.test(String(request.fx_quote_id || '')) || + !this.isHexBytes(request.fx_quote_hash, 32) || + request.fx_quote_hash !== request.fx_quote_hash.toLowerCase()) { + return new Error('Targeted fiat attempt requires a canonical FX quote.'); + } + } else if (request.fx_quote_id !== null || request.fx_quote_hash !== null) { + return new Error('Direct USD fiat attempt must not contain an FX quote.'); + } + return { + processor: 'stripe', + kind: 'stripe_transfer', + destination: request.destination, + source_currency: sourceCurrency, + source_amount_minor: sourceAmountMinor, + destination_currency: destinationCurrency, + destination_amount_min_minor: destinationMin, + destination_amount_max_minor: destinationMax, + fx_quote_id: request.fx_quote_id, + fx_quote_hash: request.fx_quote_hash, + transfer_group: request.transfer_group, + metadata_hash: request.metadata_hash, + }; + } + + async targetedFiatAttemptId(economicOpId, attemptNo, request) { + return await this.opaqueHash( + 'mayhem-targeted-fiat-attempt-id-v1', + { + economic_op_id: economicOpId, + attempt_no: attemptNo, + request, + } + ); + } + + async targetedFiatAttemptIdempotencyKeyHash(attemptId) { + const digest = await blake3(b4a.from(stableJson({ + domain: 'mayhem-targeted-fiat-idempotency-key-v1', + key: `mayhem:fiat:attempt:v1:${attemptId}`, + }))); + return b4a.toString(digest, 'hex'); + } + + async normalizePrepareTargetedFiatAttemptValue(value) { + const shapeError = this.validateExactObjectKeys( + value, + [ + 'op', + 'contract_version', + 'rail', + 'epoch', + 'epoch_apply_hash', + 'plan_root', + 'economic_op_id', + 'output_index', + 'attempt_id', + 'attempt_no', + 'prepared_at', + 'quote_expires_at', + 'idempotency_key_hash', + 'request_hash', + 'request', + 'admin', + 'admin_sig', + ], + 'targeted fiat attempt preparation' + ); + if (shapeError) return shapeError; + if (value.op !== 'prepare_targeted_fiat_attempt' || + value.contract_version !== CONTRACT_VERSION || + value.rail !== 'fiat' || + !Number.isSafeInteger(value.epoch) || + value.epoch < 1 || + !this.isHexBytes(value.epoch_apply_hash, 32) || + value.epoch_apply_hash !== value.epoch_apply_hash.toLowerCase() || + !this.isHexBytes(value.plan_root, 32) || + value.plan_root !== value.plan_root.toLowerCase() || + !this.isHexBytes(value.economic_op_id, 32) || + value.economic_op_id !== value.economic_op_id.toLowerCase() || + !Number.isSafeInteger(value.output_index) || + value.output_index < 0 || + !this.isHexBytes(value.attempt_id, 32) || + value.attempt_id !== value.attempt_id.toLowerCase() || + !Number.isSafeInteger(value.attempt_no) || + value.attempt_no < 1 || + !Number.isSafeInteger(value.prepared_at) || + value.prepared_at < 0 || + !this.isHexBytes(value.idempotency_key_hash, 32) || + value.idempotency_key_hash !== value.idempotency_key_hash.toLowerCase() || + !this.isHexBytes(value.request_hash, 32) || + value.request_hash !== value.request_hash.toLowerCase() || + !this.isHexBytes(value.admin, 32) || + value.admin !== value.admin.toLowerCase() || + !this.isHexBytes(value.admin_sig, 64)) { + return new Error('Invalid targeted fiat attempt preparation.'); + } + const request = this.normalizeTargetedFiatAttemptRequest(value.request); + if (request instanceof Error) return request; + const expectedAttemptId = await this.targetedFiatAttemptId( + value.economic_op_id, + value.attempt_no, + request + ); + if (value.attempt_id !== expectedAttemptId) { + return new Error('Targeted fiat attempt id does not match its canonical request.'); + } + const expectedIdempotencyKeyHash = + await this.targetedFiatAttemptIdempotencyKeyHash(expectedAttemptId); + if (value.idempotency_key_hash !== expectedIdempotencyKeyHash) { + return new Error( + 'Targeted fiat attempt idempotency key hash does not match its canonical attempt.' + ); + } + if ((request.kind === 'stripe_transfer' && + (!Number.isSafeInteger(value.quote_expires_at) || + value.quote_expires_at <= value.prepared_at)) || + (request.kind === 'platform_balance' && value.quote_expires_at !== null)) { + return new Error('Invalid targeted fiat attempt quote expiry.'); + } + const requestHash = await this.opaqueHash( + 'mayhem-targeted-fiat-attempt-request-v1', + { + economic_op_id: value.economic_op_id, + attempt_id: value.attempt_id, + attempt_no: value.attempt_no, + request, + } + ); + if (requestHash !== value.request_hash) { + return new Error('Targeted fiat attempt request hash mismatch.'); + } + const normalized = { + op: 'prepare_targeted_fiat_attempt', + contract_version: CONTRACT_VERSION, + rail: 'fiat', + epoch: value.epoch, + epoch_apply_hash: value.epoch_apply_hash, + plan_root: value.plan_root, + economic_op_id: value.economic_op_id, + output_index: value.output_index, + attempt_id: value.attempt_id, + attempt_no: value.attempt_no, + prepared_at: value.prepared_at, + quote_expires_at: value.quote_expires_at, + idempotency_key_hash: value.idempotency_key_hash.toLowerCase(), + request_hash: requestHash, + request, + admin: value.admin, + admin_sig: value.admin_sig.toLowerCase(), + }; + return stableJson(normalized) === stableJson(value) + ? normalized + : new Error('Targeted fiat attempt preparation must be canonical.'); + } + + async applyTargetedFiatAttemptFeature(key, value) { + const normalized = await this.normalizePrepareTargetedFiatAttemptValue(value); + if (normalized instanceof Error) return normalized; + const expectedKey = await this.targetedFiatAttemptFeatureKey(normalized); + if (expectedKey instanceof Error) return expectedKey; + if (key !== expectedKey) return new Error('Invalid targeted fiat attempt feature key.'); + const adminError = await this.requireTargetedPayoutAdminSignature( + normalized, + 'Targeted fiat attempt preparation' + ); + if (adminError) return adminError; + const plan = await this.get( + this.targetedPayoutEpochPlanRecordKey('fiat', normalized.epoch) + ); + const output = plan?.value?.outputs?.[normalized.output_index]; + if (!plan || + plan.plan_root !== normalized.plan_root || + plan.value.epoch_apply_hash !== normalized.epoch_apply_hash || + output?.economic_op_id !== normalized.economic_op_id) { + return new Error('Targeted fiat attempt does not match the canonical epoch plan.'); + } + const preparation = await this.get( + this.payoutPreparationRecordKey('fiat', normalized.economic_op_id) + ); + if (!preparation || + preparation.consumed !== false || + preparation.payload?.plan_root !== normalized.plan_root || + preparation.payload?.output_index !== normalized.output_index || + stableJson(preparation.payload?.output) !== stableJson(output)) { + return new Error('Targeted fiat attempt requires its unconsumed economic preparation.'); + } + const expectedKind = output.role === 'provider' + ? 'stripe_transfer' + : 'platform_balance'; + if (normalized.request.kind !== expectedKind || + normalized.request.destination !== output.to || + normalized.request.source_currency !== output.source_currency || + normalized.request.source_amount_minor !== output.source_amount_minor || + (output.role === 'provider' && + (normalized.request.destination_currency !== output.destination_currency || + normalized.request.destination_amount_min_minor !== + output.destination_amount_min_minor || + normalized.request.destination_amount_max_minor !== + output.destination_amount_max_minor || + normalized.request.transfer_group !== + `mayhem_fiat_epoch_${normalized.epoch}_${normalized.epoch_apply_hash.slice(0, 16)}`))) { + return new Error('Targeted fiat attempt request does not match its planned output.'); + } + const recordKey = this.targetedFiatAttemptRecordKey( + normalized.economic_op_id, + normalized.attempt_id + ); + const existing = await this.get(recordKey); + if (existing !== null) { + if (stableJson(existing.preparation) === stableJson(normalized)) { + return { + ok: true, + op: 'targetedFiatAttemptPrepared', + economic_op_id: normalized.economic_op_id, + attempt_id: normalized.attempt_id, + status: existing.status, + idempotent: true, + }; + } + return new Error('Targeted fiat attempt identity already exists.'); + } + const tailKey = this.targetedFiatAttemptTailKey(normalized.economic_op_id); + const tail = await this.get(tailKey); + if ((!tail && normalized.attempt_no !== 1) || + (tail && + (tail.status !== 'expired_pre_effect' || + normalized.attempt_no !== tail.attempt_no + 1))) { + return new Error( + 'Targeted fiat attempt may renew only after definitive pre-effect expiry.' + ); + } + if ((await this.get( + `payout/attempt-idempotency/fiat/${normalized.idempotency_key_hash}` + )) !== null) { + return new Error('Targeted fiat attempt idempotency key was already used.'); + } + if (normalized.request.fx_quote_id !== null && + (await this.get( + `payout/attempt-quote/fiat/${normalized.request.fx_quote_id}` + )) !== null) { + return new Error('Targeted fiat attempt FX quote was already used.'); + } + const record = { + type: 'targeted_fiat_attempt', + economic_op_id: normalized.economic_op_id, + attempt_id: normalized.attempt_id, + attempt_no: normalized.attempt_no, + status: 'prepared', + preparation: normalized, + result: null, + consumed: false, + consumed_by: null, + updated_at: this.tx, + }; + await this.put(recordKey, record); + await this.put(tailKey, { + economic_op_id: normalized.economic_op_id, + attempt_id: normalized.attempt_id, + attempt_no: normalized.attempt_no, + status: 'prepared', + updated_at: this.tx, + }); + await this.put( + `payout/attempt-idempotency/fiat/${normalized.idempotency_key_hash}`, + { + economic_op_id: normalized.economic_op_id, + attempt_id: normalized.attempt_id, + updated_at: this.tx, + } + ); + if (normalized.request.fx_quote_id !== null) { + await this.put( + `payout/attempt-quote/fiat/${normalized.request.fx_quote_id}`, + { + economic_op_id: normalized.economic_op_id, + attempt_id: normalized.attempt_id, + quote_hash: normalized.request.fx_quote_hash, + updated_at: this.tx, + } + ); + } + return { + ok: true, + op: 'targetedFiatAttemptPrepared', + economic_op_id: normalized.economic_op_id, + attempt_id: normalized.attempt_id, + status: 'prepared', + idempotent: false, + }; + } + + normalizeFinalizeTargetedFiatAttemptValue(value) { + const shapeError = this.validateExactObjectKeys( + value, + [ + 'op', + 'contract_version', + 'rail', + 'epoch', + 'economic_op_id', + 'attempt_id', + 'status', + 'at', + 'evidence', + 'admin', + 'admin_sig', + ], + 'targeted fiat attempt finalization' + ); + if (shapeError) return shapeError; + if (value.op !== 'finalize_targeted_fiat_attempt' || + value.contract_version !== CONTRACT_VERSION || + value.rail !== 'fiat' || + !Number.isSafeInteger(value.epoch) || + value.epoch < 1 || + !this.isHexBytes(value.economic_op_id, 32) || + value.economic_op_id !== value.economic_op_id.toLowerCase() || + !this.isHexBytes(value.attempt_id, 32) || + value.attempt_id !== value.attempt_id.toLowerCase() || + !['succeeded', 'expired_pre_effect'].includes(value.status) || + !Number.isSafeInteger(value.at) || + value.at < 0 || + !this.isHexBytes(value.admin, 32) || + value.admin !== value.admin.toLowerCase() || + !this.isHexBytes(value.admin_sig, 64)) { + return new Error('Invalid targeted fiat attempt finalization.'); + } + let evidence; + if (value.status === 'succeeded') { + evidence = this.normalizeStripeTransferEvidence( + value.evidence, + 'targeted fiat attempt success evidence', + { expectedAttemptId: value.attempt_id } + ); + if (evidence instanceof Error) return evidence; + } else { + const expiryShape = this.validateExactObjectKeys( + value.evidence, + [ + 'fx_quote_id', + 'fx_quote_hash', + 'quote_expires_at', + 'error_code', + 'external_effect_absent', + ], + 'targeted fiat attempt expiry evidence' + ); + if (expiryShape) return expiryShape; + if (!/^fxq_[A-Za-z0-9._-]+$/.test(String(value.evidence.fx_quote_id || '')) || + !this.isHexBytes(value.evidence.fx_quote_hash, 32) || + !Number.isSafeInteger(value.evidence.quote_expires_at) || + value.evidence.quote_expires_at < 0 || + value.evidence.error_code !== 'fx_quote_expired' || + value.evidence.external_effect_absent !== true) { + return new Error('Invalid targeted fiat attempt expiry evidence.'); + } + evidence = stableValue(value.evidence); + } + const normalized = { + op: 'finalize_targeted_fiat_attempt', + contract_version: CONTRACT_VERSION, + rail: 'fiat', + epoch: value.epoch, + economic_op_id: value.economic_op_id, + attempt_id: value.attempt_id, + status: value.status, + at: value.at, + evidence, + admin: value.admin, + admin_sig: value.admin_sig.toLowerCase(), + }; + return stableJson(normalized) === stableJson(value) + ? normalized + : new Error('Targeted fiat attempt finalization must be canonical.'); + } + + async applyFinalizeTargetedFiatAttemptFeature(key, value) { + const normalized = this.normalizeFinalizeTargetedFiatAttemptValue(value); + if (normalized instanceof Error) return normalized; + const expectedKey = await this.finalizeTargetedFiatAttemptFeatureKey(normalized); + if (expectedKey instanceof Error) return expectedKey; + if (key !== expectedKey) { + return new Error('Invalid targeted fiat attempt finalization key.'); + } + const adminError = await this.requireTargetedPayoutAdminSignature( + normalized, + 'Targeted fiat attempt finalization' + ); + if (adminError) return adminError; + const recordKey = this.targetedFiatAttemptRecordKey( + normalized.economic_op_id, + normalized.attempt_id + ); + const record = await this.get(recordKey); + if (!record || + record.type !== 'targeted_fiat_attempt' || + record.preparation.epoch !== normalized.epoch) { + return new Error('Targeted fiat attempt preparation not found.'); + } + if (record.status !== 'prepared') { + if (record.status === normalized.status && + stableJson(record.result) === stableJson(normalized)) { + return { + ok: true, + op: 'targetedFiatAttemptFinalized', + economic_op_id: normalized.economic_op_id, + attempt_id: normalized.attempt_id, + status: normalized.status, + idempotent: true, + }; + } + return new Error('Targeted fiat attempt is already terminal.'); + } + const request = record.preparation.request; + if (normalized.status === 'expired_pre_effect') { + if (request.kind !== 'stripe_transfer' || + normalized.at < record.preparation.quote_expires_at || + normalized.evidence.fx_quote_id !== request.fx_quote_id || + normalized.evidence.fx_quote_hash !== request.fx_quote_hash || + normalized.evidence.quote_expires_at !== + record.preparation.quote_expires_at) { + return new Error('Targeted fiat attempt expiry does not match its prepared quote.'); + } + } else { + const transfer = normalized.evidence; + const expectedKind = request.kind; + if (transfer.kind !== expectedKind || + transfer.destination !== request.destination || + transfer.source_currency !== request.source_currency || + transfer.source_amount_minor !== request.source_amount_minor || + (expectedKind === 'stripe_transfer' && + (transfer.destination_currency !== request.destination_currency || + BigInt(transfer.destination_amount_minor) < + BigInt(request.destination_amount_min_minor) || + BigInt(transfer.destination_amount_minor) > + BigInt(request.destination_amount_max_minor) || + transfer.fx_quote_id !== request.fx_quote_id || + transfer.fx_quote_hash !== request.fx_quote_hash || + transfer.transfer_group !== request.transfer_group))) { + return new Error('Targeted fiat attempt result does not match its prepared request.'); + } + const effectKey = this.targetedFiatAttemptEffectKey(transfer.ref); + const priorEffect = await this.get(effectKey); + if (priorEffect !== null) { + return new Error('Targeted fiat external effect was already finalized.'); + } + await this.put(effectKey, { + economic_op_id: normalized.economic_op_id, + attempt_id: normalized.attempt_id, + effect_id: transfer.ref, + consumed: false, + updated_at: this.tx, + }); + } + const terminal = { + ...record, + status: normalized.status, + result: normalized, + updated_at: this.tx, + }; + await this.put(recordKey, terminal); + await this.put(this.targetedFiatAttemptTailKey(normalized.economic_op_id), { + economic_op_id: normalized.economic_op_id, + attempt_id: normalized.attempt_id, + attempt_no: record.attempt_no, + status: normalized.status, + updated_at: this.tx, + }); + return { + ok: true, + op: 'targetedFiatAttemptFinalized', + economic_op_id: normalized.economic_op_id, + attempt_id: normalized.attempt_id, + status: normalized.status, + idempotent: false, + }; + } + + normalizeTargetedTnkOutputSettlementValue(value) { + const shapeError = this.validateExactObjectKeys( + value, + [ + 'op', + 'contract_version', + 'rail', + 'epoch', + 'at', + 'epoch_apply_hash', + 'plan_root', + 'economic_op_id', + 'output_index', + 'preparation_id', + 'external_effect_id', + 'msb_transfer', + 'admin', + 'admin_sig', + ], + 'targeted TNK output settlement' + ); + if (shapeError) return shapeError; + const transfer = this.normalizeMsbTransferEvidence( + value.msb_transfer, + 'targeted TNK output transfer' + ); + if (transfer instanceof Error) return transfer; + if (value.op !== 'settle_targeted_tnk_output' || + value.contract_version !== CONTRACT_VERSION || + value.rail !== 'tnk' || + !Number.isSafeInteger(value.epoch) || + value.epoch < 1 || + !Number.isSafeInteger(value.at) || + value.at < 0 || + !this.isHexBytes(value.epoch_apply_hash, 32) || + value.epoch_apply_hash !== value.epoch_apply_hash.toLowerCase() || + !this.isHexBytes(value.plan_root, 32) || + value.plan_root !== value.plan_root.toLowerCase() || + !this.isHexBytes(value.economic_op_id, 32) || + value.economic_op_id !== value.economic_op_id.toLowerCase() || + !Number.isSafeInteger(value.output_index) || + value.output_index < 0 || + value.preparation_id !== value.economic_op_id || + !this.isHexBytes(value.external_effect_id, 32) || + value.external_effect_id !== transfer.tx_hash || + !this.isHexBytes(value.admin, 32) || + value.admin !== value.admin.toLowerCase() || + !this.isHexBytes(value.admin_sig, 64)) { + return new Error('Invalid targeted TNK output settlement.'); + } + const normalized = { + op: 'settle_targeted_tnk_output', + contract_version: CONTRACT_VERSION, + rail: 'tnk', + epoch: value.epoch, + at: value.at, + epoch_apply_hash: value.epoch_apply_hash, + plan_root: value.plan_root, + economic_op_id: value.economic_op_id, + output_index: value.output_index, + preparation_id: value.preparation_id, + external_effect_id: value.external_effect_id, + msb_transfer: transfer, + admin: value.admin, + admin_sig: value.admin_sig.toLowerCase(), + }; + return stableJson(normalized) === stableJson(value) + ? normalized + : new Error('Targeted TNK output settlement must be canonical.'); + } + + normalizeTargetedFiatOutputSettlementValue(value) { + const shapeError = this.validateExactObjectKeys( + value, + [ + 'op', + 'contract_version', + 'rail', + 'epoch', + 'at', + 'epoch_apply_hash', + 'plan_root', + 'economic_op_id', + 'output_index', + 'preparation_id', + 'attempt_id', + 'stripe_transfer', + 'admin', + 'admin_sig', + ], + 'targeted fiat output settlement' + ); + if (shapeError) return shapeError; + const transfer = this.normalizeStripeTransferEvidence( + value.stripe_transfer, + 'targeted fiat output transfer', + { expectedAttemptId: value.attempt_id } + ); + if (transfer instanceof Error) return transfer; + if (value.op !== 'settle_targeted_fiat_output' || + value.contract_version !== CONTRACT_VERSION || + value.rail !== 'fiat' || + !Number.isSafeInteger(value.epoch) || + value.epoch < 1 || + !Number.isSafeInteger(value.at) || + value.at < 0 || + !this.isHexBytes(value.epoch_apply_hash, 32) || + value.epoch_apply_hash !== value.epoch_apply_hash.toLowerCase() || + !this.isHexBytes(value.plan_root, 32) || + value.plan_root !== value.plan_root.toLowerCase() || + !this.isHexBytes(value.economic_op_id, 32) || + value.economic_op_id !== value.economic_op_id.toLowerCase() || + !Number.isSafeInteger(value.output_index) || + value.output_index < 0 || + value.preparation_id !== value.economic_op_id || + !this.isHexBytes(value.attempt_id, 32) || + value.attempt_id !== value.attempt_id.toLowerCase() || + !this.isHexBytes(value.admin, 32) || + value.admin !== value.admin.toLowerCase() || + !this.isHexBytes(value.admin_sig, 64)) { + return new Error('Invalid targeted fiat output settlement.'); + } + const normalized = { + op: 'settle_targeted_fiat_output', + contract_version: CONTRACT_VERSION, + rail: 'fiat', + epoch: value.epoch, + at: value.at, + epoch_apply_hash: value.epoch_apply_hash, + plan_root: value.plan_root, + economic_op_id: value.economic_op_id, + output_index: value.output_index, + preparation_id: value.preparation_id, + attempt_id: value.attempt_id, + stripe_transfer: transfer, + admin: value.admin, + admin_sig: value.admin_sig.toLowerCase(), + }; + return stableJson(normalized) === stableJson(value) + ? normalized + : new Error('Targeted fiat output settlement must be canonical.'); + } + + async targetedPayoutPlannedOutput(value, rail) { + const plan = await this.get( + this.targetedPayoutEpochPlanRecordKey(rail, value.epoch) + ); + const output = plan?.value?.outputs?.[value.output_index]; + if (!plan || + plan.plan_root !== value.plan_root || + plan.value.epoch_apply_hash !== value.epoch_apply_hash || + output?.economic_op_id !== value.economic_op_id) { + return new Error('Targeted output settlement does not match its epoch plan.'); + } + return { plan, output }; + } + + async targetedPayoutPreparationForOutput(value, output, rail) { + const preparation = await this.get( + this.payoutPreparationRecordKey(rail, value.preparation_id) + ); + if (!preparation || + preparation.type !== 'targeted_payout_preparation' || + preparation.consumed !== false || + preparation.economic_op_id !== value.economic_op_id || + preparation.epoch !== value.epoch || + preparation.epoch_apply_hash !== value.epoch_apply_hash || + preparation.output_index !== value.output_index || + preparation.payload?.plan_root !== value.plan_root || + stableJson(preparation.payload?.output) !== stableJson(output)) { + return new Error('Targeted output settlement requires its unconsumed preparation.'); + } + return preparation; + } + + async applyTargetedTnkOutputFeature(key, value) { + const normalized = this.normalizeTargetedTnkOutputSettlementValue(value); + if (normalized instanceof Error) return normalized; + const expectedKey = await this.targetedTnkOutputFeatureKey(normalized); + if (expectedKey instanceof Error) return expectedKey; + if (key !== expectedKey) return new Error('Invalid targeted TNK output feature key.'); + const adminError = await this.requireTargetedPayoutAdminSignature( + normalized, + 'Targeted TNK output settlement' + ); + if (adminError) return adminError; + const recordKey = this.targetedPayoutOutputRecordKey( + 'tnk', + normalized.epoch, + normalized.economic_op_id + ); + const existing = await this.get(recordKey); + if (existing !== null) { + if (stableJson(existing.value) === stableJson(normalized)) { + return { + ok: true, + op: 'targetedTnkOutputSettled', + epoch: normalized.epoch, + economic_op_id: normalized.economic_op_id, + idempotent: true, + }; + } + return new Error('Targeted TNK output is already settled.'); + } + const planned = await this.targetedPayoutPlannedOutput(normalized, 'tnk'); + if (planned instanceof Error) return planned; + const preparation = await this.targetedPayoutPreparationForOutput( + normalized, + planned.output, + 'tnk' + ); + if (preparation instanceof Error) return preparation; + const transfer = normalized.msb_transfer; + const payload = preparation.payload; + if (preparation.external_effect_ids.length !== 1 || + preparation.external_effect_ids[0] !== normalized.external_effect_id || + payload.msb_tx_hash !== normalized.external_effect_id || + payload.msb_payload?.tro?.tx !== normalized.external_effect_id || + transfer.network !== payload.network || + transfer.from !== payload.treasury_from || + transfer.to !== planned.output.to || + transfer.amount_e18 !== planned.output.tnk_e18) { + return new Error('Targeted TNK output evidence does not match its signed preparation.'); + } + const rate = await this.guardianRequireHistoricalTnkRate(payload, normalized.at); + if (rate instanceof Error) return rate; + const expectedTnkE18 = this.auToTnkE18Ceil( + planned.output.au, + payload.rate_tnk_usd_au + ); + if (expectedTnkE18 instanceof Error || + expectedTnkE18.toString() !== planned.output.tnk_e18) { + return new Error('Targeted TNK output does not match its oracle rate.'); + } + if ((await this.get(this.msbTransferSeenKey(transfer))) !== null) { + return new Error('Targeted TNK transfer was already consumed.'); + } + const preparationReady = await this.validatePayoutPreparationConsumption( + [preparation] + ); + if (preparationReady instanceof Error) return preparationReady; + if (planned.output.role === 'provider') { + const updates = await this.targetedPayoutSettlementUpdates( + [planned.output], + 'tnk', + normalized.epoch, + normalized.at, + [transfer.tx_hash] + ); + if (updates instanceof Error) return updates; + for (const update of updates.liabilityUpdates) { + await this.put(update.key, update.value); + } + for (const earning of updates.earningUpdates) { + await this.put(this.earningKey(earning.provider, 'tnk'), earning); + } + } else { + const fee = await this.feeCumRecord('tnk'); + if (fee instanceof Error) return fee; + const payable = this.safeSubAu(fee.cum_au, fee.swept_cum_au); + if (payable instanceof Error || this.compareAu(payable, planned.output.au) < 0) { + return new Error('Targeted TNK fee output does not match canonical fee state.'); + } + const swept = this.safeAddAu(fee.swept_cum_au, planned.output.au); + if (swept instanceof Error) return swept; + const nextFee = { + ...fee, + swept_cum_au: swept, + updated_epoch: Math.max(fee.updated_epoch, normalized.epoch), + last_targeted_settlement_epoch: normalized.epoch, + last_targeted_settlement_transfer: transfer.tx_hash, + }; + const nextFeeError = this.guardianValidateFeeRecord(nextFee, 'tnk'); + if (nextFeeError) return nextFeeError; + await this.put(this.feeCumKey('tnk'), nextFee); + } + await this.put(this.msbTransferSeenKey(transfer), { + rail: 'tnk', + purpose: 'targeted_output_settlement', + epoch: normalized.epoch, + economic_op_id: normalized.economic_op_id, + consumed_at: this.tx, + }); + const preparationError = await this.consumePayoutPreparations( + [preparation], + recordKey + ); + if (preparationError) return preparationError; + await this.put(recordKey, { + type: 'targeted_tnk_output_settlement', + rail: 'tnk', + epoch: normalized.epoch, + economic_op_id: normalized.economic_op_id, + value: normalized, + settled_at: this.tx, + }); + return { + ok: true, + op: 'targetedTnkOutputSettled', + epoch: normalized.epoch, + economic_op_id: normalized.economic_op_id, + external_effect_id: normalized.external_effect_id, + idempotent: false, + }; + } + + async applyTargetedFiatOutputFeature(key, value) { + const normalized = this.normalizeTargetedFiatOutputSettlementValue(value); + if (normalized instanceof Error) return normalized; + const expectedKey = await this.targetedFiatOutputFeatureKey(normalized); + if (expectedKey instanceof Error) return expectedKey; + if (key !== expectedKey) return new Error('Invalid targeted fiat output feature key.'); + const adminError = await this.requireTargetedPayoutAdminSignature( + normalized, + 'Targeted fiat output settlement' + ); + if (adminError) return adminError; + const recordKey = this.targetedPayoutOutputRecordKey( + 'fiat', + normalized.epoch, + normalized.economic_op_id + ); + const existing = await this.get(recordKey); + if (existing !== null) { + if (stableJson(existing.value) === stableJson(normalized)) { + return { + ok: true, + op: 'targetedFiatOutputSettled', + epoch: normalized.epoch, + economic_op_id: normalized.economic_op_id, + idempotent: true, + }; + } + return new Error('Targeted fiat output is already settled.'); + } + const planned = await this.targetedPayoutPlannedOutput(normalized, 'fiat'); + if (planned instanceof Error) return planned; + const preparation = await this.targetedPayoutPreparationForOutput( + normalized, + planned.output, + 'fiat' + ); + if (preparation instanceof Error) return preparation; + const attemptKey = this.targetedFiatAttemptRecordKey( + normalized.economic_op_id, + normalized.attempt_id + ); + const attempt = await this.get(attemptKey); + const transfer = normalized.stripe_transfer; + if (!attempt || + attempt.status !== 'succeeded' || + attempt.consumed !== false || + stableJson(attempt.result?.evidence) !== stableJson(transfer)) { + return new Error('Targeted fiat output requires a succeeded canonical attempt.'); + } + const attemptEffectKey = this.targetedFiatAttemptEffectKey(transfer.ref); + const attemptEffect = await this.get(attemptEffectKey); + if (!attemptEffect || + attemptEffect.economic_op_id !== normalized.economic_op_id || + attemptEffect.attempt_id !== normalized.attempt_id || + attemptEffect.consumed !== false) { + return new Error('Targeted fiat attempt effect lock mismatch.'); + } + for (const seenKey of [ + this.stripeTransferSeenKey(transfer), + this.stripeFxQuoteSeenKey(transfer), + this.stripeDestinationPaymentSeenKey(transfer), + ]) { + if (seenKey && (await this.get(seenKey)) !== null) { + return new Error('Targeted Stripe transfer evidence was already consumed.'); + } + } + const preparationReady = await this.validatePayoutPreparationConsumption( + [preparation] + ); + if (preparationReady instanceof Error) return preparationReady; + if (planned.output.role === 'provider') { + const updates = await this.targetedPayoutSettlementUpdates( + [planned.output], + 'fiat', + normalized.epoch, + normalized.at, + [transfer.ref] + ); + if (updates instanceof Error) return updates; + for (const update of updates.liabilityUpdates) { + await this.put(update.key, update.value); + } + for (const earning of updates.earningUpdates) { + await this.put(this.earningKey(earning.provider, 'fiat'), earning); + } + } else { + const fee = await this.feeCumRecord('fiat'); + if (fee instanceof Error) return fee; + const payable = this.safeSubAu(fee.cum_au, fee.swept_cum_au); + if (payable instanceof Error || + this.compareAu(payable, planned.output.liability_au) < 0 || + planned.output.paid_au !== planned.output.liability_au) { + return new Error('Targeted fiat fee output does not match canonical fee state.'); + } + const swept = this.safeAddAu(fee.swept_cum_au, planned.output.paid_au); + if (swept instanceof Error) return swept; + const nextFee = { + ...fee, + swept_cum_au: swept, + updated_epoch: Math.max(fee.updated_epoch, normalized.epoch), + last_targeted_settlement_epoch: normalized.epoch, + last_targeted_settlement_transfer: transfer.ref, + }; + const nextFeeError = this.guardianValidateFeeRecord(nextFee, 'fiat'); + if (nextFeeError) return nextFeeError; + await this.put(this.feeCumKey('fiat'), nextFee); + } + for (const seenKey of [ + this.stripeTransferSeenKey(transfer), + this.stripeFxQuoteSeenKey(transfer), + this.stripeDestinationPaymentSeenKey(transfer), + ]) { + if (!seenKey) continue; + await this.put(seenKey, { + rail: 'fiat', + purpose: 'targeted_output_settlement', + epoch: normalized.epoch, + economic_op_id: normalized.economic_op_id, + consumed_at: this.tx, + }); + } + const preparationError = await this.consumePayoutPreparations( + [preparation], + recordKey + ); + if (preparationError) return preparationError; + await this.put(attemptKey, { + ...attempt, + consumed: true, + consumed_by: recordKey, + updated_at: this.tx, + }); + await this.put(attemptEffectKey, { + ...attemptEffect, + consumed: true, + consumed_by: recordKey, + updated_at: this.tx, + }); + await this.put(recordKey, { + type: 'targeted_fiat_output_settlement', + rail: 'fiat', + epoch: normalized.epoch, + economic_op_id: normalized.economic_op_id, + value: normalized, + settled_at: this.tx, + }); + return { + ok: true, + op: 'targetedFiatOutputSettled', + epoch: normalized.epoch, + economic_op_id: normalized.economic_op_id, + external_effect_id: transfer.ref, + idempotent: false, + }; + } + + normalizeCloseTargetedPayoutEpochValue(value) { + const shapeError = this.validateExactObjectKeys( + value, + [ + 'op', + 'contract_version', + 'rail', + 'epoch', + 'at', + 'epoch_apply_hash', + 'plan_root', + 'admin', + 'admin_sig', + ], + 'targeted payout epoch close' + ); + if (shapeError) return shapeError; + if (value.op !== 'close_targeted_payout_epoch' || + value.contract_version !== CONTRACT_VERSION || + !['fiat', 'tnk'].includes(value.rail) || + !Number.isSafeInteger(value.epoch) || + value.epoch < 1 || + !Number.isSafeInteger(value.at) || + value.at < 0 || + !this.isHexBytes(value.epoch_apply_hash, 32) || + value.epoch_apply_hash !== value.epoch_apply_hash.toLowerCase() || + !this.isHexBytes(value.plan_root, 32) || + value.plan_root !== value.plan_root.toLowerCase() || + !this.isHexBytes(value.admin, 32) || + value.admin !== value.admin.toLowerCase() || + !this.isHexBytes(value.admin_sig, 64)) { + return new Error('Invalid targeted payout epoch close.'); + } + const normalized = { + op: 'close_targeted_payout_epoch', + contract_version: CONTRACT_VERSION, + rail: value.rail, + epoch: value.epoch, + at: value.at, + epoch_apply_hash: value.epoch_apply_hash, + plan_root: value.plan_root, + admin: value.admin, + admin_sig: value.admin_sig.toLowerCase(), + }; + return stableJson(normalized) === stableJson(value) + ? normalized + : new Error('Targeted payout epoch close must be canonical.'); + } + + async applyCloseTargetedPayoutEpochFeature(key, value) { + const normalized = this.normalizeCloseTargetedPayoutEpochValue(value); + if (normalized instanceof Error) return normalized; + const expectedKey = await this.closeTargetedPayoutEpochFeatureKey(normalized); + if (expectedKey instanceof Error) return expectedKey; + if (key !== expectedKey) return new Error('Invalid targeted payout epoch close key.'); + const adminError = await this.requireTargetedPayoutAdminSignature( + normalized, + 'Targeted payout epoch close' + ); + if (adminError) return adminError; + const recordKey = this.targetedPayoutEpochCloseRecordKey( + normalized.rail, + normalized.epoch + ); + const existing = await this.get(recordKey); + if (existing !== null) { + if (stableJson(existing.value) === stableJson(normalized)) { + return { + ok: true, + op: 'targetedPayoutEpochClosed', + rail: normalized.rail, + epoch: normalized.epoch, + idempotent: true, + }; + } + return new Error('Targeted payout epoch is already closed.'); + } + const plan = await this.get( + this.targetedPayoutEpochPlanRecordKey(normalized.rail, normalized.epoch) + ); + if (!plan || + plan.plan_root !== normalized.plan_root || + plan.value.epoch_apply_hash !== normalized.epoch_apply_hash) { + return new Error('Targeted payout epoch close does not match its plan.'); + } + for (const output of plan.value.outputs) { + const settled = await this.get( + this.targetedPayoutOutputRecordKey( + normalized.rail, + normalized.epoch, + output.economic_op_id + ) + ); + if (!settled || + settled.economic_op_id !== output.economic_op_id || + settled.value.plan_root !== normalized.plan_root) { + return new Error('Targeted payout epoch has unsettled planned outputs.'); + } + } + await this.put(recordKey, { + type: 'targeted_payout_epoch_close', + rail: normalized.rail, + epoch: normalized.epoch, + plan_root: normalized.plan_root, + outcome: plan.value.outcome, + output_count: plan.value.outputs.length, + carry_count: plan.value.carry.length, + outputs_root: plan.value.outputs_root, + carry_root: plan.value.carry_root, + value: normalized, + closed_at: this.tx, + }); + return { + ok: true, + op: 'targetedPayoutEpochClosed', + rail: normalized.rail, + epoch: normalized.epoch, + outcome: plan.value.outcome, + output_count: plan.value.outputs.length, + carry_count: plan.value.carry.length, + idempotent: false, + }; + } + + async applyTargetedTnkSettlementFeature(key, value) { + const expectedKey = await this.targetedTnkSettlementFeatureKey(value); + if (expectedKey instanceof Error) return expectedKey; + if (key !== expectedKey) return new Error('Invalid targeted TNK settlement key.'); + const previousTx = this.tx; + this.tx = key; + try { + return await this.targetedTnkSettlement(value); + } finally { + this.tx = previousTx; + } + } + + async applyTargetedTapSettlementFeature(key, value) { + const expectedKey = await this.targetedTapSettlementFeatureKey(value); + if (expectedKey instanceof Error) return expectedKey; + if (key !== expectedKey) return new Error('Invalid targeted TAP settlement key.'); + const previousTx = this.tx; + this.tx = key; + try { + return await this.targetedTapSettlement(value); + } finally { + this.tx = previousTx; + } + } + + async applyTargetedFiatSettlementFeature(key, value) { + const expectedKey = await this.targetedFiatSettlementFeatureKey(value); + if (expectedKey instanceof Error) return expectedKey; + if (key !== expectedKey) return new Error('Invalid targeted fiat settlement key.'); + const previousTx = this.tx; + this.tx = key; + try { + return await this.targetedFiatSettlement(value); + } finally { + this.tx = previousTx; + } + } + + async applyFiatDustSweepFeature(key, value) { + const expectedKey = await this.fiatDustSweepFeatureKey(value); + if (expectedKey instanceof Error) return expectedKey; + if (key !== expectedKey) return; + + const previousTx = this.tx; + this.tx = key; + try { + return await this.fiatDustSweep(); + } finally { + this.tx = previousTx; + } + } + + async applyReputationAnchorFeature(key, value) { + const expectedKey = await this.reputationAnchorFeatureKey(value); + if (expectedKey instanceof Error) return expectedKey; + if (key !== expectedKey) return; + + const previousTx = this.tx; + const previousValue = this.value; + this.tx = key; + this.value = value; + try { + return await this.anchorReputation(); + } finally { + this.tx = previousTx; + this.value = previousValue; + } + } + + async applyTier3MeasurementFeature(key, value) { + const expectedKey = await this.tier3MeasurementFeatureKey(value); + if (expectedKey instanceof Error) return expectedKey; + if (key !== expectedKey) return; + + const previousTx = this.tx; + const previousValue = this.value; + this.tx = key; + this.value = value; + try { + return await this.tier3BlessMeasurement(); + } finally { + this.tx = previousTx; + this.value = previousValue; + } + } + + async tier3BlessMeasurement() { + const adminError = await this.requireAdmin(); + if (adminError) return adminError; + const validationError = this.validateTier3MeasurementBlessValue(this.value); + if (validationError) return validationError; + + const platform = this.value.platform; + const layer = this.value.layer; + const measurementName = this.value.measurement_name; + const measurement = this.value.measurement.toLowerCase(); + const key = `tier3/measurement/${platform}`; + const current = await this.get(key); + const record = current + ? cloneValue(current) + : { + schema_version: 1, + platform, + entries: [], + measurements: {}, + created_at: this.tx, + created_by: this.address, + }; + if (record.platform !== platform) return new Error('Tier-3 measurement platform mismatch.'); + if (!Array.isArray(record.entries)) record.entries = []; + if (!record.measurements || typeof record.measurements !== 'object' || Array.isArray(record.measurements)) { + record.measurements = {}; + } + if (!record.measurements[layer] || typeof record.measurements[layer] !== 'object' || Array.isArray(record.measurements[layer])) { + record.measurements[layer] = {}; + } + + const already = record.entries.find((entry) => + entry.layer === layer && entry.measurement_name === measurementName && entry.measurement === measurement + ); + if (already) { + return { + ok: true, + op: 'tier3BlessMeasurement', + platform, + layer, + measurement_name: measurementName, + measurement, + status: 'already_blessed', + }; + } + + const entry = { + layer, + measurement_name: measurementName, + measurement, + effective_epoch: this.value.effective_epoch, + region: this.value.region ?? null, + derivation_hash: this.value.derivation_hash ?? null, + source: this.value.source ?? 'admin-derive', + blessed_at: this.tx, + blessed_by: this.address, + }; + record.entries.push(entry); + const values = Array.isArray(record.measurements[layer][measurementName]) + ? record.measurements[layer][measurementName] + : []; + if (!values.includes(measurement)) values.push(measurement); + values.sort(); + record.measurements[layer][measurementName] = values; + record.updated_at = this.tx; + record.updated_by = this.address; + await this.put(key, record); + await this.put(`tier3/measurement/${platform}/${layer}/${measurementName}/${measurement}`, entry); + console.log('mayhem tier3BlessMeasurement', entry); + return { + ok: true, + op: 'tier3BlessMeasurement', + platform, + layer, + measurement_name: measurementName, + measurement, + status: 'blessed', + }; + } + + async setRules() { + const adminError = await this.requireAdmin(); + if (adminError) return adminError; + + const current = await this.currentRules(); + if (current && this.value.ver <= current.ver) { + return new Error('Rules version must increase.'); + } + + const rules = { + ver: this.value.ver, + hash: this.value.hash, + set_by: this.address, + set_by_role: 'admin', + activated_at: this.tx, + }; + await this.put(`rules/${rules.ver}`, rules); + await this.put(CURRENT_RULES_KEY, rules); + if ((await this.get('epoch/apply/state')) === null) { + await this.put('epoch/apply/state', { + updated_epoch: 0, + updated_at: null, + last_apply_hash: null, + }); + } + console.log('mayhem setRules', rules); + return { ok: true, op: 'setRules', rules }; + } + + async setParams() { + const adminError = await this.requireAdmin(); + if (adminError) return adminError; + + const governanceParams = await this.activeParamsAt(this.value.submitted_at, [ + 'param_activation_delay_seconds', + ]); + if ( + this.value.effective_at - this.value.submitted_at < + governanceParams.param_activation_delay_seconds + ) { + return new Error('Parameter changes require at least the active param_activation_delay_seconds.'); + } + + const valuesError = this.validateParamValues(this.value.values); + if (valuesError) return valuesError; + const normalizedValues = this.normalizeParamValues(this.value.values); + if (normalizedValues instanceof Error) return normalizedValues; + + const existingAtEffective = await this.activeParamsAt(this.value.effective_at); + const mergedAtEffective = { ...existingAtEffective, ...normalizedValues }; + const boundsError = this.validateParamBounds(mergedAtEffective); + if (boundsError) return boundsError; + + const meta = await this.get('params/current'); + const ver = meta ? meta.ver + 1 : 1; + const keys = Object.keys(normalizedValues).sort(); + const update = { + ver, + values: cloneValue(normalizedValues), + submitted_at: this.value.submitted_at, + effective_at: this.value.effective_at, + set_by: this.address, + set_by_role: 'admin', + tx: this.tx, + }; + + for (const key of keys) { + const record = await this.paramRecord(key); + if (record.pending && record.pending.effective_at > this.value.submitted_at) { + return new Error(`Pending parameter change already scheduled for ${key}.`); + } + + const current = this.paramActiveEntry(record, this.value.submitted_at); + const updated = { + key, + current, + pending: { + value: normalizedValues[key], + ver, + submitted_at: this.value.submitted_at, + effective_at: this.value.effective_at, + set_by: this.address, + set_by_role: 'admin', + set_at: this.tx, + }, + }; + await this.put(`params/${key}`, updated); + } + + await this.put(`params/update/${ver}`, update); + await this.put('params/current', { + ver, + keys, + set_by: this.address, + set_by_role: 'admin', + updated_at: this.tx, + effective_at: this.value.effective_at, + }); + console.log('mayhem setParams', update); + return { ok: true, op: 'setParams', ver, effective_at: this.value.effective_at, keys }; + } + + async setPayments() { + const adminError = await this.requireAdmin(); + if (adminError) return adminError; + + const normalized = this.normalizePaymentConfig(this.value); + if (normalized instanceof Error) return normalized; + const current = await this.get('payments/current'); + if (current && this.value.ver <= current.ver) { + return new Error('Payment config version must increase.'); + } + const record = { + denom: PRICE_DENOMINATION, + rails: PROVIDER_ACCEPTED_RAIL_ORDER.slice(), + ...normalized, + ver: this.value.ver, + updated_at: this.tx, + set_by: this.address, + set_by_role: 'admin', + }; + await this.put('payments/current', record); + console.log('mayhem setPayments', record); + return { ok: true, op: 'setPayments', ver: record.ver }; + } + + async readParams() { + const keys = this.value.keys ?? Object.keys(PARAM_DEFINITIONS); + const keyError = this.validateParamKeys(keys); + if (keyError) return keyError; + + const params = await this.activeParamsAt(this.value.at, keys); + console.log('mayhem readParams', { at: this.value.at, params }); + return { ok: true, op: 'readParams', at: this.value.at, params }; + } + + async setCtxBrackets() { + const adminError = await this.requireAdmin(); + if (adminError) return adminError; + + const governanceParams = await this.activeParamsAt(this.value.submitted_at, [ + 'param_activation_delay_seconds', + ]); + if ( + this.value.effective_at - this.value.submitted_at < + governanceParams.param_activation_delay_seconds + ) { + return new Error('Context bracket changes require at least the active param_activation_delay_seconds.'); + } + + const brackets = this.normalizeCtxBracketTable(this.value.brackets); + if (brackets instanceof Error) return brackets; + + const schedule = await this.ctxBracketSchedule(); + if (schedule.pending && schedule.pending.effective_at > this.value.submitted_at) { + return new Error('Pending context bracket table already scheduled.'); + } + const latest = this.ctxBracketLatestEntry(schedule); + const record = { + ver: latest.ver + 1, + brackets, + submitted_at: this.value.submitted_at, + effective_at: this.value.effective_at, + effective_from: this.tx, + updated_at: this.tx, + set_by: this.address, + set_by_role: 'admin', + }; + const updated = { + current: schedule.current, + pending: schedule.pending, + }; + if (updated.pending && updated.pending.effective_at <= this.value.effective_at) { + updated.current = updated.pending; + updated.pending = null; + } + if (updated.pending) return new Error('Pending context bracket table already scheduled.'); + updated.pending = record; + + await this.put('ctx_brackets', updated); + await this.put(`ctx_brackets/v/${record.ver}`, record); + console.log('mayhem setCtxBrackets', record); + return { ok: true, op: 'setCtxBrackets', ver: record.ver, effective_at: record.effective_at }; + } + + async readCtxBrackets() { + if (hasOwn(this.value, 'ver') && hasOwn(this.value, 'at')) { + return new Error('Read context brackets by either ver or at, not both.'); + } + const table = hasOwn(this.value, 'ver') + ? await this.ctxBracketTableByVersion(this.value.ver) + : await this.ctxBracketTableAt(this.value.at ?? 0); + if (table instanceof Error) return table; + console.log('mayhem readCtxBrackets', table); + return { ok: true, op: 'readCtxBrackets', table }; + } + + async consent() { + const rules = await this.currentRules(); + if (!rules) return new Error('Rules are not set.'); + if (this.value.ver !== rules.ver || this.value.hash !== rules.hash) { + return new Error('Consent must match the current rules.'); + } + if (!this.verifyConsentSignature(this.address, this.value.ver, this.value.hash, this.value.sig)) { + return new Error('Invalid consent signature.'); + } + + const record = { + ver: this.value.ver, + hash: this.value.hash, + at: this.tx, + }; + await this.put(`consent/${this.address}`, record); + console.log('mayhem consent', { address: this.address, ...record }); + return { ok: true, op: 'consent', address: this.address, ...record }; + } + + async registerProvider() { + const shapeError = this.validateExactCommandValue(['op'], 'register_provider'); + if (shapeError) return shapeError; + if (this.value.op !== 'register_provider') return new Error('Invalid provider registration op.'); + + return this.applyRegisterProvider(this.address, this.tx); + } + + async applyRegisterProvider(providerId, stamp) { + const consentError = await this.requireConsent(providerId); + if (consentError) return consentError; + + const auditor = await this.get(`auditor/${providerId}`); + if (auditor?.status === 'active') { + return new Error('Auditor keys cannot register as providers.'); + } + + const key = `prov/${providerId}`; + if ((await this.get(key)) !== null) return new Error('Provider already registered.'); + + const record = { + provider: providerId, + accepted_rails: ['fiat'], + accepted_rails_schema_version: PROVIDER_RAIL_SCHEMA_VERSION, + accepted_rails_set_by: providerId, + accepted_rails_set_at: stamp, + status: 'active', + enclaves: [], + probation: { + since: stamp, + since_seconds: 0, + successful_sessions: 0, + }, + registered_at: stamp, + updated_at: stamp, + }; + await this.put(key, record); + console.log('mayhem registerProvider', record); + return { ok: true, op: 'registerProvider', provider: providerId }; + } + + normalizeProviderAcceptedRails(rails) { + if (!Array.isArray(rails) || rails.length === 0) { + return new Error('Provider accepted rails cannot be empty.'); + } + const accepted = []; + for (const rawRail of rails) { + if (typeof rawRail !== 'string') return new Error('Invalid provider rail.'); + const rail = rawRail.toLowerCase(); + if (!PROVIDER_ACCEPTED_RAILS.has(rail)) { + return new Error('Unsupported provider payment rail.'); + } + if (accepted.includes(rail)) return new Error('Duplicate provider payment rail.'); + accepted.push(rail); + } + if (accepted.length === 0) return new Error('Provider accepted rails cannot be empty.'); + accepted.sort( + (left, right) => + PROVIDER_ACCEPTED_RAIL_ORDER.indexOf(left) - PROVIDER_ACCEPTED_RAIL_ORDER.indexOf(right) + ); + return accepted; + } + + normalizeLedgerRail(value, label = 'ledger rail') { + if (typeof value !== 'string') return new Error(`Invalid ${label}.`); + const rail = value.toLowerCase(); + if (!PROVIDER_ACCEPTED_RAILS.has(rail)) return new Error(`Unsupported ${label}.`); + return rail; + } + + balanceKey(user, rail) { + return `bal/${user}/${rail}`; + } + + spendHoldKey(user, rail, epoch) { + return `hold/${rail}/${user}/${epoch}`; + } + + targetedSpendHoldKey(user, rail) { + return `hold/targeted-outstanding/${rail}/${user}`; + } + + targetedSpendSummaryKey(user, rail) { + return `hold/targeted-summary/${rail}/${user}`; + } + + targetedSpendLegacyReleaseSummaryKey(user, rail) { + return `hold/targeted-legacy-release/${rail}/${user}`; + } + + targetedSpendSessionKey(user, rail, reservationId) { + return `hold/targeted-session/${rail}/${user}/${reservationId}`; + } + + targetedSpendLegacySessionKey(user, rail, reservationId) { + return `hold/targeted-legacy-session/${rail}/${user}/${reservationId}`; + } + + targetedSpendSessionIndexKey(user, rail, sessionId) { + return `hold/targeted-session-index/${rail}/${user}/${sessionId}`; + } + + targetedSpendBillingAttemptKey(user, rail, billingId, billingAttempt) { + return `hold/targeted-billing/${rail}/${user}/${billingId}/${billingAttempt}`; + } + + receiptBillingKey(billingId) { + return `receipt/billing/${billingId}`; + } + + receiptReservationKey(reservationId) { + return `receipt/reservation/${reservationId}`; + } + + receiptReservationCloseKey(reservationId) { + return `receipt/reservation-close/${reservationId}`; + } + + receiptHeadKey(billingId, billingAttempt) { + return `receipt/head/${billingId}/${billingAttempt}`; + } + + receiptConsumedKey(billingId, billingAttempt) { + return `receipt/consumed/${billingId}/${billingAttempt}`; + } + + receiptEpochIndexKey(epoch) { + return `receipt/epoch/${epoch}/index`; + } + + receiptEpochPageKey(epoch, page) { + return `receipt/epoch/${epoch}/page/${page}`; + } + + disputeOpenCountKey(opener) { + return `disp/open/${opener}`; + } + + providerOpenDisputeCountKey(provider) { + return `disp/provider-open/${provider}`; + } + + async disputeOpenCount(key) { + const record = await this.get(key); + const count = record?.count ?? 0; + if (!Number.isSafeInteger(count) || count < 0) { + return new Error('Invalid open dispute count.'); + } + return count; + } + + async providerHasOpenDispute(provider) { + const count = await this.disputeOpenCount(this.providerOpenDisputeCountKey(provider)); + if (count instanceof Error) return count; + return count > 0; + } + + async closeDisputeCounts(dispute) { + const openerKey = this.disputeOpenCountKey(dispute.opened_by); + const providerKey = this.providerOpenDisputeCountKey(dispute.provider); + const openerCount = await this.disputeOpenCount(openerKey); + if (openerCount instanceof Error) return openerCount; + const providerCount = await this.disputeOpenCount(providerKey); + if (providerCount instanceof Error) return providerCount; + if (openerCount < 1 || providerCount < 1) { + return new Error('Open dispute count underflow.'); + } + await this.put(openerKey, { + opener: dispute.opened_by, + count: openerCount - 1, + updated_at: this.tx, + }); + await this.put(providerKey, { + provider: dispute.provider, + count: providerCount - 1, + updated_at: this.tx, + }); + return null; + } + + earningKey(provider, rail) { + return `earn/${rail}/${provider}`; + } + + feeCumKey(rail) { + return `fee/${rail}/cum`; + } + + burnCumKey(rail) { + return `burn/${rail}/cum`; + } + + async setProviderRails() { + const shapeError = this.validateExactCommandValue(['op', 'rails'], 'set_provider_rails'); + if (shapeError) return shapeError; + return await this.applySetProviderRails(this.address, this.value.rails, this.tx); + } + + async applySetProviderRails(providerId, acceptedRails, stamp) { + const consentError = await this.requireConsent(providerId); + if (consentError) return consentError; + const provider = await this.get(`prov/${providerId}`); + if (!provider || provider.status !== 'active') return new Error('Provider registration required.'); + const rails = this.normalizeProviderAcceptedRails(acceptedRails); + if (rails instanceof Error) return rails; + + const updated = { + ...provider, + accepted_rails: rails, + accepted_rails_schema_version: PROVIDER_RAIL_SCHEMA_VERSION, + accepted_rails_set_by: providerId, + accepted_rails_set_at: stamp, + updated_at: stamp, + }; + await this.put(`prov/${providerId}`, updated); + console.log('mayhem setProviderRails', updated); + return { ok: true, op: 'setProviderRails', provider: providerId, rails }; + } + + async setProviderKyb() { + const adminError = await this.requireAdmin(); + if (adminError) return adminError; + const shapeError = this.validateExactCommandValue( + [ + 'op', + 'provider', + 'legal_name', + 'jurisdiction', + 'proof_hash', + 'kyb_ref', + 'verified_at', + 'admin_sig', + ], + 'set_provider_kyb', + ['schema_version'] + ); + if (shapeError) return shapeError; + + const normalized = this.normalizeProviderKybValue(this.value); + if (normalized instanceof Error) return normalized; + if (!(await this.verifyProviderKybSignature(normalized))) { + return new Error('Invalid provider KYB admin signature.'); + } + + const key = `prov/${normalized.provider}`; + const provider = await this.get(key); + if (!provider) return new Error('Provider not found.'); + if (provider.status === 'banned') return new Error('Provider is banned.'); + const kybBanError = await this.rejectBannedProviderKyb(normalized); + if (kybBanError) return kybBanError; + + const record = { + status: 'verified', + provider: normalized.provider, + legal_name: normalized.legal_name, + jurisdiction: normalized.jurisdiction, + proof_hash: normalized.proof_hash, + kyb_ref: normalized.kyb_ref, + verified_at: normalized.verified_at, + verified_by: this.address, + verified_by_role: 'admin', + admin_sig: normalized.admin_sig, + schema_version: normalized.schema_version, + updated_at: this.tx, + }; + const providerSummary = { + status: 'verified', + legal_name: record.legal_name, + jurisdiction: record.jurisdiction, + proof_hash: record.proof_hash, + kyb_ref: record.kyb_ref, + verified_at: record.verified_at, + verified_by: record.verified_by, + verified_by_role: 'admin', + schema_version: record.schema_version, + set_at: this.tx, + }; + const updatedProvider = { + ...provider, + kyb: providerSummary, + updated_at: this.tx, + }; + + await this.put(`kyb/${normalized.provider}`, record); + await this.put(key, updatedProvider); + console.log('mayhem setProviderKyb', record); + return { + ok: true, + op: 'setProviderKyb', + provider: normalized.provider, + att_tier: 4, + }; + } + + async revokeProviderKyb() { + const adminError = await this.requireAdmin(); + if (adminError) return adminError; + const shapeError = this.validateExactCommandValue( + ['op', 'provider'], + 'revoke_provider_kyb', + ['reason_hash'] + ); + if (shapeError) return shapeError; + if (!this.isHexBytes(this.value.provider, 32)) return new Error('Invalid provider id.'); + if ( + this.value.reason_hash !== undefined && + !this.isHexBytes(this.value.reason_hash, 32) + ) { + return new Error('Invalid provider KYB revoke reason hash.'); + } + + const key = `prov/${this.value.provider}`; + const provider = await this.get(key); + if (!provider) return new Error('Provider not found.'); + const current = await this.get(`kyb/${this.value.provider}`); + if (!current || current.status !== 'verified') return new Error('Active provider KYB not found.'); + + const revoked = { + ...current, + status: 'revoked', + revoked_at: this.tx, + revoked_by: this.address, + revoked_by_role: 'admin', + revoke_reason_hash: this.value.reason_hash ?? null, + updated_at: this.tx, + }; + const updatedProvider = { + ...provider, + kyb: { + ...(provider.kyb ?? {}), + status: 'revoked', + revoked_at: this.tx, + revoked_by: this.address, + revoked_by_role: 'admin', + revoke_reason_hash: this.value.reason_hash ?? null, + }, + updated_at: this.tx, + }; + + await this.put(`kyb/${this.value.provider}`, revoked); + await this.put(key, updatedProvider); + const kybBanIndexError = await this.writeProviderKybBanIndexes(revoked, { + status: 'revoked', + source: 'revoke_provider_kyb', + provider: this.value.provider, + reason_hash: this.value.reason_hash ?? null, + recorded_at: this.tx, + recorded_by: this.address, + recorded_by_role: 'admin', + }); + if (kybBanIndexError instanceof Error) return kybBanIndexError; + console.log('mayhem revokeProviderKyb', revoked); + return { + ok: true, + op: 'revokeProviderKyb', + provider: this.value.provider, + }; + } + + async banProvider() { + const adminError = await this.requireAdmin(); + if (adminError) return adminError; + if (!this.isSafeKeyPart(this.value.provider)) return new Error('Invalid provider id.'); + if (this.value.device_key !== undefined && !this.isHexBytes(this.value.device_key, 32)) { + return new Error('Invalid provider device key.'); + } + if (this.value.hardware_fingerprint !== undefined && !this.isHexBytes(this.value.hardware_fingerprint, 32)) { + return new Error('Invalid provider hardware fingerprint.'); + } + + const key = `prov/${this.value.provider}`; + const record = await this.get(key); + if (!record) return new Error('Provider not found.'); + if (record.status === 'banned') return new Error('Provider already banned.'); + + const tombstones = await this.tombstoneProviderEnclaves( + this.value.provider, + this.providerActiveEnclaves(record), + this.value.reason_hash ?? null + ); + if (tombstones instanceof Error) return tombstones; + + const updated = { + ...record, + status: 'banned', + enclaves: [], + tombstoned_enclaves: tombstones.map((tombstone) => tombstone.enclave_id), + banned_at: this.tx, + banned_by: this.address, + banned_by_role: 'admin', + ban_reason_hash: this.value.reason_hash ?? null, + updated_at: this.tx, + }; + const providerBan = { + target_type: 'provider', + target: this.value.provider, + provider: this.value.provider, + status: 'banned', + reason_hash: this.value.reason_hash ?? null, + banned_at: this.tx, + banned_by: this.address, + banned_by_role: 'admin', + reversible: true, + }; + const deviceKey = this.value.device_key ?? record.device_key ?? null; + const fingerprint = this.value.hardware_fingerprint ?? record.hardware_fingerprint ?? null; + await this.put(key, updated); + await this.put(`ban/provider/${this.value.provider}`, providerBan); + if (deviceKey) { + await this.put(`ban/device/${deviceKey}`, { + target_type: 'device', + target: deviceKey, + provider: this.value.provider, + status: 'banned', + reason_hash: this.value.reason_hash ?? null, + banned_at: this.tx, + banned_by: this.address, + banned_by_role: 'admin', + reversible: true, + }); + } + if (fingerprint) { + const fpKey = `ban/fingerprint/${fingerprint}`; + const current = await this.get(fpKey); + const wallets = { + ...(current?.wallets ?? {}), + [this.value.provider]: { + provider: this.value.provider, + reason_hash: this.value.reason_hash ?? null, + banned_at: this.tx, + }, + }; + await this.put(fpKey, { + target_type: 'fingerprint', + target: fingerprint, + status: 'banned', + wallets, + reason_hash: this.value.reason_hash ?? null, + banned_at: current?.banned_at ?? this.tx, + updated_at: this.tx, + banned_by: this.address, + banned_by_role: 'admin', + reversible: true, + auto_reject: true, + }); + } + if (record.kyb?.status === 'verified') { + const kybBanIndexError = await this.writeProviderKybBanIndexes(record.kyb, { + status: 'banned', + source: 'ban_provider', + provider: this.value.provider, + reason_hash: this.value.reason_hash ?? null, + recorded_at: this.tx, + recorded_by: this.address, + recorded_by_role: 'admin', + }); + if (kybBanIndexError instanceof Error) return kybBanIndexError; + } + console.log('mayhem banProvider', updated); + return { + ok: true, + op: 'banProvider', + provider: this.value.provider, + tombstoned_enclaves: updated.tombstoned_enclaves, + device_key: deviceKey, + hardware_fingerprint: fingerprint, + }; + } + + async unban() { + const adminError = await this.requireAdmin(); + if (adminError) return adminError; + const shapeError = this.validateExactCommandValue( + ['op', 'target_type', 'target', 'reason_hash'], + 'unban' + ); + if (shapeError) return shapeError; + const targetType = String(this.value.target_type).toLowerCase(); + if (!BAN_TARGET_TYPES.has(targetType)) return new Error('Unsupported ban target type.'); + if (!this.isHexBytes(this.value.target, 32)) return new Error('Invalid ban target.'); + if (!this.isHexBytes(this.value.reason_hash, 32)) return new Error('Invalid unban reason hash.'); + + if (targetType === 'kyb') { + const provider = await this.get(`prov/${this.value.target}`); + if (!provider) return new Error('Provider not found.'); + const kyb = await this.get(`kyb/${this.value.target}`); + if (!kyb || kyb.status !== 'revoked') { + return new Error('Revoked provider KYB not found.'); + } + const keys = await this.kybBanIndexKeys(kyb); + if (keys instanceof Error) return keys; + const records = []; + for (const key of keys) { + const current = await this.get(key); + if (!current || current.target_type !== 'kyb' || current.reversible !== true) { + return new Error('Reversible provider KYB ban index not found.'); + } + if (!['banned', 'revoked', 'unbanned'].includes(current.status)) { + return new Error('Invalid provider KYB ban index status.'); + } + if (!current.providers?.[this.value.target]) { + return new Error('Provider KYB ban index does not bind this provider.'); + } + records.push([key, current]); + } + for (const [key, current] of records) { + await this.put(key, { + ...current, + status: 'unbanned', + unbanned_at: this.tx, + unbanned_by: this.address, + unbanned_by_role: 'admin', + unban_reason_hash: this.value.reason_hash, + reversible: true, + }); + } + return { + ok: true, + op: 'unban', + target_type: targetType, + target: this.value.target, + }; + } + + if (targetType === 'provider') { + const provider = await this.get(`prov/${this.value.target}`); + if (!provider) return new Error('Provider not found.'); + if (provider.status === 'banned') { + await this.put(`prov/${this.value.target}`, { + ...provider, + status: 'active', + unbanned_at: this.tx, + unbanned_by: this.address, + unbanned_by_role: 'admin', + unban_reason_hash: this.value.reason_hash, + updated_at: this.tx, + }); + } + } + + const key = this.banRecordKey(targetType, this.value.target); + const current = await this.get(key); + const record = { + ...(current ?? {}), + target_type: targetType, + target: this.value.target, + status: 'unbanned', + unbanned_at: this.tx, + unbanned_by: this.address, + unbanned_by_role: 'admin', + unban_reason_hash: this.value.reason_hash, + reversible: true, + }; + await this.put(key, record); + console.log('mayhem unban', record); + return { + ok: true, + op: 'unban', + target_type: targetType, + target: this.value.target, + }; + } + + async deviceRebind() { + const adminError = await this.requireAdmin(); + if (adminError) return adminError; + const shapeError = this.validateExactCommandValue( + ['op', 'device_key', 'provider', 'reason_hash'], + 'device_rebind' + ); + if (shapeError) return shapeError; + if (!this.isHexBytes(this.value.device_key, 32)) return new Error('Invalid device key.'); + if (!this.isHexBytes(this.value.provider, 32)) return new Error('Invalid provider id.'); + if (!this.isHexBytes(this.value.reason_hash, 32)) return new Error('Invalid rebind reason hash.'); + const provider = await this.get(`prov/${this.value.provider}`); + if (!provider || provider.status !== 'active') return new Error('Active provider not found.'); + const deviceBan = await this.get(`ban/device/${this.value.device_key}`); + if (deviceBan?.status === 'banned') return new Error('Device key is banned.'); + const current = await this.get(`device/${this.value.device_key}`); + const record = { + ...(current ?? {}), + device_key: this.value.device_key, + provider: this.value.provider, + status: 'active', + rebound_at: this.tx, + rebound_by: this.address, + rebound_by_role: 'admin', + rebind_reason_hash: this.value.reason_hash, + previous_provider: current?.provider ?? null, + }; + await this.put(`device/${this.value.device_key}`, record); + await this.put(`prov/${this.value.provider}`, { + ...provider, + device_key: this.value.device_key, + device_key_bound_at: this.tx, + updated_at: this.tx, + }); + console.log('mayhem deviceRebind', record); + return { + ok: true, + op: 'deviceRebind', + device_key: this.value.device_key, + provider: this.value.provider, + }; + } + + async setModelRef() { + const adminError = await this.requireAdmin(); + if (adminError) return adminError; + + const validationError = this.validateModelRef(this.value); + if (validationError) return validationError; + + const key = `modelref/${this.value.model_id}`; + const current = await this.get(key); + const calibration = Object.hasOwn(this.value, 'activity_calibration') + ? this.value.activity_calibration : current?.activity_calibration; + if (calibration) { + const error = this.validateActivityCalibration(calibration, this.modelClassFor(this.value), this.value.rate_map); + if (error) return error; + } + const record = { + model_id: this.value.model_id, + model_class: this.modelClassFor(this.value), + denom: PRICE_DENOMINATION, + rate_map: this.normalizeRateMap(this.value.rate_map), + ver: (current?.ver ?? 0) + 1, + source_hash: this.value.source_hash ?? null, + ...(calibration ? { + activity_calibration: cloneValue(calibration), + } : {}), + updated_at: this.tx, + set_by: this.address, + set_by_role: 'admin', + }; + await this.put(key, record); + console.log('mayhem setModelRef', record); + return { ok: true, op: 'setModelRef', model_id: record.model_id, ver: record.ver }; + } + + async publishCatalog() { + const adminError = await this.requireAdmin(); + if (adminError) return adminError; + + const validationError = this.validateCatalogRelease(this.value); + if (validationError) return validationError; + + const current = await this.get('catalog/current'); + const record = { + catalog_id: this.value.catalog_id, + source_kind: this.value.source_kind, + catalog_url: this.value.catalog_url, + signature_url: this.value.signature_url, + catalog_hash: this.value.catalog_hash, + signature_hash: this.value.signature_hash, + key_id: this.value.key_id, + public_key: this.value.public_key, + model_count: this.value.model_count, + artifact_count: this.value.artifact_count, + canaries: cloneValue(this.value.canaries), + ver: (current?.ver ?? 0) + 1, + supersedes: current?.catalog_hash ?? null, + status: 'active', + published_at: this.tx, + published_by: this.address, + published_by_role: 'admin', + }; + if (hasOwn(this.value, 'parts_anchor')) { + record.parts_anchor = cloneValue(this.value.parts_anchor); + } + if (hasOwn(this.value, 'blessed_runtimes')) { + record.blessed_runtimes = cloneValue(this.value.blessed_runtimes); + } + if (hasOwn(this.value, 'outcome_classes')) { + record.outcome_classes = cloneValue(this.value.outcome_classes); + } + await this.put(`catalog/release/${record.catalog_hash}`, record); + await this.put('catalog/current', record); + console.log('mayhem publishCatalog', record); + return { + ok: true, + op: 'publishCatalog', + catalog_hash: record.catalog_hash, + ver: record.ver, + }; + } + + async registerEnclave() { + const adminError = await this.requireAdmin(); + if (adminError) return adminError; + if (!this.isSafeKeyPart(this.value.enclave_id)) return new Error('Invalid enclave id.'); + if (!this.isSafeModelId(this.value.model_id)) return new Error('Invalid model id.'); + + const key = `enclave/${this.value.enclave_id}`; + if ((await this.get(key)) !== null) return new Error('Enclave already registered.'); + const modelClass = this.modelClassFor(this.value); + const classError = this.validateModelClass(modelClass, 'Enclave model_class'); + if (classError) return classError; + const tierError = this.validateLaunchEnclaveAttestationTier(this.value.att_tier); + if (tierError) return tierError; + const capsError = this.validateEnclaveCaps(this.value.caps, modelClass); + if (capsError) return capsError; + const artifactError = this.validateEnclaveArtifactBinding(this.value); + if (artifactError) return artifactError; + if (hasOwn(this.value, 'approved_binary_hashes') && !Array.isArray(this.value.approved_binary_hashes)) { + return new Error('Enclave approved_binary_hashes must be an array.'); + } + const approvedBinaryHashes = this.normalizeApprovedBinaryHashes( + this.value.binary_hash, + this.value.approved_binary_hashes + ); + const binaryHashesError = this.validateApprovedBinaryHashes( + this.value.binary_hash, + approvedBinaryHashes + ); + if (binaryHashesError) return binaryHashesError; + const launchMeasurements = this.normalizeEnclaveLaunchMeasurements(this.value.launch_measurements); + const measurementsError = this.validateEnclaveLaunchMeasurements(launchMeasurements, this.value.att_tier); + if (measurementsError) return measurementsError; + const quant = this.normalizeEnclaveQuant(this.value.quant ?? 'unknown'); + const quantError = this.validateEnclaveQuant(quant); + if (quantError) return quantError; + + const record = { + enclave_id: this.value.enclave_id, + model_id: this.value.model_id, + model_class: modelClass, + backend: this.value.backend, + artifact_root: this.value.artifact_root, + artifact_root_kind: this.value.artifact_root_kind, + artifact_source: cloneValue(this.value.artifact_source), + artifact_sidecars: cloneValue(this.value.artifact_sidecars ?? {}), + source_sha256: this.value.source_sha256 ?? null, + manifest_hash: this.value.manifest_hash, + att_tier: this.value.att_tier, + min_att_tier: this.value.att_tier, + pending_min_att_tier: null, + quant, + binary_hash: this.value.binary_hash, + approved_binary_hashes: approvedBinaryHashes, + launch_measurements: launchMeasurements, + caps: cloneValue(this.value.caps), + status: 'active', + providers: [], + created_by: this.address, + created_by_role: 'admin', + registered_at: this.tx, + updated_at: this.tx, + retired_at: null, + }; + await this.put(key, record); + console.log('mayhem registerEnclave', record); + return { ok: true, op: 'registerEnclave', enclave_id: record.enclave_id }; + } + + async updateEnclave() { + const adminError = await this.requireAdmin(); + if (adminError) return adminError; + if (!this.isSafeKeyPart(this.value.enclave_id)) return new Error('Invalid enclave id.'); + const allowedFields = new Set(['op', 'enclave_id', ...ENCLAVE_UPDATE_FIELDS]); + const unknownFields = Object.keys(this.value).filter((field) => !allowedFields.has(field)).sort(); + if (unknownFields.length > 0) { + return new Error(`update_enclave does not accept immutable fields: ${unknownFields.join(', ')}.`); + } + + const key = `enclave/${this.value.enclave_id}`; + const record = await this.get(key); + if (!record) return new Error('Enclave not found.'); + if (record.status === 'retired') return new Error('Enclave is retired.'); + + let changed = false; + const updated = cloneValue(record); + for (const field of ENCLAVE_UPDATE_FIELDS) { + if (!hasOwn(this.value, field)) continue; + updated[field] = cloneValue(this.value[field]); + changed = true; + } + if (!changed) return new Error('No enclave fields to update.'); + if (hasOwn(this.value, 'quant')) { + updated.quant = this.normalizeEnclaveQuant(updated.quant); + } + const modelClass = this.modelClassFor(updated); + const classError = this.validateModelClass(modelClass, 'Enclave model_class'); + if (classError) return classError; + const tierError = this.validateLaunchEnclaveAttestationTier(updated.att_tier); + if (tierError) return tierError; + const capsError = this.validateEnclaveCaps(updated.caps, modelClass); + if (capsError) return capsError; + const artifactError = this.validateEnclaveArtifactBinding(updated); + if (artifactError) return artifactError; + if (hasOwn(this.value, 'approved_binary_hashes')) { + if (!Array.isArray(this.value.approved_binary_hashes)) { + return new Error('Enclave approved_binary_hashes must be an array.'); + } + updated.approved_binary_hashes = this.normalizeApprovedBinaryHashes( + updated.binary_hash, + this.value.approved_binary_hashes + ); + } else { + updated.approved_binary_hashes = this.normalizeApprovedBinaryHashes( + updated.binary_hash, + [record.binary_hash, ...(record.approved_binary_hashes ?? [])] + ); + } + const binaryHashesError = this.validateApprovedBinaryHashes( + updated.binary_hash, + updated.approved_binary_hashes + ); + if (binaryHashesError) return binaryHashesError; + updated.launch_measurements = this.normalizeEnclaveLaunchMeasurements(updated.launch_measurements); + const measurementsError = this.validateEnclaveLaunchMeasurements(updated.launch_measurements, updated.att_tier); + if (measurementsError) return measurementsError; + const quantError = this.validateEnclaveQuant(updated.quant ?? 'unknown'); + if (quantError) return quantError; + updated.quant = this.normalizeEnclaveQuant(updated.quant ?? 'unknown'); + + updated.updated_by = this.address; + updated.updated_by_role = 'admin'; + updated.updated_at = this.tx; + await this.put(key, updated); + console.log('mayhem updateEnclave', updated); + return { ok: true, op: 'updateEnclave', enclave_id: updated.enclave_id }; + } + + async setEnclaveMinTier() { + const adminError = await this.requireAdmin(); + if (adminError) return adminError; + const shapeError = this.validateExactCommandValue( + [ + 'op', + 'enclave_id', + 'min_att_tier', + 'submitted_epoch', + 'effective_epoch', + 'submitted_at', + 'reason_hash', + ], + 'set_enclave_min_tier' + ); + if (shapeError) return shapeError; + if (!this.isSafeKeyPart(this.value.enclave_id)) return new Error('Invalid enclave id.'); + if (!this.isHexBytes(this.value.reason_hash, 32)) return new Error('Invalid min-tier reason hash.'); + + const tierError = this.validateLaunchEnclaveAttestationTier(this.value.min_att_tier); + if (tierError) return tierError; + if (this.value.effective_epoch <= this.value.submitted_epoch) { + return new Error('Enclave min-tier effective_epoch must be after submitted_epoch.'); + } + + const enclaveKey = `enclave/${this.value.enclave_id}`; + const enclave = await this.get(enclaveKey); + if (!enclave) return new Error('Enclave not found.'); + if (enclave.status !== 'active') return new Error('Enclave is not active.'); + const enclaveRoleError = this.requireAdminCreatedEnclave(enclave); + if (enclaveRoleError) return enclaveRoleError; + + const params = await this.activeParamsAt(this.value.submitted_at, ['min_tier_notice_epochs']); + if ( + this.value.effective_epoch - this.value.submitted_epoch < + params.min_tier_notice_epochs + ) { + return new Error('Enclave min-tier changes require at least min_tier_notice_epochs notice.'); + } + + const currentEpoch = await this.currentAppliedEpoch(); + const activePolicy = await this.enclaveMinTierPolicy(enclave, currentEpoch); + const pending = { + min_att_tier: this.value.min_att_tier, + previous_min_att_tier: activePolicy.min_att_tier, + submitted_epoch: this.value.submitted_epoch, + effective_epoch: this.value.effective_epoch, + submitted_at: this.value.submitted_at, + reason_hash: this.value.reason_hash, + scheduled_at: this.tx, + scheduled_by: this.address, + scheduled_by_role: 'admin', + }; + const record = { + enclave_id: this.value.enclave_id, + current_min_att_tier: activePolicy.min_att_tier, + current_epoch: currentEpoch, + pending, + updated_at: this.tx, + updated_by: this.address, + updated_by_role: 'admin', + }; + await this.put(`tierpolicy/enclave/${this.value.enclave_id}`, record); + await this.put(enclaveKey, { + ...enclave, + min_att_tier: activePolicy.min_att_tier, + pending_min_att_tier: pending, + updated_at: this.tx, + updated_by: this.address, + updated_by_role: 'admin', + }); + console.log('mayhem setEnclaveMinTier', record); + return { + ok: true, + op: 'setEnclaveMinTier', + enclave_id: this.value.enclave_id, + min_att_tier: this.value.min_att_tier, + effective_epoch: this.value.effective_epoch, + }; + } + + async retireEnclave() { + const adminError = await this.requireAdmin(); + if (adminError) return adminError; + if (!this.isSafeKeyPart(this.value.enclave_id)) return new Error('Invalid enclave id.'); + + const key = `enclave/${this.value.enclave_id}`; + const record = await this.get(key); + if (!record) return new Error('Enclave not found.'); + if (record.status === 'retired') return new Error('Enclave already retired.'); + + const activeProviders = this.enclaveActiveProviders(record); + const tombstones = await this.tombstoneEnclaveProviders( + this.value.enclave_id, + activeProviders, + null + ); + if (tombstones instanceof Error) return tombstones; + + const updated = { + ...record, + status: 'retired', + providers: [], + tombstoned_providers: tombstones + .filter((tombstone) => tombstone.serve_tombstoned) + .map((tombstone) => tombstone.provider), + retired_at: this.tx, + retired_by: this.address, + retired_by_role: 'admin', + updated_by: this.address, + updated_by_role: 'admin', + updated_at: this.tx, + }; + await this.put(key, updated); + console.log('mayhem retireEnclave', updated); + return { + ok: true, + op: 'retireEnclave', + enclave_id: updated.enclave_id, + tombstoned_providers: updated.tombstoned_providers, + }; + } + + async joinEnclave() { + const shapeError = this.validateExactCommandValue( + [ + 'op', + 'enclave_id', + 'att_tier', + 'attestation_head', + 'served_ctx', + 'served_modalities', + 'served_specialities', + 'ctx_bracket', + 'ctx_bracket_table_ver', + ], + 'join_enclave', + ['hardware_fingerprint', 'device_key'] + ); + if (shapeError) return shapeError; + if (!this.isSafeKeyPart(this.value.enclave_id)) return new Error('Invalid enclave id.'); + + return this.applyJoinEnclave( + this.address, + this.value.enclave_id, + this.tx, + this.value.att_tier, + this.value.attestation_head, + this.value.hardware_fingerprint ?? null, + this.value.device_key ?? null, + { + served_ctx: this.value.served_ctx, + served_modalities: this.value.served_modalities, + served_specialities: this.value.served_specialities, + ctx_bracket: this.value.ctx_bracket, + ctx_bracket_table_ver: this.value.ctx_bracket_table_ver, + } + ); + } + + async applyJoinEnclave( + providerId, + enclaveId, + stamp, + attTier, + attestationHead, + hardwareFingerprint = null, + deviceKey = null, + serveTerms = null + ) { + const consentError = await this.requireConsent(providerId); + if (consentError) return consentError; + const providerError = await this.requireProvider(providerId); + if (providerError) return providerError; + if (!Number.isSafeInteger(attTier) || attTier < 1 || attTier > MAX_LAUNCH_ENCLAVE_ATTESTATION_TIER) { + return new Error('Invalid provider attestation tier.'); + } + if (!this.isHexBytes(attestationHead, 32)) { + return new Error('Invalid provider attestation head.'); + } + if (hardwareFingerprint !== null && !this.isHexBytes(hardwareFingerprint, 32)) { + return new Error('Invalid provider hardware fingerprint.'); + } + if (deviceKey !== null && !this.isHexBytes(deviceKey, 32)) { + return new Error('Invalid provider device key.'); + } + + const enclave = await this.get(`enclave/${enclaveId}`); + if (!enclave) return new Error('Enclave not found.'); + if (enclave.status !== 'active') return new Error('Enclave is not active.'); + const enclaveRoleError = this.requireAdminCreatedEnclave(enclave); + if (enclaveRoleError) return enclaveRoleError; + const normalizedServeTerms = await this.normalizeProviderServeTerms( + enclaveId, + serveTerms, + 'provider serve' + ); + if (normalizedServeTerms instanceof Error) return normalizedServeTerms; + const priceError = await this.requireCurrentAdminPrice( + enclaveId, + normalizedServeTerms?.ctx_bracket ?? null + ); + if (priceError) return priceError; + const minTierPolicy = await this.enclaveMinTierPolicy(enclave); + if (attTier !== enclave.att_tier) { + return new Error( + `Provider attestation tier ${attTier} does not match enclave tier ${enclave.att_tier}.` + ); + } + const provider = await this.get(`prov/${providerId}`); + if (!provider || provider.status !== 'active') return new Error('Provider registration required.'); + const effectiveTier = provider.kyb?.status === 'verified' ? 4 : attTier; + if (effectiveTier < minTierPolicy.min_att_tier) { + return new Error( + `Enclave now requires minimum attestation tier ${minTierPolicy.min_att_tier}; provider proved tier ${effectiveTier}.` + ); + } + + const key = `serve/${providerId}/${enclaveId}`; + const existing = await this.get(key); + if (existing && existing.status === 'active') return new Error('Provider already serving enclave.'); + if (deviceKey !== null) { + const deviceBan = await this.get(`ban/device/${deviceKey}`); + if (deviceBan?.status === 'banned') return new Error('Provider device key is banned.'); + const binding = await this.get(`device/${deviceKey}`); + if (binding?.provider && binding.provider !== providerId) { + return new Error('Provider device key is bound to a different wallet; admin rebind required.'); + } + } + + if (hardwareFingerprint !== null) { + const fingerprintBan = await this.get(`ban/fingerprint/${hardwareFingerprint}`); + if (fingerprintBan?.status === 'banned') { + return new Error('Provider hardware fingerprint is banned.'); + } + } + + const record = { + provider: providerId, + enclave_id: enclaveId, + model_id: enclave.model_id, + status: 'active', + att_tier: attTier, + effective_att_tier: effectiveTier, + attestation_head: attestationHead.toLowerCase(), + ...(normalizedServeTerms ?? {}), + ...(hardwareFingerprint !== null ? { hardware_fingerprint: hardwareFingerprint } : {}), + ...(deviceKey !== null ? { device_key: deviceKey } : {}), + joined_at: existing?.joined_at ?? stamp, + updated_at: stamp, + left_at: null, + rooms: Array.isArray(existing?.rooms) ? existing.rooms.slice() : [], + }; + await this.put(key, record); + await this.put(`enclave/${enclaveId}`, { + ...enclave, + providers: this.enclaveProvidersWith(enclave, providerId), + updated_at: stamp, + }); + if (deviceKey !== null) { + const currentDevice = await this.get(`device/${deviceKey}`); + await this.put(`device/${deviceKey}`, { + ...(currentDevice ?? {}), + device_key: deviceKey, + provider: providerId, + status: 'active', + bound_at: stamp, + updated_at: stamp, + }); + } + await this.put(`prov/${providerId}`, { + ...provider, + enclaves: this.providerEnclavesWith(provider, enclaveId), + ...(hardwareFingerprint !== null ? { hardware_fingerprint: hardwareFingerprint } : {}), + ...(deviceKey !== null ? { device_key: deviceKey, device_key_bound_at: stamp } : {}), + updated_at: stamp, + }); + console.log('mayhem joinEnclave', record); + return { ok: true, op: 'joinEnclave', provider: providerId, enclave_id: enclaveId }; + } + + async leaveEnclave() { + const shapeError = this.validateExactCommandValue(['op', 'enclave_id'], 'leave_enclave'); + if (shapeError) return shapeError; + if (!this.isSafeKeyPart(this.value.enclave_id)) return new Error('Invalid enclave id.'); + + return this.applyLeaveEnclave(this.address, this.value.enclave_id, this.tx); + } + + async applyLeaveEnclave(providerId, enclaveId, stamp) { + const key = `serve/${providerId}/${enclaveId}`; + const record = await this.get(key); + if (!record || record.status !== 'active') return new Error('Provider is not serving enclave.'); + if (Array.isArray(record.rooms) && record.rooms.length > 0) { + return new Error('Provider must leave rooms before leaving enclave.'); + } + + const updated = { + ...record, + status: 'inactive', + updated_at: stamp, + left_at: stamp, + }; + await this.put(key, updated); + const provider = await this.get(`prov/${providerId}`); + if (provider) { + await this.put(`prov/${providerId}`, { + ...provider, + enclaves: this.providerEnclavesWithout(provider, enclaveId), + updated_at: stamp, + }); + } + const enclave = await this.get(`enclave/${enclaveId}`); + if (enclave) { + await this.put(`enclave/${enclaveId}`, { + ...enclave, + providers: this.enclaveProvidersWithout(enclave, providerId), + updated_at: stamp, + }); + } + console.log('mayhem leaveEnclave', updated); + return { ok: true, op: 'leaveEnclave', provider: providerId, enclave_id: enclaveId }; + } + + async joinRoom() { + const shapeError = this.validateExactCommandValue( + ['op', 'room_id', 'enclave_id'], + 'join_room' + ); + if (shapeError) return shapeError; + if (!this.isSafeKeyPart(this.value.room_id)) return new Error('Invalid room id.'); + if (!this.isSafeKeyPart(this.value.enclave_id)) return new Error('Invalid enclave id.'); + + return this.applyJoinRoom(this.address, this.value.room_id, this.value.enclave_id, this.tx); + } + + async applyJoinRoom(providerId, roomId, enclaveId, stamp) { + const consentError = await this.requireConsent(providerId); + if (consentError) return consentError; + const providerError = await this.requireProvider(providerId); + if (providerError) return providerError; + + const room = await this.get(`room/${roomId}`); + if (!room) return new Error('Room not found.'); + if (room.status !== 'open') return new Error('Room is not open.'); + + const serving = await this.get(`serve/${providerId}/${enclaveId}`); + if (!serving || serving.status !== 'active') return new Error('Provider is not serving enclave.'); + const enclave = await this.get(`enclave/${enclaveId}`); + if (!enclave || enclave.status !== 'active') return new Error('Enclave is not active.'); + const enclaveRoleError = this.requireAdminCreatedEnclave(enclave); + if (enclaveRoleError) return enclaveRoleError; + const roomRoleError = this.requireAdminCreatedRoom(room); + if (roomRoleError) return roomRoleError; + const priceError = await this.requireCurrentAdminPrice(enclaveId, serving.ctx_bracket ?? null); + if (priceError) return priceError; + if (room.enclave_id !== enclaveId) { + return new Error('Room enclave does not match served enclave.'); + } + if (serving.model_id !== enclave.model_id || enclave.model_id !== room.model_id) { + return new Error('Enclave model does not match room model.'); + } + + const key = `roomserve/${roomId}/${providerId}/${enclaveId}`; + const existing = await this.get(key); + if (existing && existing.status === 'active') return new Error('Provider already joined room with enclave.'); + + const rooms = Array.isArray(serving.rooms) ? serving.rooms.slice() : []; + if (!rooms.includes(roomId)) rooms.push(roomId); + rooms.sort(); + const record = { + room_id: roomId, + sidechannel: room.sidechannel, + provider: providerId, + enclave_id: enclaveId, + model_id: enclave.model_id, + status: 'active', + joined_at: existing?.joined_at ?? stamp, + updated_at: stamp, + left_at: null, + }; + await this.put(key, record); + await this.put(`serve/${providerId}/${enclaveId}`, { + ...serving, + rooms, + updated_at: stamp, + }); + await this.put(`room/${roomId}`, { + ...room, + serves: this.roomServesWith(room, providerId, enclaveId), + serves_updated_at: stamp, + }); + console.log('mayhem joinRoom', record); + return { + ok: true, + op: 'joinRoom', + room_id: roomId, + provider: providerId, + enclave_id: enclaveId, + sidechannel: room.sidechannel, + }; + } + + async leaveRoom() { + const shapeError = this.validateExactCommandValue( + ['op', 'room_id', 'enclave_id'], + 'leave_room' + ); + if (shapeError) return shapeError; + if (!this.isSafeKeyPart(this.value.room_id)) return new Error('Invalid room id.'); + if (!this.isSafeKeyPart(this.value.enclave_id)) return new Error('Invalid enclave id.'); + + return this.applyLeaveRoom(this.address, this.value.room_id, this.value.enclave_id, this.tx); + } + + async applyLeaveRoom(providerId, roomId, enclaveId, stamp) { + const key = `roomserve/${roomId}/${providerId}/${enclaveId}`; + const record = await this.get(key); + if (!record) return new Error('Provider has not joined room with enclave.'); + if (record.status !== 'active') { + return { + ok: true, + op: 'leaveRoom', + room_id: roomId, + provider: providerId, + enclave_id: enclaveId, + sidechannel: record.sidechannel, + status: record.status, + idempotent: true, + }; + } + + const servingKey = `serve/${providerId}/${enclaveId}`; + const serving = await this.get(servingKey); + const rooms = Array.isArray(serving?.rooms) + ? serving.rooms.filter((servingRoomId) => servingRoomId !== roomId) + : []; + const roomKey = `room/${roomId}`; + const room = await this.get(roomKey); + const updated = { + ...record, + status: 'inactive', + updated_at: stamp, + left_at: stamp, + }; + await this.put(key, updated); + if (serving) { + await this.put(servingKey, { + ...serving, + rooms, + updated_at: stamp, + }); + } + if (room) { + await this.put(roomKey, { + ...room, + serves: this.roomServesWithout(room, providerId, enclaveId), + serves_updated_at: stamp, + }); + } + console.log('mayhem leaveRoom', updated); + return { + ok: true, + op: 'leaveRoom', + room_id: roomId, + provider: providerId, + enclave_id: enclaveId, + sidechannel: updated.sidechannel, + }; + } + + async openRoom() { + const adminError = await this.requireAdmin(); + if (adminError) return adminError; + if (!this.isSafeKeyPart(this.value.enclave_id)) return new Error('Invalid enclave id.'); + if (this.value.model_id && !this.isSafeModelId(this.value.model_id)) { + return new Error('Invalid model id.'); + } + + const policyError = this.validateRoomPolicy(this.value.policy); + if (policyError) return policyError; + + let recordModelId = this.value.model_id; + const enclave = await this.get(`enclave/${this.value.enclave_id}`); + if (!enclave) return new Error('Enclave not found.'); + if (enclave.status !== 'active') return new Error('Enclave is not active.'); + const enclaveRoleError = this.requireAdminCreatedEnclave(enclave); + if (enclaveRoleError) return enclaveRoleError; + if (this.value.model_id && this.value.model_id !== enclave.model_id) { + return new Error('Room model does not match enclave model.'); + } + recordModelId = enclave.model_id; + + const roomId = await deriveRoomId(this.value.enclave_id, this.address, this.value.nonce); + const key = `room/${roomId}`; + const existing = await this.get(key); + if (existing && existing.status !== 'closed') return new Error('Room already open.'); + + const record = { + room_id: roomId, + sidechannel: roomSidechannelName(roomId), + enclave_id: this.value.enclave_id, + model_id: recordModelId, + label: this.value.label, + creator: this.address, + creator_role: 'admin', + policy: cloneValue(this.value.policy), + serves: [], + serves_updated_at: null, + created_at: this.tx, + updated_at: this.tx, + closed_at: null, + status: 'open', + }; + await this.put(key, record); + console.log('mayhem openRoom', record); + return { ok: true, op: 'openRoom', room_id: roomId, sidechannel: record.sidechannel }; + } + + async closeRoom() { + const adminError = await this.requireAdmin(); + if (adminError) return adminError; + if (!this.isSafeKeyPart(this.value.room_id)) return new Error('Invalid room id.'); + + const key = `room/${this.value.room_id}`; + const record = await this.get(key); + if (!record) return new Error('Room not found.'); + if (record.status === 'closed') return new Error('Room already closed.'); + + const tombstones = await this.tombstoneRoomServes( + this.value.room_id, + this.roomServingEntries(record), + null + ); + if (tombstones instanceof Error) return tombstones; + const current = (await this.get(key)) ?? record; + const updated = { + ...current, + status: 'closed', + serves: [], + serves_updated_at: this.tx, + tombstoned_serves: tombstones + .filter((tombstone) => tombstone.roomserve_tombstoned) + .map(({ provider, enclave_id }) => ({ provider, enclave_id })), + updated_at: this.tx, + closed_at: this.tx, + closed_by: this.address, + closed_by_role: 'admin', + }; + await this.put(key, updated); + console.log('mayhem closeRoom', updated); + return { + ok: true, + op: 'closeRoom', + room_id: updated.room_id, + sidechannel: updated.sidechannel, + tombstoned_serves: updated.tombstoned_serves, + }; + } + + async setPrice() { + const adminError = await this.requireAdmin(); + if (adminError) return adminError; + if (!this.isSafeKeyPart(this.value.enclave_id)) return new Error('Invalid enclave id.'); + + const enclave = await this.get(`enclave/${this.value.enclave_id}`); + if (!enclave) return new Error('Enclave not found.'); + if (enclave.status === 'retired') return new Error('Enclave is retired.'); + + const modelRef = await this.get(`modelref/${enclave.model_id}`); + if (!modelRef) return new Error('Model reference not found.'); + const enclaveClass = this.modelClassFor(enclave); + const enclaveClassError = this.validateModelClass(enclaveClass, 'Enclave model_class'); + if (enclaveClassError) return enclaveClassError; + const modelRefClass = this.modelClassFor(modelRef); + const modelRefClassError = this.validateModelClass(modelRefClass, 'Model reference model_class'); + if (modelRefClassError) return modelRefClassError; + if (modelRefClass !== enclaveClass) { + return new Error('Model reference model_class must match enclave model_class.'); + } + + const rateError = this.validateRateMap(this.value.rate_map, enclaveClass, 'Enclave price rate_map', { + allowZeroPrice: true, + }); + if (rateError) return rateError; + const priceRateMap = this.normalizeRateMap(this.value.rate_map); + const modalityRateError = this.validateEnclaveModalityRateMap(enclave, priceRateMap); + if (modalityRateError) return modalityRateError; + const perReqAu = this.normalizeAu(this.value.per_req_au, 'Enclave price per_req_au'); + if (perReqAu instanceof Error) return perReqAu; + const minSessionAu = this.normalizeAu(this.value.min_session_au, 'Enclave price min_session_au'); + if (minSessionAu instanceof Error) return minSessionAu; + const params = await this.activeParamsAt(this.value.effective_at, [ + 'price_min_bps', + 'price_max_bps', + 'price_rate_limit_seconds', + ]); + const boundsError = this.validateRateMapBounds(priceRateMap, modelRef.rate_map, params); + if (boundsError) return boundsError; + + const ctxMeta = await this.priceCtxMetaForEnclave(enclave, this.value.ctx_bracket, this.value.effective_at, 'Enclave price'); + if (ctxMeta instanceof Error) return ctxMeta; + const key = this.priceScheduleKey(this.value.enclave_id, ctxMeta?.ctx_bracket ?? null); + const schedule = await this.priceSchedule(key, enclave, ctxMeta); + const latest = this.priceLatestEntry(schedule); + const latestSeed = this.priceLatestSeedEntry(schedule); + if ( + latestSeed && + this.value.effective_at - latestSeed.effective_at < params.price_rate_limit_seconds + ) { + return new Error('Price seed changes are limited by price_rate_limit_seconds.'); + } + + const record = { + enclave_id: this.value.enclave_id, + model_id: enclave.model_id, + denom: PRICE_DENOMINATION, + ver: latest ? latest.ver + 1 : 1, + rate_map: priceRateMap, + per_req_au: perReqAu, + min_session_au: minSessionAu, + effective_at: this.value.effective_at, + effective_from: this.tx, + updated_at: this.tx, + set_by: this.address, + set_by_role: 'admin', + ...(ctxMeta ? { + ctx_bracket: ctxMeta.ctx_bracket, + ctx_bracket_table_ver: ctxMeta.ctx_bracket_table_ver, + } : {}), + }; + + const updated = { + enclave_id: this.value.enclave_id, + model_id: enclave.model_id, + denom: PRICE_DENOMINATION, + ...(ctxMeta ? { + ctx_bracket: ctxMeta.ctx_bracket, + ctx_bracket_table_ver: ctxMeta.ctx_bracket_table_ver, + } : {}), + current: schedule.current, + pending: schedule.pending, + }; + if (!updated.current) { + updated.current = record; + } else { + if (updated.pending && updated.pending.effective_at <= this.value.effective_at) { + updated.current = updated.pending; + updated.pending = null; + } + if (updated.pending) return new Error('Pending price change already scheduled.'); + updated.pending = record; + } + + await this.put(key, updated); + await this.put(this.priceRecordKey(this.value.enclave_id, record.ver, ctxMeta?.ctx_bracket ?? null), record); + console.log('mayhem setPrice', { schedule: updated, record }); + return { + ok: true, + op: 'setPrice', + enclave_id: record.enclave_id, + ver: record.ver, + }; + } + + async readPrice() { + if (!this.isSafeKeyPart(this.value.enclave_id)) return new Error('Invalid enclave id.'); + const enclave = await this.get(`enclave/${this.value.enclave_id}`); + if (!enclave) return new Error('Enclave not found.'); + const ctxMeta = await this.priceCtxMetaForEnclave(enclave, this.value.ctx_bracket, this.value.at, 'Enclave price'); + if (ctxMeta instanceof Error) return ctxMeta; + const key = this.priceScheduleKey(this.value.enclave_id, ctxMeta?.ctx_bracket ?? null); + const schedule = await this.get(key); + if (!schedule) { + return { + ok: true, + op: 'readPrice', + enclave_id: this.value.enclave_id, + at: this.value.at, + ...(ctxMeta ? { + ctx_bracket: ctxMeta.ctx_bracket, + ctx_bracket_table_ver: ctxMeta.ctx_bracket_table_ver, + } : {}), + price: null, + }; + } + + const price = this.priceActiveEntry(schedule, this.value.at); + console.log('mayhem readPrice', { enclave_id: this.value.enclave_id, at: this.value.at, price }); + return { + ok: true, + op: 'readPrice', + enclave_id: this.value.enclave_id, + at: this.value.at, + ...(ctxMeta ? { + ctx_bracket: ctxMeta.ctx_bracket, + ctx_bracket_table_ver: ctxMeta.ctx_bracket_table_ver, + } : {}), + price, + }; + } + + async recordReputationEvent() { + const adminError = await this.requireAdmin(); + if (adminError) return adminError; + + const validationError = this.validateReputationEvent(this.value); + if (validationError) return validationError; + + const provider = await this.get(`prov/${this.value.provider}`); + if (!provider) return new Error('Provider not found.'); + + const record = await this.appendReputationEvent({ + provider: this.value.provider, + event_id: this.value.event_id, + kind: this.value.kind, + epoch: this.value.epoch, + at: this.value.at, + paid_au: this.value.paid_au !== undefined + ? this.normalizeAu(this.value.paid_au, 'reputation paid amount') + : null, + max_spend_au: this.value.max_spend_au !== undefined + ? this.normalizeAu(this.value.max_spend_au, 'reputation max spend') + : null, + evidence_hash: this.value.evidence_hash ?? null, + }); + if (record instanceof Error) return record; + + let slash = null; + if (this.value.kind === 'dispute_lost') { + const params = await this.activeParamsAt(this.value.at, ['dispute_lost_slash_bps']); + slash = await this.applyProviderSlash({ + providerId: this.value.provider, + source: 'dispute', + reason: 'dispute_lost', + evidenceHash: this.value.evidence_hash ?? null, + epoch: this.value.epoch, + at: this.value.at, + slashBps: params.dispute_lost_slash_bps, + beneficiary: this.value.beneficiary ?? null, + enclaveId: this.value.enclave_id ?? null, + banProvider: false, + tombstoneEnclave: false, + }); + if (slash instanceof Error) return slash; + } + + console.log('mayhem recordReputationEvent', record); + return { + ok: true, + op: 'recordReputationEvent', + provider: this.value.provider, + event_id: this.value.event_id, + head: record.head, + slash, + }; + } + + async anchorReputation() { + const adminError = await this.requireAdmin(); + if (adminError) return adminError; + + const validationError = this.validateReputationAnchor(this.value); + if (validationError) return validationError; + + const providerKey = `prov/${this.value.provider}`; + const provider = await this.get(providerKey); + if (!provider) return new Error('Provider not found.'); + + const head = await this.get(`ev/rep/head/${this.value.provider}`); + if (!head || head.head !== this.value.events_head) { + return new Error('Reputation events head mismatch.'); + } + const fold = await this.get(`ev/rep/fold/${this.value.provider}`); + if ( + !fold || + fold.events_head !== head.head || + fold.event_count !== head.count + ) { + return new Error('Reputation fold state mismatch.'); + } + if (this.value.epoch < fold.max_epoch) { + return new Error('Reputation anchor epoch precedes an event.'); + } + const expected = this.reputationFoldAt(fold, this.value.folded_at); + if (expected instanceof Error) return expected; + if ( + this.value.r_bps !== expected.r_bps || + this.value.raw_milli !== expected.raw_milli || + this.value.successful_sessions !== expected.successful_sessions || + (this.value.provenance_violation === true) !== expected.provenance_violation + ) { + return new Error('Reputation anchor does not match the contract fold.'); + } + + const params = await this.activeParamsAt(this.value.folded_at, [ + 'probation_successful_sessions', + 'probation_seconds', + 'probation_max_concurrent_sessions_per_user', + 'probation_price_max_bps', + 'probation_weight_bps', + ]); + const sinceSeconds = provider.probation?.since_seconds ?? 0; + const probationActive = ( + this.value.successful_sessions < params.probation_successful_sessions || + this.value.folded_at - sinceSeconds < params.probation_seconds + ); + const probation = { + active: probationActive, + since: provider.probation?.since ?? provider.registered_at, + since_seconds: sinceSeconds, + successful_sessions: this.value.successful_sessions, + required_successful_sessions: params.probation_successful_sessions, + required_seconds: params.probation_seconds, + caps: { + max_concurrent_sessions_per_user: params.probation_max_concurrent_sessions_per_user, + price_max_bps: params.probation_price_max_bps, + weight_bps: params.probation_weight_bps, + }, + }; + const snapshot = { + provider: this.value.provider, + r: this.value.r_bps / 10_000, + r_bps: this.value.r_bps, + raw: this.value.raw_milli / 1_000, + raw_milli: this.value.raw_milli, + events_head: this.value.events_head, + epoch: this.value.epoch, + folded_at: this.value.folded_at, + updated_at: this.tx, + probation, + provenance_violation: this.value.provenance_violation === true, + }; + const updatedProvider = { + ...provider, + probation: { + ...(provider.probation ?? {}), + successful_sessions: this.value.successful_sessions, + since_seconds: sinceSeconds, + }, + updated_at: this.tx, + }; + + await this.put(`rep/${this.value.provider}`, snapshot); + await this.put(providerKey, updatedProvider); + console.log('mayhem anchorReputation', snapshot); + return { + ok: true, + op: 'anchorReputation', + provider: this.value.provider, + epoch: this.value.epoch, + events_head: this.value.events_head, + }; + } + + async auditorRegister() { + const target = this.value.auditor ?? this.address; + if (!this.isSafeKeyPart(target)) return new Error('Invalid auditor id.'); + + const consentError = await this.requireConsent(target); + if (consentError) return consentError; + + const provider = await this.get(`prov/${target}`); + if (provider) return new Error('Provider keys cannot register as auditors.'); + + const adminRegistersOther = target !== this.address; + if (adminRegistersOther) { + const adminError = await this.requireAdmin(); + if (adminError) return adminError; + } else { + const eligibilityError = await this.requireAuditorEligibility( + target, + this.value.registered_at_seconds ?? 0 + ); + if (eligibilityError) return eligibilityError; + } + + const key = `auditor/${target}`; + const existing = await this.get(key); + if (existing?.status === 'active') return new Error('Auditor already registered.'); + if (existing?.status === 'slashed') return new Error('Auditor is slashed.'); + + const record = { + auditor: target, + status: 'active', + registered_at: this.tx, + registered_at_seconds: this.value.registered_at_seconds ?? 0, + accredited_by: adminRegistersOther ? this.address : null, + successful_probes: existing?.successful_probes ?? 0, + submitted_probes: existing?.submitted_probes ?? 0, + false_reports: existing?.false_reports ?? 0, + updated_at: this.tx, + }; + await this.put(key, record); + console.log('mayhem auditorRegister', record); + return { ok: true, op: 'auditorRegister', auditor: target }; + } + + async auditorSlash() { + const adminError = await this.requireAdmin(); + if (adminError) return adminError; + if (this.value.op !== 'auditor_slash') return new Error('Invalid auditor slash op.'); + if (!this.isHexBytes(this.value.auditor, 32)) return new Error('Invalid auditor slash target.'); + if (!this.isHexBytes(this.value.provider, 32)) return new Error('Invalid auditor slash provider.'); + if (!this.isSafeKeyPart(this.value.probe_id)) return new Error('Invalid auditor slash probe id.'); + if (!AUDITOR_SLASH_REASONS.has(this.value.reason)) return new Error('Unsupported auditor slash reason.'); + if (!this.isHexBytes(this.value.evidence_hash, 32)) return new Error('Invalid auditor slash evidence hash.'); + + const auditorKey = `auditor/${this.value.auditor}`; + const auditor = await this.get(auditorKey); + if (!auditor) return new Error('Auditor not found.'); + if (auditor.status === 'slashed') return new Error('Auditor is already slashed.'); + if (auditor.status !== 'active') return new Error('Auditor is not active.'); + const probeKey = `ev/probe/${this.value.probe_id}`; + const probe = await this.get(probeKey); + if (!probe || probe.probe_kind !== 'canary') return new Error('Canary probe not found.'); + if ( + probe.auditor !== this.value.auditor || + probe.provider !== this.value.provider || + probe.epoch !== this.value.epoch || + probe.pass !== true + ) { + return new Error('Auditor slash evidence does not match the passing probe.'); + } + if (probe.status === 'slashed') return new Error('Canary probe is already slashed.'); + const slashKey = `ev/auditor-slash/${this.value.auditor}/${this.value.probe_id}`; + if ((await this.get(slashKey)) !== null) return new Error('Auditor slash already recorded.'); + + const passKey = `probe/pass/${this.value.provider}/${this.value.epoch}`; + const passRecord = await this.get(passKey); + if (!passRecord || !Array.isArray(passRecord.probes)) { + return new Error('Canary pass record not found.'); + } + const remaining = passRecord.probes.filter((entry) => !( + entry.auditor === this.value.auditor && entry.probe_id === this.value.probe_id + )); + if (remaining.length === passRecord.probes.length) { + return new Error('Canary pass record does not contain the slashed probe.'); + } + const falseReports = this.safeAddCount(auditor.false_reports ?? 0, 1, 'auditor false report count'); + if (falseReports instanceof Error) return falseReports; + const slash = { + auditor: this.value.auditor, + provider: this.value.provider, + probe_id: this.value.probe_id, + epoch: this.value.epoch, + reason: this.value.reason, + evidence_hash: this.value.evidence_hash.toLowerCase(), + reward_forfeited_au: probe.probe_reward_au ?? ZERO_AU, + slashed_at_seconds: this.value.at, + slashed_at: this.tx, + slashed_by: this.address, + slashed_by_role: 'admin', + }; + await this.put(passKey, { + ...passRecord, + pass_count: remaining.length, + auditors: remaining.map((entry) => entry.auditor), + probes: remaining, + last_slash_evidence_hash: slash.evidence_hash, + updated_at: this.tx, + }); + await this.put(probeKey, { + ...probe, + status: 'slashed', + collusion_evidence_hash: slash.evidence_hash, + slashed_at: this.tx, + }); + await this.put(auditorKey, { + ...auditor, + status: 'slashed', + false_reports: falseReports, + slash_reason: this.value.reason, + slash_evidence_hash: slash.evidence_hash, + slashed_at: this.tx, + slashed_by: this.address, + updated_at: this.tx, + }); + await this.put(slashKey, slash); + console.log('mayhem auditorSlash', slash); + return { + ok: true, + op: 'auditorSlash', + auditor: this.value.auditor, + probe_id: this.value.probe_id, + evidence_hash: slash.evidence_hash, + }; + } + + async probeResult() { + const auditor = await this.get(`auditor/${this.address}`); + if (!auditor || auditor.status !== 'active') return new Error('Auditor registration required.'); + if ((await this.get(`prov/${this.address}`)) !== null) { + return new Error('Provider keys cannot submit auditor probes.'); + } + + const validationError = this.validateProbeResult(this.value); + if (validationError) return validationError; + if ((await this.get(`ev/probe/${this.value.probe_id}`)) !== null) { + return new Error('Probe result already recorded.'); + } + + const providerKey = `prov/${this.value.provider}`; + const provider = await this.get(providerKey); + if (!provider) return new Error('Provider not found.'); + + if (this.value.probe_kind === 'canary') { + const canaryBindingError = await this.requireBoundCanaryProbe(this.value, this.address); + if (canaryBindingError) return canaryBindingError; + } + + const params = await this.activeParamsAt(this.value.at, [ + 'canary_match_min_bps', + 'probe_reward_au', + 'uptime_tick_seconds', + 'fraud_slash_bps', + ]); + const pass = this.probePass(this.value, params); + if (this.value.pass !== undefined && this.value.pass !== pass) { + return new Error('Probe pass flag does not match contract threshold.'); + } + if (this.value.probe_kind === 'canary' && pass) { + const passRecord = await this.get(`probe/pass/${this.value.provider}/${this.value.epoch}`); + if (passRecord?.auditors?.includes(this.address)) { + return new Error('Auditor already supplied a canary pass for this provider epoch.'); + } + } + + const reputationEvent = await this.appendReputationEvent({ + provider: this.value.provider, + event_id: `probe-${this.value.probe_id}`, + kind: this.value.probe_kind === 'uptime_tick' + ? 'uptime_tick' + : pass + ? 'probe_ok' + : 'probe_fail', + epoch: this.value.epoch, + at: this.value.at, + paid_au: null, + max_spend_au: null, + evidence_hash: this.value.evidence_hash ?? null, + }); + if (reputationEvent instanceof Error) return reputationEvent; + + let provenanceViolation = false; + let slash = null; + if (this.value.probe_kind === 'canary' && !pass) { + provenanceViolation = true; + const violationEvent = await this.appendReputationEvent({ + provider: this.value.provider, + event_id: `probe-${this.value.probe_id}-violation`, + kind: 'provenance_violation', + epoch: this.value.epoch, + at: this.value.at, + paid_au: null, + max_spend_au: null, + evidence_hash: this.value.evidence_hash ?? null, + }); + if (violationEvent instanceof Error) return violationEvent; + + slash = await this.applyProviderSlash({ + providerId: this.value.provider, + source: 'probe', + reason: 'canary_mismatch', + evidenceHash: this.value.evidence_hash ?? null, + epoch: this.value.epoch, + at: this.value.at, + slashBps: params.fraud_slash_bps, + beneficiary: this.address, + enclaveId: this.value.enclave_id ?? null, + probeId: this.value.probe_id, + banProvider: true, + tombstoneEnclave: true, + }); + if (slash instanceof Error) return slash; + } + + const record = { + probe_id: this.value.probe_id, + probe_kind: this.value.probe_kind, + auditor: this.address, + provider: this.value.provider, + enclave_id: this.value.enclave_id ?? null, + epoch: this.value.epoch, + at: this.value.at, + canary_set: this.value.canary_set ?? null, + canary_prompt_id: this.value.canary_prompt_id ?? null, + challenge_epoch: this.value.challenge_epoch ?? null, + challenge_apply_hash: this.value.challenge_apply_hash ?? null, + challenge_seed: this.value.challenge_seed ?? null, + verification_method: this.value.verification_method ?? null, + binary_hash: this.value.binary_hash ?? null, + match_bps: this.value.match_bps ?? null, + pass, + session_receipt_hash: this.value.session_receipt_hash ?? null, + evidence_hash: this.value.evidence_hash ?? null, + auditor_sig: this.value.auditor_sig ?? null, + reputation_head: (await this.get(`ev/rep/head/${this.value.provider}`))?.head ?? null, + provenance_violation: provenanceViolation, + probe_reward_au: params.probe_reward_au, + slash, + recorded_at: this.tx, + }; + await this.put(`ev/probe/${this.value.probe_id}`, record); + let probePassRecord = null; + if (this.value.probe_kind === 'canary' && pass) { + probePassRecord = await this.recordCanaryProbePass(this.value, this.address); + if (probePassRecord instanceof Error) return probePassRecord; + } + await this.put(`auditor/${this.address}`, { + ...auditor, + submitted_probes: (auditor.submitted_probes ?? 0) + 1, + successful_probes: (auditor.successful_probes ?? 0) + (pass ? 1 : 0), + updated_at: this.tx, + }); + console.log('mayhem probeResult', record); + return { + ok: true, + op: 'probeResult', + probe_id: this.value.probe_id, + provider: this.value.provider, + pass, + ...(probePassRecord ? { probe_pass_record: probePassRecord } : {}), + provenance_violation: provenanceViolation, + }; + } + + async epochApply() { + if (CONTRACT_VERSION >= 17) { + return new Error('Aggregate epoch_apply is disabled; use receipt-bound apply_targeted_epoch.'); + } + const adminError = await this.requireAdmin(); + if (adminError) return adminError; + + const shapeError = this.validateEpochApplyShape(this.value); + if (shapeError) return shapeError; + const roots = this.value.roots === undefined ? null : this.normalizeEpochRoots(this.value.roots); + if (roots instanceof Error) return roots; + const totals = this.value.totals === undefined ? null : this.normalizeEpochTotals(this.value.totals); + if (totals instanceof Error) return totals; + if ((roots && !totals) || (!roots && totals)) { + return new Error('Epoch apply roots and totals must be provided together.'); + } + const page = this.epochApplyPage(this.value); + if (page instanceof Error) return page; + const lastPage = this.epochApplyLastPage(this.value); + if (lastPage instanceof Error) return lastPage; + const pagedApply = hasOwn(this.value, 'page'); + if (pagedApply && (roots || totals)) { + return new Error('Paged epochApply pages must omit aggregate roots and totals.'); + } + + const params = await this.activeParamsAt(this.value.at, [ + 'epoch_seconds', + 'fee_bps', + 'max_apply_batch', + 'max_market_usage_entries', + 'probation_successful_sessions', + 'new_provider_holdback_epochs', + 'holdback_epochs', + 'challenge_epochs', + 'probation_successful_sessions', + 'new_provider_holdback_epochs', + 'canary_probe_holdback_bps', + 'canary_probe_release_min_passes', + ]); + if (this.value.debits.length + this.value.earnings.length > params.max_apply_batch) { + return new Error('Epoch apply batch exceeds max_apply_batch.'); + } + if ((this.value.market_usage?.length ?? 0) > params.max_market_usage_entries) { + return new Error('Epoch market usage batch exceeds max_market_usage_entries.'); + } + + const debitMap = this.aggregateRailLedgerEntries(this.value.debits, 'user', 'au', 'debit'); + if (debitMap instanceof Error) return debitMap; + const grossEarningMap = this.aggregateRailLedgerEntries( + this.value.earnings, + 'provider', + 'gross_au', + 'earning' + ); + if (grossEarningMap instanceof Error) return grossEarningMap; + + const debitTotal = this.sumRailAu(debitMap, 'au'); + if (debitTotal instanceof Error) return debitTotal; + const grossTotal = this.sumRailAu(grossEarningMap, 'gross_au'); + if (grossTotal instanceof Error) return grossTotal; + const debitRailTotals = this.railTotals(debitMap, 'au'); + if (debitRailTotals instanceof Error) return debitRailTotals; + const grossRailTotals = this.railTotals(grossEarningMap, 'gross_au'); + if (grossRailTotals instanceof Error) return grossRailTotals; + const railTotalError = this.assertMatchingRailTotals(debitRailTotals, grossRailTotals); + if (railTotalError) return railTotalError; + const marketUsageProvided = hasOwn(this.value, 'market_usage'); + const marketUsageMap = marketUsageProvided + ? this.aggregateMarketUsageEntries(this.value.market_usage) + : new Map(); + if (marketUsageMap instanceof Error) return marketUsageMap; + const marketUsageTotal = this.sumMarketDemandAu(marketUsageMap); + if (marketUsageTotal instanceof Error) return marketUsageTotal; + const applyState = await this.epochApplyStateRecord(); + const previousApplyHash = page === 0 ? null : applyState.last_apply_hash; + const normalized = { + epoch: this.value.epoch, + page, + last_page: lastPage, + at: this.value.at, + epoch_seconds: params.epoch_seconds, + fee_bps: params.fee_bps, + tap_burn_bps: TAP_BURN_BPS, + debits: this.mapRailEntriesForHash(debitMap, 'user', 'au'), + earnings: this.mapRailEntriesForHash(grossEarningMap, 'provider', 'gross_au'), + roots, + totals, + }; + if (previousApplyHash !== null) { + normalized.previous_apply_hash = previousApplyHash; + } + if (marketUsageProvided) { + normalized.market_usage = this.mapMarketUsageEntriesForHash(marketUsageMap); + } + const applyHash = await this.epochApplyHash(normalized); + if (this.isIdempotentEpochApplyPage(applyState, this.value.epoch, page, lastPage, applyHash)) { + const result = { + ok: true, + op: 'epochApply', + epoch: this.value.epoch, + idempotent: true, + debited_au: ZERO_AU, + earned_au: ZERO_AU, + fee_au: ZERO_AU, + burn_au: ZERO_AU, + }; + if (pagedApply) { + result.page = page; + result.last_page = lastPage; + } + return result; + } + const pageOrderError = this.validateEpochApplyPageOrder(applyState, this.value.epoch, page); + if (pageOrderError) return pageOrderError; + const reservationDebitTotals = this.nextReservationDebitTotals(applyState, this.value.epoch, page, debitMap); + if (reservationDebitTotals instanceof Error) return reservationDebitTotals; + const reservationError = await this.validateEpochDebitReservations(this.value.epoch, reservationDebitTotals); + if (reservationError) return reservationError; + + const balances = new Map(); + for (const debit of debitMap.values()) { + const { rail, user, au: debitAu } = debit; + const balance = await this.balanceRecord(user, rail); + if (balance instanceof Error) return balance; + const balanceError = this.guardianValidateBalanceRecord(balance, user, rail); + if (balanceError) return balanceError; + if (this.compareAu(balance.au, debitAu) < 0) return new Error('Insufficient credit balance.'); + const nextBalanceAu = this.safeSubAu(balance.au, debitAu); + if (nextBalanceAu instanceof Error) return nextBalanceAu; + balances.set(stableJson([rail, user]), { + ...balance, + rail, + au: nextBalanceAu, + updated_epoch: this.value.epoch, + updated_at: this.tx, + }); + } + + const earningDeltas = new Map(); + const feeDeltaByRail = new Map(PROVIDER_ACCEPTED_RAIL_ORDER.map((rail) => [rail, ZERO_AU])); + const burnDeltaByRail = new Map(PROVIDER_ACCEPTED_RAIL_ORDER.map((rail) => [rail, ZERO_AU])); + let feeDeltaTotalAu = ZERO_AU; + let burnDeltaTotalAu = ZERO_AU; + for (const earning of grossEarningMap.values()) { + const { rail, provider, gross_au: grossAu } = earning; + const providerRecord = await this.get(`prov/${provider}`); + if (!providerRecord) return new Error('Provider not found.'); + if (!Array.isArray(providerRecord.accepted_rails)) { + return new Error('Provider accepted rails are not set.'); + } + if (!providerRecord.accepted_rails.includes(rail)) { + return new Error('Provider does not accept payment rail.'); + } + + const feeAu = this.safeMulDivAu(grossAu, params.fee_bps, 10_000); + if (feeAu instanceof Error) return feeAu; + const burnAu = rail === 'tap' + ? this.safeMulDivAu(grossAu, TAP_BURN_BPS, 10_000) + : ZERO_AU; + if (burnAu instanceof Error) return burnAu; + const afterFeeAu = this.safeSubAu(grossAu, feeAu); + if (afterFeeAu instanceof Error) return afterFeeAu; + const providerAu = this.safeSubAu(afterFeeAu, burnAu); + if (providerAu instanceof Error) return providerAu; + feeDeltaTotalAu = this.safeAddAu(feeDeltaTotalAu, feeAu); + if (feeDeltaTotalAu instanceof Error) return feeDeltaTotalAu; + const nextRailFee = this.safeAddAu(feeDeltaByRail.get(rail) ?? ZERO_AU, feeAu); + if (nextRailFee instanceof Error) return nextRailFee; + feeDeltaByRail.set(rail, nextRailFee); + burnDeltaTotalAu = this.safeAddAu(burnDeltaTotalAu, burnAu); + if (burnDeltaTotalAu instanceof Error) return burnDeltaTotalAu; + const nextRailBurn = this.safeAddAu(burnDeltaByRail.get(rail) ?? ZERO_AU, burnAu); + if (nextRailBurn instanceof Error) return nextRailBurn; + burnDeltaByRail.set(rail, nextRailBurn); + const key = stableJson([rail, provider]); + const current = earningDeltas.get(key) ?? { rail, provider, provider_record: providerRecord, au: ZERO_AU }; + const next = this.safeAddAu(current.au, providerAu); + if (next instanceof Error) return next; + earningDeltas.set(key, { ...current, au: next }); + } + + const earnings = new Map(); + let earnCumTotal = ZERO_AU; + for (const delta of earningDeltas.values()) { + const { rail, provider, provider_record: providerRecord, au: deltaAu } = delta; + const current = await this.earningRecord(provider, rail); + if (current instanceof Error) return current; + const currentError = this.guardianValidateEarningRecord(current, provider, rail); + if (currentError) return currentError; + const probeGate = await this.probeGateForEarning(provider, current, params); + if (probeGate instanceof Error) return probeGate; + const lockedEarningEpochs = this.providerLockedEarningEpochs(providerRecord, params); + if (lockedEarningEpochs instanceof Error) return lockedEarningEpochs; + const disputeGate = await this.providerHasOpenDispute(provider); + if (disputeGate instanceof Error) return disputeGate; + const refreshed = this.refreshEarningHoldback( + current, + this.value.epoch, + lockedEarningEpochs, + probeGate, + disputeGate + ); + if (refreshed instanceof Error) return refreshed; + const totalAu = this.safeAddAu(refreshed.total_au, deltaAu); + if (totalAu instanceof Error) return totalAu; + const heldAu = this.safeAddAu(refreshed.held_au, deltaAu); + if (heldAu instanceof Error) return heldAu; + const holdbacks = this.appendHoldbackBucket( + refreshed.holdbacks, + this.value.epoch, + deltaAu, + lockedEarningEpochs + ); + if (holdbacks instanceof Error) return holdbacks; + earnings.set(stableJson([rail, provider]), { + ...refreshed, + rail, + total_au: totalAu, + held_au: heldAu, + holdbacks, + updated_epoch: this.value.epoch, + updated_at: this.tx, + }); + earnCumTotal = this.safeAddAu(earnCumTotal, totalAu); + if (earnCumTotal instanceof Error) return earnCumTotal; + } + + const feeRecords = new Map(); + const nextFeeRecords = new Map(); + const burnRecords = new Map(); + const nextBurnRecords = new Map(); + let nextFeeCumTotal = ZERO_AU; + let nextBurnCumTotal = ZERO_AU; + const touchedRails = new Set([ + ...Array.from(debitRailTotals.entries()).filter(([, au]) => this.compareAu(au, ZERO_AU) > 0).map(([rail]) => rail), + ...Array.from(grossRailTotals.entries()).filter(([, au]) => this.compareAu(au, ZERO_AU) > 0).map(([rail]) => rail), + ]); + for (const rail of PROVIDER_ACCEPTED_RAIL_ORDER) { + const fee = await this.feeCumRecord(rail); + if (fee instanceof Error) return fee; + const feeError = this.guardianValidateFeeRecord(fee, rail); + if (feeError) return feeError; + feeRecords.set(rail, fee); + const feeDeltaAu = feeDeltaByRail.get(rail) ?? ZERO_AU; + const nextFeeCum = this.safeAddAu(fee.cum_au, feeDeltaAu); + if (nextFeeCum instanceof Error) return nextFeeCum; + // settled_cum_au must advance with this epoch's settled rail debits in + // the SAME record that carries the new cum_au, otherwise + // guardianValidateFeeRecord rejects every fee-bearing epoch apply + // (settled < cum). This mirrors next_settled_cum_by_rail in + // guardianValidateEpochTotals, which is also what gets persisted. + const nextFeeSettledCum = this.safeAddAu( + fee.settled_cum_au ?? fee.cum_au, + debitRailTotals.get(rail) ?? ZERO_AU + ); + if (nextFeeSettledCum instanceof Error) return nextFeeSettledCum; + const nextFee = touchedRails.has(rail) + ? { + ...fee, + cum_au: nextFeeCum, + settled_cum_au: nextFeeSettledCum, + updated_epoch: this.value.epoch, + updated_at: this.tx, + last_apply_hash: applyHash, + last_fee_bps: params.fee_bps, + } + : fee; + nextFeeRecords.set(rail, nextFee); + nextFeeCumTotal = this.safeAddAu(nextFeeCumTotal, nextFee.cum_au); + if (nextFeeCumTotal instanceof Error) return nextFeeCumTotal; + + const burn = await this.burnCumRecord(rail); + if (burn instanceof Error) return burn; + const burnError = this.guardianValidateBurnRecord(burn, rail); + if (burnError) return burnError; + burnRecords.set(rail, burn); + const burnDeltaAu = burnDeltaByRail.get(rail) ?? ZERO_AU; + const nextBurnCum = this.safeAddAu(burn.cum_au, burnDeltaAu); + if (nextBurnCum instanceof Error) return nextBurnCum; + const nextBurn = touchedRails.has(rail) + ? { + ...burn, + cum_au: nextBurnCum, + updated_epoch: this.value.epoch, + updated_at: this.tx, + last_apply_hash: applyHash, + burn_bps: rail === 'tap' ? TAP_BURN_BPS : 0, + } + : burn; + nextBurnRecords.set(rail, nextBurn); + nextBurnCumTotal = this.safeAddAu(nextBurnCumTotal, nextBurn.cum_au); + if (nextBurnCumTotal instanceof Error) return nextBurnCumTotal; + } + + const grossAfterFees = this.safeSubAu(grossTotal, feeDeltaTotalAu); + if (grossAfterFees instanceof Error) return grossAfterFees; + const providerDeltaTotal = this.safeSubAu(grossAfterFees, burnDeltaTotalAu); + if (providerDeltaTotal instanceof Error) return providerDeltaTotal; + const guardian = this.guardianCheckEpochApply({ + epoch: this.value.epoch, + page, + applyState, + feeRecords, + burnRecords, + debitTotal, + debitRailTotals, + feeDeltaByRail, + burnDeltaByRail, + providerDeltaTotal, + nextFeeRecords, + nextBurnRecords, + balances, + earnings, + }); + if (guardian instanceof Error) return guardian; + + let marketPriceUpdates = []; + if (marketUsageProvided) { + marketPriceUpdates = await this.computeMarketPriceUpdates(marketUsageMap, { + epoch: this.value.epoch, + at: this.value.at, + epochSeconds: params.epoch_seconds, + }); + if (marketPriceUpdates instanceof Error) return marketPriceUpdates; + } + const priceDerivations = await this.priceDerivationsFromMarketUpdates(marketPriceUpdates, { + epoch: this.value.epoch, + at: this.value.at, + epochSeconds: params.epoch_seconds, + usageRoot: roots?.use ?? null, + }); + if (priceDerivations instanceof Error) return priceDerivations; + + if (totals) { + const totalsError = await this.validateEpochApplyTotals({ + epoch: this.value.epoch, + roots, + totals, + debitTotal, + feeDeltaAu: feeDeltaTotalAu, + nextFeeCum: nextFeeCumTotal, + burnDeltaAu: burnDeltaTotalAu, + nextBurnCum: nextBurnCumTotal, + providerCount: grossEarningMap.size, + earnCumTotal, + epochSeconds: params.epoch_seconds, + priceDerivations, + }); + if (totalsError) return totalsError; + } + + for (const balance of this.sortedRailRecords(balances, 'user')) { + await this.put(this.balanceKey(balance.user, balance.rail), balance); + } + for (const earning of this.sortedRailRecords(earnings, 'provider')) { + await this.put(this.earningKey(earning.provider, earning.rail), earning); + } + for (const rail of PROVIDER_ACCEPTED_RAIL_ORDER.filter((rail) => touchedRails.has(rail))) { + const feeRecord = { + ...nextFeeRecords.get(rail), + settled_cum_au: guardian.next_settled_cum_by_rail.get(rail), + }; + await this.put(this.feeCumKey(rail), feeRecord); + await this.put(this.burnCumKey(rail), nextBurnRecords.get(rail)); + } + const nextApplyState = this.nextEpochApplyState({ + applyState, + epoch: this.value.epoch, + page, + lastPage, + applyHash, + epochSeconds: params.epoch_seconds, + settledAt: this.value.at, + reservationDebitTotals, + }); + const previousChallengeError = await this.rememberCanaryChallengeAnchor(applyState); + if (previousChallengeError) return previousChallengeError; + await this.put('epoch/apply/state', nextApplyState); + if (lastPage) { + const anchorError = await this.rememberEpochApplyAnchor(nextApplyState); + if (anchorError) return anchorError; + const challengeError = await this.rememberCanaryChallengeAnchor(nextApplyState); + if (challengeError) return challengeError; + } + if (totals) { + await this.writeEpochEvidenceRoots({ + epoch: this.value.epoch, + at: this.value.at, + epoch_seconds: params.epoch_seconds, + roots, + totals, + feeDeltaAu: feeDeltaTotalAu, + feeCumAu: nextFeeCumTotal, + burnDeltaAu: burnDeltaTotalAu, + burnCumAu: nextBurnCumTotal, + priceDerivations, + }); + } else if (priceDerivations.length > 0) { + await this.writePriceDerivationEvidence({ + epoch: this.value.epoch, + at: this.value.at, + epoch_seconds: params.epoch_seconds, + root: await this.priceDerivationRoot(priceDerivations), + count: priceDerivations.length, + derivations: priceDerivations, + }); + } + const activityIndexError = await this.writeActivityMarketIndex(marketPriceUpdates); + if (activityIndexError instanceof Error) return activityIndexError; + for (const update of marketPriceUpdates) { + await this.put(update.schedule_key, update.schedule); + await this.put(update.record_key, update.record); + } + + const result = { + ok: true, + op: 'epochApply', + epoch: this.value.epoch, + idempotent: false, + debited_au: debitTotal, + earned_au: providerDeltaTotal, + fee_au: feeDeltaTotalAu, + burn_au: burnDeltaTotalAu, + rails: Array.from(touchedRails).sort( + (left, right) => PROVIDER_ACCEPTED_RAIL_ORDER.indexOf(left) - PROVIDER_ACCEPTED_RAIL_ORDER.indexOf(right) + ), + }; + if (pagedApply) { + result.page = page; + result.last_page = lastPage; + } + if (marketPriceUpdates.length > 0) { + result.market_prices = marketPriceUpdates.map((update) => ({ + enclave_id: update.enclave_id, + ...(update.ctx_bracket ? { + ctx_bracket: update.ctx_bracket, + ctx_bracket_table_ver: update.ctx_bracket_table_ver, + } : {}), + ver: update.ver, + momentum_bps: update.momentum_bps, + activity_rate: update.activity_rate, + ema_activity_rate: update.ema_activity_rate, + active_supply: update.active_supply, + active_demand_au: update.active_demand_au, + frozen: update.frozen, + derivation_hash: update.derivation_hash, + })); + result.price_root = await this.priceDerivationRoot(priceDerivations); + } + console.log('mayhem epochApply', result); + return result; + } + + async targetedEpochApply(value, revisionBindings, allocations, options = {}) { + const adminError = await this.requireAdmin(); + if (adminError) return adminError; + + const shapeError = this.validateEpochApplyShape(value); + if (shapeError) return shapeError; + const roots = value.roots === undefined ? null : this.normalizeEpochRoots(value.roots); + if (roots instanceof Error) return roots; + const totals = value.totals === undefined ? null : this.normalizeEpochTotals(value.totals); + if (totals instanceof Error) return totals; + if ((roots && !totals) || (!roots && totals)) { + return new Error('Epoch apply roots and totals must be provided together.'); + } + const page = this.epochApplyPage(value); + if (page instanceof Error) return page; + const lastPage = this.epochApplyLastPage(value); + if (lastPage instanceof Error) return lastPage; + const pagedApply = hasOwn(value, 'page'); + if (pagedApply && (roots || totals)) { + return new Error('Paged epochApply pages must omit aggregate roots and totals.'); + } + + const params = await this.activeParamsAt(value.at, [ + 'epoch_seconds', + 'fee_bps', + 'max_apply_batch', + 'max_market_usage_entries', + 'probation_successful_sessions', + 'new_provider_holdback_epochs', + 'holdback_epochs', + 'challenge_epochs', + 'probation_successful_sessions', + 'new_provider_holdback_epochs', + 'canary_probe_holdback_bps', + 'canary_probe_release_min_passes', + ]); + if (value.debits.length + value.earnings.length + allocations.length > params.max_apply_batch) { + return new Error('Epoch apply batch exceeds max_apply_batch.'); + } + if ((value.market_usage?.length ?? 0) > params.max_market_usage_entries) { + return new Error('Epoch market usage batch exceeds max_market_usage_entries.'); + } + + const debitMap = this.aggregateRailLedgerEntries(value.debits, 'user', 'au', 'debit'); + if (debitMap instanceof Error) return debitMap; + const grossEarningMap = this.aggregateRailLedgerEntries( + value.earnings, + 'provider', + 'gross_au', + 'earning' + ); + if (grossEarningMap instanceof Error) return grossEarningMap; + + const debitTotal = this.sumRailAu(debitMap, 'au'); + if (debitTotal instanceof Error) return debitTotal; + const grossTotal = this.sumRailAu(grossEarningMap, 'gross_au'); + if (grossTotal instanceof Error) return grossTotal; + const debitRailTotals = this.railTotals(debitMap, 'au'); + if (debitRailTotals instanceof Error) return debitRailTotals; + const grossRailTotals = this.railTotals(grossEarningMap, 'gross_au'); + if (grossRailTotals instanceof Error) return grossRailTotals; + const railTotalError = this.assertMatchingRailTotals(debitRailTotals, grossRailTotals); + if (railTotalError) return railTotalError; + const marketUsageProvided = hasOwn(value, 'market_usage'); + const marketUsageMap = marketUsageProvided + ? this.aggregateMarketUsageEntries(value.market_usage) + : new Map(); + if (marketUsageMap instanceof Error) return marketUsageMap; + const marketUsageTotal = this.sumMarketDemandAu(marketUsageMap); + if (marketUsageTotal instanceof Error) return marketUsageTotal; + const earningFinals = value.earning_finals ?? []; + + const receiptIndex = this.normalizeReceiptEpochIndexMetadata( + value.receipt_index, + value.epoch + ); + if (receiptIndex instanceof Error) return receiptIndex; + const currentReceiptIndex = this.normalizeReceiptEpochIndexMetadata( + await this.get(this.receiptEpochIndexKey(value.epoch)), + value.epoch + ); + if (currentReceiptIndex instanceof Error) return currentReceiptIndex; + if (stableJson(currentReceiptIndex) !== stableJson(receiptIndex)) { + return new Error('Canonical receipt epoch index changed after the apply snapshot.'); + } + const freezeError = await this.validateFrozenEpoch(value.epoch, value.at, receiptIndex); + if (freezeError) return freezeError; + const epochCommit = options.commitTransition?.record ?? + await this.get(`epoch/commit/${value.epoch}`); + if (!epochCommit || + epochCommit.type !== 'epoch_commit' || + epochCommit.epoch !== value.epoch || + epochCommit.status !== 'provisional' || + epochCommit.commit_hash !== value.epoch_commit_hash) { + return new Error('Matching provisional epoch commit required for targeted apply.'); + } + const boundedReceiptSettlement = + epochCommit.apply_mode === 'targeted_receipt_pages_v1'; + const canonicalPageMarketUsageMap = new Map(); + if (boundedReceiptSettlement) { + if (!Array.isArray(options.canonicalMarketUsage) || + options.canonicalMarketUsage.length === 0) { + return new Error('Bounded receipt canonical market usage is missing.'); + } + let canonicalPageDemandAu = ZERO_AU; + let canonicalPageSessionCount = 0; + for (const usage of options.canonicalMarketUsage) { + const ctxBracket = usage?.ctx_bracket ?? null; + const marketKey = this.priceMarketKey(usage?.enclave_id, ctxBracket); + const demandAu = this.normalizeAu( + usage?.demand_au, + 'bounded receipt canonical market demand', + { allowZero: false } + ); + const providers = Array.isArray(usage?.providers) + ? usage.providers.slice().sort(compareCodepoint) + : []; + if (!usage || + !this.isSafeKeyPart(usage.enclave_id) || + (ctxBracket !== null && !this.isSafeKeyPart(ctxBracket)) || + (usage.ctx_bracket_table_ver !== undefined && + (!Number.isSafeInteger(usage.ctx_bracket_table_ver) || + usage.ctx_bracket_table_ver < 1)) || + demandAu instanceof Error || + !Number.isSafeInteger(usage.session_count) || + usage.session_count < 1 || + providers.length < 1 || + providers.some((provider) => !this.isSafeKeyPart(provider)) || + new Set(providers).size !== providers.length || + stableJson(providers) !== stableJson(usage.providers) || + canonicalPageMarketUsageMap.has(marketKey)) { + return new Error('Bounded receipt canonical market usage is invalid.'); + } + canonicalPageDemandAu = this.safeAddAu(canonicalPageDemandAu, demandAu); + canonicalPageSessionCount = this.safeAddCount( + canonicalPageSessionCount, + usage.session_count, + 'bounded receipt canonical market sessions' + ); + if (canonicalPageDemandAu instanceof Error || + canonicalPageSessionCount instanceof Error) { + return new Error('Bounded receipt canonical market usage overflow.'); + } + canonicalPageMarketUsageMap.set(marketKey, { + enclave_id: usage.enclave_id, + ...(ctxBracket ? { ctx_bracket: ctxBracket } : {}), + ...(usage.ctx_bracket_table_ver ? { + ctx_bracket_table_ver: usage.ctx_bracket_table_ver, + } : {}), + demand_au: demandAu, + settled_usage: usage.settled_usage, + session_count: usage.session_count, + providers, + }); + } + if (this.compareAu(canonicalPageDemandAu, grossTotal) !== 0 || + canonicalPageSessionCount !== allocations.length) { + return new Error('Bounded receipt canonical market usage does not match page allocations.'); + } + } + if (boundedReceiptSettlement && epochCommit.at !== value.at) { + return new Error('Bounded receipt settlement timestamp must match its epoch commit.'); + } + if (epochCommit.totals?.use_count !== receiptIndex.count) { + return new Error('Epoch commit receipt count does not match the canonical receipt index.'); + } + if (boundedReceiptSettlement) { + if (lastPage !== hasOwn(value, 'earning_finals') || + lastPage !== marketUsageProvided) { + return new Error( + 'Bounded receipt settlement requires earning_finals and market_usage on its final page only.' + ); + } + if (lastPage && this.compareAu(marketUsageTotal, epochCommit.totals.use_au) !== 0) { + return new Error('Final epoch market usage demand must equal committed usage.'); + } + } else if (marketUsageProvided && this.compareAu(marketUsageTotal, grossTotal) !== 0) { + return new Error('Epoch market usage demand must equal gross provider earnings.'); + } + + const applyState = await this.epochApplyStateRecord(); + const replayPosition = this.isEpochApplyPagePositionReplay( + applyState, + value.epoch, + page, + lastPage + ); + const previousApplyHash = replayPosition + ? (applyState.last_apply_previous_hash ?? null) + : page === 0 + ? null + : applyState.last_apply_hash; + const expectedStatePrefix = replayPosition ? 'last_receipt' : 'pending_receipt'; + if (replayPosition || page > 0) { + if ( + applyState[`${expectedStatePrefix}_index_count`] !== receiptIndex.count || + applyState[`${expectedStatePrefix}_index_revision`] !== receiptIndex.revision || + applyState[`${expectedStatePrefix}_index_page_count`] !== receiptIndex.page_count || + applyState[`${expectedStatePrefix}_index_updated_at`] !== receiptIndex.updated_at || + applyState[`${expectedStatePrefix}_commit_hash`] !== value.epoch_commit_hash + ) { + return new Error('Paged targeted apply receipt snapshot changed between pages.'); + } + } + let cumulativeAllocationCount; + if (replayPosition) { + cumulativeAllocationCount = applyState.last_receipt_allocation_count; + } else { + const priorAllocationCount = page === 0 + ? 0 + : applyState.pending_receipt_allocation_count; + if (!Number.isSafeInteger(priorAllocationCount) || priorAllocationCount < 0) { + return new Error('Paged targeted apply allocation count is missing.'); + } + cumulativeAllocationCount = priorAllocationCount + allocations.length; + } + if (!Number.isSafeInteger(cumulativeAllocationCount) || + cumulativeAllocationCount < 1 || + cumulativeAllocationCount > receiptIndex.count) { + return new Error('Paged targeted apply allocation count exceeds the canonical receipt index.'); + } + if (lastPage && cumulativeAllocationCount !== receiptIndex.count) { + return new Error('Last targeted apply page does not consume the complete canonical receipt index.'); + } + if (!lastPage && cumulativeAllocationCount >= receiptIndex.count) { + return new Error('Non-final targeted apply page already consumes the complete canonical receipt index.'); + } + const normalized = { + epoch: value.epoch, + page, + last_page: lastPage, + at: value.at, + epoch_seconds: params.epoch_seconds, + fee_bps: params.fee_bps, + tap_burn_bps: TAP_BURN_BPS, + epoch_commit_hash: value.epoch_commit_hash, + receipt_index: receiptIndex, + debits: this.mapRailEntriesForHash(debitMap, 'user', 'au'), + earnings: this.mapRailEntriesForHash(grossEarningMap, 'provider', 'gross_au'), + roots, + totals, + }; + if (previousApplyHash !== null) { + normalized.previous_apply_hash = previousApplyHash; + } + if (marketUsageProvided) { + normalized.market_usage = this.mapMarketUsageEntriesForHash(marketUsageMap); + } + if (hasOwn(value, 'earning_finals')) { + normalized.earning_finals = earningFinals; + } + const applyHash = await this.opaqueHash( + 'mayhem-targeted-epoch-apply-v1', + { + value: normalized, + payout_revisions: revisionBindings, + allocations, + } + ); + if (this.isIdempotentEpochApplyPage(applyState, value.epoch, page, lastPage, applyHash)) { + const result = { + ok: true, + op: 'epochApply', + epoch: value.epoch, + idempotent: true, + debited_au: ZERO_AU, + earned_au: ZERO_AU, + fee_au: ZERO_AU, + burn_au: ZERO_AU, + }; + if (pagedApply) { + result.page = page; + result.last_page = lastPage; + } + return result; + } + const cadenceError = await this.validateEpochCadenceTime( + applyState, + value.epoch, + page, + value.at, + params.epoch_seconds + ); + if (cadenceError) return cadenceError; + const pageOrderError = this.validateEpochApplyPageOrder(applyState, value.epoch, page); + if (pageOrderError) return pageOrderError; + const reservationDebitTotals = this.nextReservationDebitTotals(applyState, value.epoch, page, debitMap); + if (reservationDebitTotals instanceof Error) return reservationDebitTotals; + if (lastPage) { + const cumulativeDebitAu = this.sumRailAu(reservationDebitTotals, 'au'); + if (cumulativeDebitAu instanceof Error) return cumulativeDebitAu; + if (this.compareAu(cumulativeDebitAu, epochCommit.totals.use_au) !== 0) { + return new Error('Targeted apply cumulative debit does not match the epoch commit.'); + } + } + + const balances = new Map(); + for (const debit of debitMap.values()) { + const { rail, user, au: debitAu } = debit; + const balance = await this.balanceRecord(user, rail); + if (balance instanceof Error) return balance; + const balanceError = this.guardianValidateBalanceRecord(balance, user, rail); + if (balanceError) return balanceError; + if (this.compareAu(balance.au, debitAu) < 0) return new Error('Insufficient credit balance.'); + const nextBalanceAu = this.safeSubAu(balance.au, debitAu); + if (nextBalanceAu instanceof Error) return nextBalanceAu; + balances.set(stableJson([rail, user]), { + ...balance, + rail, + au: nextBalanceAu, + updated_epoch: value.epoch, + updated_at: this.tx, + }); + } + + const earningDeltas = new Map(); + const feeDeltaByRail = new Map(PROVIDER_ACCEPTED_RAIL_ORDER.map((rail) => [rail, ZERO_AU])); + const burnDeltaByRail = new Map(PROVIDER_ACCEPTED_RAIL_ORDER.map((rail) => [rail, ZERO_AU])); + let feeDeltaTotalAu = ZERO_AU; + let burnDeltaTotalAu = ZERO_AU; + for (const earning of grossEarningMap.values()) { + const { rail, provider, gross_au: grossAu } = earning; + const providerRecord = await this.get(`prov/${provider}`); + if (!providerRecord) return new Error('Provider not found.'); + if (!Array.isArray(providerRecord.accepted_rails)) { + return new Error('Provider accepted rails are not set.'); + } + if (!providerRecord.accepted_rails.includes(rail)) { + return new Error('Provider does not accept payment rail.'); + } + + const settlementDelta = boundedReceiptSettlement + ? options.providerSettlementDeltas?.get(stableJson([rail, provider])) + : null; + if (boundedReceiptSettlement && + (!settlementDelta || settlementDelta.gross_au !== grossAu)) { + return new Error('Bounded receipt provider settlement delta is missing.'); + } + const feeAu = settlementDelta?.fee_au ?? + this.safeMulDivAu(grossAu, params.fee_bps, 10_000); + const burnAu = settlementDelta?.burn_au ?? (rail === 'tap' + ? this.safeMulDivAu(grossAu, TAP_BURN_BPS, 10_000) + : ZERO_AU); + const providerAu = settlementDelta?.provider_au ?? this.safeSubAu( + this.safeSubAu(grossAu, feeAu), + burnAu + ); + if (feeAu instanceof Error || burnAu instanceof Error || providerAu instanceof Error) { + return new Error('Invalid provider settlement delta.'); + } + feeDeltaTotalAu = this.safeAddAu(feeDeltaTotalAu, feeAu); + if (feeDeltaTotalAu instanceof Error) return feeDeltaTotalAu; + const nextRailFee = this.safeAddAu(feeDeltaByRail.get(rail) ?? ZERO_AU, feeAu); + if (nextRailFee instanceof Error) return nextRailFee; + feeDeltaByRail.set(rail, nextRailFee); + burnDeltaTotalAu = this.safeAddAu(burnDeltaTotalAu, burnAu); + if (burnDeltaTotalAu instanceof Error) return burnDeltaTotalAu; + const nextRailBurn = this.safeAddAu(burnDeltaByRail.get(rail) ?? ZERO_AU, burnAu); + if (nextRailBurn instanceof Error) return nextRailBurn; + burnDeltaByRail.set(rail, nextRailBurn); + const key = stableJson([rail, provider]); + const current = earningDeltas.get(key) ?? { rail, provider, provider_record: providerRecord, au: ZERO_AU }; + const next = this.safeAddAu(current.au, providerAu); + if (next instanceof Error) return next; + earningDeltas.set(key, { ...current, au: next }); + } + + const earnings = new Map(); + let earnCumTotal = ZERO_AU; + for (const delta of earningDeltas.values()) { + const { rail, provider, provider_record: providerRecord, au: deltaAu } = delta; + const current = await this.earningRecord(provider, rail); + if (current instanceof Error) return current; + const currentError = this.guardianValidateEarningRecord(current, provider, rail); + if (currentError) return currentError; + const probeGate = await this.probeGateForEarning(provider, current, params); + if (probeGate instanceof Error) return probeGate; + const lockedEarningEpochs = this.providerLockedEarningEpochs(providerRecord, params); + if (lockedEarningEpochs instanceof Error) return lockedEarningEpochs; + const disputeGate = await this.providerHasOpenDispute(provider); + if (disputeGate instanceof Error) return disputeGate; + const refreshed = this.refreshEarningHoldback( + current, + value.epoch, + lockedEarningEpochs, + probeGate, + disputeGate + ); + if (refreshed instanceof Error) return refreshed; + const totalAu = this.safeAddAu(refreshed.total_au, deltaAu); + if (totalAu instanceof Error) return totalAu; + const heldAu = this.safeAddAu(refreshed.held_au, deltaAu); + if (heldAu instanceof Error) return heldAu; + const holdbacks = this.appendHoldbackBucket( + refreshed.holdbacks, + value.epoch, + deltaAu, + lockedEarningEpochs + ); + if (holdbacks instanceof Error) return holdbacks; + earnings.set(stableJson([rail, provider]), { + ...refreshed, + rail, + total_au: totalAu, + held_au: heldAu, + holdbacks, + updated_epoch: value.epoch, + updated_at: this.tx, + }); + earnCumTotal = this.safeAddAu(earnCumTotal, totalAu); + if (earnCumTotal instanceof Error) return earnCumTotal; + } + + const epochEarningUpdates = []; + const epochMarketUsageUpdates = []; + const epochMarketProviderUpdates = []; + let epochProviderCount = 0; + let epochMarketCount = 0; + let epochEarnCumAu = ZERO_AU; + let epochFeeAu = feeDeltaTotalAu; + let epochBurnAu = burnDeltaTotalAu; + if (boundedReceiptSettlement) { + const priorProviderCount = page === 0 + ? 0 + : applyState.pending_receipt_provider_count; + const priorMarketCount = page === 0 + ? 0 + : applyState.pending_receipt_market_count; + const priorEarnCumAu = page === 0 + ? ZERO_AU + : this.normalizeAu( + applyState.pending_receipt_earn_cum_au, + 'pending receipt cumulative earning', + { allowZero: true } + ); + const priorFeeAu = page === 0 + ? ZERO_AU + : this.normalizeAu( + applyState.pending_receipt_fee_au, + 'pending receipt epoch fee', + { allowZero: true } + ); + const priorBurnAu = page === 0 + ? ZERO_AU + : this.normalizeAu( + applyState.pending_receipt_burn_au, + 'pending receipt epoch burn', + { allowZero: true } + ); + if (!Number.isSafeInteger(priorProviderCount) || priorProviderCount < 0 || + !Number.isSafeInteger(priorMarketCount) || priorMarketCount < 0 || + priorEarnCumAu instanceof Error || priorFeeAu instanceof Error || + priorBurnAu instanceof Error) { + return new Error('Bounded receipt settlement cumulative state is invalid.'); + } + let newProviderCount = 0; + for (const earning of this.sortedRailRecords(grossEarningMap, 'provider')) { + const identity = stableJson([earning.rail, earning.provider]); + const delta = earningDeltas.get(identity); + const nextEarning = earnings.get(identity); + if (!delta || !nextEarning) { + return new Error('Bounded receipt provider earning update is missing.'); + } + const key = `epoch/earning-provider/${value.epoch}/${earning.rail}/${earning.provider}`; + const existing = await this.get(key); + const priorTotalAu = this.safeSubAu(nextEarning.total_au, delta.au); + if (priorTotalAu instanceof Error) return priorTotalAu; + let marker = existing; + if (marker === null) { + newProviderCount += 1; + marker = { + type: 'epoch_provider_earning', + epoch: value.epoch, + rail: earning.rail, + provider: earning.provider, + prior_cumulative_au: priorTotalAu, + gross_au: ZERO_AU, + net_au: ZERO_AU, + cumulative_au: priorTotalAu, + first_page: page, + last_page: page - 1, + updated_at: null, + }; + } else if ( + marker.type !== 'epoch_provider_earning' || + marker.epoch !== value.epoch || + marker.rail !== earning.rail || + marker.provider !== earning.provider || + marker.cumulative_au !== priorTotalAu || + !Number.isSafeInteger(marker.first_page) || + !Number.isSafeInteger(marker.last_page) || + marker.first_page < 0 || + marker.last_page >= page + ) { + return new Error('Bounded receipt provider earning marker is inconsistent.'); + } + const grossAu = this.safeAddAu(marker.gross_au, earning.gross_au); + const netAu = this.safeAddAu(marker.net_au, delta.au); + if (grossAu instanceof Error || netAu instanceof Error) { + return new Error('Bounded receipt provider earning marker overflow.'); + } + epochEarningUpdates.push({ + key, + value: { + ...marker, + gross_au: grossAu, + net_au: netAu, + cumulative_au: nextEarning.total_au, + last_page: page, + updated_at: this.tx, + }, + }); + } + let newMarketCount = 0; + const updatedMarketMarkers = new Map(); + for (const usage of canonicalPageMarketUsageMap.values()) { + const ctxKey = usage.ctx_bracket ? `ctx/${usage.ctx_bracket}` : 'base'; + const markerKey = + `epoch/market-usage/${value.epoch}/${usage.enclave_id}/${ctxKey}`; + const existing = await this.get(markerKey); + let marker = existing; + if (marker === null) { + newMarketCount += 1; + marker = { + type: 'epoch_market_usage', + epoch: value.epoch, + enclave_id: usage.enclave_id, + ...(usage.ctx_bracket ? { ctx_bracket: usage.ctx_bracket } : {}), + ...(usage.ctx_bracket_table_ver ? { + ctx_bracket_table_ver: usage.ctx_bracket_table_ver, + } : {}), + demand_au: ZERO_AU, + settled_usage: {}, + session_count: 0, + provider_count: 0, + first_page: page, + last_page: page - 1, + updated_at: null, + }; + } else if ( + marker.type !== 'epoch_market_usage' || + marker.epoch !== value.epoch || + marker.enclave_id !== usage.enclave_id || + (marker.ctx_bracket ?? null) !== (usage.ctx_bracket ?? null) || + (marker.ctx_bracket_table_ver ?? null) !== + (usage.ctx_bracket_table_ver ?? null) || + !Number.isSafeInteger(marker.first_page) || + !Number.isSafeInteger(marker.last_page) || + marker.first_page < 0 || + marker.last_page >= page || + !Number.isSafeInteger(marker.session_count) || + marker.session_count < 1 || + !Number.isSafeInteger(marker.provider_count) || + marker.provider_count < 1 + ) { + return new Error('Bounded receipt market usage marker is inconsistent.'); + } + let newProviderCount = 0; + for (const provider of usage.providers) { + const providerKey = + `epoch/market-provider/${value.epoch}/${usage.enclave_id}/${ctxKey}/${provider}`; + const providerMarker = await this.get(providerKey); + if (providerMarker === null) { + newProviderCount += 1; + epochMarketProviderUpdates.push({ + key: providerKey, + value: { + type: 'epoch_market_provider', + epoch: value.epoch, + enclave_id: usage.enclave_id, + ...(usage.ctx_bracket ? { ctx_bracket: usage.ctx_bracket } : {}), + provider, + first_page: page, + updated_at: this.tx, + }, + }); + } else if ( + providerMarker.type !== 'epoch_market_provider' || + providerMarker.epoch !== value.epoch || + providerMarker.enclave_id !== usage.enclave_id || + (providerMarker.ctx_bracket ?? null) !== (usage.ctx_bracket ?? null) || + providerMarker.provider !== provider || + !Number.isSafeInteger(providerMarker.first_page) || + providerMarker.first_page < 0 || + providerMarker.first_page >= page + ) { + return new Error('Bounded receipt market provider marker is inconsistent.'); + } + } + const demandAu = this.safeAddAu(marker.demand_au, usage.demand_au); + const sessionCount = this.safeAddCount( + marker.session_count, + usage.session_count, + 'bounded receipt market sessions' + ); + const providerCount = this.safeAddCount( + marker.provider_count, + newProviderCount, + 'bounded receipt market providers' + ); + if (demandAu instanceof Error || sessionCount instanceof Error || + providerCount instanceof Error) { + return new Error('Bounded receipt market usage marker overflow.'); + } + const settledUsage = this.addSettledUsage(marker.settled_usage, usage.settled_usage); + if (settledUsage instanceof Error) return settledUsage; + const nextMarker = { + ...marker, + settled_usage: settledUsage, + demand_au: demandAu, + session_count: sessionCount, + provider_count: providerCount, + last_page: page, + updated_at: this.tx, + }; + epochMarketUsageUpdates.push({ key: markerKey, value: nextMarker }); + updatedMarketMarkers.set( + this.priceMarketKey(usage.enclave_id, usage.ctx_bracket ?? null), + nextMarker + ); + } + epochProviderCount = this.safeAddCount( + priorProviderCount, + newProviderCount, + 'bounded receipt provider count' + ); + if (epochProviderCount instanceof Error) return epochProviderCount; + epochMarketCount = this.safeAddCount( + priorMarketCount, + newMarketCount, + 'bounded receipt market count' + ); + if (epochMarketCount instanceof Error) return epochMarketCount; + epochFeeAu = this.safeAddAu(priorFeeAu, feeDeltaTotalAu); + epochBurnAu = this.safeAddAu(priorBurnAu, burnDeltaTotalAu); + if (epochFeeAu instanceof Error || epochBurnAu instanceof Error) { + return new Error('Bounded receipt epoch fee or burn overflow.'); + } + if (!lastPage) { + epochEarnCumAu = priorEarnCumAu; + } else { + if (marketUsageMap.size !== epochMarketCount) { + return new Error('Final market usage count does not match bounded settlement state.'); + } + for (const usage of marketUsageMap.values()) { + const marketKey = this.priceMarketKey( + usage.enclave_id, + usage.ctx_bracket ?? null + ); + const ctxKey = usage.ctx_bracket ? `ctx/${usage.ctx_bracket}` : 'base'; + const marker = updatedMarketMarkers.get(marketKey) ?? await this.get( + `epoch/market-usage/${value.epoch}/${usage.enclave_id}/${ctxKey}` + ); + if (!marker || stableJson({ + enclave_id: marker.enclave_id, + ...(marker.ctx_bracket ? { ctx_bracket: marker.ctx_bracket } : {}), + ...(marker.ctx_bracket_table_ver ? { + ctx_bracket_table_ver: marker.ctx_bracket_table_ver, + } : {}), + demand_au: marker.demand_au, + session_count: marker.session_count, + provider_count: marker.provider_count, + }) !== stableJson(usage)) { + return new Error('Final market usage does not match canonical receipt settlement state.'); + } + } + const updatedMarkers = new Map( + epochEarningUpdates.map((update) => [ + stableJson([update.value.rail, update.value.provider]), + update.value, + ]) + ); + if (earningFinals.length !== epochProviderCount) { + return new Error('Final provider earning count does not match bounded settlement state.'); + } + const leaves = []; + for (const final of earningFinals) { + const identity = stableJson([final.rail, final.provider]); + const marker = updatedMarkers.get(identity) ?? + await this.get(`epoch/earning-provider/${value.epoch}/${final.rail}/${final.provider}`); + if (!marker || stableJson({ + rail: marker.rail, + provider: marker.provider, + gross_au: marker.gross_au, + net_au: marker.net_au, + cumulative_au: marker.cumulative_au, + }) !== stableJson(final)) { + return new Error('Final provider earning evidence does not match applied settlement state.'); + } + epochEarnCumAu = this.safeAddAu(epochEarnCumAu, final.cumulative_au); + if (epochEarnCumAu instanceof Error) return epochEarnCumAu; + leaves.push(await this.opaqueHash('mayhem-earn-leaf-v1', final)); + } + if (epochProviderCount !== epochCommit.totals.provider_count || + this.compareAu(epochEarnCumAu, epochCommit.totals.earn_au) !== 0 || + this.compareAu(epochFeeAu, epochCommit.totals.fee_au) !== 0 || + this.compareAu(epochBurnAu, epochCommit.totals.burn_au) !== 0) { + return new Error('Final bounded settlement totals do not match the epoch commit.'); + } + const earnRoot = await this.merkleRoot('earn', leaves); + if (earnRoot !== epochCommit.roots.earn) { + return new Error('Final provider earning root does not match the epoch commit.'); + } + const feeRoot = await this.opaqueHash('mayhem-fee-root-v1', { + epoch: value.epoch, + fee_au: epochFeeAu, + fee_cum_au: epochCommit.totals.fee_cum_au, + burn_au: epochBurnAu, + burn_cum_au: epochCommit.totals.burn_cum_au, + tap_burn_bps: TAP_BURN_BPS, + }); + if (feeRoot !== epochCommit.roots.fee) { + return new Error('Final fee root does not match the epoch commit.'); + } + } + } + + const feeRecords = new Map(); + const nextFeeRecords = new Map(); + const burnRecords = new Map(); + const nextBurnRecords = new Map(); + let nextFeeCumTotal = ZERO_AU; + let nextBurnCumTotal = ZERO_AU; + const touchedRails = new Set([ + ...Array.from(debitRailTotals.entries()).filter(([, au]) => this.compareAu(au, ZERO_AU) > 0).map(([rail]) => rail), + ...Array.from(grossRailTotals.entries()).filter(([, au]) => this.compareAu(au, ZERO_AU) > 0).map(([rail]) => rail), + ]); + for (const rail of PROVIDER_ACCEPTED_RAIL_ORDER) { + const fee = await this.feeCumRecord(rail); + if (fee instanceof Error) return fee; + const feeError = this.guardianValidateFeeRecord(fee, rail); + if (feeError) return feeError; + feeRecords.set(rail, fee); + const feeDeltaAu = feeDeltaByRail.get(rail) ?? ZERO_AU; + const nextFeeCum = this.safeAddAu(fee.cum_au, feeDeltaAu); + if (nextFeeCum instanceof Error) return nextFeeCum; + // settled_cum_au must advance with this epoch's settled rail debits in + // the SAME record that carries the new cum_au, otherwise + // guardianValidateFeeRecord rejects every fee-bearing epoch apply + // (settled < cum). This mirrors next_settled_cum_by_rail in + // guardianValidateEpochTotals, which is also what gets persisted. + const nextFeeSettledCum = this.safeAddAu( + fee.settled_cum_au ?? fee.cum_au, + debitRailTotals.get(rail) ?? ZERO_AU + ); + if (nextFeeSettledCum instanceof Error) return nextFeeSettledCum; + const nextFee = touchedRails.has(rail) + ? { + ...fee, + cum_au: nextFeeCum, + settled_cum_au: nextFeeSettledCum, + updated_epoch: value.epoch, + updated_at: this.tx, + last_apply_hash: applyHash, + last_fee_bps: params.fee_bps, + } + : fee; + nextFeeRecords.set(rail, nextFee); + nextFeeCumTotal = this.safeAddAu(nextFeeCumTotal, nextFee.cum_au); + if (nextFeeCumTotal instanceof Error) return nextFeeCumTotal; + + const burn = await this.burnCumRecord(rail); + if (burn instanceof Error) return burn; + const burnError = this.guardianValidateBurnRecord(burn, rail); + if (burnError) return burnError; + burnRecords.set(rail, burn); + const burnDeltaAu = burnDeltaByRail.get(rail) ?? ZERO_AU; + const nextBurnCum = this.safeAddAu(burn.cum_au, burnDeltaAu); + if (nextBurnCum instanceof Error) return nextBurnCum; + const nextBurn = touchedRails.has(rail) + ? { + ...burn, + cum_au: nextBurnCum, + updated_epoch: value.epoch, + updated_at: this.tx, + last_apply_hash: applyHash, + burn_bps: rail === 'tap' ? TAP_BURN_BPS : 0, + } + : burn; + nextBurnRecords.set(rail, nextBurn); + nextBurnCumTotal = this.safeAddAu(nextBurnCumTotal, nextBurn.cum_au); + if (nextBurnCumTotal instanceof Error) return nextBurnCumTotal; + } + + const grossAfterFees = this.safeSubAu(grossTotal, feeDeltaTotalAu); + if (grossAfterFees instanceof Error) return grossAfterFees; + const providerDeltaTotal = this.safeSubAu(grossAfterFees, burnDeltaTotalAu); + if (providerDeltaTotal instanceof Error) return providerDeltaTotal; + const guardian = this.guardianCheckEpochApply({ + epoch: value.epoch, + page, + applyState, + feeRecords, + burnRecords, + debitTotal, + debitRailTotals, + feeDeltaByRail, + burnDeltaByRail, + providerDeltaTotal, + nextFeeRecords, + nextBurnRecords, + balances, + earnings, + }); + if (guardian instanceof Error) return guardian; + + const canonicalActivity = new Map(); + if (Array.isArray(options.canonicalMarketUsage)) { + for (const usage of options.canonicalMarketUsage) canonicalActivity.set( + this.priceMarketKey(usage.enclave_id, usage.ctx_bracket ?? null), usage); + } + if (boundedReceiptSettlement && lastPage) { + for (const usage of marketUsageMap.values()) { + const ctxKey = usage.ctx_bracket ? `ctx/${usage.ctx_bracket}` : 'base'; + const markerKey = `epoch/market-usage/${value.epoch}/${usage.enclave_id}/${ctxKey}`; + const marker = epochMarketUsageUpdates.find((u) => u.key === markerKey)?.value ?? await this.get(markerKey); + canonicalActivity.set(this.priceMarketKey(usage.enclave_id, usage.ctx_bracket ?? null), marker); + } + } + let marketPriceUpdates = []; + if (marketUsageProvided) { + marketPriceUpdates = await this.computeMarketPriceUpdates(marketUsageMap, { + epoch: value.epoch, + at: value.at, + epochSeconds: params.epoch_seconds, + canonicalActivity, + includeDormant: boundedReceiptSettlement && lastPage, + }); + if (marketPriceUpdates instanceof Error) return marketPriceUpdates; + } + const priceDerivations = await this.priceDerivationsFromMarketUpdates(marketPriceUpdates, { + epoch: value.epoch, + at: value.at, + epochSeconds: params.epoch_seconds, + usageRoot: boundedReceiptSettlement ? epochCommit.roots.use : (roots?.use ?? null), + }); + if (priceDerivations instanceof Error) return priceDerivations; + + if (totals) { + const totalsError = await this.validateEpochApplyTotals({ + epoch: value.epoch, + roots, + totals, + debitTotal, + feeDeltaAu: feeDeltaTotalAu, + nextFeeCum: nextFeeCumTotal, + burnDeltaAu: burnDeltaTotalAu, + nextBurnCum: nextBurnCumTotal, + providerCount: grossEarningMap.size, + earnCumTotal, + epochSeconds: params.epoch_seconds, + priceDerivations, + }); + if (totalsError) return totalsError; + } + + if (lastPage && boundedReceiptSettlement) { + const finalEvidenceError = await this.validatePagedEpochCommitEvidence({ + commit: epochCommit, + feeCumAu: nextFeeCumTotal, + burnCumAu: nextBurnCumTotal, + priceDerivations, + }); + if (finalEvidenceError) return finalEvidenceError; + } + + const nextApplyState = this.nextEpochApplyState({ + applyState, + epoch: value.epoch, + page, + lastPage, + applyHash, + epochSeconds: params.epoch_seconds, + settledAt: value.at, + reservationDebitTotals, + receiptApply: { + index_count: receiptIndex.count, + index_revision: receiptIndex.revision, + index_page_count: receiptIndex.page_count, + index_updated_at: receiptIndex.updated_at, + commit_hash: value.epoch_commit_hash, + allocation_count: cumulativeAllocationCount, + provider_count: epochProviderCount, + market_count: epochMarketCount, + earn_cum_au: epochEarnCumAu, + fee_au: epochFeeAu, + burn_au: epochBurnAu, + }, + }); + const previousChallengeAnchor = await this.prepareCanaryChallengeAnchor(applyState); + if (previousChallengeAnchor instanceof Error) return previousChallengeAnchor; + const epochApplyAnchor = lastPage + ? await this.prepareEpochApplyAnchor(nextApplyState) + : null; + if (epochApplyAnchor instanceof Error) return epochApplyAnchor; + const challengeAnchor = lastPage + ? await this.prepareCanaryChallengeAnchor(nextApplyState) + : null; + if (challengeAnchor instanceof Error) return challengeAnchor; + + if (options.commitTransition?.write) { + if (options.commitTransition.archive) { + await this.put( + options.commitTransition.archive.key, + options.commitTransition.archive.value + ); + } + await this.put(options.commitTransition.key, options.commitTransition.record); + } + + for (const balance of this.sortedRailRecords(balances, 'user')) { + await this.put(this.balanceKey(balance.user, balance.rail), balance); + } + for (const earning of this.sortedRailRecords(earnings, 'provider')) { + await this.put(this.earningKey(earning.provider, earning.rail), earning); + } + for (const update of epochEarningUpdates) { + await this.put(update.key, update.value); + } + for (const update of epochMarketProviderUpdates) { + await this.put(update.key, update.value); + } + for (const update of epochMarketUsageUpdates) { + await this.put(update.key, update.value); + } + for (const rail of PROVIDER_ACCEPTED_RAIL_ORDER.filter((rail) => touchedRails.has(rail))) { + const feeRecord = { + ...nextFeeRecords.get(rail), + settled_cum_au: guardian.next_settled_cum_by_rail.get(rail), + }; + await this.put(this.feeCumKey(rail), feeRecord); + await this.put(this.burnCumKey(rail), nextBurnRecords.get(rail)); + } + await this.writePreparedAnchor(previousChallengeAnchor); + await this.put('epoch/apply/state', nextApplyState); + await this.writePreparedAnchor(epochApplyAnchor); + await this.writePreparedAnchor(challengeAnchor); + if (lastPage && boundedReceiptSettlement) { + await this.writeEpochEvidenceRoots({ + epoch: value.epoch, + at: value.at, + epoch_seconds: params.epoch_seconds, + roots: epochCommit.roots, + totals: epochCommit.totals, + feeDeltaAu: epochCommit.totals.fee_au, + feeCumAu: nextFeeCumTotal, + burnDeltaAu: epochCommit.totals.burn_au, + burnCumAu: nextBurnCumTotal, + priceDerivations: [], + }); + await this.writeBoundedMarketPriceEvidence({ + epoch: value.epoch, + at: value.at, + epochSeconds: params.epoch_seconds, + usageRoot: epochCommit.roots.use, + derivations: priceDerivations, + }); + } else if (totals) { + await this.writeEpochEvidenceRoots({ + epoch: value.epoch, + at: value.at, + epoch_seconds: params.epoch_seconds, + roots, + totals, + feeDeltaAu: feeDeltaTotalAu, + feeCumAu: nextFeeCumTotal, + burnDeltaAu: burnDeltaTotalAu, + burnCumAu: nextBurnCumTotal, + priceDerivations, + }); + } else if (priceDerivations.length > 0) { + await this.writePriceDerivationEvidence({ + epoch: value.epoch, + at: value.at, + epoch_seconds: params.epoch_seconds, + root: await this.priceDerivationRoot(priceDerivations), + count: priceDerivations.length, + derivations: priceDerivations, + }); + } + const activityIndexError = await this.writeActivityMarketIndex(marketPriceUpdates); + if (activityIndexError instanceof Error) return activityIndexError; + for (const update of marketPriceUpdates) { + await this.put(update.schedule_key, update.schedule); + await this.put(update.record_key, update.record); + } + + const result = { + ok: true, + op: 'epochApply', + epoch: value.epoch, + idempotent: false, + debited_au: debitTotal, + earned_au: providerDeltaTotal, + fee_au: feeDeltaTotalAu, + burn_au: burnDeltaTotalAu, + rails: Array.from(touchedRails).sort( + (left, right) => PROVIDER_ACCEPTED_RAIL_ORDER.indexOf(left) - PROVIDER_ACCEPTED_RAIL_ORDER.indexOf(right) + ), + }; + if (pagedApply) { + result.page = page; + result.last_page = lastPage; + } + if (marketPriceUpdates.length > 0) { + result.market_prices = marketPriceUpdates.map((update) => ({ + enclave_id: update.enclave_id, + ...(update.ctx_bracket ? { + ctx_bracket: update.ctx_bracket, + ctx_bracket_table_ver: update.ctx_bracket_table_ver, + } : {}), + ver: update.ver, + momentum_bps: update.momentum_bps, + activity_rate: update.activity_rate, + ema_activity_rate: update.ema_activity_rate, + active_supply: update.active_supply, + active_demand_au: update.active_demand_au, + frozen: update.frozen, + derivation_hash: update.derivation_hash, + })); + result.price_root = await this.priceDerivationRoot(priceDerivations); + } + console.log('mayhem epochApply', result); + return result; + } + + + async epochSealEmpty() { + const adminError = await this.requireAdmin(); + if (adminError) return adminError; + + const shapeError = this.validateExactCommandValue( + ['op', 'epoch', 'at', 'reason_hash'], + 'epoch_seal_empty' + ); + if (shapeError) return shapeError; + if (!Number.isSafeInteger(this.value.epoch) || this.value.epoch < 1) { + return new Error('Invalid empty epoch seal epoch.'); + } + if (!Number.isSafeInteger(this.value.at) || this.value.at < 0) { + return new Error('Invalid empty epoch seal timestamp.'); + } + if (!this.isHexBytes(this.value.reason_hash, 32)) { + return new Error('Invalid empty epoch seal reason hash.'); + } + + const params = await this.activeParamsAt(this.value.at, ['epoch_seconds']); + const applyState = await this.epochApplyStateRecord(); + const reasonHash = this.value.reason_hash.toLowerCase(); + const key = `epoch/seal/${this.value.epoch}`; + const existing = await this.get(key); + if (existing) { + const existingHash = await this.epochEmptySealHash(this.epochEmptySealHashValue(existing)); + if ( + existing.seal_hash === existingHash && + existing.reason_hash === reasonHash && + existing.at === this.value.at && + existing.sealed_by === this.address && + applyState.updated_epoch === this.value.epoch && + applyState.last_apply_hash === existing.seal_hash && + (applyState.pending_epoch ?? null) === null + ) { + return { + ok: true, + op: 'epochSealEmpty', + epoch: this.value.epoch, + idempotent: true, + seal_hash: existing.seal_hash, + }; + } + return new Error('Empty epoch seal already exists.'); + } + + const receiptIndex = await this.get(this.receiptEpochIndexKey(this.value.epoch)); + if (receiptIndex !== null) { + if (receiptIndex.type !== 'canonical_receipt_epoch_index' || + receiptIndex.epoch !== this.value.epoch || + !Number.isSafeInteger(receiptIndex.count) || + receiptIndex.count < 0 || + !Number.isSafeInteger(receiptIndex.revision) || + receiptIndex.revision < 0) { + return new Error('Canonical receipt epoch index is invalid.'); + } + if (receiptIndex.count !== 0 || receiptIndex.revision !== 0) { + return new Error('Cannot seal an epoch empty while canonical receipts exist.'); + } + } + + const freezeError = await this.validateFrozenEpoch(this.value.epoch, this.value.at, + receiptIndex ?? { + type: 'canonical_receipt_epoch_index', epoch: this.value.epoch, count: 0, + page_size: RECEIPT_EPOCH_INDEX_PAGE_SIZE, page_count: 0, revision: 0, updated_at: null, + }); + if (freezeError) return freezeError; + const notYetActiveAt = this.value.epoch * params.epoch_seconds; + if (this.value.at < notYetActiveAt) { + return new Error('Empty epoch seal is not active until the epoch window ends.'); + } + const cadenceError = await this.validateEpochCadenceTime( + applyState, + this.value.epoch, + 0, + this.value.at, + params.epoch_seconds + ); + if (cadenceError) return cadenceError; + const pageOrderError = this.validateEpochApplyPageOrder(applyState, this.value.epoch, 0); + if (pageOrderError) return pageOrderError; + + const activityUpdates = await this.computeMarketPriceUpdates(new Map(), { + epoch: this.value.epoch, at: this.value.at, epochSeconds: params.epoch_seconds, + canonicalActivity: new Map(), includeDormant: true, + }); + if (activityUpdates instanceof Error) return activityUpdates; + const usageRoot = await this.merkleRoot('use', []); + const activityDerivations = await this.priceDerivationsFromMarketUpdates(activityUpdates, { + epoch: this.value.epoch, at: this.value.at, epochSeconds: params.epoch_seconds, usageRoot, + }); + if (activityDerivations instanceof Error) return activityDerivations; + if ((await this.get(`market/price/${this.value.epoch}`)) !== null) { + return new Error('Empty epoch market price evidence already exists.'); + } + const activityRoot = await this.priceDerivationRoot(activityDerivations); + const sealValue = { + type: 'epoch_empty_seal', + market_price_root: activityRoot, + market_price_count: activityDerivations.length, + epoch: this.value.epoch, + at: this.value.at, + epoch_seconds: params.epoch_seconds, + previous_apply_hash: applyState.last_apply_hash ?? null, + reason_hash: reasonHash, + sealed_by: this.address, + sealed_by_role: 'admin', + totals: { + debited_au: ZERO_AU, + earned_au: ZERO_AU, + fee_au: ZERO_AU, + burn_au: ZERO_AU, + }, + }; + const sealHash = await this.epochEmptySealHash(sealValue); + const record = { + ...sealValue, + seal_hash: sealHash, + sealed_at: this.tx, + }; + const nextApplyState = this.nextEpochApplyState({ + applyState, + epoch: this.value.epoch, + page: 0, + lastPage: true, + applyHash: sealHash, + epochSeconds: params.epoch_seconds, + settledAt: this.value.at, + }); + const previousChallengeAnchor = await this.prepareCanaryChallengeAnchor(applyState); + if (previousChallengeAnchor instanceof Error) return previousChallengeAnchor; + const epochApplyAnchor = await this.prepareEpochApplyAnchor(nextApplyState); + if (epochApplyAnchor instanceof Error) return epochApplyAnchor; + const challengeAnchor = await this.prepareCanaryChallengeAnchor(nextApplyState); + if (challengeAnchor instanceof Error) return challengeAnchor; + + await this.writeBoundedMarketPriceEvidence({ + epoch: this.value.epoch, at: this.value.at, epochSeconds: params.epoch_seconds, + usageRoot, derivations: activityDerivations, + }); + const activityIndexError = await this.writeActivityMarketIndex(activityUpdates); + if (activityIndexError instanceof Error) return activityIndexError; + for (const update of activityUpdates) { + await this.put(update.schedule_key, update.schedule); + await this.put(update.record_key, update.record); + } + await this.put(key, record); + await this.writePreparedAnchor(previousChallengeAnchor); + await this.put('epoch/apply/state', nextApplyState); + await this.writePreparedAnchor(epochApplyAnchor); + await this.writePreparedAnchor(challengeAnchor); + const result = { + ok: true, + op: 'epochSealEmpty', + epoch: this.value.epoch, + idempotent: false, + seal_hash: sealHash, + }; + console.log('mayhem epochSealEmpty', result); + return result; + } + + async epochCommit() { + const banned = await this.get(`committer/ban/${this.address}`); + if (banned?.status === 'banned') return new Error('Epoch committer is banned.'); + + const roots = this.normalizeEpochRoots(this.value.roots); + if (roots instanceof Error) return roots; + const totals = this.normalizeEpochTotals(this.value.totals); + if (totals instanceof Error) return totals; + const params = await this.activeParamsAt(this.value.at, ['challenge_epochs', 'epoch_seconds']); + const normalized = { + epoch: this.value.epoch, + epoch_seconds: params.epoch_seconds, + roots, + totals, + }; + const commitHash = await this.epochCommitHash(normalized); + const key = `epoch/commit/${this.value.epoch}`; + const existing = await this.get(key); + if (existing) { + if (existing.commit_hash === commitHash) { + return { + ok: true, + op: 'epochCommit', + epoch: this.value.epoch, + idempotent: true, + commit_hash: commitHash, + }; + } + return new Error('Epoch commit already exists.'); + } + + const activityEvidence = await this.prepareCommittedActivityEvidence({ + epoch: this.value.epoch, at: this.value.at, epochSeconds: params.epoch_seconds, roots, totals, + }); + if (activityEvidence instanceof Error) return activityEvidence; + const record = { + type: 'epoch_commit', + pricing_schema_version: 2, + ...(activityEvidence ? { expected_activity_evidence: activityEvidence } : {}), + epoch: this.value.epoch, + epoch_seconds: params.epoch_seconds, + roots, + totals, + status: 'provisional', + challenge_epochs: params.challenge_epochs, + provisional_until_epoch: this.value.epoch + params.challenge_epochs, + commit_hash: commitHash, + submitted_by: this.address, + submitted_at: this.tx, + at: this.value.at, + }; + await this.put(key, record); + console.log('mayhem epochCommit', record); + return { + ok: true, + op: 'epochCommit', + epoch: this.value.epoch, + idempotent: false, + commit_hash: commitHash, + }; + } + + async fraudProof() { + if (!FRAUD_PROOF_REASONS.has(this.value.reason)) { + return new Error('Unsupported fraud proof reason.'); + } + if (b4a.from(stableJson(this.value)).byteLength > FRAUD_PROOF_MAX_BYTES) { + return new Error('Fraud proof exceeds 4096 bytes.'); + } + + const commitKey = `epoch/commit/${this.value.epoch}`; + const commit = await this.get(commitKey); + if (!commit) return new Error('Epoch commit not found.'); + + let proofHashPayload; + let proof; + let proofHash = null; + let slashReason = 'receipt_forgery'; + let slashEnclaveId = null; + + if (this.value.reason === 'over_credit') { + if (!hasOwn(this.value, 'receipt')) return new Error('Over-credit fraud proof requires a receipt.'); + if (!hasOwn(this.value, 'claimed_au_owed_cum')) { + return new Error('Over-credit fraud proof requires claimed_au_owed_cum.'); + } + const receipt = await this.normalizeReceiptEnvelope(this.value.receipt); + if (receipt instanceof Error) return receipt; + if (!this.verifyReceiptEnvelope(receipt)) { + return new Error('Invalid receipt signature.'); + } + proofHashPayload = { + epoch: this.value.epoch, + proof_epoch: this.value.proof_epoch, + reason: this.value.reason, + receipt, + claimed_au_owed_cum: this.value.claimed_au_owed_cum, + previous_au_owed_cum: this.value.previous_au_owed_cum ?? ZERO_AU, + }; + proofHash = await this.fraudProofHash(proofHashPayload); + const existingProof = await this.get(`ev/fraud/${this.value.epoch}/${proofHash}`); + if (existingProof) { + return { + ok: true, + op: 'fraudProof', + epoch: this.value.epoch, + idempotent: true, + proof_hash: proofHash, + voided_commit: commit.commit_hash, + banned_submitter: commit.submitted_by, + }; + } + proof = await this.validateOverCreditFraudProof(commit, receipt); + if (proof instanceof Error) return proof; + slashEnclaveId = receipt.body.enclave_id; + } else if (this.value.reason === 'price_derivation') { + if (!hasOwn(this.value, 'price_usage')) { + return new Error('Price derivation fraud proof requires price_usage.'); + } + proof = await this.validatePriceDerivationFraudProof(commit); + if (proof instanceof Error) return proof; + proofHashPayload = { + epoch: this.value.epoch, + proof_epoch: this.value.proof_epoch, + reason: this.value.reason, + price_usage: proof.price_usage, + expected_price_root: proof.expected_price_root, + committed_price_root: proof.committed_price_root, + price_derivation_hash: proof.price_derivation_hash, + }; + slashReason = 'price_forgery'; + slashEnclaveId = proof.enclave_id; + } + + proofHash ??= await this.fraudProofHash(proofHashPayload); + const proofKey = `ev/fraud/${this.value.epoch}/${proofHash}`; + const existingProof = await this.get(proofKey); + if (existingProof) { + return { + ok: true, + op: 'fraudProof', + epoch: this.value.epoch, + idempotent: true, + proof_hash: proofHash, + voided_commit: commit.commit_hash, + banned_submitter: commit.submitted_by, + }; + } + + if (commit.status === 'void') return new Error('Epoch commit is already void.'); + if (this.value.proof_epoch > commit.provisional_until_epoch) { + return new Error('Epoch commit challenge window has closed.'); + } + + const record = { + type: 'fraud_proof', + epoch: this.value.epoch, + proof_epoch: this.value.proof_epoch, + reason: this.value.reason, + proof_hash: proofHash, + submitted_by: this.address, + submitted_at: this.tx, + at: this.value.at, + voided_commit: commit.commit_hash, + banned_submitter: commit.submitted_by, + }; + if (this.value.reason === 'over_credit') { + record.receipt_hash = proof.receipt_hash; + record.actual_au = proof.actual_au; + record.claimed_au = proof.claimed_au; + record.committed_use_root = commit.roots.use; + } else if (this.value.reason === 'price_derivation') { + record.price_usage = proof.price_usage; + record.committed_price_root = proof.committed_price_root; + record.expected_price_root = proof.expected_price_root; + record.price_derivation_hash = proof.price_derivation_hash; + record.price_derivation = proof.price_derivation; + } + const updatedCommit = { + ...commit, + status: 'void', + voided_at: this.tx, + voided_by: this.address, + fraud_reason: this.value.reason, + fraud_proof_hash: proofHash, + }; + const banKey = `committer/ban/${commit.submitted_by}`; + const existingBan = await this.get(banKey); + const ban = existingBan?.status === 'banned' ? existingBan : { + ...(existingBan ?? {}), + submitter: commit.submitted_by, + status: 'banned', + reason: 'fraud_proof', + epoch: this.value.epoch, + proof_hash: proofHash, + banned_at: this.tx, + banned_by: this.address, + }; + + let slash = null; + if ((await this.get(`prov/${commit.submitted_by}`)) !== null) { + const params = await this.activeParamsAt(this.value.at, ['fraud_slash_bps']); + slash = await this.applyProviderSlash({ + providerId: commit.submitted_by, + source: 'fraud_proof', + reason: slashReason, + evidenceHash: proofHash, + epoch: this.value.epoch, + at: this.value.at, + slashBps: params.fraud_slash_bps, + beneficiary: this.address, + enclaveId: slashEnclaveId, + banProvider: true, + tombstoneEnclave: true, + }); + if (slash instanceof Error) return slash; + } + record.slash = slash; + + await this.put(proofKey, record); + await this.put(commitKey, updatedCommit); + await this.put(banKey, ban); + console.log('mayhem fraudProof', record); + return { + ok: true, + op: 'fraudProof', + epoch: this.value.epoch, + idempotent: false, + proof_hash: proofHash, + voided_commit: commit.commit_hash, + banned_submitter: commit.submitted_by, + slash, + }; + } + + async dispute() { + if (!(await this.isAdmin())) { + const consentError = await this.requireConsent(); + if (consentError) return consentError; + } + const validationError = this.validateDisputeOpen(this.value); + if (validationError) return validationError; + + const rail = this.normalizeLedgerRail(this.value.rail, 'dispute rail'); + if (rail instanceof Error) return rail; + const balance = await this.balanceRecord(this.address, rail); + if (balance instanceof Error) return balance; + const balanceError = this.guardianValidateBalanceRecord(balance, this.address, rail); + if (balanceError) return balanceError; + const params = await this.activeParamsAt(this.value.at, [ + 'dispute_deposit_au', + 'dispute_timeout_epochs', + 'max_open_disputes_per_opener', + ]); + const depositAu = params.dispute_deposit_au; + if (this.compareAu(balance.au, depositAu) < 0) return new Error('Insufficient balance for dispute deposit.'); + const applyState = await this.epochApplyStateRecord(); + const disputeEpoch = this.value.epoch ?? applyState.updated_epoch; + if (disputeEpoch > applyState.updated_epoch) { + return new Error('Dispute epoch cannot exceed the latest applied epoch.'); + } + const hold = await this.normalizeSpendHoldRecord( + (await this.get(this.spendHoldKey(this.address, rail, disputeEpoch))) ?? null, + this.address, + rail, + disputeEpoch + ); + if (hold instanceof Error) return hold; + const linkedSession = hold.sessions.find((session) => session.session_id === this.value.session_id.toLowerCase()); + if (!linkedSession) { + return new Error('Dispute must reference an opener-linked spend reservation.'); + } + const sessionDisputeKey = `disp/session/${this.address}/${linkedSession.session_id}`; + if ((await this.get(sessionDisputeKey)) !== null) { + return new Error('Session already has a dispute from this opener.'); + } + if (linkedSession.provider !== this.value.provider.toLowerCase()) { + return new Error('Dispute provider does not match the linked session.'); + } + if (linkedSession.enclave_id !== this.value.enclave_id.toLowerCase()) { + return new Error('Dispute enclave does not match the linked session.'); + } + if ( + this.value.counterparty !== undefined && + this.value.counterparty.toLowerCase() !== linkedSession.provider + ) { + return new Error('Dispute counterparty does not match the linked session provider.'); + } + const openerCountKey = this.disputeOpenCountKey(this.address); + const openerOpenCount = await this.disputeOpenCount(openerCountKey); + if (openerOpenCount instanceof Error) return openerOpenCount; + if (openerOpenCount >= params.max_open_disputes_per_opener) { + return new Error('Open dispute limit reached.'); + } + const providerCountKey = this.providerOpenDisputeCountKey(linkedSession.provider); + const providerOpenCount = await this.disputeOpenCount(providerCountKey); + if (providerOpenCount instanceof Error) return providerOpenCount; + const expiresAfterEpoch = disputeEpoch + params.dispute_timeout_epochs; + if (!Number.isSafeInteger(expiresAfterEpoch)) return new Error('Dispute timeout epoch overflow.'); + + const nextBalanceAu = this.safeSubAu(balance.au, depositAu); + if (nextBalanceAu instanceof Error) return nextBalanceAu; + const nextBalance = { + ...balance, + rail, + au: nextBalanceAu, + updated_epoch: Math.max(balance.updated_epoch, this.value.epoch ?? 0), + updated_at: this.tx, + }; + const nextBalanceError = this.guardianValidateBalanceRecord(nextBalance, this.address, rail); + if (nextBalanceError) return nextBalanceError; + + const next = await this.get('disp/next'); + const disputeId = next?.next ?? 1; + const record = { + type: 'dispute', + dispute_id: disputeId, + status: 'open', + rail, + opened_by: this.address, + session_id: linkedSession.session_id, + reason: this.value.reason, + provider: linkedSession.provider, + counterparty: this.value.counterparty?.toLowerCase() ?? linkedSession.provider, + enclave_id: linkedSession.enclave_id, + reservation_key: this.spendHoldKey(this.address, rail, disputeEpoch), + reservation_voucher_hash: linkedSession.voucher_hash, + epoch: disputeEpoch, + at: this.value.at, + evidence_hash: this.value.evidence_hash ?? null, + evidence: cloneValue(this.value.evidence ?? null), + deposit_au: depositAu, + deposit_holder: this.address, + timeout_epochs: params.dispute_timeout_epochs, + expires_after_epoch: expiresAfterEpoch, + opened_at: this.tx, + updated_at: this.tx, + }; + record.dispute_hash = await this.opaqueHash('mayhem-dispute-v1', record); + + await this.put(this.balanceKey(this.address, rail), nextBalance); + await this.put(`disp/${disputeId}`, record); + await this.put(sessionDisputeKey, { + opener: this.address, + session_id: linkedSession.session_id, + dispute_id: disputeId, + opened_at: this.tx, + }); + await this.put('disp/next', { next: disputeId + 1, updated_at: this.tx }); + await this.put(openerCountKey, { + opener: this.address, + count: openerOpenCount + 1, + updated_at: this.tx, + }); + await this.put(providerCountKey, { + provider: linkedSession.provider, + count: providerOpenCount + 1, + updated_at: this.tx, + }); + console.log('mayhem dispute', record); + return { + ok: true, + op: 'dispute', + dispute_id: disputeId, + deposit_au: depositAu, + expires_after_epoch: expiresAfterEpoch, + dispute_hash: record.dispute_hash, + }; + } + + async disputeResolve() { + const adminError = await this.requireAdmin(); + if (adminError) return adminError; + if (!DISPUTE_OUTCOMES.has(this.value.outcome)) return new Error('Unsupported dispute outcome.'); + if (!DISPUTE_DEPOSIT_ACTIONS.has(this.value.deposit_action)) { + return new Error('Unsupported dispute deposit action.'); + } + + const key = `disp/${this.value.dispute_id}`; + const dispute = await this.get(key); + if (!dispute || dispute.type !== 'dispute') return new Error('Dispute not found.'); + if (dispute.status !== 'open') return new Error('Dispute is not open.'); + if (this.value.beneficiary !== undefined && !this.isSafeKeyPart(this.value.beneficiary)) { + return new Error('Invalid slash beneficiary.'); + } + if (this.value.outcome === 'provider_fault' && !dispute.provider) { + return new Error('Provider fault disputes require a provider.'); + } + if (this.value.outcome === 'provider_fault') { + const provider = await this.get(`prov/${dispute.provider}`); + if (!provider) return new Error('Provider not found.'); + } + if (this.value.slash === true && !dispute.provider) { + return new Error('Dispute slash requires a provider.'); + } + if (this.value.slash === true && this.value.outcome !== 'provider_fault') { + return new Error('Only provider_fault disputes may slash a provider.'); + } + + const applyState = await this.epochApplyStateRecord(); + if (applyState.updated_epoch > dispute.expires_after_epoch) { + return new Error('Dispute resolution window has closed; expire the dispute.'); + } + if ( + this.value.outcome === 'opener_fault' && + this.value.deposit_action !== 'partial_forfeit' + ) { + return new Error('Opener-fault disputes require a partial deposit forfeit.'); + } + if ( + this.value.outcome !== 'opener_fault' && + this.value.deposit_action === 'partial_forfeit' + ) { + return new Error('Partial deposit forfeit is reserved for opener-fault disputes.'); + } + + let depositRefundedAu = ZERO_AU; + let depositForfeitedAu = ZERO_AU; + const rail = this.normalizeLedgerRail(dispute.rail, 'dispute rail'); + if (rail instanceof Error) return rail; + if (this.value.deposit_action === 'refund') { + depositRefundedAu = dispute.deposit_au; + } else if (this.value.deposit_action === 'forfeit') { + depositForfeitedAu = dispute.deposit_au; + } else { + const params = await this.activeParamsAt(this.value.at, [ + 'dispute_opener_fault_forfeit_bps', + ]); + const deposit = this.parseAu(dispute.deposit_au, 'dispute deposit', { allowZero: false }); + if (deposit instanceof Error) return deposit; + if (deposit < 2n) return new Error('Dispute deposit is too small to split.'); + let forfeited = (deposit * BigInt(params.dispute_opener_fault_forfeit_bps)) / 10_000n; + if (forfeited < 1n) forfeited = 1n; + if (forfeited >= deposit) forfeited = deposit - 1n; + depositForfeitedAu = this.canonicalAu(forfeited); + depositRefundedAu = this.safeSubAu(dispute.deposit_au, depositForfeitedAu); + if (depositRefundedAu instanceof Error) return depositRefundedAu; + } + if (this.compareAu(depositRefundedAu, ZERO_AU) > 0) { + const balance = await this.balanceRecord(dispute.opened_by, rail); + if (balance instanceof Error) return balance; + const balanceError = this.guardianValidateBalanceRecord(balance, dispute.opened_by, rail); + if (balanceError) return balanceError; + const nextAu = this.safeAddAu(balance.au, depositRefundedAu); + if (nextAu instanceof Error) return nextAu; + await this.put(this.balanceKey(dispute.opened_by, rail), { + ...balance, + rail, + au: nextAu, + updated_epoch: Math.max(balance.updated_epoch, dispute.epoch ?? 0), + updated_at: this.tx, + }); + } + if (this.compareAu(depositForfeitedAu, ZERO_AU) > 0) { + const fee = await this.feeCumRecord(rail); + if (fee instanceof Error) return fee; + const feeError = this.guardianValidateFeeRecord(fee, rail); + if (feeError) return feeError; + const cumAu = this.safeAddAu(fee.cum_au, depositForfeitedAu); + if (cumAu instanceof Error) return cumAu; + const settledCumAu = this.safeAddAu(fee.settled_cum_au ?? fee.cum_au, depositForfeitedAu); + if (settledCumAu instanceof Error) return settledCumAu; + const updatedFee = { + ...fee, + cum_au: cumAu, + settled_cum_au: settledCumAu, + updated_epoch: Math.max(fee.updated_epoch, dispute.epoch ?? 0), + updated_at: this.tx, + last_dispute_forfeit_at: this.tx, + }; + const updatedFeeError = this.guardianValidateFeeRecord(updatedFee, rail); + if (updatedFeeError) return updatedFeeError; + await this.put(this.feeCumKey(rail), updatedFee); + } + + let reputationEvent = null; + let slash = null; + if (this.value.outcome === 'provider_fault' && dispute.provider) { + reputationEvent = await this.appendReputationEvent({ + provider: dispute.provider, + event_id: `dispute-${dispute.dispute_id}-lost`, + kind: 'dispute_lost', + epoch: dispute.epoch ?? 0, + at: this.value.at, + paid_au: null, + max_spend_au: null, + evidence_hash: this.value.rationale_hash, + }); + if (reputationEvent instanceof Error) return reputationEvent; + + if (this.value.slash === true) { + const params = await this.activeParamsAt(this.value.at, ['dispute_lost_slash_bps']); + slash = await this.applyProviderSlash({ + providerId: dispute.provider, + source: 'dispute', + reason: 'dispute_lost', + evidenceHash: this.value.rationale_hash, + epoch: dispute.epoch ?? 0, + at: this.value.at, + slashBps: params.dispute_lost_slash_bps, + beneficiary: this.value.beneficiary ?? dispute.opened_by, + enclaveId: dispute.enclave_id, + eventId: reputationEvent.event_id, + banProvider: false, + tombstoneEnclave: false, + }); + if (slash instanceof Error) return slash; + } + } + + const resolved = { + ...dispute, + status: 'resolved', + outcome: this.value.outcome, + deposit_action: this.value.deposit_action, + rationale_hash: this.value.rationale_hash, + resolved_by: this.address, + resolved_at: this.tx, + resolved_at_seconds: this.value.at, + deposit_refunded_au: depositRefundedAu, + deposit_forfeited_au: depositForfeitedAu, + reputation_event: reputationEvent, + slash, + updated_at: this.tx, + }; + resolved.resolution_hash = await this.opaqueHash('mayhem-dispute-resolution-v1', resolved); + const countError = await this.closeDisputeCounts(dispute); + if (countError) return countError; + await this.put(key, resolved); + console.log('mayhem disputeResolve', resolved); + return { + ok: true, + op: 'disputeResolve', + dispute_id: dispute.dispute_id, + outcome: resolved.outcome, + deposit_action: resolved.deposit_action, + deposit_refunded_au: depositRefundedAu, + deposit_forfeited_au: depositForfeitedAu, + slash, + resolution_hash: resolved.resolution_hash, + }; + } + + async disputeExpire() { + const key = `disp/${this.value.dispute_id}`; + const dispute = await this.get(key); + if (!dispute || dispute.type !== 'dispute') return new Error('Dispute not found.'); + if (dispute.status !== 'open') return new Error('Dispute is not open.'); + if (!Number.isSafeInteger(dispute.expires_after_epoch) || dispute.expires_after_epoch < 0) { + return new Error('Dispute has no valid timeout epoch.'); + } + + const applyState = await this.epochApplyStateRecord(); + if (applyState.updated_epoch < dispute.expires_after_epoch) { + return new Error('Dispute timeout epoch not reached.'); + } + + const rail = this.normalizeLedgerRail(dispute.rail, 'dispute rail'); + if (rail instanceof Error) return rail; + const balance = await this.balanceRecord(dispute.opened_by, rail); + if (balance instanceof Error) return balance; + const balanceError = this.guardianValidateBalanceRecord(balance, dispute.opened_by, rail); + if (balanceError) return balanceError; + const depositRefundedAu = dispute.deposit_au; + const nextAu = this.safeAddAu(balance.au, depositRefundedAu); + if (nextAu instanceof Error) return nextAu; + const nextBalance = { + ...balance, + rail, + au: nextAu, + updated_epoch: Math.max(balance.updated_epoch, applyState.updated_epoch, dispute.epoch ?? 0), + updated_at: this.tx, + }; + const nextBalanceError = this.guardianValidateBalanceRecord(nextBalance, dispute.opened_by, rail); + if (nextBalanceError) return nextBalanceError; + + const expired = { + ...dispute, + status: 'expired', + outcome: 'timeout_refund', + deposit_action: 'refund', + deposit_holder: null, + expired_by: this.address, + expired_at: this.tx, + expired_at_epoch: applyState.updated_epoch, + expired_at_seconds: this.value.at, + deposit_refunded_au: depositRefundedAu, + deposit_forfeited_au: ZERO_AU, + reputation_event: null, + slash: null, + updated_at: this.tx, + }; + expired.expiry_hash = await this.opaqueHash('mayhem-dispute-expiry-v1', expired); + const countError = await this.closeDisputeCounts(dispute); + if (countError) return countError; + await this.put(this.balanceKey(dispute.opened_by, rail), nextBalance); + await this.put(key, expired); + console.log('mayhem disputeExpire', expired); + return { + ok: true, + op: 'disputeExpire', + dispute_id: dispute.dispute_id, + outcome: expired.outcome, + deposit_action: expired.deposit_action, + deposit_refunded_au: depositRefundedAu, + expired_at_epoch: expired.expired_at_epoch, + expiry_hash: expired.expiry_hash, + }; + } + + async rateOracle() { + this._mayhemApplyStage = 'rate:contract:require-admin'; + const adminError = await this.requireAdmin(); + if (adminError) return adminError; + const shapeError = this.validateRateOracleValue(this.value); + if (shapeError) return shapeError; + if (!RATE_SOURCES.has(this.value.source)) return new Error('Unsupported rate source.'); + + this._mayhemApplyStage = 'rate:contract:read-latest'; + const current = await this.get('rate/latest'); + if (current && this.value.ts < current.ts) { + return new Error('Rate timestamp must not decrease.'); + } + + const record = { + denom: 'tnk_usd_au', + tnk_usd_au: this.normalizeAu(this.value.tnk_usd_au, 'TNK/USD atto-rate', { allowZero: false }), + source: this.value.source, + ts: this.value.ts, + updated_at: this.tx, + posted_by: this.address, + posted_by_role: 'admin', + }; + this._mayhemApplyStage = 'rate:contract:read-history'; + const history = await this.get(this.tx); + if (history && stableJson(history) !== stableJson(record)) { + return new Error('TNK rate oracle history collision.'); + } + if (!history) { + this._mayhemApplyStage = 'rate:contract:write-history'; + await this.put(this.tx, record); + } + this._mayhemApplyStage = 'rate:contract:write-latest'; + await this.put('rate/latest', record); + console.log('mayhem rateOracle', record); + return { ok: true, op: 'rateOracle', ts: record.ts, source: record.source }; + } + + async tapRateOracle() { + this._mayhemApplyStage = 'rate:contract:require-admin'; + const adminError = await this.requireAdmin(); + if (adminError) return adminError; + const shapeError = this.validateTapRateOracleValue(this.value); + if (shapeError) return shapeError; + + this._mayhemApplyStage = 'rate:contract:read-latest'; + const current = await this.get('tap/rate/latest'); + if (current && this.value.ts < current.ts) { + return new Error('TAP rate timestamp must not decrease.'); + } + + const record = { + denom: 'tap_usd_au', + tap_usd_au: this.normalizeAu(this.value.tap_usd_au, 'TAP/USD atto-rate', { allowZero: false }), + source: this.value.source, + ts: this.value.ts, + updated_at: this.tx, + posted_by: this.address, + posted_by_role: 'admin', + }; + this._mayhemApplyStage = 'rate:contract:read-history'; + const history = await this.get(this.tx); + if (history && stableJson(history) !== stableJson(record)) { + return new Error('TAP rate oracle history collision.'); + } + if (!history) { + this._mayhemApplyStage = 'rate:contract:write-history'; + await this.put(this.tx, record); + } + this._mayhemApplyStage = 'rate:contract:write-latest'; + await this.put('tap/rate/latest', record); + console.log('mayhem tapRateOracle', record); + return { ok: true, op: 'tapRateOracle', ts: record.ts, source: record.source }; + } + + validateRateOracleValue(value) { + const shapeError = this.validateExactObjectKeys( + value, + ['op', 'tnk_usd_au', 'source', 'ts'], + 'rate oracle' + ); + if (shapeError) return shapeError; + if (value.op !== 'rate_oracle') return new Error('Invalid rate oracle op.'); + const rate = this.normalizeAu(value.tnk_usd_au, 'TNK/USD atto-rate', { allowZero: false }); + if (rate instanceof Error) { + return new Error('Invalid TNK/USD atto-rate.'); + } + if (typeof value.source !== 'string' || value.source.length < 1 || value.source.length > 64) { + return new Error('Invalid rate source.'); + } + if (!Number.isSafeInteger(value.ts) || value.ts < 0) { + return new Error('Invalid rate timestamp.'); + } + return null; + } + + validateTapRateOracleValue(value) { + const shapeError = this.validateExactObjectKeys( + value, + ['op', 'tap_usd_au', 'source', 'ts'], + 'TAP rate oracle' + ); + if (shapeError) return shapeError; + if (value.op !== 'tap_rate_oracle') return new Error('Invalid TAP rate oracle op.'); + const rate = this.normalizeAu(value.tap_usd_au, 'TAP/USD atto-rate', { allowZero: false }); + if (rate instanceof Error) { + return new Error('Invalid TAP/USD atto-rate.'); + } + if (typeof value.source !== 'string' + || !/^[a-z0-9][a-z0-9._-]{0,63}$/.test(value.source)) { + return new Error('Invalid TAP rate source.'); + } + if (!Number.isSafeInteger(value.ts) || value.ts < 0) { + return new Error('Invalid TAP rate timestamp.'); + } + return null; + } + + async canonicalTnkPaymentConfig() { + const payments = await this.get('payments/current'); + if (!payments || payments.set_by_role !== 'admin' || !payments.tnk) { + return new Error('Canonical TNK payment config required.'); + } + if (!['mainnet', 'testnet1'].includes(payments.tnk.network)) { + return new Error('Canonical TNK payment network is invalid.'); + } + if (!this.isSafeKeyPart(payments.tnk.treasury_address)) { + return new Error('Canonical TNK treasury address is invalid.'); + } + return payments.tnk; + } + + async canonicalTapPaymentConfig({ optional = false } = {}) { + const payments = await this.get('payments/current'); + if (!payments && optional) return null; + if (!payments || payments.set_by_role !== 'admin' || !payments.tap) { + return new Error('Canonical TAP payment config required.'); + } + if (!Number.isSafeInteger(payments.tap.chain_id) || payments.tap.chain_id < 1) { + return new Error('Canonical TAP chain id is invalid.'); + } + if (!this.isEthHexBytes(payments.tap.pool_address, 20)) { + return new Error('Canonical TAP pool address is invalid.'); + } + return { + chain_id: payments.tap.chain_id, + pool_address: payments.tap.pool_address.toLowerCase(), + }; + } + + async requireCanonicalTapPool(chainId, poolAddress) { + const tap = await this.canonicalTapPaymentConfig(); + if (tap instanceof Error) return tap; + if ( + chainId !== tap.chain_id || + typeof poolAddress !== 'string' || + poolAddress.toLowerCase() !== tap.pool_address + ) { + return new Error('TAP operation does not match the canonical payment pool.'); + } + return null; + } + + guardianValidateTapScope(record, amountFields, label) { + const hasChain = record.chain_id !== undefined && record.chain_id !== null; + const hasPool = record.pool_address !== undefined && record.pool_address !== null; + if (!hasChain && !hasPool) { + for (const field of amountFields) { + if (this.compareAu(record[field] ?? ZERO_AU, ZERO_AU) !== 0) { + return new Error(`Guardian TAP ${label} scope invariant failed.`); + } + } + return null; + } + if (!Number.isSafeInteger(record.chain_id) || record.chain_id < 1) { + return new Error(`Guardian TAP ${label} chain invariant failed.`); + } + if (!this.isEthHexBytes(record.pool_address, 20)) { + return new Error(`Guardian TAP ${label} pool invariant failed.`); + } + return null; + } + + msbAddressForPublicKey(publicKey, network) { + if (!this.isHexBytes(publicKey, 32)) return new Error('Invalid MSB owner public key.'); + const prefix = network === 'mainnet' + ? 'trac' + : network === 'testnet1' + ? 'testtrac' + : null; + if (!prefix) return new Error('Invalid MSB network.'); + const address = PeerWallet.encodeBech32mSafe(prefix, b4a.from(publicKey, 'hex')); + return typeof address === 'string' && address.length > 0 + ? address + : new Error('Unable to derive MSB owner address.'); + } + + normalizeMsbTransferEvidence(value, label = 'MSB transfer evidence') { + const shapeError = this.validateExactObjectKeys( + value, + [ + 'schema_version', + 'network', + 'tx_hash', + 'confirmed_length', + 'observed_signed_length', + 'from', + 'to', + 'amount_e18', + ], + label + ); + if (shapeError) return shapeError; + if (value.schema_version !== MSB_TRANSFER_EVIDENCE_VERSION) { + return new Error(`${label} schema version is invalid.`); + } + if (!['mainnet', 'testnet1'].includes(value.network)) { + return new Error(`${label} network is invalid.`); + } + if (!this.isHexBytes(value.tx_hash, 32) || value.tx_hash !== value.tx_hash.toLowerCase()) { + return new Error(`${label} transaction hash is invalid.`); + } + if (!Number.isSafeInteger(value.confirmed_length) || value.confirmed_length < 1) { + return new Error(`${label} confirmed length is invalid.`); + } + if ( + !Number.isSafeInteger(value.observed_signed_length) || + value.observed_signed_length < value.confirmed_length + ) { + return new Error(`${label} observed signed length is invalid.`); + } + if (!this.isSafeKeyPart(value.from) || !this.isSafeKeyPart(value.to)) { + return new Error(`${label} address is invalid.`); + } + const amount = this.parseTnkE18(value.amount_e18); + if (amount instanceof Error) return new Error(`${label} amount is invalid.`); + return { + schema_version: MSB_TRANSFER_EVIDENCE_VERSION, + network: value.network, + tx_hash: value.tx_hash, + confirmed_length: value.confirmed_length, + observed_signed_length: value.observed_signed_length, + from: value.from, + to: value.to, + amount_e18: amount.toString(), + }; + } + + msbTransferSeenKey(evidence) { + return `rail/seen/msb/${evidence.network}/${evidence.tx_hash}`; + } + + normalizeStripeTransferEvidence( + value, + label = 'Stripe settlement evidence', + { expectedAttemptId = null } = {} + ) { + const providerTransfer = value?.kind === 'stripe_transfer'; + const hasAttemptId = hasOwn(value, 'attempt_id'); + const hasFxQuoteId = providerTransfer && hasOwn(value, 'fx_quote_id'); + const hasFxQuoteHash = providerTransfer && hasOwn(value, 'fx_quote_hash'); + if (hasFxQuoteId !== hasFxQuoteHash) { + return new Error(`${label} FX quote identity must be present or absent as a pair.`); + } + if ((hasAttemptId && + (!this.isHexBytes(value.attempt_id, 32) || + value.attempt_id !== value.attempt_id.toLowerCase())) || + (expectedAttemptId !== null && + (!hasAttemptId || value.attempt_id !== expectedAttemptId))) { + return new Error(`${label} attempt id does not match its canonical attempt.`); + } + const shapeError = this.validateExactObjectKeys(value, providerTransfer + ? [ + 'schema_version', + 'kind', + ...(hasAttemptId ? ['attempt_id'] : []), + 'ref', + 'destination', + 'source_currency', + 'source_amount_minor', + 'destination_currency', + 'destination_amount_minor', + ...(hasFxQuoteId ? ['fx_quote_id', 'fx_quote_hash'] : []), + 'destination_payment', + 'transfer_group', + ] + : [ + 'schema_version', + 'kind', + ...(hasAttemptId ? ['attempt_id'] : []), + 'ref', + 'destination', + 'source_currency', + 'source_amount_minor', + 'transfer_group', + ], label); + if (shapeError) return shapeError; + if (value.schema_version !== STRIPE_TRANSFER_EVIDENCE_VERSION) { + return new Error(`${label} schema version is invalid.`); + } + if (!['stripe_transfer', 'platform_balance'].includes(value.kind)) { + return new Error(`${label} kind is invalid.`); + } + if (!this.isSafeKeyPart(value.ref) || !this.isSafeKeyPart(value.destination)) { + return new Error(`${label} reference or destination is invalid.`); + } + if (value.kind === 'stripe_transfer' && !value.ref.startsWith('tr_')) { + return new Error(`${label} requires a Stripe transfer id.`); + } + if (value.kind === 'platform_balance' && !value.ref.startsWith('platform_balance:')) { + return new Error(`${label} platform balance reference is invalid.`); + } + const sourceCurrency = this.normalizeFiatCurrency(value.source_currency); + if (sourceCurrency instanceof Error || sourceCurrency !== value.source_currency) { + return new Error(`${label} source currency is invalid.`); + } + const sourceAmountMinor = this.normalizeFiatMinor(value.source_amount_minor); + if (sourceAmountMinor instanceof Error || sourceAmountMinor === '0') { + return new Error(`${label} source amount must be positive.`); + } + if (providerTransfer && + (typeof value.transfer_group !== 'string' || !this.isSafeKeyPart(value.transfer_group))) { + return new Error(`${label} transfer group is invalid.`); + } + if (!providerTransfer && value.transfer_group !== null) { + return new Error(`${label} platform balance transfer group must be null.`); + } + if (!providerTransfer) return { + schema_version: STRIPE_TRANSFER_EVIDENCE_VERSION, + kind: 'platform_balance', + ...(hasAttemptId ? { attempt_id: value.attempt_id } : {}), + ref: value.ref, + destination: value.destination, + source_currency: sourceCurrency, + source_amount_minor: sourceAmountMinor, + transfer_group: null, + }; + + const destinationCurrency = this.normalizeFiatCurrency(value.destination_currency); + if (destinationCurrency instanceof Error || + destinationCurrency !== value.destination_currency) { + return new Error(`${label} destination currency is invalid.`); + } + const destinationAmountMinor = this.normalizeFiatMinor(value.destination_amount_minor); + if (destinationAmountMinor instanceof Error || destinationAmountMinor === '0') { + return new Error(`${label} destination amount must be positive.`); + } + const requiresUsdValuationQuote = + sourceCurrency !== 'usd' || destinationCurrency !== 'usd'; + if (requiresUsdValuationQuote && + (!hasFxQuoteId || + !/^fxq_[A-Za-z0-9._-]+$/.test(String(value.fx_quote_id || '')) || + !this.isHexBytes(value.fx_quote_hash, 32) || + value.fx_quote_hash !== value.fx_quote_hash.toLowerCase())) { + return new Error(`${label} FX quote identity is required and invalid.`); + } + if (!requiresUsdValuationQuote && hasFxQuoteId && + (value.fx_quote_id !== null || value.fx_quote_hash !== null)) { + return new Error(`${label} direct USD transfer must not include an FX quote identity.`); + } + if (!/^py_[A-Za-z0-9._-]+$/.test(String(value.destination_payment || ''))) { + return new Error(`${label} destination payment readback is invalid.`); + } + const normalized = { + schema_version: STRIPE_TRANSFER_EVIDENCE_VERSION, + kind: 'stripe_transfer', + ...(hasAttemptId ? { attempt_id: value.attempt_id } : {}), + ref: value.ref, + destination: value.destination, + source_currency: sourceCurrency, + source_amount_minor: sourceAmountMinor, + destination_currency: destinationCurrency, + destination_amount_minor: destinationAmountMinor, + destination_payment: value.destination_payment, + transfer_group: value.transfer_group, + }; + if (hasFxQuoteId) { + normalized.fx_quote_id = value.fx_quote_id; + normalized.fx_quote_hash = value.fx_quote_hash; + } + return normalized; + } + + stripeTransferSeenKey(evidence) { + return evidence.kind === 'stripe_transfer' + ? `rail/seen/stripe/${evidence.ref}` + : `rail/seen/stripe-platform/${evidence.ref}`; + } + + stripeFxQuoteSeenKey(evidence) { + return evidence.kind === 'stripe_transfer' && evidence.fx_quote_id != null + ? `rail/seen/stripe-fx-quote/${evidence.fx_quote_id}` + : null; + } + + stripeDestinationPaymentSeenKey(evidence) { + return evidence.kind === 'stripe_transfer' + ? `rail/seen/stripe-destination-payment/${evidence.destination_payment}` + : null; + } + + normalizeTargetedTapRateLock(value) { + const shapeError = this.validateExactObjectKeys( + value, + [ + 'type', + 'epoch', + 'bundle_sha256', + 'denom', + 'tap_usd_au', + 'source', + 'rate_ts', + 'rate_record_key', + 'posted_by', + 'posted_by_role', + 'chain_id', + 'token_address', + 'pool_address', + 'payment_config_ver', + ], + 'targeted TAP settlement rate lock' + ); + if (shapeError) return shapeError; + const tapUsdAu = this.normalizeAu( + value.tap_usd_au, + 'targeted TAP settlement rate', + { allowZero: false } + ); + if (tapUsdAu instanceof Error || + value.type !== 'tap_settlement_rate_lock' || + value.denom !== 'tap_usd_au' || + !Number.isSafeInteger(value.epoch) || + value.epoch < 1 || + !this.isHexBytes(value.bundle_sha256, 32) || + typeof value.source !== 'string' || + !/^[a-z0-9][a-z0-9._-]{0,63}$/.test(value.source) || + !Number.isSafeInteger(value.rate_ts) || + value.rate_ts < 0 || + typeof value.rate_record_key !== 'string' || + !value.rate_record_key.startsWith(`rate/tap/${value.rate_ts}/`) || + !this.isHexBytes(value.rate_record_key.slice(`rate/tap/${value.rate_ts}/`.length), 32) || + !this.isHexBytes(value.posted_by, 32) || + value.posted_by_role !== 'admin' || + !Number.isSafeInteger(value.chain_id) || + value.chain_id < 1 || + !this.isEthHexBytes(value.token_address, 20) || + value.token_address !== value.token_address.toLowerCase() || + !this.isEthHexBytes(value.pool_address, 20) || + value.pool_address !== value.pool_address.toLowerCase() || + !Number.isSafeInteger(value.payment_config_ver) || + value.payment_config_ver < 1) { + return new Error('Invalid targeted TAP settlement rate lock.'); + } + return { + type: value.type, + epoch: value.epoch, + bundle_sha256: value.bundle_sha256.toLowerCase(), + denom: value.denom, + tap_usd_au: tapUsdAu, + source: value.source, + rate_ts: value.rate_ts, + rate_record_key: value.rate_record_key, + posted_by: value.posted_by.toLowerCase(), + posted_by_role: value.posted_by_role, + chain_id: value.chain_id, + token_address: value.token_address.toLowerCase(), + pool_address: value.pool_address.toLowerCase(), + payment_config_ver: value.payment_config_ver, + }; + } + + normalizeTargetedTapSettlementEntry(value) { + const shapeError = this.validateExactObjectKeys( + value, + ['account', 'cumulative_wei'], + 'targeted TAP settlement distribution entry' + ); + if (shapeError) return shapeError; + if (!this.isEthHexBytes(value.account, 20) || + value.account !== value.account.toLowerCase()) { + return new Error('Invalid targeted TAP settlement distribution account.'); + } + const cumulativeWei = this.parseTapWei(value.cumulative_wei); + if (cumulativeWei instanceof Error) { + return new Error('Invalid targeted TAP settlement cumulative claim.'); + } + return { + account: value.account, + cumulative_wei: cumulativeWei.toString(), + }; + } + + normalizeTargetedTapSettlementOutput(value, tapUsdAu) { + const shapeError = this.validateExactObjectKeys( + value, + [ + 'provider', + 'payout_revision', + 'to', + 'paid_cum_au_before', + 'aggregate_paid_cum_au_before', + 'paid_au', + 'tap_wei', + 'prior_cumulative_claim_wei', + 'cumulative_claim_wei', + ], + 'targeted TAP provider output' + ); + if (shapeError) return shapeError; + const paidAu = this.normalizeAu( + value.paid_au, + 'targeted TAP provider paid amount', + { allowZero: false } + ); + const paidCumAuBefore = this.normalizeAu( + value.paid_cum_au_before, + 'targeted TAP provider paid cumulative watermark', + { allowZero: true } + ); + const aggregatePaidCumAuBefore = this.normalizeAu( + value.aggregate_paid_cum_au_before, + 'targeted TAP provider aggregate paid cumulative watermark', + { allowZero: true } + ); + const tapWei = this.parseTapWei(value.tap_wei); + const priorCumulativeClaimWei = this.parseTapWei( + value.prior_cumulative_claim_wei, + { allowZero: true } + ); + const cumulativeClaimWei = this.parseTapWei(value.cumulative_claim_wei); + if (!this.isHexBytes(value.provider, 32) || + value.provider !== value.provider.toLowerCase() || + !this.isHexBytes(value.payout_revision, 32) || + value.payout_revision !== value.payout_revision.toLowerCase() || + !this.isEthHexBytes(value.to, 20) || + value.to !== value.to.toLowerCase() || + paidAu instanceof Error || + paidCumAuBefore instanceof Error || + aggregatePaidCumAuBefore instanceof Error || + tapWei instanceof Error || + priorCumulativeClaimWei instanceof Error || + cumulativeClaimWei instanceof Error) { + return new Error('Invalid targeted TAP provider output.'); + } + const rate = this.parseAu( + tapUsdAu, + 'targeted TAP settlement rate', + { allowZero: false } + ); + if (rate instanceof Error) return rate; + const expectedTapWei = (this.parseAu(paidAu, 'targeted TAP provider paid amount') * TAP_WEI) / rate; + if (expectedTapWei <= 0n || tapWei !== expectedTapWei) { + return new Error('Targeted TAP provider output does not match the locked rate.'); + } + if (cumulativeClaimWei !== priorCumulativeClaimWei + tapWei) { + return new Error('Targeted TAP provider output cumulative claim chain is invalid.'); + } + return { + provider: value.provider, + payout_revision: value.payout_revision, + to: value.to, + paid_cum_au_before: paidCumAuBefore, + aggregate_paid_cum_au_before: aggregatePaidCumAuBefore, + paid_au: paidAu, + tap_wei: tapWei.toString(), + prior_cumulative_claim_wei: priorCumulativeClaimWei.toString(), + cumulative_claim_wei: cumulativeClaimWei.toString(), + }; + } + + targetedTapSettlementLeaf(entry) { + const encoded = b4a.alloc(64); + encoded.set(b4a.from(entry.account.slice(2), 'hex'), 12); + const amount = BigInt(entry.cumulative_wei); + if (amount < 0n || amount >= (1n << 256n)) { + return new Error('Targeted TAP settlement cumulative claim exceeds uint256.'); + } + const amountHex = amount.toString(16).padStart(64, '0'); + encoded.set(b4a.from(amountHex, 'hex'), 32); + return keccak256(keccak256(encoded)); + } + + targetedTapSettlementRoot(entries) { + if (!Array.isArray(entries) || entries.length === 0) { + return new Error('Targeted TAP settlement requires distribution entries.'); + } + const leaves = []; + for (const entry of entries) { + const leaf = this.targetedTapSettlementLeaf(entry); + if (leaf instanceof Error) return leaf; + leaves.push(b4a.toString(leaf, 'hex')); + } + leaves.sort(compareCodepoint); + const tree = new Array(2 * leaves.length - 1); + for (const [index, leaf] of leaves.entries()) { + tree[tree.length - 1 - index] = leaf; + } + for (let index = tree.length - 1 - leaves.length; index >= 0; index -= 1) { + const left = tree[2 * index + 1]; + const right = tree[2 * index + 2]; + const [first, second] = compareCodepoint(left, right) <= 0 + ? [left, right] + : [right, left]; + tree[index] = b4a.toString( + keccak256(b4a.concat([b4a.from(first, 'hex'), b4a.from(second, 'hex')])), + 'hex' + ); + } + return `0x${tree[0]}`; + } + + normalizeTargetedTapSettlementValue(value) { + const shapeError = this.validateExactObjectKeys( + value, + [ + 'op', + 'epoch', + 'at', + 'rail', + 'chain_id', + 'token_address', + 'pool_address', + 'payment_config_ver', + 'epoch_apply_hash', + 'preparation_ids', + 'root_preparation_id', + 'external_effect_ids', + 'tap_rate_lock', + 'root', + 'root_confirmed', + 'proposal_tx', + 'proposal_block_number', + 'proposal_block_hash', + 'execution_tx', + 'execution_status', + 'execution_block_number', + 'execution_block_hash', + 'finalized_block_number', + 'confirmation_depth', + 'confirmation_policy', + 'cumulative_spent_wei', + 'provider_cumulative_claimed_wei', + 'buyer_refund_wei', + 'fee_bps', + 'tap_burn_bps', + 'provider_share_bps', + 'provider_count', + 'provider_paid_au', + 'provider_tap_wei', + 'provider_entries', + 'refunds', + 'entries', + 'outputs', + ], + 'targeted TAP settlement' + ); + if (shapeError) return shapeError; + const rateLock = this.normalizeTargetedTapRateLock(value.tap_rate_lock); + if (rateLock instanceof Error) return rateLock; + if (value.op !== 'settle_targeted_tap' || + value.rail !== 'tap' || + !Number.isSafeInteger(value.epoch) || + value.epoch < 1 || + !Number.isSafeInteger(value.at) || + value.at < 0 || + !Number.isSafeInteger(value.chain_id) || + value.chain_id < 1 || + !this.isEthHexBytes(value.token_address, 20) || + value.token_address !== value.token_address.toLowerCase() || + !this.isEthHexBytes(value.pool_address, 20) || + value.pool_address !== value.pool_address.toLowerCase() || + !Number.isSafeInteger(value.payment_config_ver) || + value.payment_config_ver < 1 || + !this.isHexBytes(value.epoch_apply_hash, 32) || + !Array.isArray(value.preparation_ids) || + value.preparation_ids.length < 1 || + !this.isHexBytes(value.root_preparation_id, 32) || + !Array.isArray(value.external_effect_ids) || + value.external_effect_ids.length !== 2 || + !this.isEthHexBytes(value.root, 32) || + value.root !== value.root.toLowerCase() || + value.root_confirmed !== true || + !this.isEthHexBytes(value.proposal_tx, 32) || + value.proposal_tx !== value.proposal_tx.toLowerCase() || + !Number.isSafeInteger(value.proposal_block_number) || + value.proposal_block_number < 0 || + !this.isEthHexBytes(value.proposal_block_hash, 32) || + value.proposal_block_hash !== value.proposal_block_hash.toLowerCase() || + !this.isEthHexBytes(value.execution_tx, 32) || + value.execution_tx !== value.execution_tx.toLowerCase() || + value.execution_status !== 1 || + !Number.isSafeInteger(value.execution_block_number) || + value.execution_block_number < value.proposal_block_number || + !this.isEthHexBytes(value.execution_block_hash, 32) || + value.execution_block_hash !== value.execution_block_hash.toLowerCase() || + !Number.isSafeInteger(value.finalized_block_number) || + value.finalized_block_number < value.execution_block_number || + !Number.isSafeInteger(value.confirmation_depth) || + value.confirmation_depth !== value.finalized_block_number - value.execution_block_number || + value.confirmation_depth < MIN_TAP_CONFIRMATION_DEPTH || + !this.isSafeKeyPart(value.confirmation_policy) || + (value.chain_id === 1 && value.confirmation_policy !== 'finalized-tag') || + (value.confirmation_policy !== 'finalized-tag' && + value.confirmation_policy !== `depth-${value.confirmation_depth}`) || + value.proposal_tx === value.execution_tx || + !Number.isSafeInteger(value.provider_count) || + value.provider_count < 1 || + !Number.isSafeInteger(value.fee_bps) || + value.fee_bps < 0 || + !Number.isSafeInteger(value.tap_burn_bps) || + value.tap_burn_bps !== TAP_BURN_BPS || + !Number.isSafeInteger(value.provider_share_bps) || + value.provider_share_bps <= 0 || + value.fee_bps + value.tap_burn_bps + value.provider_share_bps !== 10_000 || + !Array.isArray(value.entries) || + value.entries.length < 1 || + !Array.isArray(value.provider_entries) || + value.provider_entries.length < 1 || + !Array.isArray(value.refunds) || + !Array.isArray(value.outputs) || + value.outputs.length < 1) { + return new Error('Invalid targeted TAP settlement.'); + } + if (value.preparation_ids.some((entry) => !this.isHexBytes(entry, 32)) || + new Set(value.preparation_ids).size !== value.preparation_ids.length || + value.external_effect_ids.some((entry) => !this.isHexBytes(entry, 32)) || + new Set(value.external_effect_ids).size !== 2) { + return new Error('Invalid targeted TAP settlement preparation identities.'); + } + if (rateLock.epoch !== value.epoch || + rateLock.chain_id !== value.chain_id || + rateLock.token_address !== value.token_address || + rateLock.pool_address !== value.pool_address || + rateLock.payment_config_ver !== value.payment_config_ver) { + return new Error('Targeted TAP settlement rate lock scope mismatch.'); + } + const cumulativeSpentWei = this.parseTapWei(value.cumulative_spent_wei); + const providerCumulativeClaimedWei = this.parseTapWei( + value.provider_cumulative_claimed_wei + ); + const buyerRefundWei = this.parseTapWei(value.buyer_refund_wei, { + allowZero: true, + }); + const providerPaidAu = this.normalizeAu( + value.provider_paid_au, + 'targeted TAP provider paid total', + { allowZero: false } + ); + const providerTapWei = this.parseTapWei(value.provider_tap_wei); + if (cumulativeSpentWei instanceof Error || + providerCumulativeClaimedWei instanceof Error || + buyerRefundWei instanceof Error || + providerPaidAu instanceof Error || + providerTapWei instanceof Error) { + return new Error('Invalid targeted TAP settlement totals.'); + } + const entries = []; + const seenAccounts = new Set(); + for (const rawEntry of value.entries) { + const entry = this.normalizeTargetedTapSettlementEntry(rawEntry); + if (entry instanceof Error) return entry; + if (seenAccounts.has(entry.account)) { + return new Error('Duplicate targeted TAP settlement distribution account.'); + } + seenAccounts.add(entry.account); + entries.push(entry); + } + entries.sort((left, right) => compareCodepoint(left.account, right.account)); + if (stableJson(entries) !== stableJson(value.entries)) { + return new Error('Targeted TAP settlement distribution entries must be canonical.'); + } + const normalizeClaimEntries = (rawEntries, label) => { + const normalizedEntries = []; + const seen = new Set(); + for (const rawEntry of rawEntries) { + const entry = this.normalizeTargetedTapSettlementEntry(rawEntry); + if (entry instanceof Error) return entry; + if (seen.has(entry.account)) { + return new Error(`Duplicate targeted TAP ${label} account.`); + } + seen.add(entry.account); + normalizedEntries.push(entry); + } + normalizedEntries.sort((left, right) => compareCodepoint(left.account, right.account)); + if (stableJson(normalizedEntries) !== stableJson(rawEntries)) { + return new Error(`Targeted TAP ${label} entries must be canonical.`); + } + return normalizedEntries; + }; + const providerEntries = normalizeClaimEntries( + value.provider_entries, + 'provider claim' + ); + if (providerEntries instanceof Error) return providerEntries; + const refunds = normalizeClaimEntries(value.refunds, 'buyer refund'); + if (refunds instanceof Error) return refunds; + const providerEntryTotal = providerEntries.reduce( + (sum, entry) => sum + BigInt(entry.cumulative_wei), + 0n + ); + const refundTotal = refunds.reduce( + (sum, entry) => sum + BigInt(entry.cumulative_wei), + 0n + ); + if (providerEntryTotal !== providerCumulativeClaimedWei || + refundTotal !== buyerRefundWei) { + return new Error('Targeted TAP provider/refund totals do not match claim entries.'); + } + const combinedClaims = new Map(); + for (const entry of [...providerEntries, ...refunds]) { + combinedClaims.set( + entry.account, + (combinedClaims.get(entry.account) ?? 0n) + BigInt(entry.cumulative_wei) + ); + } + const combinedEntries = [...combinedClaims.entries()] + .map(([account, cumulativeWei]) => ({ + account, + cumulative_wei: cumulativeWei.toString(), + })) + .sort((left, right) => compareCodepoint(left.account, right.account)); + if (stableJson(combinedEntries) !== stableJson(entries)) { + return new Error('Targeted TAP executed entries do not merge provider and refund claims.'); + } + const outputs = []; + const seenLiabilities = new Set(); + for (const rawOutput of value.outputs) { + const output = this.normalizeTargetedTapSettlementOutput( + rawOutput, + rateLock.tap_usd_au + ); + if (output instanceof Error) return output; + const identity = `${output.provider}/${output.payout_revision}`; + if (seenLiabilities.has(identity)) { + return new Error('Duplicate targeted TAP settlement payout liability.'); + } + seenLiabilities.add(identity); + outputs.push(output); + } + outputs.sort((left, right) => ( + compareCodepoint(left.provider, right.provider) || + compareCodepoint(left.payout_revision, right.payout_revision) + )); + if (stableJson(outputs) !== stableJson(value.outputs)) { + return new Error('Targeted TAP settlement outputs must be canonical.'); + } + const paidTotal = outputs.reduce( + (sum, output) => sum + this.parseAu(output.paid_au, 'targeted TAP paid output'), + 0n + ); + const tapTotal = outputs.reduce( + (sum, output) => sum + BigInt(output.tap_wei), + 0n + ); + if (value.provider_count !== outputs.length || + paidTotal.toString() !== providerPaidAu || + tapTotal !== providerTapWei) { + return new Error('Targeted TAP settlement totals do not match outputs.'); + } + const entryMap = new Map(providerEntries.map((entry) => [entry.account, entry])); + const targetCursors = new Map(); + for (const output of outputs) { + const prior = targetCursors.get(output.to); + if (prior !== undefined && + prior !== output.prior_cumulative_claim_wei) { + return new Error('Targeted TAP outputs do not form a canonical per-target claim chain.'); + } + targetCursors.set(output.to, output.cumulative_claim_wei); + } + for (const [target, cumulativeClaimWei] of targetCursors) { + if (entryMap.get(target)?.cumulative_wei !== cumulativeClaimWei) { + return new Error('Targeted TAP output claim chain does not match the executed root entry.'); + } + } + const root = this.targetedTapSettlementRoot(entries); + if (root instanceof Error || root !== value.root) { + return new Error('Targeted TAP settlement root mismatch.'); + } + return { + rate_lock: rateLock, + entries, + provider_entries: providerEntries, + refunds, + outputs, + cumulative_spent_wei: cumulativeSpentWei.toString(), + provider_cumulative_claimed_wei: providerCumulativeClaimedWei.toString(), + buyer_refund_wei: buyerRefundWei.toString(), + fee_bps: value.fee_bps, + tap_burn_bps: value.tap_burn_bps, + provider_share_bps: value.provider_share_bps, + provider_paid_au: providerPaidAu, + provider_tap_wei: providerTapWei.toString(), + }; + } + + normalizeTargetedTnkSettlementValue(value) { + const shapeError = this.validateExactObjectKeys( + value, + [ + 'op', + 'epoch', + 'at', + 'rail', + 'network', + 'treasury_from', + 'operator_to', + 'epoch_apply_hash', + 'preparation_ids', + 'external_effect_ids', + 'rate_tnk_usd_au', + 'rate_source', + 'rate_ts', + 'msb_transfers', + 'transfer_root', + 'provider_count', + 'provider_au', + 'operator_fee_au', + 'gross_au', + 'tnk_e18', + 'outputs', + ], + 'targeted TNK settlement' + ); + if (shapeError) return shapeError; + if (value.op !== 'settle_targeted_tnk' || value.rail !== 'tnk' || + !Number.isSafeInteger(value.epoch) || value.epoch < 1 || + !Number.isSafeInteger(value.at) || value.at < 0 || + !this.isSafeKeyPart(value.network) || + !this.isSafeKeyPart(value.treasury_from) || + !this.isSafeKeyPart(value.operator_to) || + !this.isHexBytes(value.epoch_apply_hash, 32) || + !Array.isArray(value.preparation_ids) || + !Array.isArray(value.external_effect_ids) || + value.preparation_ids.length !== value.outputs?.length || + value.external_effect_ids.length !== value.outputs?.length || + !this.isHexBytes(value.transfer_root, 32) || + !Number.isSafeInteger(value.provider_count) || value.provider_count < 0 || + !Number.isSafeInteger(value.rate_ts) || value.rate_ts < 0 || + !RATE_SOURCES.has(value.rate_source) || + !Array.isArray(value.outputs) || value.outputs.length === 0 || + !Array.isArray(value.msb_transfers) || + value.msb_transfers.length !== value.outputs.length) { + return new Error('Invalid targeted TNK settlement.'); + } + if (value.preparation_ids.some((entry) => !this.isHexBytes(entry, 32)) || + new Set(value.preparation_ids).size !== value.preparation_ids.length || + value.external_effect_ids.some((entry) => !this.isHexBytes(entry, 32)) || + new Set(value.external_effect_ids).size !== value.external_effect_ids.length) { + return new Error('Invalid targeted TNK settlement preparation identities.'); + } + if (this.normalizeAu( + value.rate_tnk_usd_au, + 'targeted TNK settlement rate', + { allowZero: false } + ) instanceof Error || this.parseTnkE18(value.tnk_e18) instanceof Error) { + return new Error('Invalid targeted TNK settlement amount or rate.'); + } + for (const field of ['provider_au', 'operator_fee_au', 'gross_au']) { + if (this.normalizeAu( + value[field], + `targeted TNK settlement ${field}`, + { allowZero: field !== 'gross_au' } + ) instanceof Error) { + return new Error('Invalid targeted TNK settlement total.'); + } + } + const gross = this.safeAddAu(value.provider_au, value.operator_fee_au); + if (gross instanceof Error || this.compareAu(gross, value.gross_au) !== 0) { + return new Error('Targeted TNK settlement gross amount does not balance.'); + } + const seenTransfers = new Set(); + for (const entry of value.msb_transfers) { + const transfer = this.normalizeMsbTransferEvidence( + entry, + 'targeted TNK settlement transfer' + ); + if (transfer instanceof Error) return transfer; + if (seenTransfers.has(transfer.tx_hash)) { + return new Error('Duplicate targeted TNK settlement transfer.'); + } + seenTransfers.add(transfer.tx_hash); + } + const outputs = []; + const seen = new Set(); + let operatorSeen = false; + for (const output of value.outputs) { + if (output?.role === 'provider') { + const shapeError = this.validateExactObjectKeys( + output, + [ + 'role', + 'provider', + 'payout_revision', + 'to', + 'paid_cum_au_before', + 'aggregate_paid_cum_au_before', + 'au', + 'tnk_e18', + ], + 'targeted TNK provider output' + ); + if (shapeError) return shapeError; + if (!this.isHexBytes(output.payout_revision, 32) || + output.payout_revision !== output.payout_revision.toLowerCase()) { + return new Error('Invalid targeted TNK payout revision.'); + } + const identity = stableJson([output.provider, output.payout_revision]); + if (seen.has(identity)) return new Error('Duplicate targeted TNK payout liability.'); + seen.add(identity); + const normalized = this.normalizeTargetedTnkSettlementOutput({ + role: output.role, + provider: output.provider, + to: output.to, + au: output.au, + tnk_e18: output.tnk_e18, + }); + if (normalized instanceof Error) return normalized; + const paidCumAuBefore = this.normalizeAu( + output.paid_cum_au_before, + 'targeted TNK provider paid cumulative watermark', + { allowZero: true } + ); + if (paidCumAuBefore instanceof Error) return paidCumAuBefore; + const aggregatePaidCumAuBefore = this.normalizeAu( + output.aggregate_paid_cum_au_before, + 'targeted TNK provider aggregate paid cumulative watermark', + { allowZero: true } + ); + if (aggregatePaidCumAuBefore instanceof Error) { + return aggregatePaidCumAuBefore; + } + outputs.push({ + ...normalized, + payout_revision: output.payout_revision, + paid_cum_au_before: paidCumAuBefore, + aggregate_paid_cum_au_before: aggregatePaidCumAuBefore, + }); + } else if (output?.role === 'operator_fee') { + const shapeError = this.validateExactObjectKeys( + output, + ['role', 'to', 'au', 'tnk_e18'], + 'targeted TNK operator output' + ); + if (shapeError) return shapeError; + if (operatorSeen) return new Error('Duplicate targeted TNK operator output.'); + operatorSeen = true; + const normalized = this.normalizeTargetedTnkSettlementOutput(output); + if (normalized instanceof Error) return normalized; + outputs.push(normalized); + } else { + return new Error('Invalid targeted TNK settlement output role.'); + } + } + outputs.sort((left, right) => { + if (left.role !== right.role) return left.role === 'provider' ? -1 : 1; + if (left.role === 'operator_fee') return 0; + return compareCodepoint(left.provider, right.provider) || + compareCodepoint(left.payout_revision, right.payout_revision); + }); + if (stableJson(outputs) !== stableJson(value.outputs)) { + return new Error('Targeted TNK settlement outputs must be canonical.'); + } + return { outputs }; + } + + normalizeTargetedFiatSettlementValue(value) { + const shapeError = this.validateExactObjectKeys( + value, + [ + 'op', + 'epoch', + 'at', + 'rail', + 'processor', + 'source_currency', + 'operator_to', + 'epoch_apply_hash', + 'preparation_ids', + 'external_effect_ids', + 'stripe_transfers', + 'transfer_root', + 'provider_count', + 'provider_liability_au', + 'provider_paid_au', + 'operator_fee_liability_au', + 'operator_fee_retained_au', + 'gross_liability_au', + 'gross_paid_au', + 'rounding_au', + 'dust_au', + 'source_amount_minor', + 'destination_totals', + 'outputs', + ], + 'targeted fiat settlement' + ); + if (shapeError) return shapeError; + if (value.op !== 'settle_targeted_fiat' || + value.rail !== 'fiat' || + value.processor !== 'stripe' || + !Number.isSafeInteger(value.epoch) || value.epoch < 1 || + !Number.isSafeInteger(value.at) || value.at < 0 || + !this.isSafeKeyPart(value.operator_to) || + !this.isHexBytes(value.epoch_apply_hash, 32) || + !Array.isArray(value.preparation_ids) || + !Array.isArray(value.external_effect_ids) || + value.preparation_ids.length !== value.outputs?.length || + value.external_effect_ids.length !== value.outputs?.length || + !this.isHexBytes(value.transfer_root, 32) || + !Number.isSafeInteger(value.provider_count) || value.provider_count < 0 || + !Array.isArray(value.outputs) || value.outputs.length === 0 || + !Array.isArray(value.destination_totals) || + !Array.isArray(value.stripe_transfers) || + value.stripe_transfers.length !== value.outputs.length) { + return new Error('Invalid targeted fiat settlement.'); + } + if (value.preparation_ids.some((entry) => !this.isHexBytes(entry, 32)) || + new Set(value.preparation_ids).size !== value.preparation_ids.length || + value.external_effect_ids.some((entry) => !this.isHexBytes(entry, 32)) || + new Set(value.external_effect_ids).size !== value.external_effect_ids.length) { + return new Error('Invalid targeted fiat settlement preparation identities.'); + } + const sourceCurrency = this.normalizeFiatCurrency(value.source_currency); + if (sourceCurrency instanceof Error || sourceCurrency !== value.source_currency) { + return new Error('Invalid targeted fiat settlement source currency.'); + } + const sourceAmountMinor = this.normalizeFiatMinor(value.source_amount_minor); + if (sourceAmountMinor instanceof Error || sourceAmountMinor === '0' || + sourceAmountMinor !== value.source_amount_minor) { + return new Error('Invalid targeted fiat settlement source amount.'); + } + const auFields = [ + 'provider_liability_au', + 'provider_paid_au', + 'operator_fee_liability_au', + 'operator_fee_retained_au', + 'gross_liability_au', + 'gross_paid_au', + 'rounding_au', + 'dust_au', + ]; + for (const field of auFields) { + if (this.normalizeAu( + value[field], + `targeted fiat settlement ${field}`, + { allowZero: !['gross_liability_au', 'gross_paid_au'].includes(field) } + ) instanceof Error) { + return new Error('Invalid targeted fiat settlement total.'); + } + } + const grossLiability = this.safeAddAu( + value.provider_liability_au, + value.operator_fee_liability_au + ); + const grossPaid = this.safeAddAu(value.provider_paid_au, value.operator_fee_retained_au); + const paidPlusDust = this.safeAddAu(value.gross_paid_au, value.dust_au); + if (grossLiability instanceof Error || grossPaid instanceof Error || paidPlusDust instanceof Error || + this.compareAu(grossLiability, value.gross_liability_au) !== 0 || + this.compareAu(grossPaid, value.gross_paid_au) !== 0 || + this.compareAu(paidPlusDust, value.gross_liability_au) !== 0 || + this.compareAu(value.rounding_au, value.dust_au) !== 0) { + return new Error('Targeted fiat settlement canonical AU totals do not balance.'); + } + const seenTransfers = new Set(); + const seenQuotes = new Set(); + const seenDestinationPayments = new Set(); + for (const entry of value.stripe_transfers) { + const transfer = this.normalizeStripeTransferEvidence( + entry, + 'targeted fiat settlement transfer' + ); + if (transfer instanceof Error) return transfer; + if (seenTransfers.has(transfer.ref)) { + return new Error('Duplicate targeted fiat settlement transfer.'); + } + seenTransfers.add(transfer.ref); + if (transfer.kind === 'stripe_transfer') { + if (transfer.fx_quote_id != null) { + if (seenQuotes.has(transfer.fx_quote_id)) { + return new Error('Duplicate targeted fiat settlement FX quote.'); + } + seenQuotes.add(transfer.fx_quote_id); + } + if (seenDestinationPayments.has(transfer.destination_payment)) { + return new Error('Duplicate targeted fiat settlement destination payment.'); + } + seenDestinationPayments.add(transfer.destination_payment); + } + } + const outputs = []; + const seen = new Set(); + let operatorSeen = false; + for (const output of value.outputs) { + if (output?.role === 'provider') { + const hasFxQuoteId = hasOwn(output, 'fx_quote_id'); + const hasFxQuoteHash = hasOwn(output, 'fx_quote_hash'); + if (hasFxQuoteId !== hasFxQuoteHash) { + return new Error( + 'Targeted fiat provider output FX quote identity must be present or absent as a pair.' + ); + } + const shapeError = this.validateExactObjectKeys( + output, + [ + 'role', + 'provider', + 'payout_revision', + 'to', + 'paid_cum_au_before', + 'aggregate_paid_cum_au_before', + 'liability_au', + 'paid_au', + 'rounding_au', + 'dust_au', + 'source_currency', + 'source_amount_minor', + 'destination_currency', + 'destination_amount_min_minor', + 'destination_amount_max_minor', + 'destination_amount_minor', + ...(hasFxQuoteId ? ['fx_quote_id', 'fx_quote_hash'] : []), + ], + 'targeted fiat provider output' + ); + if (shapeError) return shapeError; + if (!this.isHexBytes(output.payout_revision, 32) || + output.payout_revision !== output.payout_revision.toLowerCase()) { + return new Error('Invalid targeted fiat payout revision.'); + } + const identity = stableJson([output.provider, output.payout_revision]); + if (seen.has(identity)) return new Error('Duplicate targeted fiat payout liability.'); + seen.add(identity); + const { payout_revision: payoutRevision, ...settlementOutput } = output; + const paidCumAuBefore = this.normalizeAu( + settlementOutput.paid_cum_au_before, + 'targeted fiat provider paid cumulative watermark', + { allowZero: true } + ); + if (paidCumAuBefore instanceof Error) return paidCumAuBefore; + const aggregatePaidCumAuBefore = this.normalizeAu( + settlementOutput.aggregate_paid_cum_au_before, + 'targeted fiat provider aggregate paid cumulative watermark', + { allowZero: true } + ); + if (aggregatePaidCumAuBefore instanceof Error) { + return aggregatePaidCumAuBefore; + } + delete settlementOutput.paid_cum_au_before; + delete settlementOutput.aggregate_paid_cum_au_before; + const normalized = this.normalizeTargetedFiatSettlementOutput(settlementOutput); + if (normalized instanceof Error) return normalized; + outputs.push({ + ...normalized, + payout_revision: payoutRevision, + paid_cum_au_before: paidCumAuBefore, + aggregate_paid_cum_au_before: aggregatePaidCumAuBefore, + }); + } else if (output?.role === 'operator_fee') { + const shapeError = this.validateExactObjectKeys( + output, + [ + 'role', + 'to', + 'liability_au', + 'paid_au', + 'rounding_au', + 'dust_au', + 'source_currency', + 'source_amount_minor', + ], + 'targeted fiat operator output' + ); + if (shapeError) return shapeError; + if (operatorSeen) return new Error('Duplicate targeted fiat operator output.'); + operatorSeen = true; + const normalized = this.normalizeTargetedFiatSettlementOutput(output); + if (normalized instanceof Error) return normalized; + outputs.push(normalized); + } else { + return new Error('Invalid targeted fiat settlement output role.'); + } + } + outputs.sort((left, right) => { + if (left.role !== right.role) return left.role === 'provider' ? -1 : 1; + if (left.role === 'operator_fee') return 0; + return compareCodepoint(left.provider, right.provider) || + compareCodepoint(left.payout_revision, right.payout_revision); + }); + if (stableJson(outputs) !== stableJson(value.outputs)) { + return new Error('Targeted fiat settlement outputs must be canonical.'); + } + const destinationTotals = this.normalizeFiatDestinationTotals(value.destination_totals); + if (destinationTotals instanceof Error) return destinationTotals; + return { outputs, destination_totals: destinationTotals }; + } + + async targetedTnkSettlementTransferRoot(outputs) { + return await this.opaqueHash( + 'mayhem-targeted-tnk-settlement-transfer-root-v1', + outputs + ); + } + + async targetedFiatSettlementTransferRoot(outputs) { + return await this.opaqueHash( + 'mayhem-targeted-fiat-settlement-transfer-root-v2', + outputs + ); + } + + payoutPreparationLiabilityForOutput(value, output, rail) { + if (output.role === 'operator_fee') return null; + return { + provider: output.provider, + payout_revision: output.payout_revision, + target: output.to, + currency: rail === 'fiat' ? output.destination_currency : null, + chain_id: rail === 'tap' ? value.chain_id : null, + paid_cum_au_before: output.paid_cum_au_before, + aggregate_paid_cum_au_before: output.aggregate_paid_cum_au_before, + liability_au: rail === 'fiat' ? output.liability_au : (output.au ?? output.paid_au), + paid_au: rail === 'fiat' ? output.paid_au : (output.au ?? output.paid_au), + }; + } + + targetedFiatPreparationOutputProjection(output) { + if (output.role === 'operator_fee') { + return stableValue({ + role: output.role, + to: output.to, + liability_au: output.liability_au, + paid_au: output.paid_au, + rounding_au: output.rounding_au, + dust_au: output.dust_au, + source_currency: output.source_currency, + source_amount_minor: output.source_amount_minor, + }); + } + const projection = { + role: output.role, + provider: output.provider, + payout_revision: output.payout_revision, + to: output.to, + paid_cum_au_before: output.paid_cum_au_before, + aggregate_paid_cum_au_before: output.aggregate_paid_cum_au_before, + liability_au: output.liability_au, + paid_au: output.paid_au, + rounding_au: output.rounding_au, + dust_au: output.dust_au, + source_currency: output.source_currency, + source_amount_minor: output.source_amount_minor, + destination_currency: output.destination_currency, + destination_amount_min_minor: output.destination_amount_min_minor, + destination_amount_max_minor: output.destination_amount_max_minor, + }; + return stableValue(projection); + } + + normalizeTargetedFiatPreparationOutput(output) { + if (output?.role === 'operator_fee') { + const shapeError = this.validateExactObjectKeys( + output, + [ + 'role', + 'to', + 'liability_au', + 'paid_au', + 'rounding_au', + 'dust_au', + 'source_currency', + 'source_amount_minor', + ], + 'targeted fiat preparation operator output' + ); + if (shapeError) return shapeError; + const normalized = this.normalizeTargetedFiatSettlementOutput(output); + if (normalized instanceof Error) return normalized; + if (this.compareAu(normalized.paid_au, normalized.liability_au) !== 0 || + !this.isZeroAu(normalized.rounding_au) || + !this.isZeroAu(normalized.dust_au)) { + return new Error( + 'Targeted fiat operator fee must retain its exact AU liability with zero dust.' + ); + } + return stableJson(normalized) === stableJson(output) + ? normalized + : new Error('Targeted fiat preparation operator output must be canonical.'); + } + if (output?.role !== 'provider') { + return new Error('Invalid targeted fiat preparation output role.'); + } + const shapeError = this.validateExactObjectKeys( + output, + [ + 'role', + 'provider', + 'payout_revision', + 'to', + 'paid_cum_au_before', + 'aggregate_paid_cum_au_before', + 'liability_au', + 'paid_au', + 'rounding_au', + 'dust_au', + 'source_currency', + 'source_amount_minor', + 'destination_currency', + 'destination_amount_min_minor', + 'destination_amount_max_minor', + ], + 'targeted fiat preparation provider output' + ); + if (shapeError) return shapeError; + if (!this.isHexBytes(output.payout_revision, 32) || + output.payout_revision !== output.payout_revision.toLowerCase()) { + return new Error('Invalid targeted fiat preparation payout revision.'); + } + const paidCumAuBefore = this.normalizeAu( + output.paid_cum_au_before, + 'targeted fiat preparation paid cumulative watermark', + { allowZero: true } + ); + const aggregatePaidCumAuBefore = this.normalizeAu( + output.aggregate_paid_cum_au_before, + 'targeted fiat preparation aggregate paid cumulative watermark', + { allowZero: true } + ); + if (paidCumAuBefore instanceof Error || + aggregatePaidCumAuBefore instanceof Error) { + return new Error('Invalid targeted fiat preparation cumulative watermark.'); + } + if (!this.isHexBytes(output.provider, 32) || + output.provider !== output.provider.toLowerCase() || + !this.isSafeKeyPart(output.to)) { + return new Error('Invalid targeted fiat preparation provider identity.'); + } + const sourceCurrency = this.normalizeFiatCurrency(output.source_currency); + const destinationCurrency = this.normalizeFiatCurrency(output.destination_currency); + const sourceAmountMinor = this.normalizeFiatMinor(output.source_amount_minor); + const destinationMin = this.normalizeFiatMinor(output.destination_amount_min_minor); + const destinationMax = this.normalizeFiatMinor(output.destination_amount_max_minor); + const canonicalAu = {}; + for (const [field, allowZero] of [ + ['liability_au', false], + ['paid_au', false], + ['rounding_au', true], + ['dust_au', true], + ]) { + canonicalAu[field] = this.normalizeAu( + output[field], + `targeted fiat preparation ${field}`, + { allowZero } + ); + if (canonicalAu[field] instanceof Error) { + return new Error(`Invalid targeted fiat preparation ${field}.`); + } + } + const paidPlusDust = this.safeAddAu(canonicalAu.paid_au, canonicalAu.dust_au); + if (sourceCurrency instanceof Error || + sourceCurrency !== output.source_currency || + destinationCurrency instanceof Error || + destinationCurrency !== output.destination_currency || + sourceAmountMinor instanceof Error || + destinationMin instanceof Error || + destinationMax instanceof Error || + BigInt(destinationMax) < BigInt(destinationMin) || + paidPlusDust instanceof Error || + paidPlusDust !== canonicalAu.liability_au || + canonicalAu.rounding_au !== canonicalAu.dust_au) { + return new Error('Invalid targeted fiat preparation economic terms.'); + } + const projection = { + role: 'provider', + provider: output.provider, + to: output.to, + ...canonicalAu, + source_currency: sourceCurrency, + source_amount_minor: sourceAmountMinor, + destination_currency: destinationCurrency, + destination_amount_min_minor: destinationMin, + destination_amount_max_minor: destinationMax, + payout_revision: output.payout_revision, + paid_cum_au_before: paidCumAuBefore, + aggregate_paid_cum_au_before: aggregatePaidCumAuBefore, + }; + return stableJson(projection) === stableJson(output) + ? projection + : new Error('Targeted fiat preparation provider output must be canonical.'); + } + + normalizeTargetedFiatPreparationPayload(value, payload, liability) { + const shapeError = this.validateExactObjectKeys( + payload, + [ + 'settlement_op', + 'rail', + 'epoch', + 'epoch_apply_hash', + 'plan_root', + 'economic_op_id', + 'output_index', + 'output', + 'processor', + 'source_currency', + ], + 'targeted fiat preparation payload' + ); + if (shapeError) return shapeError; + const sourceCurrency = this.normalizeFiatCurrency(payload.source_currency); + if (payload.settlement_op !== 'settle_targeted_fiat_output' || + payload.rail !== 'fiat' || + payload.epoch !== value.epoch || + payload.epoch_apply_hash !== value.epoch_apply_hash || + !this.isHexBytes(payload.plan_root, 32) || + payload.economic_op_id !== value.economic_op_id || + payload.output_index !== value.output_index || + payload.processor !== 'stripe' || + sourceCurrency instanceof Error || + sourceCurrency !== payload.source_currency) { + return new Error('Invalid targeted fiat preparation payload.'); + } + const output = this.normalizeTargetedPayoutPlanOutput( + 'fiat', + payload.output, + value.output_index + ); + if (output instanceof Error) return output; + if (output.source_currency !== sourceCurrency) { + return new Error('Targeted fiat preparation source currency mismatch.'); + } + if (value.kind === 'liability') { + const expectedLiability = this.payoutPreparationLiabilityForOutput( + value, + output, + 'fiat' + ); + if (output.role !== 'provider' || + stableJson(expectedLiability) !== stableJson(liability)) { + return new Error('Targeted fiat preparation output does not match liability.'); + } + } else if (value.kind !== 'fee' || + output.role !== 'operator_fee' || + liability !== null) { + return new Error('Targeted fiat preparation output does not match kind.'); + } + return stableValue({ + settlement_op: 'settle_targeted_fiat_output', + plan_root: payload.plan_root, + economic_op_id: value.economic_op_id, + rail: 'fiat', + epoch: value.epoch, + epoch_apply_hash: value.epoch_apply_hash, + output_index: value.output_index, + output, + processor: 'stripe', + source_currency: sourceCurrency, + }); + } + + normalizeMsbSignedPayoutPayload(payload, output, treasuryFrom) { + const payloadShape = this.validateExactObjectKeys( + payload, + ['type', 'address', 'tro'], + 'targeted TNK signed MSB payload' + ); + if (payloadShape) return payloadShape; + const transferShape = this.validateExactObjectKeys( + payload?.tro, + ['tx', 'txv', 'to', 'am', 'in', 'is'], + 'targeted TNK signed MSB transfer' + ); + if (transferShape) return transferShape; + const expectedAmountHex = BigInt(output.tnk_e18).toString(16).padStart(32, '0'); + if (!Number.isSafeInteger(payload.type) || + payload.type < 0 || + payload.address !== treasuryFrom || + !this.isHexBytes(payload.tro.tx, 32) || + payload.tro.tx !== payload.tro.tx.toLowerCase() || + !this.isHexBytes(payload.tro.txv, 32) || + payload.tro.txv !== payload.tro.txv.toLowerCase() || + payload.tro.to !== output.to || + !/^[0-9a-f]{32}$/.test(payload.tro.am) || + payload.tro.am !== expectedAmountHex || + !this.isHexBytes(payload.tro.in, 32) || + payload.tro.in !== payload.tro.in.toLowerCase() || + !this.isHexBytes(payload.tro.is, 64) || + payload.tro.is !== payload.tro.is.toLowerCase()) { + return new Error('Invalid targeted TNK signed MSB payload.'); + } + return stableValue(payload); + } + + async normalizeTargetedTnkPreparationPayload( + value, + payload, + liability, + externalEffectIds + ) { + const shapeError = this.validateExactObjectKeys( + payload, + [ + 'settlement_op', + 'rail', + 'epoch', + 'epoch_apply_hash', + 'plan_root', + 'economic_op_id', + 'output_index', + 'output', + 'network', + 'treasury_from', + 'rate_tnk_usd_au', + 'rate_source', + 'rate_ts', + 'msb_tx_hash', + 'msb_payload', + ], + 'targeted TNK preparation payload' + ); + if (shapeError) return shapeError; + if (payload.settlement_op !== 'settle_targeted_tnk_output' || + payload.rail !== 'tnk' || + payload.epoch !== value.epoch || + payload.epoch_apply_hash !== value.epoch_apply_hash || + !this.isHexBytes(payload.plan_root, 32) || + payload.economic_op_id !== value.economic_op_id || + payload.output_index !== value.output_index || + !this.isSafeKeyPart(payload.network) || + !this.isSafeKeyPart(payload.treasury_from) || + !Number.isSafeInteger(payload.rate_ts) || + payload.rate_ts < 0 || + !RATE_SOURCES.has(payload.rate_source) || + !this.isHexBytes(payload.msb_tx_hash, 32) || + payload.msb_tx_hash !== payload.msb_tx_hash.toLowerCase() || + this.normalizeAu( + payload.rate_tnk_usd_au, + 'targeted TNK preparation rate', + { allowZero: false } + ) instanceof Error) { + return new Error('Invalid targeted TNK preparation payload.'); + } + const output = this.normalizeTargetedPayoutPlanOutput( + 'tnk', + payload.output, + value.output_index + ); + if (output instanceof Error) return output; + const msbPayload = this.normalizeMsbSignedPayoutPayload( + payload.msb_payload, + output, + payload.treasury_from + ); + if (msbPayload instanceof Error) return msbPayload; + if (payload.msb_tx_hash !== msbPayload.tro.tx || + externalEffectIds.length !== 1 || + externalEffectIds[0] !== payload.msb_tx_hash) { + return new Error('Targeted TNK preparation effect must equal its signed MSB tx hash.'); + } + const expectedLiability = this.payoutPreparationLiabilityForOutput( + value, + output, + 'tnk' + ); + if ((value.kind === 'liability' && + (output.role !== 'provider' || + stableJson(expectedLiability) !== stableJson(liability))) || + (value.kind === 'fee' && + (output.role !== 'operator_fee' || liability !== null))) { + return new Error('Targeted TNK preparation output does not match its kind.'); + } + return stableValue({ + settlement_op: 'settle_targeted_tnk_output', + rail: 'tnk', + epoch: value.epoch, + epoch_apply_hash: value.epoch_apply_hash, + plan_root: payload.plan_root, + economic_op_id: value.economic_op_id, + output_index: value.output_index, + output, + network: payload.network, + treasury_from: payload.treasury_from, + rate_tnk_usd_au: payload.rate_tnk_usd_au, + rate_source: payload.rate_source, + rate_ts: payload.rate_ts, + msb_tx_hash: payload.msb_tx_hash, + msb_payload: msbPayload, + }); + } + + payoutPreparationOutputPayload(value, output, outputIndex, rail) { + const common = { + settlement_op: value.op, + rail, + epoch: value.epoch, + epoch_apply_hash: value.epoch_apply_hash, + output_index: outputIndex, + output: rail === 'fiat' + ? this.targetedFiatPreparationOutputProjection(output) + : stableValue(output), + }; + if (rail === 'tnk') { + return { + ...common, + network: value.network, + treasury_from: value.treasury_from, + rate_tnk_usd_au: value.rate_tnk_usd_au, + rate_source: value.rate_source, + rate_ts: value.rate_ts, + }; + } + if (rail === 'fiat') { + return { + ...common, + processor: value.processor, + source_currency: value.source_currency, + }; + } + return { + ...common, + chain_id: value.chain_id, + token_address: value.token_address, + pool_address: value.pool_address, + payment_config_ver: value.payment_config_ver, + fee_bps: value.fee_bps, + tap_burn_bps: value.tap_burn_bps, + provider_share_bps: value.provider_share_bps, + tap_rate_lock: stableValue(value.tap_rate_lock), + }; + } + + targetedTapPayoutSplit(feeBps) { + if (!Number.isSafeInteger(feeBps) || + feeBps !== TAP_OPERATOR_BPS) { + return new Error( + 'Targeted TAP fee schedule does not match the fixed on-chain operator split.' + ); + } + return { + fee_bps: feeBps, + tap_burn_bps: TAP_BURN_BPS, + provider_share_bps: 10_000 - feeBps - TAP_BURN_BPS, + }; + } + + async payoutPreparationTapRootPayload(value, normalized) { + return { + settlement_op: value.op, + rail: 'tap', + epoch: value.epoch, + epoch_apply_hash: value.epoch_apply_hash, + chain_id: value.chain_id, + token_address: value.token_address, + pool_address: value.pool_address, + payment_config_ver: value.payment_config_ver, + tap_rate_lock: stableValue(normalized.rate_lock), + root: value.root, + cumulative_spent_wei: normalized.cumulative_spent_wei, + provider_cumulative_claimed_wei: normalized.provider_cumulative_claimed_wei, + buyer_refund_wei: normalized.buyer_refund_wei, + provider_count: value.provider_count, + provider_paid_au: normalized.provider_paid_au, + provider_tap_wei: normalized.provider_tap_wei, + fee_bps: normalized.fee_bps, + tap_burn_bps: normalized.tap_burn_bps, + provider_share_bps: normalized.provider_share_bps, + entries_hash: await this.opaqueHash( + 'mayhem-targeted-tap-preparation-entries-v1', + normalized.entries + ), + provider_entries_hash: await this.opaqueHash( + 'mayhem-targeted-tap-preparation-provider-entries-v1', + normalized.provider_entries + ), + refunds_hash: await this.opaqueHash( + 'mayhem-targeted-tap-preparation-refunds-v1', + normalized.refunds + ), + outputs_hash: await this.opaqueHash( + 'mayhem-targeted-tap-preparation-outputs-v1', + normalized.outputs + ), + }; + } + + async validatePayoutPreparationRecord({ + economicOpId, + rail, + epoch, + epochApplyHash, + kind, + outputIndex, + payload, + liability, + externalEffectIds, + }) { + const record = await this.get(this.payoutPreparationRecordKey(rail, economicOpId)); + if (!record || + record.type !== 'targeted_payout_preparation' || + record.economic_op_id !== economicOpId || + record.rail !== rail || + record.epoch !== epoch || + record.epoch_apply_hash !== epochApplyHash || + record.kind !== kind || + record.output_index !== outputIndex || + record.consumed !== false || + stableJson(record.payload) !== stableJson(payload) || + stableJson(record.liability) !== stableJson(liability) || + stableJson(record.external_effect_ids) !== stableJson(externalEffectIds)) { + return new Error('Targeted settlement does not match an unconsumed canonical preparation.'); + } + const payloadHash = await this.opaqueHash( + 'mayhem-targeted-payout-preparation-payload-v1', + { + economic_op_id: economicOpId, + rail, + epoch, + epoch_apply_hash: epochApplyHash, + kind, + output_index: outputIndex, + payload, + } + ); + if (record.payload_hash !== payloadHash) { + return new Error('Targeted settlement preparation payload hash mismatch.'); + } + if (liability !== null) { + const liabilityLock = await this.get( + this.payoutPreparationLiabilityLockKey(rail, liability) + ); + if (liabilityLock?.economic_op_id !== economicOpId) { + return new Error('Targeted settlement preparation liability lock mismatch.'); + } + } + for (const effectId of externalEffectIds) { + const effectLock = await this.get( + this.payoutPreparationEffectLockKey(rail, effectId) + ); + if (effectLock?.economic_op_id !== economicOpId || + effectLock.consumed !== false) { + return new Error('Targeted settlement preparation effect lock mismatch.'); + } + } + return record; + } + + async payoutPreparationsForSettlement(value, normalized, rail) { + const plans = []; + for (const [outputIndex, output] of normalized.outputs.entries()) { + const economicOpId = value.preparation_ids[outputIndex]; + const liability = this.payoutPreparationLiabilityForOutput(value, output, rail); + const externalEffectIds = rail === 'tap' + ? [] + : [value.external_effect_ids[outputIndex]]; + const record = await this.validatePayoutPreparationRecord({ + economicOpId, + rail, + epoch: value.epoch, + epochApplyHash: value.epoch_apply_hash, + kind: liability === null ? 'fee' : 'liability', + outputIndex, + payload: this.payoutPreparationOutputPayload(value, output, outputIndex, rail), + liability, + externalEffectIds, + }); + if (record instanceof Error) return record; + plans.push(record); + } + if (rail === 'tap') { + const rootPayload = await this.payoutPreparationTapRootPayload(value, normalized); + if (rootPayload instanceof Error) return rootPayload; + const rootRecord = await this.validatePayoutPreparationRecord({ + economicOpId: value.root_preparation_id, + rail, + epoch: value.epoch, + epochApplyHash: value.epoch_apply_hash, + kind: 'tap_root', + outputIndex: 0, + payload: rootPayload, + liability: null, + externalEffectIds: value.external_effect_ids, + }); + if (rootRecord instanceof Error) return rootRecord; + plans.push(rootRecord); + } + return plans; + } + + async validatePayoutPreparationConsumption(plans) { + const lastByProvider = new Map(); + for (const record of plans) { + if (record.liability !== null) { + lastByProvider.set( + `${record.rail}/${record.liability.provider}`, + record + ); + } + } + const validatedTails = []; + for (const record of lastByProvider.values()) { + const tailKey = this.payoutPreparationAggregateTailKey( + record.rail, + record.liability.provider + ); + const tail = await this.get(tailKey); + if (!tail || + tail.economic_op_id !== record.economic_op_id || + tail.consumed !== false) { + return new Error('Targeted payout preparation aggregate tail mismatch.'); + } + validatedTails.push({ key: tailKey, value: tail }); + } + return validatedTails; + } + + async consumePayoutPreparations(plans, settlementKey) { + const validatedTails = await this.validatePayoutPreparationConsumption(plans); + if (validatedTails instanceof Error) return validatedTails; + for (const record of plans) { + await this.put( + this.payoutPreparationRecordKey(record.rail, record.economic_op_id), + { + ...record, + consumed: true, + consumed_by: settlementKey, + } + ); + for (const effectId of record.external_effect_ids) { + await this.put(this.payoutPreparationEffectLockKey(record.rail, effectId), { + economic_op_id: record.economic_op_id, + effect_id: effectId, + consumed: true, + consumed_by: settlementKey, + updated_at: this.tx, + }); + } + } + for (const tail of validatedTails) { + await this.put(tail.key, { + ...tail.value, + consumed: true, + consumed_by: settlementKey, + updated_at: this.tx, + }); + } + return null; + } + + async targetedPayoutSettlementUpdates(outputs, rail, epoch, at, transferIds) { + const params = await this.activeParamsAt(at, [ + 'holdback_epochs', + 'challenge_epochs', + 'canary_probe_holdback_bps', + 'canary_probe_release_min_passes', + ]); + const liabilityUpdates = []; + const paidByProvider = new Map(); + for (const [outputIndex, output] of outputs.entries()) { + if (output.role !== 'provider') continue; + const provider = await this.get(`prov/${output.provider}`); + if (!provider) return new Error('Provider not found.'); + if (provider.status !== 'active' && provider.status !== 'banned') { + return new Error('Targeted settlement provider status is not payable.'); + } + const binding = await this.get( + this.providerPayoutBindingFeatureKey( + rail, + output.provider, + output.payout_revision + ) + ); + if (!binding || + binding.verified !== true || + binding.provider !== output.provider || + binding.rail !== rail || + binding.revision !== output.payout_revision || + binding.activation_epoch > epoch) { + return new Error('Targeted settlement requires its immutable payout binding.'); + } + if (binding.target !== output.to) { + return new Error('Targeted settlement payout target mismatch.'); + } + if (rail === 'fiat' && binding.currency !== output.destination_currency) { + return new Error('Targeted settlement payout currency mismatch.'); + } + const liabilityKey = this.providerPayoutLiabilityKey( + output.provider, + rail, + output.payout_revision + ); + const liability = await this.get(liabilityKey); + if (!liability || + liability.provider !== output.provider || + liability.rail !== rail || + liability.revision !== output.payout_revision || + liability.target !== binding.target || + (liability.currency ?? null) !== binding.currency || + (liability.chain_id ?? null) !== binding.chain_id) { + return new Error('Targeted settlement payout liability mismatch.'); + } + const liabilityError = this.guardianValidatePayoutLiabilityRecord( + liability, + output.provider, + rail, + output.payout_revision + ); + if (liabilityError) return liabilityError; + const probeGate = await this.probeGateForEarning(output.provider, liability, params); + if (probeGate instanceof Error) return probeGate; + const lockedEarningEpochs = this.providerLockedEarningEpochs(provider, params); + if (lockedEarningEpochs instanceof Error) return lockedEarningEpochs; + const disputeGate = await this.providerHasOpenDispute(output.provider); + if (disputeGate instanceof Error) return disputeGate; + const refreshed = this.refreshEarningHoldback( + liability, + epoch, + lockedEarningEpochs, + probeGate, + disputeGate + ); + if (refreshed instanceof Error) return refreshed; + const payable = this.safeSubAu( + this.safeSubAu(refreshed.total_au, refreshed.held_au), + refreshed.paid_cum_au + ); + if (payable instanceof Error) return payable; + if (refreshed.paid_cum_au !== output.paid_cum_au_before) { + return new Error('Targeted settlement paid cumulative watermark mismatch.'); + } + let settledAu = output.au; + if (rail === 'fiat') { + const dust = this.safeSubAu(output.liability_au, output.paid_au); + if (dust instanceof Error || + this.isZeroAu(output.liability_au) || + this.compareAu(output.liability_au, payable) > 0 || + this.compareAu(output.dust_au, dust) !== 0 || + this.compareAu(output.rounding_au, dust) !== 0) { + return new Error('Targeted fiat settlement exceeds its frozen revision liability or mismatches dust.'); + } + settledAu = output.paid_au; + } + if (this.isZeroAu(settledAu)) { + return new Error('Targeted settlement liability has no payable earnings.'); + } + if (rail !== 'fiat' && this.compareAu(output.au, payable) > 0) { + return new Error('Targeted settlement amount exceeds revision liability.'); + } + const paidCumAu = this.safeAddAu(refreshed.paid_cum_au, settledAu); + if (paidCumAu instanceof Error) return paidCumAu; + const nextLiability = { + ...refreshed, + paid_cum_au: paidCumAu, + updated_epoch: Math.max(refreshed.updated_epoch, epoch), + last_settlement_epoch: epoch, + last_settlement_transfer: transferIds[outputIndex], + updated_at: this.tx, + }; + const nextLiabilityError = this.guardianValidatePayoutLiabilityRecord( + nextLiability, + output.provider, + rail, + output.payout_revision + ); + if (nextLiabilityError) return nextLiabilityError; + liabilityUpdates.push({ + key: liabilityKey, + value: nextLiability, + }); + const providerPaid = paidByProvider.get(output.provider) ?? { + aggregate_paid_cum_au_before: output.aggregate_paid_cum_au_before, + paid_au: ZERO_AU, + }; + const expectedWatermark = this.safeAddAu( + providerPaid.aggregate_paid_cum_au_before, + providerPaid.paid_au + ); + if (expectedWatermark instanceof Error) return expectedWatermark; + if (expectedWatermark !== output.aggregate_paid_cum_au_before) { + return new Error('Targeted settlement provider outputs have a discontinuous aggregate paid watermark.'); + } + const nextProviderPaid = this.safeAddAu(providerPaid.paid_au, settledAu); + if (nextProviderPaid instanceof Error) return nextProviderPaid; + paidByProvider.set(output.provider, { + aggregate_paid_cum_au_before: providerPaid.aggregate_paid_cum_au_before, + paid_au: nextProviderPaid, + }); + } + + const earningUpdates = []; + for (const [providerId, providerPaid] of paidByProvider) { + const paidAu = providerPaid.paid_au; + const provider = await this.get(`prov/${providerId}`); + const earning = await this.earningRecord(providerId, rail); + if (earning instanceof Error) return earning; + const earningError = this.guardianValidateEarningRecord( + earning, + providerId, + rail + ); + if (earningError) return earningError; + const probeGate = await this.probeGateForEarning(providerId, earning, params); + if (probeGate instanceof Error) return probeGate; + const lockedEarningEpochs = this.providerLockedEarningEpochs(provider, params); + if (lockedEarningEpochs instanceof Error) return lockedEarningEpochs; + const disputeGate = await this.providerHasOpenDispute(providerId); + if (disputeGate instanceof Error) return disputeGate; + const refreshed = this.refreshEarningHoldback( + earning, + epoch, + lockedEarningEpochs, + probeGate, + disputeGate + ); + if (refreshed instanceof Error) return refreshed; + const payable = this.safeSubAu( + this.safeSubAu(refreshed.total_au, refreshed.held_au), + refreshed.paid_cum_au + ); + if (payable instanceof Error) return payable; + if (refreshed.paid_cum_au !== providerPaid.aggregate_paid_cum_au_before) { + return new Error('Targeted settlement aggregate paid cumulative watermark mismatch.'); + } + if (this.compareAu(paidAu, payable) > 0) { + return new Error('Targeted settlement exceeds aggregate provider earnings.'); + } + const paidCumAu = this.safeAddAu(refreshed.paid_cum_au, paidAu); + if (paidCumAu instanceof Error) return paidCumAu; + const nextEarning = { + ...refreshed, + paid_cum_au: paidCumAu, + updated_epoch: Math.max(refreshed.updated_epoch, epoch), + last_targeted_settlement_epoch: epoch, + updated_at: this.tx, + }; + const nextError = this.guardianValidateEarningRecord( + nextEarning, + providerId, + rail + ); + if (nextError) return nextError; + earningUpdates.push(nextEarning); + } + return { liabilityUpdates, earningUpdates }; + } + + async targetedTapSettlement(value) { + const adminError = await this.requireAdmin(); + if (adminError) return adminError; + const normalized = this.normalizeTargetedTapSettlementValue(value); + if (normalized instanceof Error) return normalized; + const settlementParams = await this.activeParamsAt( + value.at, + ['max_tap_settlement_outputs', 'fee_bps'] + ); + const payoutSplit = this.targetedTapPayoutSplit(settlementParams.fee_bps); + if (payoutSplit instanceof Error) return payoutSplit; + if (normalized.fee_bps !== payoutSplit.fee_bps || + normalized.tap_burn_bps !== payoutSplit.tap_burn_bps || + normalized.provider_share_bps !== payoutSplit.provider_share_bps) { + return new Error( + 'Targeted TAP settlement fee/burn split does not match the historical schedule.' + ); + } + if (normalized.outputs.length > settlementParams.max_tap_settlement_outputs) { + return new Error('Targeted TAP settlement output count exceeds limit.'); + } + + const admin = await this.get('admin'); + const payments = await this.get('payments/current'); + if (!payments || payments.set_by !== admin || payments.set_by_role !== 'admin' || + payments.ver !== value.payment_config_ver || + payments.tap?.chain_id !== value.chain_id || + payments.tap?.token_address?.toLowerCase() !== value.token_address || + payments.tap?.pool_address?.toLowerCase() !== value.pool_address) { + return new Error('Targeted TAP settlement does not match canonical payment configuration.'); + } + const rate = await this.guardianRequireHistoricalTapRateLock( + normalized.rate_lock, + value.at + ); + if (rate instanceof Error) return rate; + + const record = { + type: 'targeted_tap_settlement', + ...value, + tap_rate_lock: normalized.rate_lock, + entries: normalized.entries, + outputs: normalized.outputs, + fee_bps: payoutSplit.fee_bps, + tap_burn_bps: payoutSplit.tap_burn_bps, + provider_share_bps: payoutSplit.provider_share_bps, + settled_by: this.address, + settled_by_role: 'admin', + }; + const recordKey = `settle/targeted/tap/${value.epoch}`; + const existing = await this.get(recordKey); + if (existing) { + if (stableJson(existing) === stableJson(record)) { + return { + ok: true, + op: 'targetedTapSettlement', + epoch: value.epoch, + rail: 'tap', + root: value.root, + execution_tx: value.execution_tx, + idempotent: true, + }; + } + return new Error('Targeted TAP settlement already exists for epoch.'); + } + + const applyAnchor = await this.requireEpochApplyAnchor( + value.epoch, + value.epoch_apply_hash, + 'Targeted TAP settlement' + ); + if (applyAnchor instanceof Error) return applyAnchor; + const preparations = await this.payoutPreparationsForSettlement( + value, + normalized, + 'tap' + ); + if (preparations instanceof Error) return preparations; + + const scope = `${value.chain_id}/${value.pool_address}`; + const rootSeenKey = `rail/seen/tap-settlement-root/${scope}/${value.root}`; + const proposalSeenKey = `rail/seen/tap-settlement-proposal/${scope}/${value.proposal_tx}`; + const executionSeenKey = `rail/seen/tap-settlement-execution/${scope}/${value.execution_tx}`; + if ((await this.get(rootSeenKey)) !== null) { + return new Error('Targeted TAP settlement root was already consumed.'); + } + if ((await this.get(proposalSeenKey)) !== null) { + return new Error('Targeted TAP settlement proposal transaction was already consumed.'); + } + if ((await this.get(executionSeenKey)) !== null) { + return new Error('Targeted TAP settlement execution transaction was already consumed.'); + } + + const stateKey = `settle/targeted/tap/state/${scope}`; + const state = (await this.get(stateKey)) ?? { + type: 'targeted_tap_settlement_state', + chain_id: value.chain_id, + token_address: value.token_address, + pool_address: value.pool_address, + payment_config_ver: value.payment_config_ver, + last_epoch: 0, + cumulative_spent_wei: '0', + cumulative_provider_claimed_wei: '0', + cumulative_buyer_refund_wei: '0', + last_root: null, + last_execution_tx: null, + updated_at: null, + }; + if (state.type !== 'targeted_tap_settlement_state' || + state.chain_id !== value.chain_id || + state.token_address !== value.token_address || + state.pool_address !== value.pool_address || + state.payment_config_ver !== value.payment_config_ver || + !Number.isSafeInteger(state.last_epoch) || + state.last_epoch < 0 || + typeof state.cumulative_spent_wei !== 'string' || + !/^(0|[1-9][0-9]*)$/.test(state.cumulative_spent_wei) || + typeof state.cumulative_provider_claimed_wei !== 'string' || + !/^(0|[1-9][0-9]*)$/.test(state.cumulative_provider_claimed_wei) || + typeof state.cumulative_buyer_refund_wei !== 'string' || + !/^(0|[1-9][0-9]*)$/.test(state.cumulative_buyer_refund_wei)) { + return new Error('Invalid targeted TAP settlement state.'); + } + if (value.epoch <= state.last_epoch) { + return new Error('Targeted TAP settlement epoch must advance.'); + } + const priorSpentWei = BigInt(state.cumulative_spent_wei); + const nextSpentWei = BigInt(normalized.cumulative_spent_wei); + const priorProviderClaimedWei = BigInt(state.cumulative_provider_claimed_wei); + const nextProviderClaimedWei = BigInt(normalized.provider_cumulative_claimed_wei); + const priorBuyerRefundWei = BigInt(state.cumulative_buyer_refund_wei); + const nextBuyerRefundWei = BigInt(normalized.buyer_refund_wei); + if (nextSpentWei <= priorSpentWei) { + return new Error('Targeted TAP cumulative gross spend must advance.'); + } + const grossSpendDeltaWei = nextSpentWei - priorSpentWei; + const expectedProviderDeltaWei = + (grossSpendDeltaWei * BigInt(payoutSplit.provider_share_bps)) / 10_000n; + if (BigInt(normalized.provider_tap_wei) !== expectedProviderDeltaWei || + nextProviderClaimedWei !== + priorProviderClaimedWei + BigInt(normalized.provider_tap_wei)) { + return new Error( + 'Targeted TAP provider entitlement does not match the historical fee/burn split.' + ); + } + if (nextBuyerRefundWei < priorBuyerRefundWei) { + return new Error('Targeted TAP cumulative buyer refunds cannot decrease.'); + } + + const outputsByTarget = new Map(); + for (const output of normalized.outputs) { + const targetOutputs = outputsByTarget.get(output.to) ?? []; + targetOutputs.push(output); + outputsByTarget.set(output.to, targetOutputs); + } + const claimUpdates = []; + for (const entry of normalized.provider_entries) { + const claimKey = `settle/targeted/tap/claim/${scope}/${entry.account}`; + const claim = (await this.get(claimKey)) ?? { + type: 'targeted_tap_cumulative_claim', + chain_id: value.chain_id, + token_address: value.token_address, + pool_address: value.pool_address, + account: entry.account, + cumulative_claim_wei: '0', + updated_epoch: 0, + updated_at: null, + }; + if (claim.type !== 'targeted_tap_cumulative_claim' || + claim.chain_id !== value.chain_id || + claim.token_address !== value.token_address || + claim.pool_address !== value.pool_address || + claim.account !== entry.account || + typeof claim.cumulative_claim_wei !== 'string' || + !/^(0|[1-9][0-9]*)$/.test(claim.cumulative_claim_wei)) { + return new Error('Invalid targeted TAP cumulative claim state.'); + } + const targetOutputs = outputsByTarget.get(entry.account) ?? []; + let cursor = BigInt(claim.cumulative_claim_wei); + for (const output of targetOutputs) { + const prior = BigInt(output.prior_cumulative_claim_wei); + const next = BigInt(output.cumulative_claim_wei); + if (prior !== cursor || next !== prior + BigInt(output.tap_wei)) { + return new Error('Targeted TAP cumulative claim output chain does not advance from canonical state.'); + } + cursor = next; + } + if (BigInt(entry.cumulative_wei) !== cursor) { + return new Error('Targeted TAP cumulative claim chain does not match executed root.'); + } + outputsByTarget.delete(entry.account); + claimUpdates.push({ + key: claimKey, + value: { + ...claim, + cumulative_claim_wei: entry.cumulative_wei, + updated_epoch: value.epoch, + updated_at: this.tx, + }, + }); + } + if (outputsByTarget.size !== 0) { + return new Error('Targeted TAP provider output target is missing from executed root.'); + } + const priorSettlement = state.last_epoch === 0 + ? null + : await this.get(`settle/targeted/tap/${state.last_epoch}`); + const priorRefunds = priorSettlement?.refunds ?? []; + if (state.last_epoch > 0 && !Array.isArray(priorRefunds)) { + return new Error('Prior targeted TAP refund distribution is invalid.'); + } + const refundUpdates = []; + const refundMap = new Map(normalized.refunds.map((entry) => [entry.account, entry])); + for (const priorRefund of priorRefunds) { + const nextRefund = refundMap.get(priorRefund.account); + if (!nextRefund || + BigInt(nextRefund.cumulative_wei) < BigInt(priorRefund.cumulative_wei)) { + return new Error('Targeted TAP refund claim cannot be removed or decreased.'); + } + } + for (const entry of normalized.refunds) { + const refundKey = `settle/targeted/tap/refund/${scope}/${entry.account}`; + const refund = (await this.get(refundKey)) ?? { + type: 'targeted_tap_cumulative_refund', + chain_id: value.chain_id, + token_address: value.token_address, + pool_address: value.pool_address, + account: entry.account, + cumulative_refund_wei: '0', + updated_epoch: 0, + updated_at: null, + }; + if (refund.type !== 'targeted_tap_cumulative_refund' || + refund.chain_id !== value.chain_id || + refund.token_address !== value.token_address || + refund.pool_address !== value.pool_address || + refund.account !== entry.account || + typeof refund.cumulative_refund_wei !== 'string' || + !/^(0|[1-9][0-9]*)$/.test(refund.cumulative_refund_wei) || + BigInt(entry.cumulative_wei) < BigInt(refund.cumulative_refund_wei)) { + return new Error('Invalid or decreasing targeted TAP cumulative refund state.'); + } + refundUpdates.push({ + key: refundKey, + value: { + ...refund, + cumulative_refund_wei: entry.cumulative_wei, + updated_epoch: value.epoch, + updated_at: this.tx, + }, + }); + } + + const settlementOutputs = normalized.outputs.map((output) => ({ + role: 'provider', + provider: output.provider, + payout_revision: output.payout_revision, + to: output.to, + paid_cum_au_before: output.paid_cum_au_before, + aggregate_paid_cum_au_before: output.aggregate_paid_cum_au_before, + au: output.paid_au, + })); + const updates = await this.targetedPayoutSettlementUpdates( + settlementOutputs, + 'tap', + value.epoch, + value.at, + normalized.outputs.map(() => value.execution_tx) + ); + if (updates instanceof Error) return updates; + + for (const update of updates.liabilityUpdates) await this.put(update.key, update.value); + for (const earning of updates.earningUpdates) { + await this.put(this.earningKey(earning.provider, 'tap'), earning); + } + for (const update of claimUpdates) await this.put(update.key, update.value); + for (const update of refundUpdates) await this.put(update.key, update.value); + const replayEvidence = { + rail: 'tap', + purpose: 'targeted_settlement', + epoch: value.epoch, + root: value.root, + proposal_tx: value.proposal_tx, + execution_tx: value.execution_tx, + epoch_apply_hash: value.epoch_apply_hash, + consumed_at: this.tx, + }; + await this.put(rootSeenKey, replayEvidence); + await this.put(proposalSeenKey, replayEvidence); + await this.put(executionSeenKey, replayEvidence); + await this.put(stateKey, { + ...state, + last_epoch: value.epoch, + cumulative_spent_wei: normalized.cumulative_spent_wei, + cumulative_provider_claimed_wei: normalized.provider_cumulative_claimed_wei, + cumulative_buyer_refund_wei: normalized.buyer_refund_wei, + last_root: value.root, + last_execution_tx: value.execution_tx, + updated_at: this.tx, + }); + const preparationError = await this.consumePayoutPreparations( + preparations, + recordKey + ); + if (preparationError) return preparationError; + await this.put(recordKey, record); + return { + ok: true, + op: 'targetedTapSettlement', + epoch: value.epoch, + rail: 'tap', + provider_paid_au: normalized.provider_paid_au, + provider_tap_wei: normalized.provider_tap_wei, + cumulative_spent_wei: normalized.cumulative_spent_wei, + root: value.root, + execution_tx: value.execution_tx, + idempotent: false, + }; + } + + async targetedTnkSettlement(value) { + const adminError = await this.requireAdmin(); + if (adminError) return adminError; + const normalized = this.normalizeTargetedTnkSettlementValue(value); + if (normalized instanceof Error) return normalized; + const settlementParams = await this.activeParamsAt( + value.at, + ['max_tnk_settlement_outputs'] + ); + if (value.outputs.length > settlementParams.max_tnk_settlement_outputs) { + return new Error('Targeted TNK settlement output count exceeds limit.'); + } + const outputs = normalized.outputs; + const transfers = value.msb_transfers.map((entry) => ( + this.normalizeMsbTransferEvidence( + entry, + 'Targeted TNK settlement MSB transfer evidence' + ) + )); + const transferError = transfers.find((entry) => entry instanceof Error); + if (transferError) return transferError; + if (stableJson(transfers) !== stableJson(value.msb_transfers)) { + return new Error('Targeted TNK settlement transfers must be canonical.'); + } + const payment = await this.canonicalTnkPaymentConfig(); + if (payment instanceof Error) return payment; + if (value.network !== payment.network || + value.treasury_from !== payment.treasury_address) { + return new Error('Targeted TNK settlement source does not match payment config.'); + } + for (const [index, output] of outputs.entries()) { + const transfer = transfers[index]; + if (transfer.network !== value.network || + transfer.from !== value.treasury_from || + transfer.to !== output.to || + transfer.amount_e18 !== output.tnk_e18) { + return new Error('Targeted TNK transfer does not match output.'); + } + } + const totals = this.targetedTnkSettlementTotals(outputs, value.rate_tnk_usd_au); + if (totals instanceof Error) return totals; + if (totals.provider_count !== value.provider_count || + this.compareAu(totals.provider_au, value.provider_au) !== 0 || + this.compareAu(totals.operator_fee_au, value.operator_fee_au) !== 0 || + this.compareAu(totals.gross_au, value.gross_au) !== 0 || + totals.tnk_e18 !== value.tnk_e18) { + return new Error('Targeted TNK settlement totals do not match outputs.'); + } + const transferRoot = await this.targetedTnkSettlementTransferRoot(outputs); + if (transferRoot !== value.transfer_root) { + return new Error('Targeted TNK settlement transfer root mismatch.'); + } + const record = { + type: 'targeted_tnk_settlement', + ...value, + outputs, + msb_transfers: transfers, + settled_by: this.address, + settled_by_role: 'admin', + }; + const recordKey = `settle/targeted/tnk/${value.epoch}`; + const existing = await this.get(recordKey); + if (existing) { + if (stableJson(existing) === stableJson(record)) { + return { + ok: true, + op: 'targetedTnkSettlement', + epoch: value.epoch, + rail: 'tnk', + idempotent: true, + msb_transfers: transfers, + }; + } + return new Error('Targeted TNK settlement already exists for epoch.'); + } + const applyAnchor = await this.requireEpochApplyAnchor( + value.epoch, + value.epoch_apply_hash, + 'Targeted TNK settlement' + ); + if (applyAnchor instanceof Error) return applyAnchor; + const preparations = await this.payoutPreparationsForSettlement( + value, + normalized, + 'tnk' + ); + if (preparations instanceof Error) return preparations; + for (const transfer of transfers) { + if ((await this.get(this.msbTransferSeenKey(transfer))) !== null) { + return new Error('Targeted TNK transfer was already consumed.'); + } + } + const rate = await this.guardianRequireHistoricalTnkRate(value, value.at); + if (rate instanceof Error) return rate; + const updates = await this.targetedPayoutSettlementUpdates( + outputs, + 'tnk', + value.epoch, + value.at, + transfers.map((entry) => entry.tx_hash) + ); + if (updates instanceof Error) return updates; + + const fee = await this.feeCumRecord('tnk'); + if (fee instanceof Error) return fee; + const feeError = this.guardianValidateFeeRecord(fee, 'tnk'); + if (feeError) return feeError; + const payableFee = this.safeSubAu(fee.cum_au, fee.swept_cum_au); + if (payableFee instanceof Error) return payableFee; + if (this.compareAu(payableFee, value.operator_fee_au) < 0) { + return new Error('Targeted TNK operator fee does not match fee state.'); + } + const operatorOutputs = outputs.filter((entry) => entry.role === 'operator_fee'); + if ((this.isZeroAu(value.operator_fee_au) && operatorOutputs.length !== 0) || + (this.compareAu(value.operator_fee_au, ZERO_AU) > 0 && + (operatorOutputs.length !== 1 || operatorOutputs[0].to !== value.operator_to))) { + return new Error('Targeted TNK operator output mismatch.'); + } + const sweptCumAu = this.safeAddAu(fee.swept_cum_au, value.operator_fee_au); + if (sweptCumAu instanceof Error) return sweptCumAu; + const operatorIndex = outputs.findIndex((entry) => entry.role === 'operator_fee'); + const nextFee = { + ...fee, + swept_cum_au: sweptCumAu, + updated_epoch: Math.max(fee.updated_epoch, value.epoch), + last_targeted_settlement_epoch: value.epoch, + last_targeted_settlement_transfer: operatorIndex >= 0 + ? transfers[operatorIndex].tx_hash + : null, + }; + const nextFeeError = this.guardianValidateFeeRecord(nextFee, 'tnk'); + if (nextFeeError) return nextFeeError; + for (const update of updates.liabilityUpdates) await this.put(update.key, update.value); + for (const earning of updates.earningUpdates) { + await this.put(this.earningKey(earning.provider, 'tnk'), earning); + } + for (const [index, transfer] of transfers.entries()) { + await this.put(this.msbTransferSeenKey(transfer), { + rail: 'tnk', + purpose: 'targeted_settlement', + epoch: value.epoch, + output_index: index, + transfer_root: value.transfer_root, + consumed_at: this.tx, + }); + } + await this.put(this.feeCumKey('tnk'), nextFee); + const preparationError = await this.consumePayoutPreparations( + preparations, + recordKey + ); + if (preparationError) return preparationError; + await this.put(recordKey, record); + return { + ok: true, + op: 'targetedTnkSettlement', + epoch: value.epoch, + rail: 'tnk', + idempotent: false, + provider_au: value.provider_au, + operator_fee_au: value.operator_fee_au, + gross_au: value.gross_au, + msb_transfers: transfers, + transfer_root: value.transfer_root, + }; + } + + async targetedFiatSettlement(value) { + const adminError = await this.requireAdmin(); + if (adminError) return adminError; + const normalized = this.normalizeTargetedFiatSettlementValue(value); + if (normalized instanceof Error) return normalized; + const settlementParams = await this.activeParamsAt( + value.at, + ['max_fiat_settlement_outputs'] + ); + if (value.outputs.length > settlementParams.max_fiat_settlement_outputs) { + return new Error('Targeted fiat settlement output count exceeds limit.'); + } + const outputs = normalized.outputs; + const payments = await this.get('payments/current'); + const admin = await this.get('admin'); + if (!payments || payments.set_by !== admin || payments.set_by_role !== 'admin' || + payments.denom !== PRICE_DENOMINATION || + payments.fiat?.processor !== 'stripe' || + payments.fiat?.integration_currency !== 'usd' || + payments.fiat?.adaptive_pricing !== true || + !Array.isArray(payments.fiat?.payout_currencies)) { + return new Error('Targeted fiat settlement does not match canonical payment configuration.'); + } + const transfers = value.stripe_transfers.map((entry) => ( + this.normalizeStripeTransferEvidence( + entry, + 'Targeted fiat settlement Stripe evidence' + ) + )); + const transferError = transfers.find((entry) => entry instanceof Error); + if (transferError) return transferError; + if (stableJson(transfers) !== stableJson(value.stripe_transfers)) { + return new Error('Targeted fiat settlement transfers must be canonical.'); + } + const expectedGroup = + `mayhem_fiat_epoch_${value.epoch}_${value.epoch_apply_hash.slice(0, 16)}`; + for (const [index, output] of outputs.entries()) { + const transfer = transfers[index]; + const expectedKind = output.role === 'provider' + ? 'stripe_transfer' + : 'platform_balance'; + if (transfer.kind !== expectedKind || + transfer.destination !== output.to || + transfer.source_currency !== output.source_currency || + transfer.source_amount_minor !== output.source_amount_minor || + (expectedKind === 'stripe_transfer' && + (transfer.destination_currency !== output.destination_currency || + transfer.destination_amount_minor !== output.destination_amount_minor || + transfer.fx_quote_id !== output.fx_quote_id || + transfer.fx_quote_hash !== output.fx_quote_hash || + transfer.transfer_group !== expectedGroup))) { + return new Error('Targeted fiat transfer does not match output.'); + } + if (output.source_currency !== value.source_currency) { + return new Error('Targeted fiat output source currency mismatch.'); + } + if (output.role === 'provider' && + !payments.fiat.payout_currencies.includes(output.destination_currency)) { + return new Error('Targeted fiat provider destination currency is not canonical.'); + } + } + const totals = this.targetedFiatSettlementTotals(outputs); + if (totals instanceof Error) return totals; + if (totals.provider_count !== value.provider_count || + this.compareAu(totals.provider_liability_au, value.provider_liability_au) !== 0 || + this.compareAu(totals.provider_paid_au, value.provider_paid_au) !== 0 || + this.compareAu(totals.operator_fee_liability_au, value.operator_fee_liability_au) !== 0 || + this.compareAu(totals.operator_fee_retained_au, value.operator_fee_retained_au) !== 0 || + this.compareAu(totals.gross_liability_au, value.gross_liability_au) !== 0 || + this.compareAu(totals.gross_paid_au, value.gross_paid_au) !== 0 || + this.compareAu(totals.rounding_au, value.rounding_au) !== 0 || + this.compareAu(totals.dust_au, value.dust_au) !== 0 || + totals.source_currency !== value.source_currency || + totals.source_amount_minor !== value.source_amount_minor || + stableJson(totals.destination_totals) !== stableJson(normalized.destination_totals)) { + return new Error('Targeted fiat settlement totals do not match outputs.'); + } + const transferRoot = await this.targetedFiatSettlementTransferRoot(outputs); + if (transferRoot !== value.transfer_root) { + return new Error('Targeted fiat settlement transfer root mismatch.'); + } + const record = { + type: 'targeted_fiat_settlement', + ...value, + outputs, + stripe_transfers: transfers, + settled_by: this.address, + settled_by_role: 'admin', + }; + const recordKey = `settle/targeted/fiat/${value.epoch}`; + const existing = await this.get(recordKey); + if (existing) { + if (stableJson(existing) === stableJson(record)) { + return { + ok: true, + op: 'targetedFiatSettlement', + epoch: value.epoch, + rail: 'fiat', + processor: 'stripe', + idempotent: true, + stripe_transfers: transfers, + }; + } + return new Error('Targeted fiat settlement already exists for epoch.'); + } + const applyAnchor = await this.requireEpochApplyAnchor( + value.epoch, + value.epoch_apply_hash, + 'Targeted fiat settlement' + ); + if (applyAnchor instanceof Error) return applyAnchor; + const preparations = await this.payoutPreparationsForSettlement( + value, + normalized, + 'fiat' + ); + if (preparations instanceof Error) return preparations; + for (const transfer of transfers) { + for (const seenKey of [ + this.stripeTransferSeenKey(transfer), + this.stripeFxQuoteSeenKey(transfer), + this.stripeDestinationPaymentSeenKey(transfer), + ]) { + if (seenKey && (await this.get(seenKey)) !== null) { + return new Error('Targeted Stripe transfer evidence was already consumed.'); + } + } + } + const updates = await this.targetedPayoutSettlementUpdates( + outputs, + 'fiat', + value.epoch, + value.at, + transfers.map((entry) => entry.ref) + ); + if (updates instanceof Error) return updates; + + const fee = await this.feeCumRecord('fiat'); + if (fee instanceof Error) return fee; + const feeError = this.guardianValidateFeeRecord(fee, 'fiat'); + if (feeError) return feeError; + const payableFee = this.safeSubAu(fee.cum_au, fee.swept_cum_au); + if (payableFee instanceof Error) return payableFee; + const operatorOutputs = outputs.filter((entry) => entry.role === 'operator_fee'); + const retainedFee = operatorOutputs[0]?.paid_au ?? ZERO_AU; + if ((operatorOutputs.length === 0 && + (!this.isZeroAu(value.operator_fee_liability_au) || + !this.isZeroAu(value.operator_fee_retained_au))) || + (operatorOutputs.length === 1 && + (operatorOutputs[0].to !== value.operator_to || + this.compareAu(operatorOutputs[0].liability_au, payableFee) > 0 || + this.compareAu(operatorOutputs[0].paid_au, value.operator_fee_retained_au) !== 0))) { + return new Error('Targeted fiat operator output mismatch.'); + } + const sweptCumAu = this.safeAddAu(fee.swept_cum_au, retainedFee); + if (sweptCumAu instanceof Error) return sweptCumAu; + const operatorIndex = outputs.findIndex((entry) => entry.role === 'operator_fee'); + const nextFee = { + ...fee, + swept_cum_au: sweptCumAu, + updated_epoch: Math.max(fee.updated_epoch, value.epoch), + last_targeted_settlement_epoch: value.epoch, + last_targeted_settlement_transfer: operatorIndex >= 0 + ? transfers[operatorIndex].ref + : null, + }; + const nextFeeError = this.guardianValidateFeeRecord(nextFee, 'fiat'); + if (nextFeeError) return nextFeeError; + for (const update of updates.liabilityUpdates) await this.put(update.key, update.value); + for (const earning of updates.earningUpdates) { + await this.put(this.earningKey(earning.provider, 'fiat'), earning); + } + for (const [index, transfer] of transfers.entries()) { + for (const seenKey of [ + this.stripeTransferSeenKey(transfer), + this.stripeFxQuoteSeenKey(transfer), + this.stripeDestinationPaymentSeenKey(transfer), + ]) { + if (!seenKey) continue; + await this.put(seenKey, { + rail: 'fiat', + purpose: 'targeted_settlement', + epoch: value.epoch, + output_index: index, + transfer_root: value.transfer_root, + consumed_at: this.tx, + }); + } + } + await this.put(this.feeCumKey('fiat'), nextFee); + const preparationError = await this.consumePayoutPreparations( + preparations, + recordKey + ); + if (preparationError) return preparationError; + await this.put(recordKey, record); + return { + ok: true, + op: 'targetedFiatSettlement', + epoch: value.epoch, + rail: 'fiat', + processor: 'stripe', + idempotent: false, + provider_liability_au: value.provider_liability_au, + provider_paid_au: value.provider_paid_au, + operator_fee_liability_au: value.operator_fee_liability_au, + operator_fee_retained_au: value.operator_fee_retained_au, + gross_liability_au: value.gross_liability_au, + gross_paid_au: value.gross_paid_au, + rounding_au: value.rounding_au, + dust_au: value.dust_au, + source_currency: value.source_currency, + source_amount_minor: value.source_amount_minor, + destination_totals: normalized.destination_totals, + stripe_transfers: transfers, + transfer_root: value.transfer_root, + }; + } + + normalizeTargetedTnkSettlementOutput(output) { + if (!this.isSafeKeyPart(output.to)) return new Error('Invalid TNK settlement output address.'); + const au = this.normalizeAu(output.au, 'TNK settlement output amount', { allowZero: false }); + if (au instanceof Error) { + return new Error('Invalid TNK settlement output amount.'); + } + const tnkE18 = this.parseTnkE18(output.tnk_e18); + if (tnkE18 instanceof Error) return tnkE18; + return output.role === 'provider' + ? { + role: 'provider', + provider: output.provider, + to: output.to, + au, + tnk_e18: output.tnk_e18, + } + : { + role: 'operator_fee', + to: output.to, + au, + tnk_e18: output.tnk_e18, + }; + } + + targetedTnkSettlementTotals(outputs, rateTnkUsdAu) { + let providerAu = ZERO_AU; + let operatorFeeAu = ZERO_AU; + let tnkE18 = 0n; + let providerCount = 0; + for (const output of outputs) { + const expectedTnkE18 = this.auToTnkE18Ceil(output.au, rateTnkUsdAu); + if (expectedTnkE18 instanceof Error) return expectedTnkE18; + if (output.tnk_e18 !== expectedTnkE18.toString()) { + return new Error('TNK settlement output amount does not match oracle rate.'); + } + const parsed = this.parseTnkE18(output.tnk_e18); + if (parsed instanceof Error) return parsed; + tnkE18 += parsed; + if (output.role === 'provider') { + providerCount += 1; + providerAu = this.safeAddAu(providerAu, output.au); + if (providerAu instanceof Error) return providerAu; + } else { + operatorFeeAu = this.safeAddAu(operatorFeeAu, output.au); + if (operatorFeeAu instanceof Error) return operatorFeeAu; + } + } + const grossAu = this.safeAddAu(providerAu, operatorFeeAu); + if (grossAu instanceof Error) return grossAu; + return { + provider_count: providerCount, + provider_au: providerAu, + operator_fee_au: operatorFeeAu, + gross_au: grossAu, + tnk_e18: tnkE18.toString(), + }; + } + + auToTnkE18Ceil(au, rateTnkUsdAu) { + const amount = this.parseAu(au, 'TNK settlement amount', { allowZero: false }); + if (amount instanceof Error) return new Error('Invalid TNK settlement amount.'); + const rate = this.parseAu(rateTnkUsdAu, 'TNK/USD atto-rate', { allowZero: false }); + if (rate instanceof Error) return new Error('Invalid TNK/USD atto-rate.'); + const numerator = amount * TNK_E18; + const denominator = rate; + // Payout conversion rounds up so the provider is never underpaid in TNK atomic units. + return (numerator + denominator - 1n) / denominator; + } + + async fiatDustSweep() { + const adminError = await this.requireAdmin(); + if (adminError) return adminError; + const shapeError = this.validateFiatDustSweepValue(this.value); + if (shapeError) return shapeError; + + const recordKey = `settle/fiat-dust/${this.value.provider}/${this.value.epoch}`; + const existing = await this.get(recordKey); + if (existing) { + return { + ok: true, + op: 'fiatDustSweep', + provider: existing.provider, + epoch: existing.epoch, + dust_au: existing.dust_au, + idempotent: true, + }; + } + + const applyState = await this.epochApplyStateRecord(); + if (applyState.updated_epoch !== this.value.epoch) { + return new Error('Fiat dust sweep epoch must equal the latest applied epoch.'); + } + const provider = await this.get(`prov/${this.value.provider}`); + if (!provider) return new Error('Provider not found.'); + if (!Array.isArray(provider.enclaves)) return new Error('Invalid provider enclave state.'); + if (provider.enclaves.length > 0) { + return new Error('Fiat dust sweep requires a provider with no active enclaves.'); + } + + const params = await this.activeParamsAt(this.value.at, [ + 'holdback_epochs', + 'challenge_epochs', + 'canary_probe_holdback_bps', + 'canary_probe_release_min_passes', + ]); + const earning = await this.earningRecord(this.value.provider, 'fiat'); + if (earning instanceof Error) return earning; + const earningError = this.guardianValidateEarningRecord(earning, this.value.provider, 'fiat'); + if (earningError) return earningError; + const probeGate = await this.probeGateForEarning(this.value.provider, earning, params); + if (probeGate instanceof Error) return probeGate; + const lockedEarningEpochs = this.providerLockedEarningEpochs(provider, params); + if (lockedEarningEpochs instanceof Error) return lockedEarningEpochs; + const disputeGate = await this.providerHasOpenDispute(this.value.provider); + if (disputeGate instanceof Error) return disputeGate; + const refreshed = this.refreshEarningHoldback( + earning, + this.value.epoch, + lockedEarningEpochs, + probeGate, + disputeGate + ); + if (refreshed instanceof Error) return refreshed; + if (!this.isZeroAu(refreshed.held_au)) { + return new Error('Fiat dust cannot be swept while provider earnings are held.'); + } + const unpaid = this.safeSubAu(refreshed.total_au, refreshed.paid_cum_au); + if (unpaid instanceof Error) return unpaid; + const unpaidValue = this.parseAu(unpaid, 'Fiat unpaid provider earnings'); + if (unpaidValue instanceof Error) return unpaidValue; + const dustValue = unpaidValue % USD_CENT_AU; + if (dustValue === 0n) return new Error('Provider has no fiat dust to sweep.'); + const dustAu = this.canonicalAu(dustValue); + const totalAu = this.safeSubAu(refreshed.total_au, dustAu); + if (totalAu instanceof Error) return totalAu; + const dustSweptCumAu = this.safeAddAu(refreshed.fiat_dust_swept_cum_au ?? ZERO_AU, dustAu); + if (dustSweptCumAu instanceof Error) return dustSweptCumAu; + const nextEarning = { + ...refreshed, + total_au: totalAu, + fiat_dust_swept_cum_au: dustSweptCumAu, + updated_epoch: Math.max(refreshed.updated_epoch, this.value.epoch), + updated_at: this.tx, + last_fiat_dust_sweep_epoch: this.value.epoch, + last_fiat_dust_sweep_at: this.tx, + }; + const nextEarningError = this.guardianValidateEarningRecord( + nextEarning, + this.value.provider, + 'fiat' + ); + if (nextEarningError) return nextEarningError; + + const fee = await this.feeCumRecord('fiat'); + if (fee instanceof Error) return fee; + const feeError = this.guardianValidateFeeRecord(fee, 'fiat'); + if (feeError) return feeError; + const cumAu = this.safeAddAu(fee.cum_au, dustAu); + if (cumAu instanceof Error) return cumAu; + const settledCumAu = this.safeAddAu(fee.settled_cum_au ?? fee.cum_au, dustAu); + if (settledCumAu instanceof Error) return settledCumAu; + const nextFee = { + ...fee, + cum_au: cumAu, + settled_cum_au: settledCumAu, + updated_epoch: Math.max(fee.updated_epoch, this.value.epoch), + updated_at: this.tx, + last_fiat_dust_sweep_at: this.tx, + }; + const nextFeeError = this.guardianValidateFeeRecord(nextFee, 'fiat'); + if (nextFeeError) return nextFeeError; + + const record = { + type: 'fiat_dust_sweep', + provider: this.value.provider, + epoch: this.value.epoch, + at: this.value.at, + dust_au: dustAu, + provider_total_before_au: refreshed.total_au, + provider_total_after_au: totalAu, + provider_paid_cum_au: refreshed.paid_cum_au, + destination: 'operator_fee', + swept_by: this.address, + swept_by_role: 'admin', + swept_at: this.tx, + }; + await this.put(this.earningKey(this.value.provider, 'fiat'), nextEarning); + await this.put(this.feeCumKey('fiat'), nextFee); + await this.put(recordKey, record); + return { + ok: true, + op: 'fiatDustSweep', + provider: this.value.provider, + epoch: this.value.epoch, + dust_au: dustAu, + idempotent: false, + }; + } + + validateFiatDustSweepValue(value) { + const shapeError = this.validateExactObjectKeys( + value, + ['op', 'provider', 'epoch', 'at'], + 'Fiat dust sweep' + ); + if (shapeError) return shapeError; + if (value.op !== 'fiat_dust_sweep') return new Error('Invalid fiat dust sweep op.'); + if (!this.isHexBytes(value.provider, 32) || value.provider !== value.provider.toLowerCase()) { + return new Error('Invalid fiat dust sweep provider.'); + } + if (!Number.isSafeInteger(value.epoch) || value.epoch < 1) { + return new Error('Invalid fiat dust sweep epoch.'); + } + if (!Number.isSafeInteger(value.at) || value.at < 0) { + return new Error('Invalid fiat dust sweep timestamp.'); + } + return null; + } + + normalizeTargetedFiatSettlementOutput(output) { + if (!this.isSafeKeyPart(output.to)) return new Error('Invalid fiat settlement output target.'); + const sourceCurrency = this.normalizeFiatCurrency(output.source_currency); + if (sourceCurrency instanceof Error || sourceCurrency !== output.source_currency) { + return new Error('Invalid fiat settlement source currency.'); + } + const sourceAmountMinor = this.normalizeFiatMinor(output.source_amount_minor); + if (sourceAmountMinor instanceof Error || sourceAmountMinor === '0') { + return new Error('Fiat settlement source amount must be positive.'); + } + const canonicalAu = {}; + for (const [field, allowZero] of [ + ['liability_au', false], + ['paid_au', false], + ['rounding_au', true], + ['dust_au', true], + ]) { + canonicalAu[field] = this.normalizeAu( + output[field], + `Fiat settlement output ${field}`, + { allowZero } + ); + if (canonicalAu[field] instanceof Error) { + return new Error(`Invalid fiat settlement output ${field}.`); + } + } + const paidPlusDust = this.safeAddAu(canonicalAu.paid_au, canonicalAu.dust_au); + if (paidPlusDust instanceof Error || + this.compareAu(paidPlusDust, canonicalAu.liability_au) !== 0 || + this.compareAu(canonicalAu.rounding_au, canonicalAu.dust_au) !== 0) { + return new Error('Fiat settlement output liability, paid amount, rounding, and dust do not balance.'); + } + if (output.role === 'operator_fee') return { + role: 'operator_fee', + to: output.to, + ...canonicalAu, + source_currency: sourceCurrency, + source_amount_minor: sourceAmountMinor, + }; + if (!this.isHexBytes(output.provider, 32) || + output.provider !== output.provider.toLowerCase()) { + return new Error('Invalid fiat settlement provider.'); + } + const destinationCurrency = this.normalizeFiatCurrency(output.destination_currency); + if (destinationCurrency instanceof Error || + destinationCurrency !== output.destination_currency) { + return new Error('Invalid fiat settlement destination currency.'); + } + const destinationAmountMinor = this.normalizeFiatMinor(output.destination_amount_minor); + if (destinationAmountMinor instanceof Error || destinationAmountMinor === '0') { + return new Error('Fiat settlement destination amount must be positive.'); + } + const destinationAmountMinMinor = this.normalizeFiatMinor( + output.destination_amount_min_minor + ); + const destinationAmountMaxMinor = this.normalizeFiatMinor( + output.destination_amount_max_minor + ); + if (destinationAmountMinMinor instanceof Error || + destinationAmountMaxMinor instanceof Error || + destinationAmountMinMinor === '0' || + BigInt(destinationAmountMaxMinor) < BigInt(destinationAmountMinMinor) || + BigInt(destinationAmountMinor) < BigInt(destinationAmountMinMinor) || + BigInt(destinationAmountMinor) > BigInt(destinationAmountMaxMinor)) { + return new Error( + 'Fiat settlement destination amount must be within its authorized range.' + ); + } + const hasFxQuoteId = hasOwn(output, 'fx_quote_id'); + const hasFxQuoteHash = hasOwn(output, 'fx_quote_hash'); + if (hasFxQuoteId !== hasFxQuoteHash) { + return new Error('Fiat settlement FX quote identity must be present or absent as a pair.'); + } + const requiresUsdValuationQuote = + sourceCurrency !== 'usd' || destinationCurrency !== 'usd'; + if (requiresUsdValuationQuote && + (!hasFxQuoteId || + !/^fxq_[A-Za-z0-9._-]+$/.test(String(output.fx_quote_id || '')) || + !this.isHexBytes(output.fx_quote_hash, 32) || + output.fx_quote_hash !== output.fx_quote_hash.toLowerCase())) { + return new Error('Fiat settlement FX quote identity is required and invalid.'); + } + if (!requiresUsdValuationQuote && hasFxQuoteId && + (output.fx_quote_id !== null || output.fx_quote_hash !== null)) { + return new Error('Direct USD fiat settlement must not include an FX quote identity.'); + } + const normalized = { + role: 'provider', + provider: output.provider, + to: output.to, + ...canonicalAu, + source_currency: sourceCurrency, + source_amount_minor: sourceAmountMinor, + destination_currency: destinationCurrency, + destination_amount_min_minor: destinationAmountMinMinor, + destination_amount_max_minor: destinationAmountMaxMinor, + destination_amount_minor: destinationAmountMinor, + }; + if (hasFxQuoteId) { + normalized.fx_quote_id = output.fx_quote_id; + normalized.fx_quote_hash = output.fx_quote_hash; + } + return normalized; + } + + targetedFiatSettlementTotals(outputs) { + let providerLiabilityAu = ZERO_AU; + let providerPaidAu = ZERO_AU; + let operatorFeeLiabilityAu = ZERO_AU; + let operatorFeeRetainedAu = ZERO_AU; + let roundingAu = ZERO_AU; + let dustAu = ZERO_AU; + let sourceAmountMinor = 0n; + let sourceCurrency = null; + let providerCount = 0; + const destinationTotals = new Map(); + for (const output of outputs) { + if (sourceCurrency === null) sourceCurrency = output.source_currency; + if (sourceCurrency !== output.source_currency) { + return new Error('Targeted fiat outputs must use one platform source currency.'); + } + sourceAmountMinor += BigInt(output.source_amount_minor); + roundingAu = this.safeAddAu(roundingAu, output.rounding_au); + if (roundingAu instanceof Error) return roundingAu; + dustAu = this.safeAddAu(dustAu, output.dust_au); + if (dustAu instanceof Error) return dustAu; + if (output.role === 'provider') { + providerCount += 1; + providerLiabilityAu = this.safeAddAu(providerLiabilityAu, output.liability_au); + if (providerLiabilityAu instanceof Error) return providerLiabilityAu; + providerPaidAu = this.safeAddAu(providerPaidAu, output.paid_au); + if (providerPaidAu instanceof Error) return providerPaidAu; + destinationTotals.set( + output.destination_currency, + (destinationTotals.get(output.destination_currency) ?? 0n) + + BigInt(output.destination_amount_minor) + ); + } else { + operatorFeeLiabilityAu = this.safeAddAu( + operatorFeeLiabilityAu, + output.liability_au + ); + if (operatorFeeLiabilityAu instanceof Error) return operatorFeeLiabilityAu; + operatorFeeRetainedAu = this.safeAddAu(operatorFeeRetainedAu, output.paid_au); + if (operatorFeeRetainedAu instanceof Error) return operatorFeeRetainedAu; + } + } + const grossLiabilityAu = this.safeAddAu(providerLiabilityAu, operatorFeeLiabilityAu); + if (grossLiabilityAu instanceof Error) return grossLiabilityAu; + const grossPaidAu = this.safeAddAu(providerPaidAu, operatorFeeRetainedAu); + if (grossPaidAu instanceof Error) return grossPaidAu; + return { + provider_count: providerCount, + provider_liability_au: providerLiabilityAu, + provider_paid_au: providerPaidAu, + operator_fee_liability_au: operatorFeeLiabilityAu, + operator_fee_retained_au: operatorFeeRetainedAu, + gross_liability_au: grossLiabilityAu, + gross_paid_au: grossPaidAu, + rounding_au: roundingAu, + dust_au: dustAu, + source_currency: sourceCurrency, + source_amount_minor: sourceAmountMinor.toString(), + destination_totals: [...destinationTotals] + .sort(([left], [right]) => compareCodepoint(left, right)) + .map(([currency, amount]) => ({ currency, amount_minor: amount.toString() })), + }; + } + + normalizeFiatDestinationTotals(value) { + const totals = []; + const seen = new Set(); + for (const entry of value) { + const shapeError = this.validateExactObjectKeys( + entry, + ['currency', 'amount_minor'], + 'targeted fiat destination total' + ); + if (shapeError) return shapeError; + const currency = this.normalizeFiatCurrency(entry.currency); + const amountMinor = this.normalizeFiatMinor(entry.amount_minor); + if (currency instanceof Error || currency !== entry.currency || + amountMinor instanceof Error || amountMinor === '0' || + amountMinor !== entry.amount_minor || + seen.has(currency)) { + return new Error('Invalid targeted fiat destination total.'); + } + seen.add(currency); + totals.push({ currency, amount_minor: amountMinor }); + } + totals.sort((left, right) => compareCodepoint(left.currency, right.currency)); + if (stableJson(totals) !== stableJson(value)) { + return new Error('Targeted fiat destination totals must be canonical.'); + } + return totals; + } + + normalizeFiatMinor(value) { + if (typeof value !== 'string' || !/^(0|[1-9][0-9]*)$/.test(value)) { + return new Error('Fiat minor amount must be a canonical decimal string.'); + } + const parsed = BigInt(value); + if (parsed <= 0n) return new Error('Fiat minor amount must be positive.'); + return parsed.toString(); + } + + async depositTnk() { + const consentError = await this.requireConsent(); + if (consentError) return consentError; + if (!this.isSafeKeyPart(this.value.memo_hash)) return new Error('Invalid deposit memo hash.'); + if (!this.isSafeKeyPart(this.value.treasury_address)) return new Error('Invalid TNK treasury address.'); + const tnkE18 = this.parseTnkE18(this.value.tnk_e18); + if (tnkE18 instanceof Error) return tnkE18; + const quotedAu = this.normalizeAu(this.value.quoted_au, 'TNK quoted credit', { allowZero: false }); + if (quotedAu instanceof Error) return quotedAu; + const quotedRate = this.normalizeAu(this.value.rate_tnk_usd_au, 'TNK quoted rate', { allowZero: false }); + if (quotedRate instanceof Error) return quotedRate; + + const payment = await this.canonicalTnkPaymentConfig(); + if (payment instanceof Error) return payment; + if (this.value.treasury_address !== payment.treasury_address) { + return new Error('TNK deposit intent treasury does not match canonical payment config.'); + } + const rateLock = await this.guardianAcceptTnkDepositIntentRate(this.value); + if (rateLock instanceof Error) return rateLock; + const msbFrom = this.msbAddressForPublicKey(this.address, payment.network); + if (msbFrom instanceof Error) return msbFrom; + + const key = `dep/pending/${this.value.memo_hash}`; + if ((await this.get(key)) !== null) return new Error('TNK deposit memo already pending.'); + if ((await this.get(`dep/tnk-credited/${this.value.memo_hash}`)) !== null) { + return new Error('TNK deposit memo already credited.'); + } + + const record = { + memo_hash: this.value.memo_hash, + user: this.address, + status: 'pending', + requested_at: this.tx, + msb_network: payment.network, + msb_from: msbFrom, + treasury_address: this.value.treasury_address, + tnk_e18: this.value.tnk_e18, + quoted_au: quotedAu, + rate_tnk_usd_au: quotedRate, + rate_source: rateLock.source, + rate_ts: rateLock.ts, + rate_record_key: rateLock.updated_at, + }; + await this.put(key, record); + console.log('mayhem depositTnk', record); + return { + ok: true, + op: 'depositTnk', + memo_hash: this.value.memo_hash, + user: this.address, + }; + } + + async tnkDeposit() { + const adminError = await this.requireAdmin(); + if (adminError) return adminError; + const shapeError = this.validateExactObjectKeys( + this.value, + ['op', 'memo_hash', 'msb_transfer', 'epoch', 'at'], + 'TNK deposit credit' + ); + if (shapeError) return shapeError; + if (this.value.op !== 'tnk_deposit') return new Error('Invalid TNK deposit credit op.'); + if (!this.isSafeKeyPart(this.value.memo_hash)) return new Error('Invalid deposit memo hash.'); + if (!Number.isSafeInteger(this.value.epoch) || this.value.epoch < 1) { + return new Error('Invalid TNK deposit epoch.'); + } + if (!Number.isSafeInteger(this.value.at) || this.value.at < 0) { + return new Error('Invalid TNK deposit timestamp.'); + } + + const transfer = this.normalizeMsbTransferEvidence( + this.value.msb_transfer, + 'TNK deposit MSB transfer evidence' + ); + if (transfer instanceof Error) return transfer; + const creditedKey = `dep/tnk-credited/${this.value.memo_hash}`; + const existingCredit = await this.get(creditedKey); + if (existingCredit) { + if (stableJson(existingCredit.msb_transfer) !== stableJson(transfer)) { + return new Error('TNK deposit memo already credited by a different MSB transfer.'); + } + return { + ok: true, + op: 'tnkDeposit', + who: existingCredit.user, + au: existingCredit.au, + epoch: existingCredit.epoch, + deposit_root: existingCredit.deposit_root, + rate_ts: existingCredit.rate_ts, + msb_tx_hash: transfer.tx_hash, + idempotent: true, + }; + } + + const pendingKey = `dep/pending/${this.value.memo_hash}`; + const pending = await this.get(pendingKey); + if (!pending || pending.status !== 'pending') return new Error('Pending TNK deposit intent not found.'); + + const payment = await this.canonicalTnkPaymentConfig(); + if (payment instanceof Error) return payment; + if ( + pending.msb_network !== payment.network || + pending.treasury_address !== payment.treasury_address + ) { + return new Error('Pending TNK deposit no longer matches canonical payment config.'); + } + if ( + transfer.network !== payment.network || + transfer.from !== pending.msb_from || + transfer.to !== payment.treasury_address + ) { + return new Error('TNK deposit MSB transfer identity does not match pending intent.'); + } + const transferSeenKey = this.msbTransferSeenKey(transfer); + if ((await this.get(transferSeenKey)) !== null) { + return new Error('MSB transfer already consumed by Mayhem.'); + } + + const tnkE18 = this.parseTnkE18(transfer.amount_e18); + if (tnkE18 instanceof Error) return tnkE18; + const pendingTnkE18 = this.parseTnkE18(pending.tnk_e18); + if (pendingTnkE18 instanceof Error) return pendingTnkE18; + if (pendingTnkE18 !== tnkE18) return new Error('TNK deposit amount does not match pending intent.'); + const rate = await this.guardianRequireTnkDepositRateLock(pending, this.value.at); + if (rate instanceof Error) return rate; + const au = this.tnkE18ToAu(tnkE18, pending.rate_tnk_usd_au); + if (au instanceof Error) return au; + if (this.isZeroAu(au)) return new Error('TNK deposit converts to zero au.'); + if (this.compareAu(pending.quoted_au, au) !== 0) { + return new Error('TNK deposit credit does not match pending intent.'); + } + + const ledgerRail = 'tnk'; + const balance = await this.balanceRecord(pending.user, ledgerRail); + if (balance instanceof Error) return balance; + const nextAu = this.safeAddAu(balance.au, au); + if (nextAu instanceof Error) return nextAu; + const leaf = await this.depositLeafHash({ + rail: 'tnk', + memo_hash: this.value.memo_hash, + user_hash: await this.opaqueHash('deposit-user', pending.user), + au, + msb_transfer: transfer, + rate_ts: rate.ts, + treasury_address_hash: await this.opaqueHash('deposit-treasury', pending.treasury_address), + quoted_au: pending.quoted_au, + rate_tnk_usd_au: pending.rate_tnk_usd_au, + }); + const depositRoot = await this.nextDepositRoot({ + epoch: this.value.epoch, + leaf, + au, + at: this.value.at, + }); + if (depositRoot instanceof Error) return depositRoot; + + const record = { + ...balance, + rail: ledgerRail, + au: nextAu, + updated_at: this.tx, + last_deposit_rail: ledgerRail, + last_deposit_rate_ts: rate.ts, + }; + await this.put(this.balanceKey(pending.user, ledgerRail), record); + await this.put(`ev/dep/${this.value.epoch}`, depositRoot); + await this.put(creditedKey, { + rail: 'tnk', + memo_hash: this.value.memo_hash, + user: pending.user, + au, + epoch: this.value.epoch, + rate_ts: rate.ts, + rate_source: rate.source, + rate_tnk_usd_au: pending.rate_tnk_usd_au, + deposit_root: depositRoot.merkle_root, + msb_transfer: transfer, + credited_at: this.tx, + }); + await this.put(transferSeenKey, { + rail: 'tnk', + purpose: 'deposit', + memo_hash: this.value.memo_hash, + user: pending.user, + amount_e18: transfer.amount_e18, + consumed_at: this.tx, + }); + await this.del(pendingKey); + console.log('mayhem tnkDeposit', { + who: pending.user, + au, + tnk_e18: transfer.amount_e18, + msb_tx_hash: transfer.tx_hash, + rate_ts: rate.ts, + rate_tnk_usd_au: pending.rate_tnk_usd_au, + epoch: this.value.epoch, + }); + return { + ok: true, + op: 'tnkDeposit', + who: pending.user, + au, + epoch: this.value.epoch, + deposit_root: depositRoot.merkle_root, + rate_ts: rate.ts, + msb_tx_hash: transfer.tx_hash, + idempotent: false, + }; + } + + normalizeTapAccountBinding(value) { + const shapeError = this.validateExactObjectKeys( + value, + [ + 'op', + 'user', + 'ethereum_address', + 'chain_id', + 'pool_address', + 'user_sig', + 'ethereum_sig', + ], + 'TAP account binding' + ); + if (shapeError) return shapeError; + if (value.op !== 'tap_account_bind') return new Error('Invalid TAP account binding op.'); + if (!this.isHexBytes(value.user, 32)) return new Error('Invalid TAP account user.'); + if (!this.isEthHexBytes(value.ethereum_address, 20)) { + return new Error('Invalid TAP Ethereum account.'); + } + if (!Number.isSafeInteger(value.chain_id) || value.chain_id < 1) { + return new Error('Invalid TAP account chain id.'); + } + if (!this.isEthHexBytes(value.pool_address, 20)) { + return new Error('Invalid TAP account pool address.'); + } + if (!this.isHexBytes(value.user_sig, 64)) { + return new Error('Invalid TAP account user signature.'); + } + if (!this.isEthHexBytes(value.ethereum_sig, 65)) { + return new Error('Invalid TAP account Ethereum signature.'); + } + return { + op: 'tap_account_bind', + user: value.user.toLowerCase(), + ethereum_address: value.ethereum_address.toLowerCase(), + chain_id: value.chain_id, + pool_address: value.pool_address.toLowerCase(), + user_sig: value.user_sig.toLowerCase(), + ethereum_sig: value.ethereum_sig.toLowerCase(), + }; + } + + async tapDeposit() { + const adminError = await this.requireAdmin(); + if (adminError) return adminError; + const shapeError = this.validateTapDepositValue(this.value); + if (shapeError) return shapeError; + const poolError = await this.requireCanonicalTapPool( + this.value.chain_id, + this.value.pool_address + ); + if (poolError) return poolError; + + const ethereumAddress = this.value.who.toLowerCase(); + const ethTxHash = this.value.eth_tx_hash.toLowerCase(); + const blockHash = this.value.block_hash.toLowerCase(); + const poolAddress = this.value.pool_address.toLowerCase(); + const eventSignature = this.value.event_signature.toLowerCase(); + const seenKey = `dep/tap/${this.tapDepositIdentity(this.value)}`; + const existing = await this.get(seenKey); + if (existing !== null) { + return { + ok: true, + op: 'tapDeposit', + duplicate: true, + who: existing.who, + ethereum_address: existing.ethereum_address ?? this.value.who.toLowerCase(), + au: ZERO_AU, + credited_au: existing.au ?? null, + epoch: existing.epoch ?? this.value.epoch, + deposit_root: (await this.get(`ev/dep/${existing.epoch ?? this.value.epoch}`))?.merkle_root ?? null, + }; + } + + const tapWei = this.parseTapWei(this.value.tap_wei); + if (tapWei instanceof Error) return tapWei; + const rate = await this.guardianRequireFreshTapRate(this.value.at); + if (rate instanceof Error) return rate; + const au = this.tapWeiToAu(tapWei, rate.tap_usd_au); + if (au instanceof Error) return au; + if (this.isZeroAu(au)) return new Error('TAP deposit converts to zero au.'); + + const binding = await this.get( + this.tapAccountAddressKey(ethereumAddress, this.value.chain_id, poolAddress) + ); + if (!binding || binding.status !== 'active') { + return new Error('TAP account binding required before deposit credit.'); + } + if (!this.isHexBytes(binding.user, 32)) { + return new Error('Invalid TAP account binding user.'); + } + if ( + binding.ethereum_address !== ethereumAddress || + binding.chain_id !== this.value.chain_id || + binding.pool_address !== poolAddress + ) { + return new Error('TAP account binding does not match deposit evidence.'); + } + const who = binding.user; + + const ledgerRail = 'tap'; + const balance = await this.balanceRecord(who, ledgerRail); + if (balance instanceof Error) return balance; + const balanceError = this.guardianValidateBalanceRecord(balance, who, ledgerRail); + if (balanceError) return balanceError; + const nextAu = this.safeAddAu(balance.au, au); + if (nextAu instanceof Error) return nextAu; + const leaf = await this.depositLeafHash({ + rail: 'tap', + user_hash: await this.opaqueHash('deposit-user', who), + ethereum_address_hash: await this.opaqueHash('deposit-ethereum-account', ethereumAddress), + au, + tap_wei: this.value.tap_wei, + tap_usd_au: rate.tap_usd_au, + rate_ts: rate.ts, + rate_source: rate.source, + eth_tx_hash: ethTxHash, + log_index: this.value.log_index, + block_number: this.value.block_number, + block_hash: blockHash, + chain_id: this.value.chain_id, + pool_address_hash: await this.opaqueHash('deposit-pool', poolAddress), + finalized_block_number: this.value.finalized_block_number, + confirmation_depth: this.value.confirmation_depth, + confirmation_policy: this.value.confirmation_policy, + event_signature: eventSignature, + watcher_id: this.value.watcher_id, + }); + const depositRoot = await this.nextDepositRoot({ + epoch: this.value.epoch, + leaf, + au, + at: this.value.at, + }); + if (depositRoot instanceof Error) return depositRoot; + + const seen = { + rail: 'tap', + who, + ethereum_address: ethereumAddress, + tap_wei: this.value.tap_wei, + tap_usd_au: rate.tap_usd_au, + rate_ts: rate.ts, + rate_source: rate.source, + au, + eth_tx_hash: ethTxHash, + log_index: this.value.log_index, + block_number: this.value.block_number, + block_hash: blockHash, + pool_address: poolAddress, + chain_id: this.value.chain_id, + finalized_block_number: this.value.finalized_block_number, + confirmation_depth: this.value.confirmation_depth, + confirmation_policy: this.value.confirmation_policy, + event_signature: eventSignature, + watcher_id: this.value.watcher_id, + epoch: this.value.epoch, + at: this.value.at, + credited_at: this.tx, + credited_by: this.address, + credited_by_role: 'admin', + }; + const record = { + ...balance, + user: who, + rail: ledgerRail, + au: nextAu, + updated_epoch: Math.max(balance.updated_epoch, this.value.epoch), + updated_at: this.tx, + last_deposit_rail: 'tap', + last_deposit_rate_ts: rate.ts, + last_deposit_rate_source: rate.source, + last_deposit_tap_usd_au: rate.tap_usd_au, + }; + await this.put(seenKey, seen); + await this.put(this.balanceKey(who, ledgerRail), record); + await this.put(`ev/dep/${this.value.epoch}`, depositRoot); + console.log('mayhem tapDeposit', { + who, + ethereum_address: ethereumAddress, + au, + tap_wei: this.value.tap_wei, + tap_usd_au: rate.tap_usd_au, + rate_ts: rate.ts, + eth_tx_hash: ethTxHash, + log_index: this.value.log_index, + epoch: this.value.epoch, + }); + return { + ok: true, + op: 'tapDeposit', + duplicate: false, + who, + ethereum_address: ethereumAddress, + au, + epoch: this.value.epoch, + deposit_root: depositRoot.merkle_root, + eth_tx_hash: ethTxHash, + log_index: this.value.log_index, + rate_ts: rate.ts, + }; + } + + validateTapDepositValue(value) { + const shapeError = this.validateExactObjectKeys( + value, + [ + 'op', + 'who', + 'tap_wei', + 'eth_tx_hash', + 'log_index', + 'block_number', + 'block_hash', + 'pool_address', + 'chain_id', + 'finalized_block_number', + 'confirmation_depth', + 'confirmation_policy', + 'event_signature', + 'watcher_id', + 'epoch', + 'at', + ], + 'TAP deposit' + ); + if (shapeError) return shapeError; + if (value.op !== 'tap_deposit') return new Error('Invalid TAP deposit op.'); + if (!this.isSafeKeyPart(value.who)) return new Error('Invalid TAP deposit recipient.'); + if (!this.isEthHexBytes(value.eth_tx_hash, 32)) return new Error('Invalid Ethereum tx hash.'); + if (!Number.isSafeInteger(value.log_index) || value.log_index < 0) { + return new Error('Invalid TAP deposit log index.'); + } + if (!Number.isSafeInteger(value.block_number) || value.block_number < 0) { + return new Error('Invalid TAP deposit block number.'); + } + if (!this.isEthHexBytes(value.block_hash, 32)) return new Error('Invalid TAP deposit block hash.'); + if (!this.isEthHexBytes(value.pool_address, 20)) return new Error('Invalid TAP pool address.'); + if (!Number.isSafeInteger(value.chain_id) || value.chain_id < 1) return new Error('Invalid TAP chain id.'); + if (!Number.isSafeInteger(value.finalized_block_number) || value.finalized_block_number < value.block_number) { + return new Error('Invalid TAP finalized block number.'); + } + if (!Number.isSafeInteger(value.confirmation_depth) || value.confirmation_depth < 0) { + return new Error('Invalid TAP confirmation depth.'); + } + if (value.confirmation_depth !== value.finalized_block_number - value.block_number) { + return new Error('TAP confirmation depth does not match finalized block.'); + } + if (!this.isSafeKeyPart(value.confirmation_policy)) return new Error('Invalid TAP confirmation policy.'); + if (value.confirmation_depth < MIN_TAP_CONFIRMATION_DEPTH) { + return new Error(`TAP confirmation depth below minimum ${MIN_TAP_CONFIRMATION_DEPTH}.`); + } + if (value.chain_id === 1 && value.confirmation_policy !== 'finalized-tag') { + return new Error('Ethereum mainnet TAP deposits require finalized-tag policy.'); + } + if (value.confirmation_policy !== 'finalized-tag') { + if (value.confirmation_policy !== `depth-${value.confirmation_depth}`) { + return new Error('TAP confirmation policy must match the confirmed depth or use finalized-tag.'); + } + } + if (!this.isEthHexBytes(value.event_signature, 32)) return new Error('Invalid TAP event signature.'); + if (value.event_signature.toLowerCase() !== TAP_DEPOSIT_EVENT_SIGNATURE) { + return new Error('TAP deposit event signature mismatch.'); + } + if (value.watcher_id !== TAP_DEPOSIT_WATCHER_ID) return new Error('Invalid TAP deposit watcher id.'); + if (!Number.isSafeInteger(value.epoch) || value.epoch < 1) return new Error('Invalid TAP deposit epoch.'); + if (!Number.isSafeInteger(value.at) || value.at < 0) return new Error('Invalid TAP deposit timestamp.'); + const tapWei = this.parseTapWei(value.tap_wei); + if (tapWei instanceof Error) return tapWei; + return null; + } + + async tapDepositReversal() { + const adminError = await this.requireAdmin(); + if (adminError) return adminError; + const shapeError = this.validateTapDepositReversalValue(this.value); + if (shapeError) return shapeError; + const poolError = await this.requireCanonicalTapPool( + this.value.chain_id, + this.value.pool_address + ); + if (poolError) return poolError; + + const depositSeenKey = `dep/tap/${this.tapDepositIdentity(this.value)}`; + const depositSeen = await this.get(depositSeenKey); + if (depositSeen === null) return new Error('TAP deposit event not found.'); + if (depositSeen.block_number !== this.value.block_number) { + return new Error('TAP reversal block number does not match credited event.'); + } + const reversalSeenKey = `${depositSeenKey}/reversal`; + const existing = await this.get(reversalSeenKey); + if (existing !== null) { + return { + ok: true, + op: 'tapDepositReversal', + duplicate: true, + who: existing.who, + au: ZERO_AU, + reversed_au: existing.au, + clawback_au: ZERO_AU, + credited_clawback_au: existing.clawback_au, + network_absorbed_au: ZERO_AU, + credited_network_absorbed_au: existing.network_absorbed_au, + frozen: existing.frozen, + epoch: existing.epoch, + }; + } + if (depositSeen.reversed === true) return new Error('TAP deposit is already reversed.'); + + const who = depositSeen.who; + const au = this.normalizeAu(depositSeen.au, 'credited TAP deposit amount', { allowZero: false }); + if (au instanceof Error) return au; + const balance = await this.balanceRecord(who, 'tap'); + if (balance instanceof Error) return balance; + const balanceError = this.guardianValidateBalanceRecord(balance, who, 'tap'); + if (balanceError) return balanceError; + const clawbackAu = this.compareAu(balance.au, au) < 0 ? balance.au : au; + const networkAbsorbedAu = this.safeSubAu(au, clawbackAu); + if (networkAbsorbedAu instanceof Error) return networkAbsorbedAu; + const nextAu = this.safeSubAu(balance.au, clawbackAu); + if (nextAu instanceof Error) return nextAu; + const frozen = !this.isZeroAu(networkAbsorbedAu); + + const leaf = await this.depositLeafHash({ + rail: 'tap', + user_hash: await this.opaqueHash('deposit-user', who), + ethereum_address_hash: await this.opaqueHash( + 'deposit-ethereum-account', + depositSeen.ethereum_address + ), + au, + clawback_au: clawbackAu, + network_absorbed_au: networkAbsorbedAu, + chain_id: this.value.chain_id, + pool_address_hash: await this.opaqueHash('deposit-pool', this.value.pool_address), + eth_tx_hash: this.value.eth_tx_hash, + log_index: this.value.log_index, + block_number: this.value.block_number, + block_hash: this.value.block_hash, + reconciliation_from_block: this.value.reconciliation_from_block, + reconciliation_to_block: this.value.reconciliation_to_block, + finalized_block_number: this.value.finalized_block_number, + confirmation_policy: this.value.confirmation_policy, + watcher_id: this.value.watcher_id, + reason: this.value.reason, + reversed: true, + }); + const depositRoot = await this.nextDepositReversalRoot({ + epoch: this.value.epoch, + leaf, + disputedAu: au, + clawbackAu, + absorbedAu: networkAbsorbedAu, + at: this.value.at, + }); + if (depositRoot instanceof Error) return depositRoot; + + const reversalSeen = { + rail: 'tap', + who, + ethereum_address: depositSeen.ethereum_address, + au, + clawback_au: clawbackAu, + network_absorbed_au: networkAbsorbedAu, + chain_id: this.value.chain_id, + pool_address: this.value.pool_address.toLowerCase(), + eth_tx_hash: this.value.eth_tx_hash.toLowerCase(), + log_index: this.value.log_index, + block_number: this.value.block_number, + block_hash: this.value.block_hash.toLowerCase(), + reconciliation_from_block: this.value.reconciliation_from_block, + reconciliation_to_block: this.value.reconciliation_to_block, + finalized_block_number: this.value.finalized_block_number, + confirmation_policy: this.value.confirmation_policy, + watcher_id: this.value.watcher_id, + reason: this.value.reason, + frozen, + epoch: this.value.epoch, + at: this.value.at, + reversed_at: this.tx, + reversed_by: this.address, + reversed_by_role: 'admin', + }; + let freezeRecord = null; + if (frozen) { + const existingFrozen = await this.get(`frozen/${who}`); + const disputedAuCum = this.safeAddAu(existingFrozen?.disputed_au_cum ?? ZERO_AU, au); + if (disputedAuCum instanceof Error) return disputedAuCum; + const clawbackAuCum = this.safeAddAu(existingFrozen?.clawback_au_cum ?? ZERO_AU, clawbackAu); + if (clawbackAuCum instanceof Error) return clawbackAuCum; + const absorbedAuCum = this.safeAddAu( + existingFrozen?.network_absorbed_au_cum ?? ZERO_AU, + networkAbsorbedAu + ); + if (absorbedAuCum instanceof Error) return absorbedAuCum; + freezeRecord = { + user: who, + status: 'frozen', + reason: 'tap_deposit_reorg_shortfall', + rail: 'tap', + first_frozen_at: existingFrozen?.first_frozen_at ?? this.tx, + first_frozen_at_seconds: existingFrozen?.first_frozen_at_seconds ?? this.value.at, + updated_at: this.tx, + updated_at_seconds: this.value.at, + updated_epoch: Math.max(existingFrozen?.updated_epoch ?? 0, this.value.epoch), + dispute_count: (existingFrozen?.dispute_count ?? 0) + 1, + disputed_au_cum: disputedAuCum, + clawback_au_cum: clawbackAuCum, + network_absorbed_au_cum: absorbedAuCum, + last_tap_deposit_identity: this.tapDepositIdentity(this.value), + }; + } + + await this.put(this.balanceKey(who, 'tap'), { + ...balance, + au: nextAu, + updated_epoch: Math.max(balance.updated_epoch, this.value.epoch), + updated_at: this.tx, + last_deposit_reversal_rail: 'tap', + last_deposit_reversal_at: this.tx, + }); + await this.put(depositSeenKey, { + ...depositSeen, + reversed: true, + reversed_au: au, + clawback_au: clawbackAu, + network_absorbed_au: networkAbsorbedAu, + reversed_at: this.tx, + reversed_at_seconds: this.value.at, + reversed_epoch: this.value.epoch, + }); + await this.put(reversalSeenKey, reversalSeen); + if (freezeRecord) await this.put(`frozen/${who}`, freezeRecord); + await this.put(`ev/dep/${this.value.epoch}`, depositRoot); + return { + ok: true, + op: 'tapDepositReversal', + duplicate: false, + who, + au, + clawback_au: clawbackAu, + network_absorbed_au: networkAbsorbedAu, + frozen, + epoch: this.value.epoch, + deposit_root: depositRoot.merkle_root, + }; + } + + validateTapDepositReversalValue(value) { + const shapeError = this.validateExactObjectKeys( + value, + [ + 'op', + 'chain_id', + 'pool_address', + 'eth_tx_hash', + 'log_index', + 'block_number', + 'block_hash', + 'reconciliation_from_block', + 'reconciliation_to_block', + 'finalized_block_number', + 'confirmation_policy', + 'watcher_id', + 'reason', + 'epoch', + 'at', + ], + 'TAP deposit reversal' + ); + if (shapeError) return shapeError; + if (value.op !== 'tap_deposit_reversal') return new Error('Invalid TAP deposit reversal op.'); + const identityError = this.validateTapDepositIdentity(value); + if (identityError) return identityError; + if (!Number.isSafeInteger(value.block_number) || value.block_number < 0) { + return new Error('Invalid TAP reversal block number.'); + } + if (!Number.isSafeInteger(value.reconciliation_from_block) + || value.reconciliation_from_block > value.block_number) { + return new Error('Invalid TAP reversal reconciliation start.'); + } + if (!Number.isSafeInteger(value.reconciliation_to_block) + || value.reconciliation_to_block < value.block_number) { + return new Error('Invalid TAP reversal reconciliation end.'); + } + if (!Number.isSafeInteger(value.finalized_block_number) + || value.finalized_block_number - value.reconciliation_to_block < MIN_TAP_CONFIRMATION_DEPTH) { + return new Error('TAP reversal is not sufficiently behind the finalized reference.'); + } + if (value.confirmation_policy !== 'finalized-tag') { + return new Error('TAP reversal requires finalized-tag policy.'); + } + if (value.watcher_id !== TAP_DEPOSIT_WATCHER_ID) return new Error('TAP watcher id mismatch.'); + if (value.reason !== 'canonical_event_missing') return new Error('Invalid TAP reversal reason.'); + if (!Number.isSafeInteger(value.epoch) || value.epoch < 1) return new Error('Invalid TAP reversal epoch.'); + if (!Number.isSafeInteger(value.at) || value.at < 0) return new Error('Invalid TAP reversal timestamp.'); + return null; + } + + async fiatDeposit() { + const adminError = await this.requireAdmin(); + if (adminError) return adminError; + if (!FIAT_DEPOSIT_RAILS.has(this.value.rail)) return new Error('Unsupported fiat deposit rail.'); + if (!this.isSafeKeyPart(this.value.who)) return new Error('Invalid deposit recipient.'); + if (!this.isSafeKeyPart(this.value.ext_ref_hash)) return new Error('Invalid external reference hash.'); + const fiat = this.fiatEvidenceFields(); + if (fiat instanceof Error) return fiat; + const au = this.normalizeAu(this.value.au, 'fiat deposit amount', { allowZero: false }); + if (au instanceof Error) return au; + + const seenKey = `dep/fiat/${this.value.ext_ref_hash}`; + const existing = await this.get(seenKey); + if (existing !== null) { + return { + ok: true, + op: 'fiatDeposit', + duplicate: true, + rail: existing.rail ?? 'fiat', + processor_rail: existing.processor_rail ?? this.value.rail, + who: existing.who, + au: ZERO_AU, + credited_au: existing.au ?? null, + epoch: existing.epoch ?? this.value.epoch, + deposit_root: (await this.get(`ev/dep/${existing.epoch ?? this.value.epoch}`))?.merkle_root ?? null, + }; + } + + const ledgerRail = 'fiat'; + const balance = await this.balanceRecord(this.value.who, ledgerRail); + if (balance instanceof Error) return balance; + const balanceError = this.guardianValidateBalanceRecord(balance, this.value.who, ledgerRail); + if (balanceError) return balanceError; + const nextAu = this.safeAddAu(balance.au, au); + if (nextAu instanceof Error) return nextAu; + const leaf = await this.depositLeafHash({ + rail: ledgerRail, + processor_rail: this.value.rail, + user_hash: await this.opaqueHash('deposit-user', this.value.who), + au, + ext_ref_hash: this.value.ext_ref_hash, + ...fiat, + }); + const depositRoot = await this.nextDepositRoot({ + epoch: this.value.epoch, + leaf, + au, + at: this.value.at, + }); + if (depositRoot instanceof Error) return depositRoot; + + const record = { + ...balance, + rail: ledgerRail, + au: nextAu, + updated_epoch: Math.max(balance.updated_epoch, this.value.epoch), + updated_at: this.tx, + last_deposit_rail: ledgerRail, + last_deposit_processor_rail: this.value.rail, + ...(fiat.fiat_currency ? { last_deposit_fiat_currency: fiat.fiat_currency } : {}), + }; + const seen = { + rail: ledgerRail, + processor_rail: this.value.rail, + who: this.value.who, + au, + ext_ref_hash: this.value.ext_ref_hash, + ...fiat, + epoch: this.value.epoch, + at: this.value.at, + credited_at: this.tx, + credited_by: this.address, + credited_by_role: 'admin', + chargeback_au_cum: ZERO_AU, + network_absorbed_au_cum: ZERO_AU, + }; + await this.put(seenKey, seen); + await this.put(this.balanceKey(this.value.who, ledgerRail), record); + await this.put(`ev/dep/${this.value.epoch}`, depositRoot); + console.log('mayhem fiatDeposit', { + rail: ledgerRail, + processor_rail: this.value.rail, + who: this.value.who, + au, + ...fiat, + epoch: this.value.epoch, + }); + return { + ok: true, + op: 'fiatDeposit', + rail: ledgerRail, + processor_rail: this.value.rail, + who: this.value.who, + au, + duplicate: false, + epoch: this.value.epoch, + deposit_root: depositRoot.merkle_root, + ...fiat, + }; + } + + async fiatChargeback() { + const adminError = await this.requireAdmin(); + if (adminError) return adminError; + if (!FIAT_DEPOSIT_RAILS.has(this.value.rail)) return new Error('Unsupported fiat chargeback rail.'); + if (!this.isSafeKeyPart(this.value.who)) return new Error('Invalid chargeback account.'); + if (!this.isSafeKeyPart(this.value.ext_ref_hash)) return new Error('Invalid external reference hash.'); + if (!this.isSafeKeyPart(this.value.dispute_ref_hash)) return new Error('Invalid dispute reference hash.'); + const fiat = this.fiatEvidenceFields(); + if (fiat instanceof Error) return fiat; + const au = this.normalizeAu(this.value.au, 'fiat chargeback amount', { allowZero: false }); + if (au instanceof Error) return au; + + const depositSeenKey = `dep/fiat/${this.value.ext_ref_hash}`; + const depositSeen = await this.get(depositSeenKey); + if (depositSeen === null) return new Error('Fiat deposit reference not found.'); + if (depositSeen.who !== this.value.who) return new Error('Fiat chargeback recipient mismatch.'); + if (depositSeen.processor_rail !== this.value.rail) return new Error('Fiat chargeback processor rail mismatch.'); + const chargebackSeenKey = `${depositSeenKey}/chargeback/${this.value.dispute_ref_hash}`; + const existingChargeback = await this.get(chargebackSeenKey); + if (existingChargeback !== null) { + return { + ok: true, + op: 'fiatChargeback', + duplicate: true, + rail: existingChargeback.rail ?? 'fiat', + processor_rail: existingChargeback.processor_rail ?? this.value.rail, + who: existingChargeback.who, + au: ZERO_AU, + disputed_au: existingChargeback.au ?? null, + clawback_au: ZERO_AU, + credited_clawback_au: existingChargeback.clawback_au ?? null, + network_absorbed_au: ZERO_AU, + frozen: true, + epoch: existingChargeback.epoch ?? this.value.epoch, + deposit_root: (await this.get(`ev/dep/${existingChargeback.epoch ?? this.value.epoch}`))?.merkle_root ?? null, + ...fiat, + }; + } + const depositDisputedAuCum = this.safeAddAu(depositSeen.disputed_au_cum ?? ZERO_AU, au); + if (depositDisputedAuCum instanceof Error) return depositDisputedAuCum; + if (this.compareAu(depositDisputedAuCum, depositSeen.au) > 0) { + return new Error('Fiat chargeback exceeds original deposit.'); + } + + const ledgerRail = 'fiat'; + const balance = await this.balanceRecord(this.value.who, ledgerRail); + if (balance instanceof Error) return balance; + const balanceError = this.guardianValidateBalanceRecord(balance, this.value.who, ledgerRail); + if (balanceError) return balanceError; + const clawbackAu = this.compareAu(balance.au, au) < 0 ? balance.au : au; + const networkAbsorbedAu = this.safeSubAu(au, clawbackAu); + if (networkAbsorbedAu instanceof Error) return networkAbsorbedAu; + const nextAu = this.safeSubAu(balance.au, clawbackAu); + if (nextAu instanceof Error) return nextAu; + + const leaf = await this.depositLeafHash({ + rail: ledgerRail, + processor_rail: this.value.rail, + user_hash: await this.opaqueHash('deposit-user', this.value.who), + au, + clawback_au: clawbackAu, + network_absorbed_au: networkAbsorbedAu, + ext_ref_hash: this.value.ext_ref_hash, + dispute_ref_hash: this.value.dispute_ref_hash, + reversed: true, + ...fiat, + }); + const depositRoot = await this.nextDepositReversalRoot({ + epoch: this.value.epoch, + leaf, + disputedAu: au, + clawbackAu, + absorbedAu: networkAbsorbedAu, + at: this.value.at, + }); + if (depositRoot instanceof Error) return depositRoot; + + const frozen = await this.get(`frozen/${this.value.who}`); + const frozenDisputedAuCum = this.safeAddAu(frozen?.disputed_au_cum ?? ZERO_AU, au); + if (frozenDisputedAuCum instanceof Error) return frozenDisputedAuCum; + const clawbackAuCum = this.safeAddAu(frozen?.clawback_au_cum ?? ZERO_AU, clawbackAu); + if (clawbackAuCum instanceof Error) return clawbackAuCum; + const absorbedAuCum = this.safeAddAu(frozen?.network_absorbed_au_cum ?? ZERO_AU, networkAbsorbedAu); + if (absorbedAuCum instanceof Error) return absorbedAuCum; + const freezeRecord = { + user: this.value.who, + status: 'frozen', + reason: 'fiat_chargeback', + rail: ledgerRail, + processor_rail: this.value.rail, + first_frozen_at: frozen?.first_frozen_at ?? this.tx, + first_frozen_at_seconds: frozen?.first_frozen_at_seconds ?? this.value.at, + updated_at: this.tx, + updated_at_seconds: this.value.at, + updated_epoch: Math.max(frozen?.updated_epoch ?? 0, this.value.epoch), + dispute_count: (frozen?.dispute_count ?? 0) + 1, + disputed_au_cum: frozenDisputedAuCum, + clawback_au_cum: clawbackAuCum, + network_absorbed_au_cum: absorbedAuCum, + last_ext_ref_hash: this.value.ext_ref_hash, + last_dispute_ref_hash: this.value.dispute_ref_hash, + last_fiat_currency: fiat.fiat_currency, + }; + const depositChargebackAuCum = this.safeAddAu(depositSeen.chargeback_au_cum ?? ZERO_AU, clawbackAu); + if (depositChargebackAuCum instanceof Error) return depositChargebackAuCum; + const depositAbsorbedAuCum = this.safeAddAu(depositSeen.network_absorbed_au_cum ?? ZERO_AU, networkAbsorbedAu); + if (depositAbsorbedAuCum instanceof Error) return depositAbsorbedAuCum; + const chargebackSeen = { + rail: ledgerRail, + processor_rail: this.value.rail, + who: this.value.who, + au, + clawback_au: clawbackAu, + network_absorbed_au: networkAbsorbedAu, + ext_ref_hash: this.value.ext_ref_hash, + dispute_ref_hash: this.value.dispute_ref_hash, + ...fiat, + epoch: this.value.epoch, + at: this.value.at, + credited_at: this.tx, + credited_by: this.address, + credited_by_role: 'admin', + }; + + await this.put(this.balanceKey(this.value.who, ledgerRail), { + ...balance, + rail: ledgerRail, + au: nextAu, + updated_epoch: Math.max(balance.updated_epoch, this.value.epoch), + updated_at: this.tx, + last_chargeback_rail: ledgerRail, + last_chargeback_processor_rail: this.value.rail, + last_chargeback_fiat_currency: fiat.fiat_currency, + }); + await this.put(depositSeenKey, { + ...depositSeen, + disputed_au_cum: depositDisputedAuCum, + chargeback_au_cum: depositChargebackAuCum, + network_absorbed_au_cum: depositAbsorbedAuCum, + last_dispute_ref_hash: this.value.dispute_ref_hash, + last_chargeback_at: this.tx, + last_chargeback_at_seconds: this.value.at, + last_chargeback_epoch: this.value.epoch, + }); + await this.put(chargebackSeenKey, chargebackSeen); + await this.put(`frozen/${this.value.who}`, freezeRecord); + await this.put(`ev/dep/${this.value.epoch}`, depositRoot); + console.log('mayhem fiatChargeback', { + rail: ledgerRail, + processor_rail: this.value.rail, + who: this.value.who, + au, + duplicate: false, + clawback_au: clawbackAu, + network_absorbed_au: networkAbsorbedAu, + ...fiat, + epoch: this.value.epoch, + }); + return { + ok: true, + op: 'fiatChargeback', + rail: ledgerRail, + processor_rail: this.value.rail, + who: this.value.who, + au: this.value.au, + clawback_au: clawbackAu, + network_absorbed_au: networkAbsorbedAu, + frozen: true, + epoch: this.value.epoch, + deposit_root: depositRoot.merkle_root, + ...fiat, + }; + } + + async currentRules() { + return await this.get(CURRENT_RULES_KEY); + } + + async isAdmin(sender = this.address) { + const admin = await this.get('admin'); + return typeof admin === 'string' && admin === sender; + } + + async requireAdmin(sender = this.address) { + if (await this.isAdmin(sender)) return null; + return new Error('Admin required.'); + } + + async requireConsent(sender = this.address) { + if (!sender) return new Error('Consent required.'); + + const rules = await this.currentRules(); + if (!rules) return new Error('Rules are not set.'); + + const consent = await this.get(`consent/${sender}`); + if (!consent || consent.ver !== rules.ver || consent.hash !== rules.hash) { + return new Error(`Consent required for rules version ${rules.ver}.`); + } + const frozen = await this.get(`frozen/${sender}`); + if (frozen?.status === 'frozen') return new Error('Account frozen.'); + return null; + } + + async requireProvider(sender = this.address) { + const provider = await this.get(`prov/${sender}`); + if (!provider || provider.status !== 'active') return new Error('Provider registration required.'); + return null; + } + + async requireCurrentAdminPrice(enclaveId, ctxBracket = null) { + const enclave = await this.get(`enclave/${enclaveId}`); + const resolvedCtxBracket = + ctxBracket ?? + (enclave && this.enclaveUsesCtxPrice(enclave) + ? await this.defaultPriceCtxBracketForEnclave(enclave, 0) + : null); + if (resolvedCtxBracket instanceof Error) return resolvedCtxBracket; + const schedule = await this.get(this.priceScheduleKey(enclaveId, resolvedCtxBracket)); + const current = schedule?.current; + if (!current) { + return new Error('Current admin price required before provider serving.'); + } + if (schedule.denom !== PRICE_DENOMINATION || current.denom !== PRICE_DENOMINATION) { + return new Error('Provider serving requires a current au_usd admin price.'); + } + if (current.enclave_id !== enclaveId) { + return new Error('Provider serving requires a current admin-set enclave price.'); + } + if ((current.ctx_bracket ?? null) !== (resolvedCtxBracket ?? null)) { + return new Error('Provider serving requires a current admin-set enclave price for the context bracket.'); + } + if (!current.set_by || typeof current.set_by !== 'string') { + return new Error('Provider serving requires a current admin-set enclave price.'); + } + const admin = await this.get('admin'); + if (admin === null) { + return new Error('Provider serving requires a current admin key.'); + } + if (current.set_by !== admin) { + return new Error('Provider serving requires a current price set by the current admin.'); + } + if (current.set_by_role !== 'admin') { + return new Error('Provider serving requires a current admin-set enclave price.'); + } + return null; + } + + enclaveUsesCtxPrice(enclave) { + return this.modelClassFor(enclave) === DEFAULT_MODEL_CLASS; + } + + enclaveCtxCapacity(enclave) { + const caps = enclave?.caps && typeof enclave.caps === 'object' && !Array.isArray(enclave.caps) + ? enclave.caps + : {}; + const value = caps.ctx_max ?? caps.ctx ?? 0; + if (!Number.isSafeInteger(value) || value < 0) { + return new Error('Invalid enclave context capacity.'); + } + return value; + } + + async defaultPriceCtxBracketForEnclave(enclave, at) { + const table = await this.ctxBracketTableAt(at); + if (table instanceof Error) return table; + const ctx = this.enclaveCtxCapacity(enclave); + if (ctx instanceof Error) return ctx; + const bracket = ctxBracketForTokens(ctx, table.brackets); + if (!bracket) return new Error('No context bracket covers enclave context capacity.'); + return bracket; + } + + async priceCtxMetaForEnclave(enclave, ctxBracket, at, label = 'Price') { + if (!this.enclaveUsesCtxPrice(enclave)) { + if (ctxBracket !== undefined && ctxBracket !== null) { + return new Error(`${label} context bracket is only valid for text-generation enclaves.`); + } + return null; + } + const table = await this.ctxBracketTableAt(at); + if (table instanceof Error) return table; + const bracket = ctxBracket ?? await this.defaultPriceCtxBracketForEnclave(enclave, at); + if (bracket instanceof Error) return bracket; + if (!this.isSafeKeyPart(bracket)) return new Error(`Invalid ${label} context bracket.`); + if (!Array.isArray(table.brackets) || !table.brackets.some((entry) => entry.id === bracket)) { + return new Error(`${label} context bracket is not in the active admin table.`); + } + return { ctx_bracket: bracket, ctx_bracket_table_ver: table.ver }; + } + + priceScheduleKey(enclaveId, ctxBracket = null) { + return ctxBracket ? `price/${enclaveId}/${ctxBracket}` : `price/${enclaveId}`; + } + + priceRecordKey(enclaveId, ver, ctxBracket = null) { + return ctxBracket ? `price/${enclaveId}/${ctxBracket}/v/${ver}` : `price/${enclaveId}/v/${ver}`; + } + + priceMarketKey(enclaveId, ctxBracket = null) { + return stableJson([enclaveId, ctxBracket ?? null]); + } + + requireAdminCreatedEnclave(enclave) { + if (enclave?.created_by_role !== 'admin') { + return new Error('Canonical serving requires an admin-created enclave.'); + } + return null; + } + + requireAdminCreatedRoom(room) { + if (room?.creator_role !== 'admin') { + return new Error('Provider room serving requires an admin-created room.'); + } + return null; + } + + validateExactCommandValue(allowedKeys, opName, optionalKeys = []) { + if (!this.value || typeof this.value !== 'object' || Array.isArray(this.value)) { + return new Error(`${opName} value must be an object.`); + } + const allowed = new Set([...allowedKeys, ...optionalKeys]); + const unknown = Object.keys(this.value).filter((key) => !allowed.has(key)).sort(); + if (unknown.length > 0) { + return new Error(`${opName} does not accept provider-authored fields: ${unknown.join(', ')}.`); + } + for (const key of allowedKeys) { + if (!hasOwn(this.value, key)) return new Error(`${opName} is missing ${key}.`); + } + if (hasOwn(this.value, 'op') && this.value.op !== opName) { + return new Error(`Invalid ${opName} op.`); + } + return null; + } + + validateExactObjectKeys(value, allowedKeys, label) { + if (!value || typeof value !== 'object' || Array.isArray(value)) { + return new Error(`${label} value must be an object.`); + } + const allowed = new Set(allowedKeys); + const unknown = Object.keys(value).filter((key) => !allowed.has(key)).sort(); + if (unknown.length > 0) { + return new Error(`${label} does not accept fields: ${unknown.join(', ')}.`); + } + for (const key of allowedKeys) { + if (!hasOwn(value, key)) return new Error(`${label} is missing ${key}.`); + } + return null; + } + + normalizeCtxBracketTable(brackets) { + if (!Array.isArray(brackets) || brackets.length === 0 || brackets.length > 32) { + return new Error('Context bracket table must be a non-empty array.'); + } + const ids = new Set(); + let previousMax = 0; + const normalized = []; + for (let idx = 0; idx < brackets.length; idx += 1) { + const entry = brackets[idx]; + const shapeError = this.validateExactObjectKeys(entry, ['id', 'max_ctx'], 'context bracket'); + if (shapeError) return shapeError; + if (!this.isSafeKeyPart(entry.id)) return new Error('Invalid context bracket id.'); + if (ids.has(entry.id)) return new Error('Duplicate context bracket id.'); + ids.add(entry.id); + + const isLast = idx === brackets.length - 1; + if (isLast) { + if (entry.max_ctx !== null) return new Error('Last context bracket max_ctx must be null.'); + normalized.push({ id: entry.id, max_ctx: null }); + continue; + } + if (!Number.isSafeInteger(entry.max_ctx) || entry.max_ctx <= previousMax) { + return new Error('Context bracket max_ctx values must increase.'); + } + previousMax = entry.max_ctx; + normalized.push({ id: entry.id, max_ctx: entry.max_ctx }); + } + return normalized; + } + + defaultCtxBracketTableRecord() { + return { + ver: CTX_BRACKET_TABLE_VERSION, + brackets: cloneValue(CTX_BRACKETS), + submitted_at: 0, + effective_at: 0, + effective_from: null, + updated_at: null, + set_by: null, + set_by_role: 'genesis', + }; + } + + async ctxBracketSchedule() { + if (!this.storage) return { current: this.defaultCtxBracketTableRecord(), pending: null }; + const stored = await this.get('ctx_brackets'); + const fallback = this.defaultCtxBracketTableRecord(); + if (!stored) return { current: fallback, pending: null }; + return { + current: stored.current ?? fallback, + pending: stored.pending ?? null, + }; + } + + ctxBracketLatestEntry(schedule) { + if (schedule.pending && schedule.pending.ver > schedule.current.ver) return schedule.pending; + return schedule.current; + } + + ctxBracketActiveEntry(schedule, at) { + if (schedule.pending && schedule.pending.effective_at <= at) return schedule.pending; + return schedule.current; + } + + async ctxBracketTableAt(at) { + if (!Number.isSafeInteger(at) || at < 0) return new Error('Invalid context bracket timestamp.'); + return cloneValue(this.ctxBracketActiveEntry(await this.ctxBracketSchedule(), at)); + } + + async ctxBracketTableByVersion(ver) { + if (!Number.isSafeInteger(ver) || ver < 1) return new Error('Invalid context bracket table version.'); + if (ver === CTX_BRACKET_TABLE_VERSION) return this.defaultCtxBracketTableRecord(); + const record = await this.get(`ctx_brackets/v/${ver}`); + if (!record) return new Error('Unknown context bracket table version.'); + return cloneValue(record); + } + + validateCtxBracketEvidence(tokens, bracket, tableVer, table, label) { + if (typeof bracket !== 'string') return new Error(`Invalid ${label} context bracket.`); + if (!Number.isSafeInteger(tableVer) || tableVer < 1) { + return new Error(`Invalid ${label} context bracket table version.`); + } + if (!table || table.ver !== tableVer) { + return new Error(`${label} context bracket table version mismatch.`); + } + if (!Array.isArray(table.brackets) || !table.brackets.some((entry) => entry.id === bracket)) { + return new Error(`Invalid ${label} context bracket.`); + } + if (ctxBracketForTokens(tokens, table.brackets) !== bracket) { + return new Error(`${label} context bracket does not match served context.`); + } + return null; + } + + async normalizeCtxBracketEvidenceForEnclave(enclaveId, tokens, bracket, tableVer, table, label) { + const enclave = await this.get(`enclave/${enclaveId}`); + if (!enclave) return new Error(`${label} enclave not found.`); + if (!this.enclaveUsesCtxPrice(enclave)) { + if (bracket !== undefined && bracket !== null) { + return new Error(`${label} context bracket is only valid for text-generation enclaves.`); + } + if (tableVer !== undefined && tableVer !== null) { + return new Error(`${label} context bracket table version is only valid for text-generation enclaves.`); + } + return { + enclave, + ctx_bracket: null, + ctx_bracket_table_ver: null, + }; + } + const ctxError = this.validateCtxBracketEvidence(tokens, bracket, tableVer, table, label); + if (ctxError) return ctxError; + return { + enclave, + ctx_bracket: bracket, + ctx_bracket_table_ver: tableVer, + }; + } + + validateProviderLifecycleIntent(intent) { + if (!PROVIDER_LIFECYCLE_OPS.has(intent.op)) return new Error('Unsupported provider lifecycle op.'); + const allowed = intent.op === 'register_provider' + ? ['op', 'provider', 'nonce'] + : intent.op === 'set_provider_rails' + ? ['op', 'provider', 'rails', 'nonce'] + : intent.op === 'join_room' || intent.op === 'leave_room' + ? ['op', 'provider', 'enclave_id', 'room_id', 'nonce'] + : intent.op === 'join_enclave' + ? [ + 'op', + 'provider', + 'enclave_id', + 'nonce', + 'att_tier', + 'attestation_head', + 'served_ctx', + 'served_modalities', + 'served_specialities', + 'ctx_bracket', + 'ctx_bracket_table_ver', + 'hardware_fingerprint', + 'device_key', + ] + : ['op', 'provider', 'enclave_id', 'nonce']; + const required = intent.op === 'join_enclave' + ? [ + 'op', + 'provider', + 'enclave_id', + 'nonce', + 'att_tier', + 'attestation_head', + 'served_ctx', + 'served_modalities', + 'served_specialities', + 'ctx_bracket', + 'ctx_bracket_table_ver', + ] + : allowed; + const allowedSet = new Set(allowed); + const unknown = Object.keys(intent).filter((key) => !allowedSet.has(key)).sort(); + if (unknown.length > 0) { + return new Error(`provider lifecycle intent does not accept fields: ${unknown.join(', ')}.`); + } + for (const key of required) { + if (!hasOwn(intent, key)) return new Error(`provider lifecycle intent is missing ${key}.`); + } + if (!this.isHexBytes(intent.provider, 32)) return new Error('Invalid provider id.'); + if (!this.isHexBytes(intent.nonce, 32)) return new Error('Invalid lifecycle nonce.'); + if (hasOwn(intent, 'enclave_id') && !this.isSafeKeyPart(intent.enclave_id)) { + return new Error('Invalid enclave id.'); + } + if (hasOwn(intent, 'room_id') && !this.isSafeKeyPart(intent.room_id)) { + return new Error('Invalid room id.'); + } + if ( + hasOwn(intent, 'served_ctx') && + (!Number.isSafeInteger(intent.served_ctx) || intent.served_ctx < 0) + ) { + return new Error('Invalid provider served context.'); + } + if (hasOwn(intent, 'served_modalities')) { + const modalitiesError = this.validateModalitySet(intent.served_modalities, 'provider served_modalities'); + if (modalitiesError) return modalitiesError; + } + if (hasOwn(intent, 'served_specialities')) { + const specialitiesError = this.validateSpecialityLevelMap( + intent.served_specialities, + 'provider served_specialities', + { allowEmpty: true } + ); + if (specialitiesError) return specialitiesError; + } + if ( + hasOwn(intent, 'ctx_bracket') && + intent.ctx_bracket !== null && + !this.isSafeKeyPart(intent.ctx_bracket) + ) { + return new Error('Invalid provider context bracket.'); + } + if ( + hasOwn(intent, 'ctx_bracket_table_ver') && + intent.ctx_bracket_table_ver !== null && + (!Number.isSafeInteger(intent.ctx_bracket_table_ver) || intent.ctx_bracket_table_ver < 1) + ) { + return new Error('Invalid provider context bracket table version.'); + } + if (hasOwn(intent, 'hardware_fingerprint') && !this.isHexBytes(intent.hardware_fingerprint, 32)) { + return new Error('Invalid provider hardware fingerprint.'); + } + if (hasOwn(intent, 'device_key') && !this.isHexBytes(intent.device_key, 32)) { + return new Error('Invalid provider device key.'); + } + if ( + hasOwn(intent, 'att_tier') && + (!Number.isSafeInteger(intent.att_tier) || intent.att_tier < 1 || intent.att_tier > MAX_LAUNCH_ENCLAVE_ATTESTATION_TIER) + ) { + return new Error('Invalid provider attestation tier.'); + } + if (hasOwn(intent, 'attestation_head') && !this.isHexBytes(intent.attestation_head, 32)) { + return new Error('Invalid provider attestation head.'); + } + if (hasOwn(intent, 'rails')) { + const rails = this.normalizeProviderAcceptedRails(intent.rails); + if (rails instanceof Error) return rails; + } + return null; + } + + async normalizeProviderServeTerms(enclaveId, terms, label) { + if (terms === null || terms === undefined) { + return new Error(`${label} terms are required.`); + } + if (!terms || typeof terms !== 'object' || Array.isArray(terms)) { + return new Error(`${label} terms must be an object.`); + } + for (const field of ['served_ctx', 'served_modalities', 'served_specialities', 'ctx_bracket', 'ctx_bracket_table_ver']) { + if (!hasOwn(terms, field)) return new Error(`${label} terms are missing ${field}.`); + } + if (!Number.isSafeInteger(terms.served_ctx) || terms.served_ctx < 0) { + return new Error(`Invalid ${label} served context.`); + } + const servedModalitiesError = this.validateModalitySet( + terms.served_modalities, + `${label} served_modalities` + ); + if (servedModalitiesError) return servedModalitiesError; + const enclave = await this.get(`enclave/${enclaveId}`); + if (!enclave || enclave.status !== 'active') return new Error('Enclave is not active.'); + const enclaveModalitiesError = this.validateModalitySet( + enclave.caps?.modality_set, + 'admin enclave modality_set' + ); + if (enclaveModalitiesError) return enclaveModalitiesError; + const enclaveModalities = new Set(enclave.caps.modality_set); + if (terms.served_modalities.some((modality) => !enclaveModalities.has(modality))) { + return new Error(`${label} served_modalities must be a subset of the admin enclave modality_set.`); + } + const coreModalities = this.coreModalitiesForModelClass(this.modelClassFor(enclave)); + if ([...coreModalities].some((modality) => !terms.served_modalities.includes(modality))) { + return new Error(`${label} cannot disable a core model modality.`); + } + const enclaveSpecialitiesError = this.validateSpecialityLevelMap( + enclave.caps?.speciality_levels, + 'admin enclave speciality_levels', + { allowEmpty: true } + ); + if (enclaveSpecialitiesError) return enclaveSpecialitiesError; + const servedSpecialitiesError = this.validateSpecialityLevelMap( + terms.served_specialities, + `${label} served_specialities`, + { allowEmpty: true } + ); + if (servedSpecialitiesError) return servedSpecialitiesError; + const adminSpecialityNames = Object.keys(enclave.caps.speciality_levels).sort(compareCodepoint); + const servedSpecialityNames = Object.keys(terms.served_specialities).sort(compareCodepoint); + if (stableJson(servedSpecialityNames) !== stableJson(adminSpecialityNames)) { + return new Error(`${label} served_specialities must cover exactly the admin enclave speciality names.`); + } + for (const name of adminSpecialityNames) { + const available = new Set(enclave.caps.speciality_levels[name]); + if (terms.served_specialities[name].some((level) => !available.has(level))) { + return new Error(`${label} served_specialities ${name} must be a subset of the admin enclave levels.`); + } + } + const table = terms.ctx_bracket_table_ver === null || terms.ctx_bracket_table_ver === undefined + ? null + : await this.ctxBracketTableByVersion(terms.ctx_bracket_table_ver); + if (table instanceof Error) return table; + const ctxMeta = await this.normalizeCtxBracketEvidenceForEnclave( + enclaveId, + terms.served_ctx, + terms.ctx_bracket, + terms.ctx_bracket_table_ver, + table, + label + ); + if (ctxMeta instanceof Error) return ctxMeta; + return { + served_ctx: terms.served_ctx, + served_modalities: terms.served_modalities.slice(), + served_specialities: Object.fromEntries( + Object.entries(terms.served_specialities) + .sort(([left], [right]) => compareCodepoint(left, right)) + .map(([name, levels]) => [name, levels.slice()]) + ), + ctx_bracket: ctxMeta.ctx_bracket, + ctx_bracket_table_ver: ctxMeta.ctx_bracket_table_ver, + }; + } + + validateCommittedServeTerms(serve, normalized) { + if (!Number.isSafeInteger(serve.served_ctx) || serve.served_ctx < 0) { + return new Error('Provider serve record is missing committed context terms.'); + } + if (serve.served_ctx !== normalized.served_ctx) { + return new Error('Spend reservation served context does not match provider committed context.'); + } + const servedModalitiesError = this.validateModalitySet( + serve.served_modalities, + 'provider committed served_modalities' + ); + if (servedModalitiesError) return servedModalitiesError; + const requiredModalitiesError = this.validateModalitySet( + normalized.required_modalities, + 'spend reservation required_modalities' + ); + if (requiredModalitiesError) return requiredModalitiesError; + const servedModalities = new Set(serve.served_modalities); + if (normalized.required_modalities.some((modality) => !servedModalities.has(modality))) { + return new Error('Provider committed modalities do not cover the spend reservation.'); + } + const servedSpecialitiesError = this.validateSpecialityLevelMap( + serve.served_specialities, + 'provider committed served_specialities', + { allowEmpty: true } + ); + if (servedSpecialitiesError) return servedSpecialitiesError; + const requiredSpecialitiesError = this.validateSpecialitySelection( + normalized.required_specialities, + 'spend reservation required_specialities' + ); + if (requiredSpecialitiesError) return requiredSpecialitiesError; + const servedSpecialityNames = Object.keys(serve.served_specialities).sort(compareCodepoint); + const requiredSpecialityNames = Object.keys(normalized.required_specialities).sort(compareCodepoint); + if (stableJson(servedSpecialityNames) !== stableJson(requiredSpecialityNames)) { + return new Error('Spend reservation must bind every provider committed speciality.'); + } + for (const [name, level] of Object.entries(normalized.required_specialities)) { + if (!serve.served_specialities[name].includes(level)) { + return new Error('Provider committed specialities do not cover the spend reservation.'); + } + } + if ((serve.ctx_bracket ?? null) !== (normalized.ctx_bracket ?? null)) { + return new Error('Spend reservation context bracket does not match provider committed context.'); + } + if ((serve.ctx_bracket_table_ver ?? null) !== (normalized.ctx_bracket_table_ver ?? null)) { + return new Error('Spend reservation context bracket table does not match provider committed context.'); + } + return null; + } + + banRecordKey(targetType, target) { + switch (targetType) { + case 'provider': + return `ban/provider/${target}`; + case 'device': + return `ban/device/${target}`; + case 'fingerprint': + return `ban/fingerprint/${target}`; + case 'committer': + return `committer/ban/${target}`; + default: + return `ban/unknown/${target}`; + } + } + + normalizedKybIdentityValues(kyb) { + if (!kyb || typeof kyb !== 'object') return new Error('Invalid KYB identity.'); + const legalName = typeof kyb.legal_name === 'string' + ? kyb.legal_name.trim().replace(/\s+/g, ' ').toLowerCase() + : ''; + const kybRef = typeof kyb.kyb_ref === 'string' ? kyb.kyb_ref.trim() : ''; + const proofHash = typeof kyb.proof_hash === 'string' ? kyb.proof_hash.toLowerCase() : ''; + if (!legalName) return new Error('Invalid KYB legal name.'); + if (!this.isSafeExternalRef(kybRef)) return new Error('Invalid provider KYB reference.'); + if (!this.isHexBytes(proofHash, 32)) return new Error('Invalid provider KYB proof hash.'); + return { + legal_name: legalName, + kyb_ref: kybRef, + proof_hash: proofHash, + }; + } + + async kybBanIndexKey(kind, value) { + return `ban/kyb/${kind}/${await this.opaqueHash('mayhem-kyb-ban-index-v1', { kind, value })}`; + } + + async kybBanIndexKeys(kyb) { + const values = this.normalizedKybIdentityValues(kyb); + if (values instanceof Error) return values; + return [ + await this.kybBanIndexKey('legal_name', values.legal_name), + await this.kybBanIndexKey('kyb_ref', values.kyb_ref), + await this.kybBanIndexKey('proof_hash', values.proof_hash), + ]; + } + + async rejectBannedProviderKyb(kyb) { + const keys = await this.kybBanIndexKeys(kyb); + if (keys instanceof Error) return keys; + for (const key of keys) { + const ban = await this.get(key); + if (ban?.status === 'banned' || ban?.status === 'revoked') { + return new Error('Provider KYB identity is banned or revoked.'); + } + } + return null; + } + + async writeProviderKybBanIndexes(kyb, meta) { + const keys = await this.kybBanIndexKeys(kyb); + if (keys instanceof Error) return keys; + const values = this.normalizedKybIdentityValues(kyb); + if (values instanceof Error) return values; + for (const key of keys) { + const current = await this.get(key); + await this.put(key, { + ...(current ?? {}), + target_type: 'kyb', + target: key.split('/').at(-1), + status: meta.status, + provider: meta.provider, + source: meta.source, + reason_hash: meta.reason_hash, + recorded_at: meta.recorded_at, + recorded_by: meta.recorded_by, + recorded_by_role: meta.recorded_by_role, + legal_name_hash: await this.kybBanIndexKey('legal_name', values.legal_name).then((k) => k.split('/').at(-1)), + kyb_ref_hash: await this.kybBanIndexKey('kyb_ref', values.kyb_ref).then((k) => k.split('/').at(-1)), + proof_hash: values.proof_hash, + providers: { + ...(current?.providers ?? {}), + [meta.provider]: { + provider: meta.provider, + source: meta.source, + reason_hash: meta.reason_hash, + recorded_at: meta.recorded_at, + }, + }, + reversible: true, + }); + } + return null; + } + + async providerLifecycleFeatureKey(intent) { + const digest = await blake3(b4a.from(providerLifecycleIntentMessage(intent))); + return `intent/provider/${intent.provider}/${intent.op}/${b4a.toString(digest, 'hex')}`; + } + + async providerLifecycleFeatureKeys(intent) { + return [await this.providerLifecycleFeatureKey(intent)]; + } + + async providerPayoutBindingRevision(intent) { + const digest = await blake3(b4a.from(providerPayoutBindingMessage(intent))); + return b4a.toString(digest, 'hex'); + } + + async targetedSpendReservationFeatureKey(value) { + const normalized = await this.normalizeTargetedSpendReserveValue(value); + if (normalized instanceof Error) return normalized; + const digest = await blake3(b4a.from(stableJson({ + domain: 'mayhem-targeted-spend-reservation-feature-v1', + value: targetedSpendReservationEvidence({ + ...normalized, + payout_revision: value.payout_revision, + }), + }))); + return `hold/targeted/${normalized.rail}/${normalized.user}/${normalized.epoch}/${normalized.session_id}/${b4a.toString(digest, 'hex')}`; + } + + async providerPayoutPaymentConfigHash(payments) { + return await this.opaqueHash('mayhem-payout-payment-config-v1', payments); + } + + async providerPayoutContextFeatureKey(value) { + const revision = await this.providerPayoutContextRevision(value); + return this.providerPayoutContextRecordKey(value.payment_config_version, revision); + } + + async providerPayoutContextRevision(value) { + const digest = await blake3(b4a.from(stableJson({ + domain: 'mayhem-payout-context-feature-v1', + value, + }))); + return b4a.toString(digest, 'hex'); + } + + providerPayoutContextRecordKey(paymentConfigVersion, revision) { + return `payout/context/${paymentConfigVersion}/${revision}`; + } + + payoutParameterKey(key) { + return `payout/params/${key}`; + } + + async payoutParameterFeatureKey(value) { + const digest = await blake3(b4a.from(stableJson({ + domain: 'mayhem-payout-parameter-feature-v1', + value, + }))); + return `payout/params/${value.key}/${b4a.toString(digest, 'hex')}`; + } + + async payoutParameterRecord(key) { + const definition = PAYOUT_PARAM_DEFINITIONS[key]; + if (!definition) return new Error('Unknown payout parameter.'); + return (await this.get(this.payoutParameterKey(key))) ?? { + key, + current: { + key, + value: definition.default, + effective_epoch: 0, + scheduled_at: null, + scheduled_by: null, + scheduled_by_role: 'default', + }, + pending: null, + }; + } + + async activePayoutParamsAtEpoch(epoch) { + if (!Number.isSafeInteger(epoch) || epoch < 0) { + return new Error('Invalid payout parameter epoch.'); + } + const values = {}; + for (const key of Object.keys(PAYOUT_PARAM_DEFINITIONS)) { + const schedule = await this.payoutParameterRecord(key); + if (schedule instanceof Error) return schedule; + const active = schedule.pending && schedule.pending.effective_epoch <= epoch + ? schedule.pending + : schedule.current; + values[key] = active.value; + } + return values; + } + + providerPayoutBindingFeatureKey(rail, provider, revision) { + return `payout/binding/${rail}/${provider}/${revision}`; + } + + providerPayoutBindingPointerKey(provider, rail) { + return `payout/current/${rail}/${provider}`; + } + + providerPayoutBindingNonceKey(provider, nonce) { + return `payout/nonce/${provider}/${nonce}`; + } + + providerPayoutLiabilityKey(provider, rail, revision) { + return `payout/liability/${rail}/${provider}/${revision}`; + } + + providerPayoutLiabilityIndexKey(rail) { + return `payout/liability-index/${rail}`; + } + + // MAYHEM PATCH: canonical per-rail liability index avoids ledger-wide scans + // when targeted payout epochs collect provider earnings. + normalizeProviderPayoutLiabilityIndex(value, rail) { + const shapeError = this.validateExactObjectKeys( + value, + ['type', 'rail', 'entries', 'updated_epoch', 'updated_at'], + 'provider payout liability index' + ); + if (shapeError) return shapeError; + if (value.type !== 'provider_payout_liability_index' || + value.rail !== rail || + !PROVIDER_PAYOUT_BINDING_RAILS.has(rail) || + !Array.isArray(value.entries) || + !Number.isSafeInteger(value.updated_epoch) || + value.updated_epoch < 0 || + (value.updated_at !== null && + (typeof value.updated_at !== 'string' || value.updated_at.length === 0))) { + return new Error('Invalid provider payout liability index.'); + } + const entries = []; + for (const entry of value.entries) { + const entryError = this.validateExactObjectKeys( + entry, + ['provider', 'payout_revision'], + 'provider payout liability index entry' + ); + if (entryError) return entryError; + if (!this.isHexBytes(entry.provider, 32) || + entry.provider !== entry.provider.toLowerCase() || + !this.isHexBytes(entry.payout_revision, 32) || + entry.payout_revision !== entry.payout_revision.toLowerCase()) { + return new Error('Invalid provider payout liability index entry.'); + } + entries.push({ + provider: entry.provider, + payout_revision: entry.payout_revision, + }); + } + entries.sort((left, right) => + compareCodepoint(left.provider, right.provider) || + compareCodepoint(left.payout_revision, right.payout_revision) + ); + const identities = entries.map((entry) => + `${entry.provider}/${entry.payout_revision}` + ); + if (new Set(identities).size !== entries.length || + stableJson(entries) !== stableJson(value.entries)) { + return new Error('Provider payout liability index must be canonical and unique.'); + } + return { + type: 'provider_payout_liability_index', + rail, + entries, + updated_epoch: value.updated_epoch, + updated_at: value.updated_at, + }; + } + + async providerPayoutLiabilityIndex(rail) { + if (!PROVIDER_PAYOUT_BINDING_RAILS.has(rail)) { + return new Error('Invalid provider payout liability index rail.'); + } + const current = await this.get(this.providerPayoutLiabilityIndexKey(rail)); + if (current === null) { + return { + type: 'provider_payout_liability_index', + rail, + entries: [], + updated_epoch: 0, + updated_at: null, + }; + } + return this.normalizeProviderPayoutLiabilityIndex(current, rail); + } + + async nextProviderPayoutLiabilityIndexes(liabilityUpdates, epoch, featureKey) { + const byRail = new Map(); + for (const update of liabilityUpdates) { + const { provider, rail, revision: payoutRevision } = update.value; + let index = byRail.get(rail); + if (!index) { + index = await this.providerPayoutLiabilityIndex(rail); + if (index instanceof Error) return index; + } + const identity = `${provider}/${payoutRevision}`; + if (!index.entries.some((entry) => + `${entry.provider}/${entry.payout_revision}` === identity + )) { + index = { + ...index, + entries: [ + ...index.entries, + { provider, payout_revision: payoutRevision }, + ].sort((left, right) => + compareCodepoint(left.provider, right.provider) || + compareCodepoint(left.payout_revision, right.payout_revision) + ), + }; + } + byRail.set(rail, { + ...index, + updated_epoch: Math.max(index.updated_epoch, epoch), + updated_at: featureKey, + }); + } + return [...byRail.values()].map((value) => ({ + key: this.providerPayoutLiabilityIndexKey(value.rail), + value, + })); + } + + providerStripePayoutVerificationTargetKey(provider, target) { + return `payout/stripe-verified/target/${provider}/${target}`; + } + + async providerStripePayoutVerificationForTarget(provider, target) { + const pointer = await this.get( + this.providerStripePayoutVerificationTargetKey(provider, target) + ); + const verification = pointer?.record_key + ? await this.get(pointer.record_key) + : null; + if (!verification || + pointer.provider !== provider || + pointer.target !== target || + pointer.revision !== verification.revision || + pointer.processor_revision !== verification.processor_revision || + verification.type !== 'stripe_payout_verification' || + verification.provider !== provider || + verification.target !== target) { + return null; + } + return verification; + } + + providerPayoutEpochSnapshotKey(epoch, page, provider, rail) { + return `payout/epoch/${epoch}/${page}/${rail}/${provider}`; + } + + async targetedEpochFeatureKey(value) { + const normalized = await this.normalizeTargetedEpochFeatureValue(value); + if (normalized instanceof Error) return normalized; + const digest = await blake3(b4a.from(stableJson({ + domain: 'mayhem-targeted-epoch-feature-v1', + value, + }))); + return `epoch/targeted/${value.epoch}/${b4a.toString(digest, 'hex')}`; + } + + async commitTargetedEpochPageZeroFeatureKey(value) { + const normalized = await this.normalizeCommitTargetedEpochPageZeroFeatureValue(value); + if (normalized instanceof Error) return normalized; + const digest = await blake3(b4a.from(stableJson({ + domain: 'mayhem-commit-targeted-epoch-page-zero-feature-v1', + value, + }))); + return `epoch/targeted/${value.epoch}/${b4a.toString(digest, 'hex')}`; + } + + async providerPayoutBindingContext(intent) { + const context = { + network: intent.network, + admin: intent.admin, + bootstrap: intent.bootstrap, + context_revision: intent.context_revision, + payment_config_version: intent.payment_config_version, + }; + if (!this.isSafeKeyPart(context.network) || + !this.isHexBytes(context.admin, 32) || + !this.isHexBytes(context.bootstrap, 32) || + !this.isHexBytes(context.context_revision, 32) || + !Number.isSafeInteger(context.payment_config_version) || + context.payment_config_version < 1) { + return new Error('Invalid provider payout binding canonical context.'); + } + return context; + } + + async providerPayoutBindingForEpoch( + provider, + rail, + revision, + epoch, + { requireCurrentReadiness = false } = {} + ) { + if (!PROVIDER_PAYOUT_BINDING_RAILS.has(rail)) { + return new Error('Targeted payout rail is not supported.'); + } + if (!this.isHexBytes(provider, 32) || + !this.isHexBytes(revision, 32) || + !Number.isSafeInteger(epoch) || + epoch < 1) { + return new Error('Invalid targeted payout binding reference.'); + } + const binding = await this.get( + this.providerPayoutBindingFeatureKey(rail, provider, revision) + ); + if (!binding || binding.verified !== true || + binding.provider !== provider || + binding.rail !== rail || + binding.revision !== revision) { + return new Error('Active verified provider payout binding required.'); + } + if (binding.activation_epoch > epoch) { + return new Error('Provider payout binding is not active for targeted epoch.'); + } + const pointer = await this.get(this.providerPayoutBindingPointerKey(provider, rail)); + if (!pointer) return new Error('Provider payout binding pointer required.'); + const activeRevision = pointer.pending_revision !== null && + pointer.pending_activation_epoch <= epoch + ? pointer.pending_revision + : pointer.current_revision; + if (activeRevision !== revision) { + return new Error('Provider payout binding revision is not active for targeted epoch.'); + } + if (requireCurrentReadiness && rail === 'fiat') { + const verification = await this.providerStripePayoutVerificationForTarget( + provider, + binding.target + ); + if (!verification || + verification.ready !== true || + verification.target !== binding.target || + verification.processor_revision !== binding.stripe_processor_revision) { + return new Error('Provider Stripe payout binding is not currently ready.'); + } + } + return binding; + } + + receiptConsumptionRecord(epoch, allocation, featureKey) { + return { + type: 'canonical_receipt_consumption', + epoch, + billing_epoch: allocation.billing_epoch, + billing_id: allocation.billing_id, + billing_attempt: allocation.billing_attempt, + receipt_seq: allocation.receipt_seq, + receipt_hash: allocation.receipt_hash, + session_id: allocation.session_id, + user: allocation.user, + rail: allocation.rail, + provider: allocation.provider, + payout_revision: allocation.payout_revision, + au: allocation.au, + feature_key: featureKey, + consumed_at: featureKey, + }; + } + + async validateTargetedEpochReservationBindings(value, earnings, featureKey) { + const debitTotals = new Map(); + for (const debit of value.debits) { + const rail = this.normalizeLedgerRail(debit.rail, 'targeted epoch debit rail'); + if (rail instanceof Error) return rail; + const key = stableJson([rail, debit.user]); + const next = this.safeAddAu(debitTotals.get(key) ?? ZERO_AU, debit.au); + if (next instanceof Error) return next; + debitTotals.set(key, next); + } + const earningTotals = new Map( + earnings.map((earning) => [ + stableJson([earning.rail, earning.provider, earning.payout_revision]), + earning.gross_au, + ]) + ); + const allocatedDebits = new Map(); + const allocatedEarnings = new Map(); + const holds = new Map(); + const summaries = new Map(); + const legacyReleases = new Map(); + const sessionDeletes = []; + const sessions = new Set(); + const billingAttempts = new Set(); + const marketUsage = new Map(); + for (const allocation of value.allocations) { + if (sessions.has(allocation.session_id)) { + return new Error('Targeted epoch session allocation is duplicated.'); + } + sessions.add(allocation.session_id); + const billingAttempt = `${allocation.billing_id}:${allocation.billing_attempt}`; + if (billingAttempts.has(billingAttempt)) { + return new Error('Targeted epoch billing attempt allocation is duplicated.'); + } + billingAttempts.add(billingAttempt); + const head = await this.get( + this.receiptHeadKey(allocation.billing_id, allocation.billing_attempt) + ); + if (!head || + head.type !== 'canonical_receipt_head' || + head.epoch !== value.epoch || + head.settlement_epoch !== value.epoch || + head.billing_epoch !== allocation.billing_epoch || + head.settlement_ready !== true || + head.billing_id !== allocation.billing_id || + head.billing_attempt !== allocation.billing_attempt || + head.receipt_seq !== allocation.receipt_seq || + head.receipt_hash !== allocation.receipt_hash || + head.session_id !== allocation.session_id || + head.user !== allocation.user || + head.rail !== allocation.rail || + head.provider !== allocation.provider || + head.payout_revision !== allocation.payout_revision || + this.compareAu(head.incremental_au, allocation.au) !== 0) { + return new Error('Targeted epoch allocation does not match its canonical receipt head.'); + } + const receiptBody = head.receipt?.body; + if (!receiptBody || + receiptBody.session_id !== allocation.session_id || + receiptBody.provider !== allocation.provider || + !this.isSafeKeyPart(receiptBody.enclave_id) || + (receiptBody.ctx_bracket !== undefined && + receiptBody.ctx_bracket !== null && + !this.isSafeKeyPart(receiptBody.ctx_bracket)) || + (receiptBody.ctx_bracket_table_ver !== undefined && + receiptBody.ctx_bracket_table_ver !== null && + (!Number.isSafeInteger(receiptBody.ctx_bracket_table_ver) || + receiptBody.ctx_bracket_table_ver < 1))) { + return new Error('Canonical receipt market identity is invalid.'); + } + const marketKey = this.priceMarketKey( + receiptBody.enclave_id, + receiptBody.ctx_bracket ?? null + ); + const currentMarket = marketUsage.get(marketKey) ?? { + enclave_id: receiptBody.enclave_id, + ...(receiptBody.ctx_bracket ? { ctx_bracket: receiptBody.ctx_bracket } : {}), + ...(receiptBody.ctx_bracket_table_ver ? { + ctx_bracket_table_ver: receiptBody.ctx_bracket_table_ver, + } : {}), + demand_au: ZERO_AU, + settled_usage: {}, + session_count: 0, + providers: new Set(), + }; + if ((currentMarket.ctx_bracket_table_ver ?? null) !== + (receiptBody.ctx_bracket_table_ver ?? currentMarket.ctx_bracket_table_ver ?? null)) { + return new Error('Canonical receipt market context version changed within an epoch.'); + } + const marketDemandAu = this.safeAddAu(currentMarket.demand_au, allocation.au); + const marketSessionCount = this.safeAddCount( + currentMarket.session_count, + 1, + 'canonical receipt market session count' + ); + if (marketDemandAu instanceof Error || marketSessionCount instanceof Error) { + return new Error('Canonical receipt market usage overflow.'); + } + const increment = this.incrementalSettledUsage(receiptBody); + if (increment instanceof Error) return increment; + const settledUsage = this.addSettledUsage(currentMarket.settled_usage, increment); + if (settledUsage instanceof Error) return settledUsage; + currentMarket.settled_usage = settledUsage; + currentMarket.demand_au = marketDemandAu; + currentMarket.session_count = marketSessionCount; + currentMarket.providers.add(allocation.provider); + marketUsage.set(marketKey, currentMarket); + const consumeKey = this.receiptConsumedKey( + allocation.billing_id, + allocation.billing_attempt + ); + const expectedConsumption = this.receiptConsumptionRecord( + value.epoch, + allocation, + featureKey + ); + const existingConsumption = await this.get(consumeKey); + if (existingConsumption !== null && + stableJson(existingConsumption) !== stableJson(expectedConsumption)) { + return new Error('Canonical receipt billing attempt is already consumed.'); + } + if (existingConsumption === null) { + const reservationState = await this.targetedSpendReservationState( + allocation.user, + allocation.rail, + head.reservation_id, + allocation.session_id + ); + if (reservationState instanceof Error) return reservationState; + if (reservationState.kind === 'missing') { + return new Error('Targeted epoch allocation does not match its reserved session.'); + } + const holdIdentity = stableJson([allocation.rail, allocation.user]); + const isLegacy = reservationState.kind === 'legacy' || + reservationState.kind === 'legacy_overlay'; + const session = isLegacy ? reservationState.session : reservationState.session; + if (!session || + session.billing_id !== allocation.billing_id || + session.billing_attempt !== allocation.billing_attempt || + session.billing_epoch !== allocation.billing_epoch || + session.provider !== allocation.provider || + session.payout_revision !== allocation.payout_revision || + session.settlement_ready !== true) { + return new Error('Targeted epoch allocation does not match its reserved session.'); + } + if (session.reservation_id !== head.reservation_id || + session.user !== allocation.user || + session.rail !== allocation.rail || + this.compareAu(allocation.au, session.max_spend_au) !== 0 || + this.compareAu(allocation.au, head.incremental_au) !== 0) { + return new Error('Targeted epoch allocation does not exactly consume its reserved receipt.'); + } + if (isLegacy) { + let legacyRelease = legacyReleases.get(holdIdentity) ?? + reservationState.legacyRelease; + const nextReleasedAu = this.safeAddAu(legacyRelease.released_au, allocation.au); + if (nextReleasedAu instanceof Error || + this.compareAu(nextReleasedAu, reservationState.hold.reserved_au) > 0) { + return new Error('Targeted epoch allocation exceeds outstanding legacy holds.'); + } + legacyRelease = { + ...legacyRelease, + released_au: nextReleasedAu, + updated_at: featureKey, + }; + legacyReleases.set(holdIdentity, legacyRelease); + if (reservationState.kind === 'legacy_overlay') { + sessionDeletes.push(reservationState.legacySessionKey); + } + } else { + let summary = summaries.get(holdIdentity) ?? reservationState.summary; + const nextReservedAu = this.safeSubAu(summary.reserved_au, allocation.au); + if (nextReservedAu instanceof Error) { + return new Error('Targeted epoch allocation exceeds outstanding sharded holds.'); + } + summary = { + ...summary, + reserved_au: nextReservedAu, + updated_at: featureKey, + }; + summaries.set(holdIdentity, summary); + sessionDeletes.push( + reservationState.sessionKey, + reservationState.sessionIndexKey, + reservationState.billingAttemptKey + ); + } + } + const debitKey = stableJson([allocation.rail, allocation.user]); + const nextDebit = this.safeAddAu( + allocatedDebits.get(debitKey) ?? ZERO_AU, + allocation.au + ); + if (nextDebit instanceof Error) return nextDebit; + allocatedDebits.set(debitKey, nextDebit); + const earningKey = stableJson([ + allocation.rail, + allocation.provider, + allocation.payout_revision, + ]); + const nextEarning = this.safeAddAu( + allocatedEarnings.get(earningKey) ?? ZERO_AU, + allocation.au + ); + if (nextEarning instanceof Error) return nextEarning; + allocatedEarnings.set(earningKey, nextEarning); + } + for (const [key, total] of debitTotals) { + if (allocatedDebits.get(key) !== total) { + return new Error('Targeted epoch debit does not equal session allocations.'); + } + } + if (allocatedDebits.size !== debitTotals.size) { + return new Error('Targeted epoch allocation has no matching debit.'); + } + for (const [key, total] of earningTotals) { + if (allocatedEarnings.get(key) !== total) { + return new Error('Targeted epoch earning does not equal session allocations.'); + } + } + if (allocatedEarnings.size !== earningTotals.size) { + return new Error('Targeted epoch allocation has no matching earning.'); + } + return { + hold_updates: Array.from(holds.values()).map((hold) => ({ + key: this.targetedSpendHoldKey(hold.user, hold.rail), + value: hold, + })), + summary_updates: Array.from(summaries.values()).map((summary) => ({ + key: this.targetedSpendSummaryKey(summary.user, summary.rail), + value: summary, + })), + legacy_release_updates: Array.from(legacyReleases.values()).map((summary) => ({ + key: this.targetedSpendLegacyReleaseSummaryKey(summary.user, summary.rail), + value: summary, + })), + session_deletes: [...new Set(sessionDeletes)], + market_usage: Array.from(marketUsage.values()) + .sort((left, right) => ( + compareCodepoint(left.enclave_id, right.enclave_id) || + compareCodepoint(left.ctx_bracket ?? '', right.ctx_bracket ?? '') + )) + .map((entry) => ({ + enclave_id: entry.enclave_id, + ...(entry.ctx_bracket ? { ctx_bracket: entry.ctx_bracket } : {}), + ...(entry.ctx_bracket_table_ver ? { + ctx_bracket_table_ver: entry.ctx_bracket_table_ver, + } : {}), + demand_au: entry.demand_au, + settled_usage: entry.settled_usage, + session_count: entry.session_count, + providers: Array.from(entry.providers).sort(compareCodepoint), + })), + }; + } + + async spendReservationFeatureKey(value) { + const normalized = value.voucher_body ? value : await this.normalizeSpendReserveValue(value); + if (normalized instanceof Error) return normalized; + const digest = b4a.toString( + await blake3(b4a.from(stableJson({ + domain: 'mayhem-spend-reservation-feature-v1', + value: spendReservationEvidence(normalized), + }))), + 'hex' + ); + return `hold/reserve/${normalized.rail}/${normalized.user}/${normalized.epoch}/${normalized.session_id}/${digest}`; + } + + async activeParamsAt(at, keys = Object.keys(PARAM_DEFINITIONS)) { + const params = {}; + for (const key of keys) { + params[key] = this.paramActiveEntry(await this.paramRecord(key), at).value; + } + return params; + } + + async paramRecord(key) { + const existing = await this.get(`params/${key}`); + if (existing) return this.sanitizeMarketBoundRecord(key, existing); + return { + key, + current: { + value: PARAM_DEFINITIONS[key].default, + ver: 0, + submitted_at: 0, + effective_at: 0, + set_at: null, + }, + pending: null, + }; + } + + paramActiveEntry(record, at) { + if (record.pending && record.pending.effective_at <= at) return cloneValue(record.pending); + return cloneValue(record.current); + } + + validateParamValues(values) { + if (!values || typeof values !== 'object' || Array.isArray(values)) { + return new Error('Parameter values must be an object.'); + } + const keys = Object.keys(values); + if (keys.length === 0) return new Error('At least one parameter is required.'); + const keyError = this.validateParamKeys(keys); + if (keyError) return keyError; + + for (const key of keys) { + const value = values[key]; + const def = PARAM_DEFINITIONS[key]; + if (def.deprecated) return new Error(`Parameter ${key} is deprecated and read-only.`); + if (def.money) { + const au = this.normalizeAu(value, `Parameter ${key}`, { allowZero: def.min === ZERO_AU }); + if (au instanceof Error) return au; + if (this.compareAu(au, def.min) < 0) return new Error(`Parameter ${key} is out of range.`); + continue; + } + if (!Number.isInteger(value)) return new Error(`Parameter ${key} must be an integer.`); + if (value < def.min || value > def.max) return new Error(`Parameter ${key} is out of range.`); + } + return null; + } + + normalizeParamValues(values) { + const normalized = {}; + for (const [key, value] of Object.entries(values)) { + const def = PARAM_DEFINITIONS[key]; + normalized[key] = def.money + ? this.normalizeAu(value, `Parameter ${key}`, { allowZero: def.min === ZERO_AU }) + : value; + if (normalized[key] instanceof Error) return normalized[key]; + } + return normalized; + } + + validateParamKeys(keys) { + if (!Array.isArray(keys) || keys.length === 0 || keys.length > 64) { + return new Error('Invalid parameter keys.'); + } + for (const key of keys) { + if (!hasOwn(PARAM_DEFINITIONS, key)) return new Error(`Unknown parameter ${key}.`); + } + return null; + } + + validateParamBounds(params) { + if (params.price_min_bps > params.price_max_bps) { + return new Error('price_min_bps must not exceed price_max_bps.'); + } + return null; + } + + validateModelRef(value) { + if (!this.isSafeModelId(value.model_id)) return new Error('Invalid model id.'); + const classError = this.validateModelClass(this.modelClassFor(value), 'Model reference model_class'); + if (classError) return classError; + const rateError = this.validateRateMap(value.rate_map, this.modelClassFor(value), 'Model reference rate_map'); + if (rateError) return rateError; + if (value.source_hash !== undefined && !this.isSafeKeyPart(value.source_hash)) { + return new Error('Invalid model reference source hash.'); + } + if (value.activity_calibration !== undefined && value.activity_calibration !== null) { + return this.validateActivityCalibration(value.activity_calibration, this.modelClassFor(value), value.rate_map); + } + return null; + } + + normalizePaymentConfig(value) { + const shapeError = this.validateExactObjectKeys( + value, + ['op', 'ver', 'fiat', 'tap', 'tnk'], + 'payment config' + ); + if (shapeError) return shapeError; + if (value.op !== 'set_payments') return new Error('Invalid set_payments op.'); + if (!Number.isSafeInteger(value.ver) || value.ver <= 0) { + return new Error('Payment config version must be a positive safe integer.'); + } + + const fiatShape = this.validateExactObjectKeys( + value.fiat, + [ + 'processor', + 'integration_currency', + 'adaptive_pricing', + 'payout_currencies', + 'locale', + ], + 'fiat payment config' + ); + if (fiatShape) return fiatShape; + if (value.fiat.processor !== 'stripe') return new Error('Fiat processor must be stripe.'); + if (value.fiat.integration_currency !== 'usd') { + return new Error('Stripe integration currency must be usd for au_usd accounting.'); + } + if (value.fiat.adaptive_pricing !== true) { + return new Error('Stripe Adaptive Pricing must be enabled.'); + } + if (!Array.isArray(value.fiat.payout_currencies) || + value.fiat.payout_currencies.length < REQUIRED_FIAT_PAYOUT_CURRENCIES.length) { + return new Error('Fiat payout currencies must include EUR, GBP, and USD.'); + } + const payoutCurrencies = []; + for (const currency of value.fiat.payout_currencies) { + const normalized = this.normalizeFiatCurrency(currency); + if (normalized instanceof Error) return normalized; + if (normalized !== currency) { + return new Error('Fiat payout currencies must be canonical lowercase codes.'); + } + if (payoutCurrencies.includes(normalized)) { + return new Error('Duplicate fiat payout currency.'); + } + payoutCurrencies.push(normalized); + } + const sortedPayoutCurrencies = payoutCurrencies.slice().sort(compareCodepoint); + if (stableJson(payoutCurrencies) !== stableJson(sortedPayoutCurrencies)) { + return new Error('Fiat payout currencies must be sorted.'); + } + for (const required of REQUIRED_FIAT_PAYOUT_CURRENCIES) { + if (!payoutCurrencies.includes(required)) { + return new Error('Fiat payout currencies must include EUR, GBP, and USD.'); + } + } + if (value.fiat.locale !== 'en') return new Error('Stripe checkout locale must be en.'); + + const tapShape = this.validateExactObjectKeys( + value.tap, + ['chain_id', 'token_address', 'pool_address'], + 'TAP payment config' + ); + if (tapShape) return tapShape; + if (!Number.isSafeInteger(value.tap.chain_id) || value.tap.chain_id <= 0) { + return new Error('Invalid TAP chain id.'); + } + if (!this.isEthHexBytes(value.tap.token_address, 20)) { + return new Error('Invalid TAP token address.'); + } + if (!this.isEthHexBytes(value.tap.pool_address, 20)) { + return new Error('Invalid TAP pool address.'); + } + + const tnkShape = this.validateExactObjectKeys( + value.tnk, + ['network', 'treasury_address'], + 'TNK payment config' + ); + if (tnkShape) return tnkShape; + if (!['mainnet', 'testnet1'].includes(value.tnk.network)) { + return new Error('TNK network must be mainnet or testnet1.'); + } + if (typeof value.tnk.treasury_address !== 'string' || + !/^(trac1|testtrac1)[a-z0-9]{20,120}$/.test(value.tnk.treasury_address)) { + return new Error('Invalid TNK treasury address.'); + } + if (value.tnk.network === 'mainnet' && !value.tnk.treasury_address.startsWith('trac1')) { + return new Error('TNK mainnet treasury must use a trac1 address.'); + } + if (value.tnk.network === 'testnet1' && !value.tnk.treasury_address.startsWith('testtrac1')) { + return new Error('TNK testnet1 treasury must use a testtrac1 address.'); + } + + return { + fiat: { + processor: 'stripe', + integration_currency: 'usd', + adaptive_pricing: true, + payout_currencies: payoutCurrencies, + locale: 'en', + }, + tap: { + chain_id: value.tap.chain_id, + token_address: value.tap.token_address.toLowerCase(), + pool_address: value.tap.pool_address.toLowerCase(), + }, + tnk: { + network: value.tnk.network, + treasury_address: value.tnk.treasury_address, + }, + }; + } + + validateCatalogRelease(value) { + const releaseKeys = [ + 'op', + 'catalog_id', + 'source_kind', + 'catalog_url', + 'signature_url', + 'catalog_hash', + 'signature_hash', + 'key_id', + 'public_key', + 'model_count', + 'artifact_count', + 'canaries', + ]; + for (const optionalKey of ['parts_anchor', 'blessed_runtimes', 'outcome_classes']) { + if (hasOwn(value, optionalKey)) releaseKeys.push(optionalKey); + } + const shapeError = this.validateExactObjectKeys( + value, + releaseKeys, + 'catalog release' + ); + if (shapeError) return shapeError; + if (value.op !== 'publish_catalog') return new Error('Invalid publish_catalog op.'); + if (!this.isSafeKeyPart(value.catalog_id)) return new Error('Invalid catalog id.'); + if (!CATALOG_SOURCE_KINDS.has(value.source_kind)) { + return new Error('Unsupported catalog source kind.'); + } + if (!this.isHttpsUrl(value.catalog_url) || !this.isHttpsUrl(value.signature_url)) { + return new Error('Catalog release URLs must be HTTPS.'); + } + if (value.source_kind === 'huggingface') { + if (!this.isPinnedHuggingFaceResolveUrl(value.catalog_url)) { + return new Error('Hugging Face catalog URL must use huggingface.co/resolve/<40-hex-revision>/.'); + } + if (!this.isPinnedHuggingFaceResolveUrl(value.signature_url)) { + return new Error('Hugging Face catalog signature URL must use huggingface.co/resolve/<40-hex-revision>/.'); + } + } + if (!this.isHexBytes(value.catalog_hash, 32)) { + return new Error('Catalog hash must be a 32-byte hex BLAKE3 hash.'); + } + if (!this.isHexBytes(value.signature_hash, 32)) { + return new Error('Catalog signature hash must be a 32-byte hex BLAKE3 hash.'); + } + if (!this.isSafeKeyPart(value.key_id)) return new Error('Invalid catalog key id.'); + if (!this.isHexBytes(value.public_key, 32)) { + return new Error('Catalog public key must be 32-byte hex.'); + } + const seen = new Set(); + for (const entry of value.canaries) { + const entryError = this.validateCatalogCanaryRef(entry); + if (entryError) return entryError; + if (value.source_kind === 'huggingface' && !this.isPinnedHuggingFaceResolveUrl(entry.url)) { + return new Error('Hugging Face catalog canary URL must use huggingface.co/resolve/<40-hex-revision>/.'); + } + if (seen.has(entry.set_id)) return new Error('Duplicate catalog canary set.'); + seen.add(entry.set_id); + } + if (hasOwn(value, 'parts_anchor')) { + const partsAnchorError = this.validateCatalogPartsAnchor(value.parts_anchor); + if (partsAnchorError) return partsAnchorError; + } + if (hasOwn(value, 'blessed_runtimes')) { + if (!Array.isArray(value.blessed_runtimes)) { + return new Error('Catalog blessed_runtimes must be an array.'); + } + const runtimeIds = new Set(); + for (const entry of value.blessed_runtimes) { + const runtimeError = this.validateCatalogBlessedRuntime(entry); + if (runtimeError) return runtimeError; + if (runtimeIds.has(entry.runtime_id)) return new Error('Duplicate catalog blessed runtime id.'); + runtimeIds.add(entry.runtime_id); + } + } + if (hasOwn(value, 'outcome_classes')) { + if (!Array.isArray(value.outcome_classes)) { + return new Error('Catalog outcome_classes must be an array.'); + } + const classIds = new Set(); + for (const entry of value.outcome_classes) { + const classError = this.validateCatalogOutcomeClassRef(entry); + if (classError) return classError; + if (classIds.has(entry.class_id)) return new Error('Duplicate catalog outcome class id.'); + classIds.add(entry.class_id); + } + } + return null; + } + + validateCatalogPartsAnchor(anchor) { + const shapeError = this.validateExactObjectKeys( + anchor, + [ + 'index_ver', + 'source_kind', + 'index_url', + 'anchor_url', + 'anchor_hash', + 'index_root', + 'record_count', + 'repo_revision', + ], + 'catalog parts anchor' + ); + if (shapeError) return shapeError; + if (!Number.isSafeInteger(anchor.index_ver) || anchor.index_ver <= 0) { + return new Error('Catalog parts anchor index_ver must be a positive integer.'); + } + if (!CATALOG_SOURCE_KINDS.has(anchor.source_kind)) { + return new Error('Unsupported catalog parts anchor source kind.'); + } + if (!this.isHttpsUrl(anchor.index_url) || !this.isHttpsUrl(anchor.anchor_url)) { + return new Error('Catalog parts anchor URLs must be HTTPS.'); + } + if (anchor.source_kind === 'huggingface') { + const indexRevision = this.pinnedHuggingFaceResolveRevision(anchor.index_url); + const anchorRevision = this.pinnedHuggingFaceResolveRevision(anchor.anchor_url); + if (indexRevision === null) { + return new Error('Hugging Face parts index URL must use huggingface.co/resolve/<40-hex-revision>/.'); + } + if (anchorRevision === null) { + return new Error('Hugging Face parts anchor URL must use huggingface.co/resolve/<40-hex-revision>/.'); + } + if (indexRevision !== anchor.repo_revision || anchorRevision !== anchor.repo_revision) { + return new Error('Hugging Face parts anchor URLs must match repo_revision.'); + } + } else if (!this.isSafeExternalRef(anchor.repo_revision)) { + return new Error('Invalid catalog parts repo revision.'); + } + if (!this.isHexBytes(anchor.anchor_hash, 32)) { + return new Error('Catalog parts anchor hash must be a 32-byte hex hash.'); + } + if (!this.isHexBytes(anchor.index_root, 32)) { + return new Error('Catalog parts index root must be a 32-byte hex hash.'); + } + if (!Number.isSafeInteger(anchor.record_count) || anchor.record_count < 1) { + return new Error('Catalog parts anchor record_count must be a positive integer.'); + } + return null; + } + + validateCatalogBlessedRuntime(entry) { + const shapeError = this.validateExactObjectKeys( + entry, + [ + 'runtime_id', + 'comfy_release_hash', + 'env_lock_hash', + 'whitelist_ver', + 'status', + 'min_grace_epochs', + ], + 'catalog blessed runtime' + ); + if (shapeError) return shapeError; + if (!this.isSafeExternalRef(entry.runtime_id)) return new Error('Invalid catalog runtime id.'); + if (!this.isHexBytes(entry.comfy_release_hash, 32)) { + return new Error('Catalog runtime comfy_release_hash must be a 32-byte hex hash.'); + } + if (!this.isHexBytes(entry.env_lock_hash, 32)) { + return new Error('Catalog runtime env_lock_hash must be a 32-byte hex hash.'); + } + if (!Number.isSafeInteger(entry.whitelist_ver) || entry.whitelist_ver <= 0) { + return new Error('Catalog runtime whitelist_ver must be a positive integer.'); + } + if (!CATALOG_RUNTIME_STATUSES.has(entry.status)) return new Error('Invalid catalog runtime status.'); + if (!Number.isSafeInteger(entry.min_grace_epochs) || entry.min_grace_epochs < 0) { + return new Error('Catalog runtime min_grace_epochs must be a non-negative integer.'); + } + return null; + } + + validateCatalogOutcomeClassRef(entry) { + const shapeError = this.validateExactObjectKeys( + entry, + ['class_id', 'enclave_id', 'definition_hash', 'status'], + 'catalog outcome class' + ); + if (shapeError) return shapeError; + if (!this.isSafeExternalRef(entry.class_id)) return new Error('Invalid catalog outcome class id.'); + if (!this.isHexBytes(entry.enclave_id, 32)) return new Error('Invalid catalog outcome class enclave id.'); + if (!this.isHexBytes(entry.definition_hash, 32)) { + return new Error('Catalog outcome class definition_hash must be a 32-byte hex hash.'); + } + if (!CATALOG_OUTCOME_CLASS_STATUSES.has(entry.status)) { + return new Error('Invalid catalog outcome class status.'); + } + return null; + } + + validateCatalogCanaryRef(entry) { + const shapeError = this.validateExactObjectKeys( + entry, + ['set_id', 'url', 'hash', 'prompt_ids'], + 'catalog canary ref' + ); + if (shapeError) return shapeError; + if (!this.isSafeKeyPart(entry.set_id)) return new Error('Invalid catalog canary set id.'); + if (!this.isHttpsUrl(entry.url)) return new Error('Catalog canary URL must be HTTPS.'); + if (!this.isHexBytes(entry.hash, 32)) { + return new Error('Catalog canary hash must be a 32-byte hex BLAKE3 hash.'); + } + if (!Array.isArray(entry.prompt_ids) || entry.prompt_ids.length < 1 || entry.prompt_ids.length > 1_024) { + return new Error('Catalog canary prompt_ids must be a non-empty bounded array.'); + } + const promptIds = new Set(); + for (const promptId of entry.prompt_ids) { + if (!this.isSafeKeyPart(promptId)) return new Error('Invalid catalog canary prompt id.'); + if (promptIds.has(promptId)) return new Error('Duplicate catalog canary prompt id.'); + promptIds.add(promptId); + } + return null; + } + + validateEnclaveCaps(caps, modelClass = DEFAULT_MODEL_CLASS) { + if (!caps || typeof caps !== 'object' || Array.isArray(caps)) { + return new Error('Enclave caps must be an object.'); + } + const classError = this.validateModelClass(modelClass, 'Enclave caps model_class'); + if (classError) return classError; + const unknown = Object.keys(caps).filter((key) => !ENCLAVE_CAP_FIELDS.has(key)).sort(); + if (unknown.length > 0) { + return new Error(`Unsupported enclave caps field: ${unknown.join(', ')}.`); + } + for (const key of ENCLAVE_CAP_BOOLEAN_FIELDS) { + if (hasOwn(caps, key) && typeof caps[key] !== 'boolean') { + return new Error(`Enclave caps ${key} must be a boolean.`); + } + } + const hasCtx = hasOwn(caps, 'ctx'); + const hasCtxMax = hasOwn(caps, 'ctx_max'); + for (const key of ENCLAVE_CAP_INTEGER_FIELDS) { + if (hasOwn(caps, key) && (!Number.isSafeInteger(caps[key]) || caps[key] <= 0)) { + return new Error(`Enclave caps ${key} must be a positive integer.`); + } + } + if (hasOwn(caps, 'vllm_gpu_memory_utilization_pct') && caps.vllm_gpu_memory_utilization_pct > 100) { + return new Error('Enclave caps vllm_gpu_memory_utilization_pct must be between 1 and 100.'); + } + for (const key of ENCLAVE_CAP_STRING_FIELDS) { + if (hasOwn(caps, key) && (typeof caps[key] !== 'string' || caps[key].length === 0 || caps[key].length > 64)) { + return new Error(`Enclave caps ${key} must be a non-empty string with at most 64 characters.`); + } + } + if (hasCtx && hasCtxMax && caps.ctx !== caps.ctx_max) { + return new Error('Enclave caps ctx and ctx_max must match when both are set.'); + } + const modalitySetError = this.validateModalitySet(caps.modality_set, 'Enclave caps modality_set'); + if (modalitySetError) return modalitySetError; + const specialityLevelsError = this.validateSpecialityLevelMap( + caps.speciality_levels, + 'Enclave caps speciality_levels', + { allowEmpty: true } + ); + if (specialityLevelsError) return specialityLevelsError; + const coreModalities = this.coreModalitiesForModelClass(modelClass); + if ([...coreModalities].some((modality) => !caps.modality_set.includes(modality))) { + return new Error(`Enclave caps modality_set is missing a core modality for model_class ${modelClass}.`); + } + if (caps.vision === true && !caps.modality_set.includes('image')) { + return new Error('Enclave caps vision requires image in modality_set.'); + } + if (caps.audio === true && !caps.modality_set.includes('audio')) { + return new Error('Enclave caps audio requires audio in modality_set.'); + } + if (caps.video === true && !caps.modality_set.includes('video')) { + return new Error('Enclave caps video requires video in modality_set.'); + } + const allowedOutputModalities = MODEL_CLASS_OUTPUT_MODALITIES[modelClass] ?? new Set(); + const validateOutputModality = (modality, label) => { + if (typeof modality !== 'string' || modality.length === 0 || modality.length > 32) { + return new Error(`Enclave caps ${label} must be a non-empty string.`); + } + if (!CAP_OUTPUT_MODALITIES.has(modality)) { + return new Error(`Unsupported enclave caps ${label}: ${modality}.`); + } + if (!allowedOutputModalities.has(modality)) { + return new Error(`Enclave caps ${label} ${modality} is not allowed for model_class ${modelClass}.`); + } + return null; + }; + const outputModalities = new Set(); + if (hasOwn(caps, 'output_modality')) { + const error = validateOutputModality(caps.output_modality, 'output_modality'); + if (error) return error; + outputModalities.add(caps.output_modality); + } + if (hasOwn(caps, 'output_modalities')) { + if (!Array.isArray(caps.output_modalities) || caps.output_modalities.length === 0 || caps.output_modalities.length > 8) { + return new Error('Enclave caps output_modalities must be a non-empty array with at most 8 entries.'); + } + const seenOutputModalities = new Set(); + for (const modality of caps.output_modalities) { + const error = validateOutputModality(modality, 'output_modalities entry'); + if (error) return error; + if (seenOutputModalities.has(modality)) { + return new Error(`Enclave caps output_modalities has duplicate modality ${modality}.`); + } + seenOutputModalities.add(modality); + outputModalities.add(modality); + } + if (hasOwn(caps, 'output_modality') && !caps.output_modalities.includes(caps.output_modality)) { + return new Error('Enclave caps output_modalities must include output_modality.'); + } + } + for (const [flag, modality] of [['image', 'image'], ['video', 'video']]) { + if (caps[flag] === true && !allowedOutputModalities.has(modality)) { + return new Error(`Enclave caps ${flag} output is not allowed for model_class ${modelClass}.`); + } + } + return null; + } + + validateModalitySet(value, label) { + if (!Array.isArray(value) || value.length === 0 || value.length > 8) { + return new Error(`${label} must be a non-empty array with at most 8 entries.`); + } + const seen = new Set(); + for (const modality of value) { + if (typeof modality !== 'string' || !ENCLAVE_MODALITIES.has(modality)) { + return new Error(`${label} contains unsupported modality ${String(modality)}.`); + } + if (seen.has(modality)) return new Error(`${label} contains duplicate modality ${modality}.`); + seen.add(modality); + } + return null; + } + + validateSpecialityLevelMap(value, label, { allowEmpty = false } = {}) { + if (!value || typeof value !== 'object' || Array.isArray(value)) { + return new Error(`${label} must be an object.`); + } + const names = Object.keys(value); + if ((!allowEmpty && names.length === 0) || names.length > 16) { + return new Error(`${label} must contain between ${allowEmpty ? 0 : 1} and 16 specialities.`); + } + for (const name of names) { + if (!this.isSafeKeyPart(name) || name.length > 128) { + return new Error(`${label} contains invalid speciality ${String(name)}.`); + } + const levels = value[name]; + if (!Array.isArray(levels) || levels.length === 0 || levels.length > 16) { + return new Error(`${label} ${name} must contain between 1 and 16 levels.`); + } + const seen = new Set(); + for (const level of levels) { + if (typeof level !== 'string' || !this.isSafeKeyPart(level) || level.length > 128) { + return new Error(`${label} ${name} contains an invalid level.`); + } + if (seen.has(level)) return new Error(`${label} ${name} contains duplicate level ${level}.`); + seen.add(level); + } + } + return null; + } + + validateSpecialitySelection(value, label, { allowEmpty = true } = {}) { + if (!value || typeof value !== 'object' || Array.isArray(value)) { + return new Error(`${label} must be an object.`); + } + const names = Object.keys(value); + if ((!allowEmpty && names.length === 0) || names.length > 16) { + return new Error(`${label} must contain between ${allowEmpty ? 0 : 1} and 16 specialities.`); + } + for (const name of names) { + const level = value[name]; + if (!this.isSafeKeyPart(name) || name.length > 128) { + return new Error(`${label} contains an invalid speciality name.`); + } + if (typeof level !== 'string' || !this.isSafeKeyPart(level) || level.length > 128) { + return new Error(`${label} ${name} contains an invalid level.`); + } + } + return null; + } + + coreModalitiesForModelClass(modelClass) { + switch (modelClass) { + case DEFAULT_MODEL_CLASS: + return new Set(['text']); + case 'embedding': + return new Set(['embedding']); + case 'image-generation': + return new Set(['image']); + case 'video-generation': + return new Set(['video']); + case 'stt': + return new Set(['audio', 'text']); + case 'tts': + case 'audio-generation': + case 'music-generation': + return new Set(['audio']); + default: + return new Set(); + } + } + + validateEnclaveArtifactBinding(value) { + const classError = this.validateModelClass(this.modelClassFor(value), 'Enclave model_class'); + if (classError) return classError; + const backendError = this.validateEnclaveBackend(value.backend); + if (backendError) return backendError; + if (!this.isHexBytes(value.artifact_root, 32)) { + return new Error('Enclave artifact_root must be a 32-byte hex Merkle root.'); + } + if (value.artifact_root_kind !== ENCLAVE_ARTIFACT_ROOT_KIND) { + return new Error(`Enclave artifact_root_kind must be ${ENCLAVE_ARTIFACT_ROOT_KIND}.`); + } + if (!this.isHexBytes(value.manifest_hash, 32)) { + return new Error('Enclave manifest_hash must be 32-byte hex.'); + } + if (!this.isHexBytes(value.binary_hash, 32)) { + return new Error('Enclave binary_hash must be 32-byte hex.'); + } + if ( + value.source_sha256 !== undefined && + value.source_sha256 !== null && + !this.isHexBytes(value.source_sha256, 32) + ) { + return new Error('Enclave source_sha256 must be 32-byte hex.'); + } + const sourceError = this.validateHuggingFaceArtifactSource(value.artifact_source, 'enclave artifact_source'); + if (sourceError) return sourceError; + return this.validateEnclaveArtifactSidecars(value.artifact_sidecars ?? {}); + } + + validateEnclaveBackend(value) { + if ( + typeof value !== 'string' || + value.length === 0 || + value.length > ENCLAVE_BACKEND_MAX_LENGTH || + !ENCLAVE_BACKEND_PATTERN.test(value) + ) { + return new Error('Enclave backend must be a lowercase canonical identifier of at most 64 ASCII characters.'); + } + return null; + } + + normalizeApprovedBinaryHashes(primary, values) { + const hashes = [primary, ...(Array.isArray(values) ? values : [])] + .filter((value) => typeof value === 'string') + .map((value) => value.toLowerCase()); + return [...new Set(hashes)].sort(); + } + + validateApprovedBinaryHashes(primary, values) { + if (!Array.isArray(values) || values.length === 0) { + return new Error('Enclave approved_binary_hashes must be a non-empty array.'); + } + if (values.length > ENCLAVE_APPROVED_BINARY_HASHES_MAX) { + return new Error(`Enclave approved_binary_hashes may contain at most ${ENCLAVE_APPROVED_BINARY_HASHES_MAX} entries.`); + } + const normalizedPrimary = typeof primary === 'string' ? primary.toLowerCase() : primary; + const seen = new Set(); + for (const hash of values) { + if (!this.isHexBytes(hash, 32)) { + return new Error('Enclave approved_binary_hashes entries must be 32-byte hex.'); + } + const normalized = hash.toLowerCase(); + if (seen.has(normalized)) { + return new Error('Enclave approved_binary_hashes must not contain duplicates.'); + } + seen.add(normalized); + } + if (!seen.has(normalizedPrimary)) { + return new Error('Enclave approved_binary_hashes must include binary_hash.'); + } + return null; + } + + normalizeEnclaveLaunchMeasurements(value) { + if (value === undefined || value === null) return null; + if (!value || typeof value !== 'object' || Array.isArray(value)) return cloneValue(value); + if (hasOwn(value, 'layers')) { + return { + schema_version: value.schema_version ?? 1, + effective_epoch: value.effective_epoch ?? 0, + ...(hasOwn(value, 'platform') ? { platform: value.platform } : {}), + layers: cloneValue(value.layers), + }; + } + if (hasOwn(value, 'measurements')) { + return { + schema_version: value.schema_version ?? 1, + effective_epoch: value.effective_epoch ?? 0, + ...(hasOwn(value, 'platform') ? { platform: value.platform } : {}), + layers: { workload: cloneValue(value.measurements) }, + }; + } + const measurements = cloneValue(value); + delete measurements.schema_version; + delete measurements.effective_epoch; + delete measurements.platform; + delete measurements.layers; + return { + schema_version: 1, + effective_epoch: value.effective_epoch ?? 0, + ...(hasOwn(value, 'platform') ? { platform: value.platform } : {}), + layers: { workload: measurements }, + }; + } + + validateEnclaveLaunchMeasurements(value, attTier) { + if (attTier < 3 && (value === undefined || value === null)) return null; + if (attTier >= 3 && (value === undefined || value === null)) { + return new Error('Tier-3 enclaves require admin-published launch_measurements.'); + } + if (!value || typeof value !== 'object' || Array.isArray(value)) { + return new Error('Enclave launch_measurements must be an object.'); + } + const allowed = new Set(['schema_version', 'effective_epoch', 'platform', 'layers']); + const unknown = Object.keys(value).filter((key) => !allowed.has(key)).sort(); + if (unknown.length > 0) { + return new Error(`enclave launch measurements does not accept fields: ${unknown.join(', ')}.`); + } + if (value.schema_version !== 1) { + return new Error('Enclave launch_measurements schema_version must be 1.'); + } + if (!Number.isSafeInteger(value.effective_epoch) || value.effective_epoch < 0) { + return new Error('Enclave launch_measurements effective_epoch must be a non-negative integer.'); + } + if (attTier >= 3 && !hasOwn(value, 'platform')) { + return new Error('Tier-3 launch_measurements must include a platform label.'); + } + if (hasOwn(value, 'platform') && !this.isSafeKeyPart(value.platform)) { + return new Error('Enclave launch_measurements platform must be a safe label.'); + } + const layers = value.layers; + if (!layers || typeof layers !== 'object' || Array.isArray(layers)) { + return new Error('Enclave launch_measurements layers must be an object.'); + } + let count = 0; + let workloadCount = 0; + let names = 0; + for (const [layer, measurements] of Object.entries(layers)) { + if (!this.isTier3MeasurementLayer(layer)) { + return new Error('Enclave launch_measurements layers must be vendor or workload.'); + } + if (!measurements || typeof measurements !== 'object' || Array.isArray(measurements)) { + return new Error('Enclave launch_measurements layer values must be objects.'); + } + const layerNames = Object.keys(measurements); + const layerCount = this.countLaunchMeasurementValues(measurements); + names += layerNames.length; + count += layerCount; + if (layer === 'workload') workloadCount += layerCount; + for (const [name, measurement] of Object.entries(measurements)) { + if (!this.isSafeLaunchMeasurementName(name)) { + return new Error('Enclave launch_measurements names must be non-empty safe labels.'); + } + const measurementError = this.validateLaunchMeasurementValueList(name, measurement, `Enclave launch_measurements ${layer}`); + if (measurementError) return measurementError; + } + } + if (attTier >= 3 && count === 0) { + return new Error('Tier-3 enclaves require at least one launch measurement.'); + } + if (attTier >= 3 && workloadCount === 0) { + return new Error('Tier-3 enclaves require workload PCR/stack measurements.'); + } + if (names > TIER3_MEASUREMENT_MAX_NAMES) { + return new Error('Enclave launch_measurements may contain at most 32 measurements.'); + } + if (count > TIER3_MEASUREMENT_MAX_VALUES) { + return new Error('Enclave launch_measurements may contain at most 128 measurement values.'); + } + return null; + } + + countLaunchMeasurementValues(measurements) { + let count = 0; + for (const value of Object.values(measurements ?? {})) { + if (typeof value === 'string') count += 1; + else if (Array.isArray(value)) count += value.length; + else if (value && typeof value === 'object' && Array.isArray(value.values)) count += value.values.length; + else if (value && typeof value === 'object' && typeof value.measurement === 'string') count += 1; + } + return count; + } + + isSafeLaunchMeasurementName(value) { + return typeof value === 'string' && value.length > 0 && value.length <= 64 && /^[A-Za-z0-9_.:-]+$/.test(value); + } + + isTier3MeasurementLayer(value) { + return value === 'vendor' || value === 'workload'; + } + + validateLaunchMeasurementHex(label, measurement) { + if ( + typeof measurement !== 'string' || + !/^[0-9a-fA-F]+$/.test(measurement) || + measurement.length < 64 || + measurement.length > 256 || + measurement.length % 2 !== 0 + ) { + return new Error(`${label} must be a 32-128 byte hex value.`); + } + return null; + } + + validateLaunchMeasurementValueList(name, value, label) { + if (typeof value === 'string') { + return this.validateLaunchMeasurementHex(`${label} ${name}`, value); + } + if (Array.isArray(value)) { + if (value.length === 0) return new Error(`${label} ${name} must not be empty.`); + for (const item of value) { + const measurement = typeof item === 'string' ? item : item?.measurement; + const error = this.validateLaunchMeasurementHex(`${label} ${name}`, measurement); + if (error) return error; + } + return null; + } + if (value && typeof value === 'object' && !Array.isArray(value)) { + if (Array.isArray(value.values)) return this.validateLaunchMeasurementValueList(name, value.values, label); + return this.validateLaunchMeasurementHex(`${label} ${name}`, value.measurement); + } + return new Error(`${label} ${name} must be a 32-128 byte hex value or array of values.`); + } + + validateTier3MeasurementBlessValue(value) { + if (!value || typeof value !== 'object' || Array.isArray(value)) { + return new Error('Tier-3 measurement blessing value must be an object.'); + } + const required = ['op', 'platform', 'layer', 'measurement_name', 'measurement', 'effective_epoch', 'at']; + const allowed = new Set([...required, 'region', 'derivation_hash', 'source']); + const unknown = Object.keys(value).filter((key) => !allowed.has(key)).sort(); + if (unknown.length > 0) { + return new Error(`tier3 measurement blessing does not accept fields: ${unknown.join(', ')}.`); + } + for (const key of required) { + if (!hasOwn(value, key)) return new Error(`Tier-3 measurement blessing is missing ${key}.`); + } + if (value.op !== 'tier3_bless_measurement') return new Error('Invalid Tier-3 measurement blessing op.'); + if (!this.isSafeKeyPart(value.platform)) return new Error('Invalid Tier-3 platform.'); + if (!this.isTier3MeasurementLayer(value.layer)) return new Error('Invalid Tier-3 measurement layer.'); + if (!this.isSafeLaunchMeasurementName(value.measurement_name)) return new Error('Invalid Tier-3 measurement name.'); + const measurementError = this.validateLaunchMeasurementHex('Tier-3 measurement', value.measurement); + if (measurementError) return measurementError; + if (!Number.isSafeInteger(value.effective_epoch) || value.effective_epoch < 0) { + return new Error('Invalid Tier-3 measurement effective epoch.'); + } + if (!Number.isSafeInteger(value.at) || value.at < 0) { + return new Error('Invalid Tier-3 measurement timestamp.'); + } + if (hasOwn(value, 'region') && value.region !== null && !this.isSafeKeyPart(value.region)) { + return new Error('Invalid Tier-3 measurement region.'); + } + if (hasOwn(value, 'derivation_hash') && value.derivation_hash !== null && !this.isHexBytes(value.derivation_hash, 32)) { + return new Error('Invalid Tier-3 derivation hash.'); + } + if (hasOwn(value, 'source') && (typeof value.source !== 'string' || value.source.length < 1 || value.source.length > 64)) { + return new Error('Invalid Tier-3 measurement source.'); + } + return null; + } + + normalizeEnclaveQuant(value) { + const normalized = String(value ?? 'unknown').trim().toLowerCase().replace(/_/g, '-'); + if (ENCLAVE_QUANT_BUCKETS.has(normalized)) return normalized; + return this.quantBucketFromDescriptor(normalized); + } + + quantBucketFromDescriptor(descriptor) { + const normalized = String(descriptor).toLowerCase().replace(/_/g, '-'); + const tokens = normalized.split(/[^a-z0-9]+/); + if (normalized.includes('nvfp4')) return 'nvfp4'; + if (normalized.includes('mxfp8')) return 'mxfp8'; + if (normalized.includes('mxfp6')) return 'mxfp6'; + if (normalized.includes('mxfp4')) return 'mxfp4'; + if (tokens.includes('nf4')) return 'nf4'; + if (normalized.includes('fp8')) return 'fp8'; + if (normalized.includes('fp6')) return 'fp6'; + if (normalized.includes('fp4')) return 'fp4'; + if (normalized.includes('bf16')) return 'bf16'; + if (normalized.includes('fp16') || normalized.includes('f16')) return 'fp16'; + if (normalized.includes('tf32')) return 'tf32'; + if (normalized.includes('fp32') || normalized.includes('f32')) return 'fp32'; + if (normalized.includes('fp64') || normalized.includes('f64')) return 'fp64'; + for (let bits = 8; bits >= 1; bits -= 1) { + const aliases = new Set([`int${bits}`, `${bits}bit`, `q${bits}`, `iq${bits}`, `tq${bits}`]); + if (tokens.some((token) => aliases.has(token))) return `int${bits}`; + } + return normalized; + } + + validateEnclaveQuant(value) { + if (typeof value !== 'string') return new Error('Enclave quant must be a string.'); + const quant = this.normalizeEnclaveQuant(value); + if (quant.length > ENCLAVE_QUANT_BUCKET_MAX_LENGTH || !ENCLAVE_QUANT_BUCKET_PATTERN.test(quant)) { + return new Error('Enclave quant must be a lowercase canonical identifier of at most 32 ASCII characters.'); + } + return null; + } + + validateEnclaveArtifactSidecars(sidecars) { + if (!sidecars || typeof sidecars !== 'object' || Array.isArray(sidecars)) { + return new Error('Enclave artifact_sidecars must be an object.'); + } + const names = Object.keys(sidecars).sort(); + if (names.length > ENCLAVE_ARTIFACT_SIDECARS_MAX) { + return new Error('Enclave artifact_sidecars has too many entries.'); + } + for (const name of names) { + if (!this.isSafeHuggingFacePathSegment(name)) { + return new Error('Enclave artifact_sidecars keys must be safe names.'); + } + const sidecar = sidecars[name]; + const shapeError = this.validateExactObjectKeys( + sidecar, + ['source', 'path', 'artifact_root', 'artifact_root_kind', 'weights_bytes', 'source_sha256'], + `enclave artifact_sidecars.${name}` + ); + if (shapeError) return shapeError; + const sourceError = this.validateHuggingFaceArtifactSource( + sidecar.source, + `enclave artifact_sidecars.${name}.source` + ); + if (sourceError) return sourceError; + if (!this.isSafeHuggingFacePath(sidecar.path)) { + return new Error(`Enclave artifact_sidecars.${name}.path must be a safe relative Hugging Face artifact path.`); + } + if (sidecar.source.path !== sidecar.path) { + return new Error(`Enclave artifact_sidecars.${name}.source.path must match path.`); + } + if (!this.isHexBytes(sidecar.artifact_root, 32)) { + return new Error(`Enclave artifact_sidecars.${name}.artifact_root must be a 32-byte hex Merkle root.`); + } + if (sidecar.artifact_root_kind !== ENCLAVE_ARTIFACT_ROOT_KIND) { + return new Error(`Enclave artifact_sidecars.${name}.artifact_root_kind must be ${ENCLAVE_ARTIFACT_ROOT_KIND}.`); + } + if (!Number.isSafeInteger(sidecar.weights_bytes) || sidecar.weights_bytes <= 0) { + return new Error(`Enclave artifact_sidecars.${name}.weights_bytes must be a positive integer.`); + } + if (!this.isHexBytes(sidecar.source_sha256, 32)) { + return new Error(`Enclave artifact_sidecars.${name}.source_sha256 must be 32-byte hex.`); + } + } + return null; + } + + validateHuggingFaceArtifactSource(source, label = 'enclave artifact_source') { + const shapeError = this.validateExactObjectKeys( + source, + ['kind', 'repo', 'revision', 'path'], + label + ); + if (shapeError) return shapeError; + if (source.kind !== 'huggingface') { + return new Error(`${label}.kind must be huggingface.`); + } + if (!this.isSafeHuggingFaceRepo(source.repo)) { + return new Error(`${label}.repo must be a safe namespace/name repo id.`); + } + if (!this.isHexBytes(source.revision, 20)) { + return new Error(`${label}.revision must be a 20-byte git commit hex.`); + } + if (!this.isSafeHuggingFacePath(source.path)) { + return new Error(`${label}.path must be a safe relative Hugging Face artifact path.`); + } + return null; + } + + validateRoomPolicy(policy) { + if (!policy || typeof policy !== 'object' || Array.isArray(policy)) { + return new Error('Room policy must be an object.'); + } + const unknown = Object.keys(policy).filter((key) => !ROOM_POLICY_FIELDS.has(key)).sort(); + if (unknown.length > 0) { + return new Error(`Unsupported room policy field: ${unknown.join(', ')}.`); + } + for (const key of ['region_hint', 'canary_set']) { + if ( + hasOwn(policy, key) && + ( + typeof policy[key] !== 'string' || + policy[key].length === 0 || + policy[key].length > 128 + ) + ) { + return new Error(`Room policy ${key} must be a non-empty string.`); + } + } + if ( + hasOwn(policy, 'min_reputation') && + ( + typeof policy.min_reputation !== 'number' || + !Number.isFinite(policy.min_reputation) || + policy.min_reputation < 0 || + policy.min_reputation > 1 + ) + ) { + return new Error('Room policy min_reputation must be between 0 and 1.'); + } + if ( + hasOwn(policy, 'max_price_mult') && + ( + typeof policy.max_price_mult !== 'number' || + !Number.isFinite(policy.max_price_mult) || + policy.max_price_mult <= 0 + ) + ) { + return new Error('Room policy max_price_mult must be positive.'); + } + return null; + } + + async priceSchedule(key, enclave, ctxMeta = null) { + const existing = await this.get(key); + if (existing) return existing; + return { + enclave_id: enclave.enclave_id, + model_id: enclave.model_id, + denom: PRICE_DENOMINATION, + ...(ctxMeta ? { + ctx_bracket: ctxMeta.ctx_bracket, + ctx_bracket_table_ver: ctxMeta.ctx_bracket_table_ver, + } : {}), + current: null, + pending: null, + }; + } + + priceScheduleAt(schedule, at) { + const updated = cloneValue(schedule); + if (updated.pending && updated.pending.effective_at <= at) { + updated.current = updated.pending; + updated.pending = null; + } + return updated; + } + + priceActiveEntry(schedule, at) { + if (schedule.pending && schedule.pending.effective_at <= at) return cloneValue(schedule.pending); + return schedule.current ? cloneValue(schedule.current) : null; + } + + priceLatestEntry(schedule) { + return schedule.pending ?? schedule.current; + } + + priceSeedSnapshot(record) { + if (!record) return null; + return { + enclave_id: record.enclave_id, + model_id: record.model_id, + denom: record.denom, + ver: record.seed_ver ?? record.ver, + ...(record.ctx_bracket ? { ctx_bracket: record.ctx_bracket } : {}), + ...(record.ctx_bracket_table_ver ? { ctx_bracket_table_ver: record.ctx_bracket_table_ver } : {}), + rate_map: cloneValue(record.seed_rate_map ?? record.rate_map), + per_req_au: record.seed_per_req_au ?? record.per_req_au, + min_session_au: record.seed_min_session_au ?? record.min_session_au, + effective_at: record.seed_effective_at ?? record.effective_at, + effective_from: record.seed_effective_from ?? record.effective_from, + updated_at: record.seed_updated_at ?? record.updated_at, + set_by: record.seed_by ?? record.set_by, + set_by_role: record.seed_by_role ?? record.set_by_role, + }; + } + + priceSeedEntry(record) { + return record?.seed ? cloneValue(record.seed) : this.priceSeedSnapshot(record); + } + + priceLatestSeedEntry(schedule) { + if (schedule.pending) return this.priceSeedEntry(schedule.pending); + if (schedule.current) return this.priceSeedEntry(schedule.current); + return null; + } + + sanitizeMarketBoundRecord(key, record) { + if (key !== 'price_min_bps' && key !== 'price_max_bps') return record; + const safe = (entry) => Number.isSafeInteger(entry?.value) && + entry.value >= 2_500 && entry.value <= 40_000; + const next = cloneValue(record); + if (!safe(next.current)) next.current = { + ...next.current, value: PARAM_DEFINITIONS[key].default, + policy_repair: 'market_activity_v2_hard_bounds', + }; + if (next.pending && !safe(next.pending)) next.pending = null; + return next; + } + + async migrateMarketPricing() { + const adminError = await this.requireAdmin(); + if (adminError) return adminError; + const shape = this.validateExactCommandValue(['op', 'at', 'markets'], 'migrate_market_pricing'); + if (shape) return shape; + if (!Number.isSafeInteger(this.value.at) || this.value.at < 0 || + !Array.isArray(this.value.markets) || this.value.markets.length > 128) { + return new Error('Migration requires a timestamp and at most 128 active market rows.'); + } + const key = 'market/activity/migration-v2'; + const existing = await this.get(key); + const state = await this.epochApplyStateRecord(); + if (state.pending_epoch !== null && state.pending_epoch !== undefined) { + return new Error('Market pricing migration requires a completed epoch boundary.'); + } + const priorIndex = await this.get('market/activity/index') ?? []; + const index = new Map(priorIndex.map((row) => [ + this.priceMarketKey(row.enclave_id, row.ctx_bracket ?? null), row, + ])); + const seen = new Set(); + for (const row of this.value.markets) { + const fields = ['enclave_id', ...(row?.ctx_bracket !== undefined + ? ['ctx_bracket', 'ctx_bracket_table_ver'] : [])]; + const rowShape = this.validateExactObjectKeys(row, fields, 'Migration market'); + if (rowShape) return rowShape; + if (!this.isSafeKeyPart(row.enclave_id)) return new Error('Invalid migration market enclave.'); + const enclave = await this.get(`enclave/${row.enclave_id}`); + if (!enclave || enclave.status !== 'active') return new Error('Migration market enclave is not active.'); + const ctx = await this.priceCtxMetaForEnclave(enclave, row.ctx_bracket, this.value.at, 'Migration market'); + if (ctx instanceof Error) return ctx; + if ((ctx?.ctx_bracket_table_ver ?? null) !== (row.ctx_bracket_table_ver ?? null)) { + return new Error('Migration market context table version is not active.'); + } + const marketKey = this.priceMarketKey(row.enclave_id, row.ctx_bracket ?? null); + if (seen.has(marketKey)) return new Error('Duplicate migration market.'); + seen.add(marketKey); + const schedule = await this.priceSchedule(this.priceScheduleKey(row.enclave_id, row.ctx_bracket ?? null), enclave, ctx); + const price = this.priceActiveEntry(schedule, this.value.at); + if (!price || this.priceSeedEntry(price)?.set_by_role !== 'admin' || + !(await this.get(`modelref/${enclave.model_id}`))) { + return new Error('Migration market requires an active admin price and model reference.'); + } + index.set(marketKey, cloneValue(row)); + } + if (index.size > DEFAULT_MAX_MARKET_USAGE_ENTRIES) return new Error('Migration market index capacity exceeded.'); + const repairs = []; + for (const name of ['price_min_bps', 'price_max_bps']) { + const before = await this.get(`params/${name}`); + if (!before) continue; + const after = this.sanitizeMarketBoundRecord(name, before); + if (stableJson(before) !== stableJson(after)) repairs.push({ key: name, before, after }); + } + const nextIndex = Array.from(index.values()).sort((a, b) => + compareCodepoint(a.enclave_id, b.enclave_id) || compareCodepoint(a.ctx_bracket ?? '', b.ctx_bracket ?? '')); + if (existing && repairs.length === 0 && stableJson(priorIndex) === stableJson(nextIndex)) { + return { ok: true, op: 'migrateMarketPricing', idempotent: true, market_count: index.size }; + } + // Complete validation before any writes. Historical params/update records stay immutable. + for (const repair of repairs) await this.put(`params/${repair.key}`, repair.after); + await this.put('market/activity/index', nextIndex); + await this.put(key, { + schema_version: 2, contract_version: CONTRACT_VERSION, + hard_min_bps: 2_500, hard_max_bps: 40_000, + previous_applied_epoch: state.updated_epoch ?? 0, + repairs: [...(existing?.repairs ?? []), ...repairs], market_count: index.size, + migrated_at: existing?.migrated_at ?? this.tx, updated_at: this.tx, migrated_by: this.address, + }); + return { ok: true, op: 'migrateMarketPricing', idempotent: false, repaired: repairs.length, market_count: index.size }; + } + + validateActivityCalibration(value, modelClass, rateMap = null) { + const shape = this.validateExactObjectKeys(value, + ['schema_version', 'source_hash', 'dimensions'], 'Activity calibration'); + if (shape) return shape; + if (value.schema_version !== 1 || !this.isHexBytes(value.source_hash, 32) || + !Array.isArray(value.dimensions) || value.dimensions.length < 1 || + value.dimensions.length > RATE_MAP_MAX_ENTRIES) { + return new Error('Invalid activity calibration metadata.'); + } + const allowed = MODEL_CLASS_RATE_UNITS[modelClass]; + const seen = new Set(); + let positive = false; + for (const row of value.dimensions) { + const error = this.validateExactObjectKeys(row, ['unit', 'units', 'work_us'], + 'Activity calibration dimension'); + if (error) return error; + if (!allowed?.has(row.unit) || seen.has(row.unit) || + !/^[1-9][0-9]{0,17}$/.test(row.units) || + !/^[1-9][0-9]{0,17}$/.test(row.work_us)) { + return new Error('Invalid activity calibration dimension.'); + } + seen.add(row.unit); + positive ||= BigInt(row.work_us) > 0n; + if ((row.unit === 'input_token' || row.unit === 'output_token') && + BigInt(row.work_us) === 0n) { + return new Error('Token activity calibration requires positive prefill/decode work.'); + } + } + if (rateMap && stableJson([...seen].sort(compareCodepoint)) !== + stableJson(rateMap.map((row) => row.unit).sort(compareCodepoint))) { + return new Error('Activity calibration must cover every model reference rate unit exactly.'); + } + if (!positive || (modelClass === DEFAULT_MODEL_CLASS && + (!seen.has('input_token') || !seen.has('output_token')))) { + return new Error('Activity calibration requires calibrated workload dimensions.'); + } + if (stableJson(value.dimensions) !== stableJson(value.dimensions.slice().sort( + (a, b) => compareCodepoint(a.unit, b.unit)))) { + return new Error('Activity calibration dimensions must be sorted.'); + } + return null; + } + + incrementalSettledUsage(body) { + const usage = this.normalizeReceiptUsage(body.usage); + const prior = this.normalizeReceiptUsage(body.billing_prior_usage); + if (usage instanceof Error || prior instanceof Error) { + return new Error('Canonical receipt activity usage is invalid.'); + } + const billed = this.normalizeLockedRateMap(body.locked_rate_map, 'activity locked rates'); + if (billed instanceof Error) return billed; + const paidUnits = new Set(billed.map((row) => row.unit)); + const result = {}; + for (const unit of new Set([...Object.keys(usage), ...Object.keys(prior)])) { + const count = BigInt(usage[unit] ?? 0) - BigInt(prior[unit] ?? 0); + if (count < 0n) return new Error('Canonical receipt activity regressed below billing baseline.'); + if (count > 0n && paidUnits.has(unit)) result[unit] = count.toString(); + } + return stableValue(result); + } + + addSettledUsage(left, right) { + if (!left || !right || typeof left !== 'object' || typeof right !== 'object' || + Array.isArray(left) || Array.isArray(right)) return new Error('Invalid settled activity units.'); + const result = {}; + for (const unit of new Set([...Object.keys(left), ...Object.keys(right)])) { + if (!this.isSafeKeyPart(unit)) return new Error('Invalid settled activity unit.'); + const a = this.parseAu(left[unit] ?? '0', 'settled activity count'); + const b = this.parseAu(right[unit] ?? '0', 'settled activity count'); + if (a instanceof Error || b instanceof Error) return new Error('Invalid settled activity count.'); + const sum = this.safeAddAu(a.toString(), b.toString()); + if (sum instanceof Error) return sum; + if (sum !== '0') result[unit] = sum; + } + return stableValue(result); + } + + calibratedActivityWork(usage, calibration) { + const dimensions = new Map(calibration.dimensions.map((row) => [row.unit, row])); + let work = 0n; + for (const [unit, count] of Object.entries(usage)) { + const dimension = dimensions.get(unit); + if (!dimension) return new Error('Settled unit is missing its signed activity calibration.'); + const n = this.parseAu(count, 'settled activity count'); + if (n instanceof Error) return n; + // Round once per epoch/axis, not per receipt or page (split-resistant). + work += (n * BigInt(dimension.work_us) * 1_000_000n) / BigInt(dimension.units); + } + return work.toString(); // picoseconds of calibrated reference work. + } + + marketActivityMomentum(currentRate, previousRate, constants) { + const current = BigInt(currentRate); + const previous = BigInt(previousRate); + const raw = previous > 0n ? current * 10_000n / previous + : (current > 0n ? BigInt(constants.max_momentum_bps) : 0n); + return Number(raw > BigInt(constants.max_momentum_bps) + ? BigInt(constants.max_momentum_bps) : raw); + } + + marketActivityEma(previousRate, currentRate, constants) { + return ((BigInt(previousRate) * BigInt(10_000 - constants.ema_alpha_bps) + + BigInt(currentRate) * BigInt(constants.ema_alpha_bps)) / 10_000n).toString(); + } + + marketActivityVector(usage, epochSeconds) { + return Object.fromEntries(Object.entries(usage).map(([unit, count]) => [ + unit, (BigInt(count) * 1_000_000_000_000n / BigInt(epochSeconds)).toString(), + ])); + } + + marketVectorMomentum(current, previous, constants) { + const units = [...new Set([...Object.keys(current), ...Object.keys(previous)])] + .filter((unit) => BigInt(current[unit] ?? '0') > 0n || BigInt(previous[unit] ?? '0') > 0n) + .sort(compareCodepoint); + if (!units.length) return 0; + let sum = 0n; + for (const unit of units) sum += BigInt(this.marketActivityMomentum( + current[unit] ?? '0', previous[unit] ?? '0', constants)); + return Number(sum / BigInt(units.length)); + } + + marketVectorEma(previous, current, constants) { + return Object.fromEntries([...new Set([...Object.keys(current), ...Object.keys(previous)])] + .sort(compareCodepoint).map((unit) => [unit, this.marketActivityEma( + previous[unit] ?? '0', current[unit] ?? '0', constants)])); + } + + async activityMarketEntries(usageMap, includeDormant) { + const entries = this.mapMarketUsageEntriesForHash(usageMap); + const known = await this.get('market/activity/index') ?? []; + if (!Array.isArray(known) || known.length > DEFAULT_MAX_MARKET_USAGE_ENTRIES) { + return new Error('Market activity index exceeds its deterministic bound.'); + } + const keys = new Set(entries.map((row) => this.priceMarketKey(row.enclave_id, row.ctx_bracket ?? null))); + for (const row of known) { + const key = this.priceMarketKey(row.enclave_id, row.ctx_bracket ?? null); + if (includeDormant && !keys.has(key)) entries.push({ ...row, demand_au: '0', session_count: 0, provider_count: 0, _activity_dormant: true }); + } + if (new Set([...keys, ...known.map((row) => this.priceMarketKey(row.enclave_id, row.ctx_bracket ?? null))]).size > DEFAULT_MAX_MARKET_USAGE_ENTRIES) { + return new Error('Market activity index capacity exceeded.'); + } + return entries.sort((a, b) => compareCodepoint(a.enclave_id, b.enclave_id) || + compareCodepoint(a.ctx_bracket ?? '', b.ctx_bracket ?? '')); + } + + async writeActivityMarketIndex(updates) { + const index = new Map((await this.get('market/activity/index') ?? []).map((row) => [ + this.priceMarketKey(row.enclave_id, row.ctx_bracket ?? null), row, + ])); + for (const update of updates) { + if (!update.record.market.activity_initialized) continue; + index.set(update.market_key, { + enclave_id: update.enclave_id, + ...(update.ctx_bracket ? { ctx_bracket: update.ctx_bracket, + ctx_bracket_table_ver: update.ctx_bracket_table_ver } : {}), + }); + } + if (index.size > DEFAULT_MAX_MARKET_USAGE_ENTRIES) return new Error('Market activity index overflow.'); + if (updates.length) await this.put('market/activity/index', Array.from(index.values()).sort( + (a, b) => compareCodepoint(a.enclave_id, b.enclave_id) || + compareCodepoint(a.ctx_bracket ?? '', b.ctx_bracket ?? ''))); + } + + marketPriceParamKeys() { + return ['price_min_bps', 'price_max_bps', 'market_ema_alpha_bps', + 'market_gain_bps', 'market_max_step_bps']; + } + + marketPriceConstants(params) { + return { + schema_version: 2, + ema_alpha_bps: params.market_ema_alpha_bps, + gain_bps: params.market_gain_bps, + max_step_bps: params.market_max_step_bps, + max_momentum_bps: 50_000, + }; + } + + scalePriceTerm(term, multiplierBps) { + const amount = this.parseAu(term, 'price term'); + if (amount instanceof Error || !Number.isSafeInteger(multiplierBps) || multiplierBps < 0) { + return new Error('Invalid price term.'); + } + if (amount === 0n) return ZERO_AU; + const scaled = (amount * BigInt(multiplierBps) + 5_000n) / 10_000n; + return this.canonicalAu(scaled > 0n ? scaled : 1n); + } + + stepPriceTerm(current, desired, constants) { + const currentAu = this.parseAu(current, 'current price term'); + const desiredAu = this.parseAu(desired, 'desired price term'); + if (currentAu instanceof Error || desiredAu instanceof Error) return new Error('Invalid price term.'); + if (currentAu === desiredAu) return this.canonicalAu(currentAu); + if (currentAu === 0n) return this.canonicalAu(desiredAu); + const delta = currentAu > desiredAu ? currentAu - desiredAu : desiredAu - currentAu; + const gainedRaw = (delta * BigInt(constants.gain_bps)) / 10_000n; + const maxStepRaw = (currentAu * BigInt(constants.max_step_bps)) / 10_000n; + const gained = gainedRaw > 0n ? gainedRaw : 1n; + const maxStep = maxStepRaw > 0n ? maxStepRaw : 1n; + const step = gained < maxStep ? gained : maxStep; + return this.canonicalAu( + desiredAu > currentAu + ? currentAu + step + : (step > currentAu ? 0n : currentAu - step) + ); + } + + scaleRateMap(rateMap, multiplierBps) { + const scaled = []; + for (const entry of rateMap) { + const perUnitAu = this.scalePriceTerm(entry.per_unit_au, multiplierBps); + if (perUnitAu instanceof Error) return perUnitAu; + scaled.push({ ...entry, per_unit_au: perUnitAu }); + } + return this.normalizeRateMap(scaled); + } + + stepRateMap(currentRateMap, desiredRateMap, constants) { + const desiredByUnit = this.rateMapByUnit(desiredRateMap); + const stepped = []; + for (const entry of currentRateMap) { + const desired = desiredByUnit.get(entry.unit); + if (!desired || desired.granularity !== entry.granularity) { + return new Error('Market price rate_map shape changed.'); + } + const perUnitAu = this.stepPriceTerm(entry.per_unit_au, desired.per_unit_au, constants); + if (perUnitAu instanceof Error) return perUnitAu; + stepped.push({ ...entry, per_unit_au: perUnitAu }); + } + return this.normalizeRateMap(stepped); + } + + clampRateMapBounds(priceRateMap, referenceRateMap, params) { + const priceByUnit = this.rateMapByUnit(priceRateMap); + const referenceByUnit = this.rateMapByUnit(referenceRateMap); + if (priceByUnit.size !== referenceByUnit.size) { + return new Error('Market price rate_map units must match model reference rate_map units.'); + } + const bounded = []; + for (const entry of priceRateMap) { + const reference = referenceByUnit.get(entry.unit); + const price = this.parseAu(entry.per_unit_au, 'market price per_unit_au'); + const referencePrice = this.parseAu( + reference?.per_unit_au, + 'market reference per_unit_au', + { allowZero: false } + ); + if ( + price instanceof Error || + referencePrice instanceof Error || + !Number.isSafeInteger(entry.granularity) || + entry.granularity <= 0 || + !Number.isSafeInteger(reference?.granularity) || + reference.granularity <= 0 + ) { + return new Error('Invalid market price rate_map bounds.'); + } + const denominator = BigInt(reference.granularity) * 10_000n; + const scaledReference = referencePrice * BigInt(entry.granularity); + const lowerNumerator = scaledReference * BigInt(Math.max(2_500, params.price_min_bps)); + const upperNumerator = scaledReference * BigInt(Math.min(40_000, params.price_max_bps)); + const lower = (lowerNumerator + denominator - 1n) / denominator; + const upper = upperNumerator / denominator; + if (lower > upper) return new Error(`Model reference bounds cannot represent unit ${entry.unit}.`); + const clamped = price < lower ? lower : (price > upper ? upper : price); + bounded.push({ ...entry, per_unit_au: this.canonicalAu(clamped) }); + } + return this.normalizeRateMap(bounded); + } + + priceTermsEqual(left, right) { + return ( + stableJson(left.rate_map) === stableJson(right.rate_map) && + this.compareAu(left.per_req_au, right.per_req_au) === 0 && + this.compareAu(left.min_session_au, right.min_session_au) === 0 + ); + } + + async computeMarketPriceUpdates(marketUsageMap, context = {}) { + const at = context.at ?? this.value.at; + const epoch = context.epoch ?? this.value.epoch; + const epochSeconds = context.epochSeconds ?? null; + const tx = context.tx ?? this.tx; + if (!Number.isSafeInteger(epochSeconds) || epochSeconds < 1) { + return new Error('Market activity requires a calibrated epoch duration.'); + } + const marketParams = await this.activeParamsAt(at, this.marketPriceParamKeys()); + const constants = this.marketPriceConstants(marketParams); + const entries = await this.activityMarketEntries(marketUsageMap, context.includeDormant === true); + if (entries instanceof Error) return entries; + const updates = []; + for (const usage of entries) { + const marketKey = this.priceMarketKey(usage.enclave_id, usage.ctx_bracket ?? null); + const enclave = await this.get(`enclave/${usage.enclave_id}`); + // Retired markets have no new orders and leave their immutable history intact. + if ((!enclave || enclave.status !== 'active') && usage.session_count === 0) continue; + if (!enclave || enclave.status !== 'active') return new Error('Market usage enclave is not active.'); + const ctxMeta = await this.priceCtxMetaForEnclave(enclave, usage.ctx_bracket, at, 'Market usage'); + if (ctxMeta instanceof Error) { + if (usage._activity_dormant) continue; + return ctxMeta; + } + if (ctxMeta && usage.ctx_bracket_table_ver !== undefined && usage.ctx_bracket_table_ver !== ctxMeta.ctx_bracket_table_ver) { + if (usage._activity_dormant) continue; + return new Error('Market usage context bracket table version is not active for the epoch.'); + } + const scheduleKey = this.priceScheduleKey(usage.enclave_id, ctxMeta?.ctx_bracket ?? null); + const schedule = this.priceScheduleAt(await this.priceSchedule(scheduleKey, enclave, ctxMeta), at); + const current = this.priceActiveEntry(schedule, at); + if (!current) return new Error('Market usage enclave has no admin price seed.'); + const seed = this.priceSeedEntry(current); + if (!seed || seed.set_by_role !== 'admin') return new Error('Market price requires an admin seed.'); + const modelRef = await this.get(`modelref/${enclave.model_id}`); + if (!modelRef) return new Error('Market price model reference not found.'); + const previousMarket = current.market ?? {}; + if (previousMarket.schema_version === 2 && previousMarket.epoch >= epoch) { + return new Error('Market activity epoch must increase exactly once per settled epoch.'); + } + const canonical = context.canonicalActivity?.get(marketKey) ?? + (context.includeDormant && usage.session_count === 0 ? { settled_usage: {} } : null); + const settledUsage = canonical?.settled_usage ?? null; + const calibration = modelRef.activity_calibration ?? null; + const calibrationError = calibration && this.validateActivityCalibration(calibration, modelRef.model_class, modelRef.rate_map); + if (calibrationError) return calibrationError; + const calibrationHash = calibration + ? await this.opaqueHash('mayhem-market-activity-calibration-v1', calibration) : null; + const work = calibration && settledUsage !== null + ? this.calibratedActivityWork(settledUsage, calibration) : null; + if (work instanceof Error) return work; + const activityRate = work === null ? null : (BigInt(work) / BigInt(epochSeconds)).toString(); + const vector = settledUsage === null ? null : this.marketActivityVector(settledUsage, epochSeconds); + // With no machine-readable calibration, compare each signed dimension only + // against its own history. No synthetic GPU capacity or monetary weights. + const activityBasis = work === null ? 'relative_dimension_vector_v1' : 'calibrated_work_v1'; + const initialized = previousMarket.schema_version === 2 && + previousMarket.activity_initialized === true && + previousMarket.calibration_hash === calibrationHash && + previousMarket.activity_basis === activityBasis && + previousMarket.epoch === epoch - 1; + const previousEma = initialized ? previousMarket.ema_activity_rate : activityRate; + const activeSupply = usage.provider_count; + if (canonical && canonical.session_count !== undefined && + (canonical.session_count !== usage.session_count || + canonical.demand_au !== usage.demand_au || + (canonical.provider_count ?? canonical.providers?.length) !== usage.provider_count)) { + return new Error('Market activity totals do not match canonical receipt evidence.'); + } + // Nonzero direction follows the previous epoch; empty epochs keep decaying. + // EMA is telemetry only; the bootstrap still holds for one epoch. + const rawMomentum = initialized && vector !== null + ? activityBasis === 'calibrated_work_v1' + ? this.marketActivityMomentum(activityRate, previousMarket.activity_rate, constants) + : this.marketVectorMomentum(vector, previousMarket.activity_vector, constants) + : 10_000; + const frozenReason = settledUsage === null ? 'missing_canonical_activity' + : !initialized ? 'activity_baseline_bootstrap' : null; + const frozen = frozenReason !== null; + const multiplierBps = frozen ? 10_000 : rawMomentum; + const activityInitialized = vector !== null; + const emaActivityRate = activityRate === null ? null : initialized + ? this.marketActivityEma(previousEma, activityRate, constants) : activityRate; + // Momentum moves the current price. The admin seed is provenance, not a dollar target. + const desiredRateMap = this.scaleRateMap(current.rate_map, multiplierBps); + const desiredPerReqAu = this.scalePriceTerm(current.per_req_au, multiplierBps); + const desiredMinSessionAu = this.scalePriceTerm(current.min_session_au, multiplierBps); + const nextTerms = { + rate_map: this.stepRateMap(current.rate_map, desiredRateMap, constants), + per_req_au: this.stepPriceTerm(current.per_req_au, desiredPerReqAu, constants), + min_session_au: this.stepPriceTerm(current.min_session_au, desiredMinSessionAu, constants), + }; + for (const term of Object.values(nextTerms)) if (term instanceof Error) return term; + for (const field of ['per_req_au', 'min_session_au']) { + const lower = BigInt(this.scalePriceTerm(seed[field], 2_500)); + const upper = BigInt(this.scalePriceTerm(seed[field], 40_000)); + const amount = BigInt(nextTerms[field]); + nextTerms[field] = (amount < lower ? lower : amount > upper ? upper : amount).toString(); + } + nextTerms.rate_map = this.clampRateMapBounds(nextTerms.rate_map, modelRef.rate_map, marketParams); + if (nextTerms.rate_map instanceof Error) return nextTerms.rate_map; + const record = { + enclave_id: current.enclave_id, model_id: current.model_id, denom: PRICE_DENOMINATION, + ver: (this.priceLatestEntry(schedule)?.ver ?? 0) + 1, + ...nextTerms, effective_at: at, effective_from: tx, updated_at: tx, + set_by: seed.set_by, set_by_role: 'admin', + ...(ctxMeta ? { ctx_bracket: ctxMeta.ctx_bracket, + ctx_bracket_table_ver: ctxMeta.ctx_bracket_table_ver } : {}), + price_source: frozen ? 'market_activity_hold' : 'market_activity_momentum', seed, + market: { + schema_version: 2, source: 'canonical_settled_work', epoch, epoch_seconds: epochSeconds, + active_supply: activeSupply, + // Gross AU remains accounting evidence only; it never enters the controller. + active_demand_au: usage.demand_au, session_count: usage.session_count, + settled_usage: settledUsage, calibration_hash: calibrationHash, + activity_basis: activityBasis, activity_vector: vector, + previous_activity_vector: initialized ? previousMarket.activity_vector : vector, + previous_activity_rate: initialized ? previousMarket.activity_rate : activityRate, + previous_ema_activity_vector: initialized ? previousMarket.ema_activity_vector : vector, + ema_activity_vector: vector === null ? null : initialized + ? this.marketVectorEma(previousMarket.ema_activity_vector, vector, constants) : vector, + calibration: cloneValue(calibration), modelref_ver: modelRef.ver ?? null, + calibrated_work_ps: work, activity_rate: activityRate, + previous_ema_activity_rate: previousEma, ema_activity_rate: emaActivityRate, + activity_initialized: activityInitialized, momentum_bps: rawMomentum, + multiplier_bps: multiplierBps, frozen, frozen_reason: frozenReason, constants, + desired_rate_map: desiredRateMap, desired_per_req_au: desiredPerReqAu, + desired_min_session_au: desiredMinSessionAu, + previous_price_ver: current.ver, previous_rate_map: cloneValue(current.rate_map), + previous_per_req_au: current.per_req_au, previous_min_session_au: current.min_session_au, + seed_price_ver: seed.ver, + }, + }; + updates.push({ + enclave_id: usage.enclave_id, + ...(ctxMeta ? { ctx_bracket: ctxMeta.ctx_bracket, + ctx_bracket_table_ver: ctxMeta.ctx_bracket_table_ver } : {}), + market_key: marketKey, ver: record.ver, rate_map: record.rate_map, + momentum_bps: rawMomentum, activity_rate: activityRate, ema_activity_rate: emaActivityRate, + active_supply: activeSupply, active_demand_au: usage.demand_au, frozen, + schedule_key: scheduleKey, schedule: { ...schedule, current: record }, + record_key: this.priceRecordKey(usage.enclave_id, record.ver, ctxMeta?.ctx_bracket ?? null), record, + }); + } + return updates; + } + + modelClassFor(value) { + if (!value || !hasOwn(value, 'model_class') || value.model_class === null || value.model_class === undefined) { + return undefined; + } + return value.model_class; + } + + validateModelClass(modelClass, label) { + if (typeof modelClass !== 'string' || modelClass.length === 0 || modelClass.length > 64) { + return new Error(`${label} must be a non-empty string.`); + } + if (!MODEL_CLASSES.has(modelClass)) return new Error(`Unsupported ${label}.`); + return null; + } + + validateLaunchEnclaveAttestationTier(attTier) { + if (attTier <= MAX_LAUNCH_ENCLAVE_ATTESTATION_TIER) return null; + return new Error( + `Enclave attestation tiers above ${MAX_LAUNCH_ENCLAVE_ATTESTATION_TIER} are not launch-advertisable; Tier 4 is provider KYB, not enclave hardware.` + ); + } + + async currentAppliedEpoch() { + const state = await this.get('epoch/apply/state'); + const epoch = state?.epoch ?? 0; + return Number.isSafeInteger(epoch) && epoch >= 0 ? epoch : 0; + } + + async enclaveMinTierPolicy(enclave, currentEpoch = null) { + const epoch = currentEpoch ?? await this.currentAppliedEpoch(); + const policy = await this.get(`tierpolicy/enclave/${enclave.enclave_id}`); + const baseMinTier = policy?.current_min_att_tier ?? enclave.min_att_tier ?? enclave.att_tier ?? 1; + const pending = policy?.pending ?? enclave.pending_min_att_tier ?? null; + if (pending && pending.effective_epoch <= epoch) { + return { + min_att_tier: pending.min_att_tier, + pending: null, + effective_epoch: pending.effective_epoch, + current_epoch: epoch, + }; + } + return { + min_att_tier: baseMinTier, + pending, + current_epoch: epoch, + }; + } + + validateRateMap(rateMap, modelClass, label, { allowZeroPrice = false } = {}) { + if (!Array.isArray(rateMap) || rateMap.length === 0 || rateMap.length > RATE_MAP_MAX_ENTRIES) { + return new Error(`${label} must be a non-empty array with at most ${RATE_MAP_MAX_ENTRIES} entries.`); + } + const validUnits = MODEL_CLASS_RATE_UNITS[modelClass]; + if (!validUnits) return new Error(`No rate units configured for model_class ${modelClass}.`); + const seen = new Set(); + for (const entry of rateMap) { + const shapeError = this.validateExactObjectKeys(entry, ['unit', 'per_unit_au', 'granularity'], `${label} entry`); + if (shapeError) return shapeError; + if (typeof entry.unit !== 'string' || entry.unit.length === 0 || entry.unit.length > 64) { + return new Error(`${label} unit must be a non-empty string.`); + } + if (!validUnits.has(entry.unit)) return new Error(`${label} unit ${entry.unit} is not allowed for model_class ${modelClass}.`); + if (seen.has(entry.unit)) return new Error(`${label} has duplicate unit ${entry.unit}.`); + seen.add(entry.unit); + const perUnitAu = this.normalizeAu(entry.per_unit_au, `${label} per_unit_au`, { allowZero: allowZeroPrice }); + if (perUnitAu instanceof Error || (!allowZeroPrice && this.isZeroAu(perUnitAu))) { + return new Error(`${label} per_unit_au must be ${allowZeroPrice ? 'a non-negative' : 'a positive'} integer.`); + } + if (!Number.isSafeInteger(entry.granularity) || entry.granularity <= 0) { + return new Error(`${label} granularity must be a positive integer.`); + } + } + return null; + } + + validateEnclaveModalityRateMap(enclave, rateMap) { + const modalityError = this.validateModalitySet( + enclave?.caps?.modality_set, + 'Enclave caps modality_set' + ); + if (modalityError) return modalityError; + const modelClass = this.modelClassFor(enclave); + const required = new Set(); + if (modelClass === DEFAULT_MODEL_CLASS) { + required.add('input_token'); + required.add('output_token'); + } else if (modelClass === 'embedding') { + required.add('input_token'); + } else if (modelClass === 'image-generation') { + required.add('image'); + required.add('step'); + } else if (modelClass === 'video-generation') { + required.add('video_second'); + required.add('frame'); + } else if (modelClass === 'tts' || modelClass === 'audio-generation' || modelClass === 'music-generation') { + required.add('input_character'); + required.add('audio_second'); + } else if (modelClass === 'stt') { + required.add('audio_second'); + } + const units = new Set(rateMap.map((entry) => entry.unit)); + for (const unit of required) { + if (!units.has(unit)) { + return new Error(`Enclave price rate_map is missing required modality unit ${unit}.`); + } + } + return null; + } + + normalizeRateMap(rateMap) { + return rateMap + .map((entry) => ({ + unit: entry.unit, + per_unit_au: this.normalizeAu(entry.per_unit_au), + granularity: entry.granularity, + })) + .sort((left, right) => compareCodepoint(left.unit, right.unit)); + } + + normalizeLockedRateMap(rateMap, label) { + if (!Array.isArray(rateMap) || rateMap.length === 0 || rateMap.length > RATE_MAP_MAX_ENTRIES) { + return new Error(`${label} must be a non-empty array with at most ${RATE_MAP_MAX_ENTRIES} entries.`); + } + const seen = new Set(); + const normalized = []; + for (const entry of rateMap) { + const shapeError = this.validateExactObjectKeys(entry, ['unit', 'per_unit_au', 'granularity'], `${label} entry`); + if (shapeError) return shapeError; + if (typeof entry.unit !== 'string' || entry.unit.length === 0 || entry.unit.length > 64) { + return new Error(`${label} unit must be a non-empty string.`); + } + const unit = this.canonicalUsageUnit(entry.unit); + if (!this.isSafeKeyPart(unit)) return new Error(`${label} unit is invalid.`); + if (seen.has(unit)) return new Error(`${label} has duplicate unit ${unit}.`); + seen.add(unit); + const perUnitAu = this.normalizeAu(entry.per_unit_au, `${label} per_unit_au`, { allowZero: false }); + if (perUnitAu instanceof Error) { + return new Error(`${label} per_unit_au must be a positive integer.`); + } + if (!Number.isSafeInteger(entry.granularity) || entry.granularity <= 0) { + return new Error(`${label} granularity must be a positive integer.`); + } + normalized.push({ + unit, + per_unit_au: perUnitAu, + granularity: entry.granularity, + }); + } + return normalized.sort((left, right) => compareCodepoint(left.unit, right.unit)); + } + + rateMapByUnit(rateMap) { + const byUnit = new Map(); + for (const entry of rateMap ?? []) byUnit.set(entry.unit, entry); + return byUnit; + } + + validateRateMapBounds(priceRateMap, referenceRateMap, params) { + const priceByUnit = this.rateMapByUnit(priceRateMap); + const referenceByUnit = this.rateMapByUnit(referenceRateMap); + if (priceByUnit.size !== referenceByUnit.size) { + return new Error('Price rate_map units must match model reference rate_map units.'); + } + for (const [unit, ref] of referenceByUnit.entries()) { + const price = priceByUnit.get(unit); + if (!price) return new Error(`Price rate_map is missing model reference unit ${unit}.`); + if (!this.rateWithinBounds(price, ref, params)) { + return new Error(`Price rate_map unit ${unit} outside model reference bounds.`); + } + } + return null; + } + + rateWithinBounds(price, ref, params = { + price_min_bps: PARAM_DEFINITIONS.price_min_bps.default, + price_max_bps: PARAM_DEFINITIONS.price_max_bps.default, + }) { + const refPerUnit = this.parseAu(ref?.per_unit_au, 'reference rate per_unit_au', { allowZero: false }); + const pricePerUnit = this.parseAu(price?.per_unit_au, 'price rate per_unit_au'); + if ( + refPerUnit instanceof Error || + !Number.isSafeInteger(ref?.granularity) || + ref.granularity <= 0 || + pricePerUnit instanceof Error || + !Number.isSafeInteger(price?.granularity) || + price.granularity <= 0 + ) { + return false; + } + const priceScaled = pricePerUnit * BigInt(ref.granularity) * 10_000n; + const refScaled = refPerUnit * BigInt(price.granularity); + return ( + priceScaled >= refScaled * BigInt(Math.max(2_500, params.price_min_bps)) && + priceScaled <= refScaled * BigInt(Math.min(40_000, params.price_max_bps)) + ); + } + + validateReputationEvent(value) { + if (!this.isSafeKeyPart(value.event_id)) return new Error('Invalid reputation event id.'); + if (!REPUTATION_EVENT_KINDS.has(value.kind)) return new Error('Unsupported reputation event kind.'); + if ( + (value.kind === 'session_ok' || value.kind === 'session_partial') && + this.normalizeAu(value.paid_au, 'reputation paid amount') instanceof Error + ) { + return new Error('Reputation event requires paid_au.'); + } + if ( + value.kind === 'session_fail' && + this.normalizeAu(value.max_spend_au, 'reputation max spend') instanceof Error + ) { + return new Error('Reputation event requires max_spend_au.'); + } + return null; + } + + validateReputationAnchor(value) { + if (!value || typeof value !== 'object' || Array.isArray(value)) { + return new Error('Invalid reputation anchor payload.'); + } + if (value.op !== 'anchor_reputation') return new Error('Invalid reputation anchor op.'); + if (!this.isSafeKeyPart(value.provider)) return new Error('Invalid reputation provider.'); + if (!Number.isSafeInteger(value.epoch) || value.epoch < 0) { + return new Error('Invalid reputation anchor epoch.'); + } + if (!Number.isSafeInteger(value.folded_at) || value.folded_at < 0) { + return new Error('Invalid reputation anchor folded_at.'); + } + if (!this.isHexBytes(value.events_head, 32)) return new Error('Invalid reputation events head.'); + if (!Number.isSafeInteger(value.r_bps) || value.r_bps < 0 || value.r_bps > 10_000) { + return new Error('Invalid reputation r_bps.'); + } + if (!Number.isSafeInteger(value.raw_milli)) return new Error('Invalid reputation raw_milli.'); + if (!Number.isSafeInteger(value.successful_sessions) || value.successful_sessions < 0) { + return new Error('Invalid reputation successful_sessions.'); + } + if ( + value.provenance_violation !== undefined && + typeof value.provenance_violation !== 'boolean' + ) { + return new Error('Invalid reputation provenance_violation.'); + } + return null; + } + + validateProbeResult(value) { + if (!this.isSafeKeyPart(value.probe_id)) return new Error('Invalid probe id.'); + if (!PROBE_KINDS.has(value.probe_kind)) return new Error('Unsupported probe kind.'); + if (value.probe_kind === 'canary') { + if (!value.enclave_id) return new Error('Canary probe requires enclave_id.'); + if (!value.binary_hash) return new Error('Canary probe requires binary_hash.'); + if (!Number.isInteger(value.match_bps)) return new Error('Canary probe requires match_bps.'); + if (typeof value.pass !== 'boolean') return new Error('Canary probe requires pass.'); + if (!value.canary_set) return new Error('Canary probe requires canary_set.'); + if (!value.canary_prompt_id) return new Error('Canary probe requires canary_prompt_id.'); + if (value.challenge_epoch === undefined) return new Error('Canary probe requires challenge_epoch.'); + if (!value.challenge_apply_hash) return new Error('Canary probe requires challenge_apply_hash.'); + if (!value.challenge_seed) return new Error('Canary probe requires challenge_seed.'); + if (!value.verification_method) return new Error('Canary probe requires verification_method.'); + if (!PROBE_VERIFICATION_METHODS.has(value.verification_method)) { + return new Error('Unsupported canary verification_method.'); + } + if (!value.session_receipt_hash) return new Error('Canary probe requires session_receipt_hash.'); + if (!value.evidence_hash) return new Error('Canary probe requires evidence_hash.'); + if (!value.auditor_sig) return new Error('Canary probe requires auditor_sig.'); + if (!this.isSafeKeyPart(value.enclave_id)) return new Error('Invalid canary enclave id.'); + if (!this.isHexBytes(value.binary_hash, 32)) return new Error('Invalid canary binary hash.'); + if (!this.isHexBytes(value.session_receipt_hash, 32)) { + return new Error('Invalid canary session receipt hash.'); + } + if (!this.isHexBytes(value.evidence_hash, 32)) return new Error('Invalid canary evidence hash.'); + if (!this.isHexBytes(value.auditor_sig, 64)) return new Error('Invalid canary auditor signature.'); + if (!this.isSafeKeyPart(value.canary_prompt_id)) return new Error('Invalid canary prompt id.'); + if (!Number.isSafeInteger(value.challenge_epoch) || value.challenge_epoch < 0) { + return new Error('Invalid canary challenge epoch.'); + } + if (!this.isHexBytes(value.challenge_apply_hash, 32)) { + return new Error('Invalid canary challenge apply hash.'); + } + if (!this.isHexBytes(value.challenge_seed, 32)) return new Error('Invalid canary challenge seed.'); + } + return null; + } + + async normalizeSpendVoucherForReserve(voucher) { + const voucherFields = [ + 'schema_version', + 'session_id', + 'billing_id', + 'billing_attempt', + 'billing_prior_usage', + 'billing_prior_au_owed_cum', + 'billing_epoch', + 'reservation_id', + 'reservation_expires_after_epoch', + 'reservation_receipt_grace_epochs', + 'user', + 'provider', + 'payout_revision', + 'rail', + 'enclave_id', + 'model_id', + 'price_ver', + 'locked_rate_map', + 'locked_per_req_au', + 'locked_min_session_au', + 'served_ctx', + 'required_modalities', + 'ctx_bracket', + 'ctx_bracket_table_ver', + 'rules_ver', + 'max_spend_au', + 'checkpoint_every', + 'user_sig', + ]; + if (hasOwn(voucher, 'required_specialities')) voucherFields.push('required_specialities'); + if (hasOwn(voucher, 'workflow')) voucherFields.push('workflow'); + const shapeError = this.validateExactObjectKeys( + voucher, + voucherFields, + 'spend voucher' + ); + if (shapeError) return shapeError; + if (voucher.schema_version !== SPEND_VOUCHER_SCHEMA_VERSION) { + return new Error('Unsupported spend voucher schema version.'); + } + const checkpointError = this.validateExactObjectKeys( + voucher.checkpoint_every, + ['tokens', 'ms'], + 'spend voucher checkpoint policy' + ); + if (checkpointError) return checkpointError; + const rail = this.normalizeLedgerRail(voucher.rail, 'spend voucher rail'); + if (rail instanceof Error) return rail; + if (!this.isHexBytes(voucher.session_id, 32)) return new Error('Invalid spend voucher session id.'); + if (!this.isHexBytes(voucher.billing_id, 32)) return new Error('Invalid spend voucher billing id.'); + if (!Number.isSafeInteger(voucher.billing_attempt) || voucher.billing_attempt < 0) { + return new Error('Invalid spend voucher billing attempt.'); + } + if (!Number.isSafeInteger(voucher.billing_epoch) || voucher.billing_epoch < 1) { + return new Error('Invalid spend voucher billing epoch.'); + } + if (!this.isHexBytes(voucher.reservation_id, 32)) { + return new Error('Invalid spend voucher reservation id.'); + } + if (!Number.isSafeInteger(voucher.reservation_expires_after_epoch) || + voucher.reservation_expires_after_epoch <= voucher.billing_epoch || + !Number.isSafeInteger(voucher.reservation_receipt_grace_epochs) || + voucher.reservation_receipt_grace_epochs < 0) { + return new Error('Invalid spend voucher reservation expiry policy.'); + } + if (!this.isHexBytes(voucher.user, 32)) return new Error('Invalid spend voucher user.'); + if (!this.isHexBytes(voucher.provider, 32)) return new Error('Invalid spend voucher provider.'); + if (!this.isHexBytes(voucher.payout_revision, 32)) { + return new Error('Invalid spend voucher payout revision.'); + } + const billingPriorUsage = this.normalizeReceiptUsage(voucher.billing_prior_usage); + if (billingPriorUsage instanceof Error) return billingPriorUsage; + if (stableJson(billingPriorUsage) !== stableJson(voucher.billing_prior_usage)) { + return new Error('Spend voucher billing prior usage must be canonical.'); + } + const billingPriorAuOwedCum = this.normalizeAu( + voucher.billing_prior_au_owed_cum, + 'spend voucher billing prior cumulative amount' + ); + if (billingPriorAuOwedCum instanceof Error) { + return new Error('Invalid spend voucher billing prior cumulative amount.'); + } + if ( + voucher.billing_attempt === 0 && + (Object.keys(billingPriorUsage).length > 0 || !this.isZeroAu(billingPriorAuOwedCum)) + ) { + return new Error('Initial spend voucher billing attempt must have an empty baseline.'); + } + if (this.isZeroAu(billingPriorAuOwedCum) && Object.keys(billingPriorUsage).length > 0) { + return new Error('Spend voucher billing prior usage requires a prior cumulative amount.'); + } + if (!this.isHexBytes(voucher.enclave_id, 32)) return new Error('Invalid spend voucher enclave id.'); + if (typeof voucher.model_id !== 'string' || + voucher.model_id.length === 0 || + voucher.model_id.length > 256) { + return new Error('Invalid spend voucher model id.'); + } + if (!Number.isSafeInteger(voucher.price_ver) || voucher.price_ver < 1) { + return new Error('Invalid spend voucher price version.'); + } + if (!Number.isSafeInteger(voucher.rules_ver) || voucher.rules_ver < 1) { + return new Error('Invalid spend voucher rules version.'); + } + const lockedRateMap = this.normalizeLockedRateMap(voucher.locked_rate_map, 'spend voucher locked_rate_map'); + if (lockedRateMap instanceof Error) return lockedRateMap; + const lockedPerReqAu = this.normalizeAu(voucher.locked_per_req_au, 'spend voucher locked per-request price'); + if (lockedPerReqAu instanceof Error) { + return new Error('Invalid spend voucher locked per-request price.'); + } + const lockedMinSessionAu = this.normalizeAu(voucher.locked_min_session_au, 'spend voucher locked minimum session price'); + if (lockedMinSessionAu instanceof Error) { + return new Error('Invalid spend voucher locked minimum session price.'); + } + if (!Number.isSafeInteger(voucher.served_ctx) || voucher.served_ctx < 0) { + return new Error('Invalid spend voucher served context.'); + } + const modalityError = this.validateModalitySet( + voucher.required_modalities, + 'spend voucher required_modalities' + ); + if (modalityError) return modalityError; + const requiredSpecialities = voucher.required_specialities ?? {}; + const specialitiesError = this.validateSpecialitySelection( + requiredSpecialities, + 'spend voucher required_specialities' + ); + if (specialitiesError) return specialitiesError; + const normalizedSpecialities = Object.fromEntries( + Object.entries(requiredSpecialities).sort(([left], [right]) => compareCodepoint(left, right)) + ); + let workflow = null; + if (hasOwn(voucher, 'workflow')) { + workflow = this.normalizeWorkflowBinding( + voucher.workflow, + 'spend voucher workflow', + lockedRateMap + ); + if (workflow instanceof Error) return workflow; + if (stableJson(workflow) !== stableJson(canonicalWorkflowBinding(voucher.workflow))) { + return new Error('Spend voucher workflow must be canonical.'); + } + } + const table = voucher.ctx_bracket_table_ver === null || voucher.ctx_bracket_table_ver === undefined + ? null + : await this.ctxBracketTableByVersion(voucher.ctx_bracket_table_ver); + if (table instanceof Error) return table; + const ctxMeta = await this.normalizeCtxBracketEvidenceForEnclave( + voucher.enclave_id.toLowerCase(), + voucher.served_ctx, + voucher.ctx_bracket, + voucher.ctx_bracket_table_ver, + table, + 'spend voucher' + ); + if (ctxMeta instanceof Error) return ctxMeta; + const maxSpendAu = this.normalizeAu(voucher.max_spend_au, 'spend voucher max spend', { allowZero: false }); + if (maxSpendAu instanceof Error) { + return new Error('Invalid spend voucher max spend.'); + } + if (!Number.isSafeInteger(voucher.checkpoint_every.tokens) || voucher.checkpoint_every.tokens < 1) { + return new Error('Invalid spend voucher checkpoint tokens.'); + } + if (!Number.isSafeInteger(voucher.checkpoint_every.ms) || voucher.checkpoint_every.ms < 1) { + return new Error('Invalid spend voucher checkpoint milliseconds.'); + } + if (!this.isHexBytes(voucher.user_sig, 64)) return new Error('Invalid spend voucher user signature.'); + const body = { + schema_version: SPEND_VOUCHER_SCHEMA_VERSION, + session_id: voucher.session_id.toLowerCase(), + billing_id: voucher.billing_id.toLowerCase(), + billing_attempt: voucher.billing_attempt, + billing_prior_usage: billingPriorUsage, + billing_prior_au_owed_cum: billingPriorAuOwedCum, + billing_epoch: voucher.billing_epoch, + reservation_id: voucher.reservation_id.toLowerCase(), + reservation_expires_after_epoch: voucher.reservation_expires_after_epoch, + reservation_receipt_grace_epochs: voucher.reservation_receipt_grace_epochs, + user: voucher.user.toLowerCase(), + provider: voucher.provider.toLowerCase(), + payout_revision: voucher.payout_revision.toLowerCase(), + rail, + enclave_id: voucher.enclave_id.toLowerCase(), + model_id: voucher.model_id, + price_ver: voucher.price_ver, + locked_rate_map: lockedRateMap, + locked_per_req_au: lockedPerReqAu, + locked_min_session_au: lockedMinSessionAu, + served_ctx: voucher.served_ctx, + required_modalities: voucher.required_modalities.slice(), + required_specialities: normalizedSpecialities, + ...(workflow ? { workflow } : {}), + ctx_bracket: ctxMeta.ctx_bracket, + ctx_bracket_table_ver: ctxMeta.ctx_bracket_table_ver, + rules_ver: voucher.rules_ver, + max_spend_au: maxSpendAu, + checkpoint_every: { + tokens: voucher.checkpoint_every.tokens, + ms: voucher.checkpoint_every.ms, + }, + }; + return { + ...body, + user_sig: voucher.user_sig.toLowerCase(), + body, + }; + } + + async normalizeTargetedSpendReserveValue(value) { + const reserveFields = [ + 'op', + 'payout_revision', + 'contract_version', + 'session_id', + 'reservation_id', + 'reservation_expires_after_epoch', + 'reservation_receipt_grace_epochs', + 'epoch', + 'at', + 'rail', + 'user', + 'provider', + 'enclave_id', + 'enclave_pubkey', + 'model_id', + 'price_ver', + 'rules_ver', + 'served_ctx', + 'required_modalities', + 'ctx_bracket', + 'ctx_bracket_table_ver', + 'max_spend_au', + 'voucher', + 'provider_sig', + ]; + if (hasOwn(value, 'required_specialities')) reserveFields.push('required_specialities'); + if (hasOwn(value, 'workflow')) reserveFields.push('workflow'); + const shapeError = this.validateExactObjectKeys( + value, + reserveFields, + 'spend reservation feature' + ); + if (shapeError) return shapeError; + if (value.op !== 'spend_reserve_targeted') return new Error('Invalid targeted spend reservation op.'); + if (!this.isHexBytes(value.payout_revision, 32) || + value.payout_revision !== value.payout_revision.toLowerCase()) { + return new Error('Invalid targeted spend reservation payout revision.'); + } + if (value.contract_version !== CONTRACT_VERSION) return new Error('Invalid spend reservation contract version.'); + if (!this.isHexBytes(value.session_id, 32)) return new Error('Invalid spend reservation session id.'); + if (!this.isHexBytes(value.reservation_id, 32) || + value.reservation_id !== value.reservation_id.toLowerCase()) { + return new Error('Invalid spend reservation reservation id.'); + } + if (!Number.isSafeInteger(value.reservation_expires_after_epoch) || + value.reservation_expires_after_epoch <= value.epoch || + !Number.isSafeInteger(value.reservation_receipt_grace_epochs) || + value.reservation_receipt_grace_epochs < 0) { + return new Error('Invalid spend reservation expiry policy.'); + } + if (!Number.isSafeInteger(value.epoch) || value.epoch < 1) return new Error('Invalid spend reservation epoch.'); + if (!Number.isSafeInteger(value.at) || value.at < 0) return new Error('Invalid spend reservation timestamp.'); + const rail = this.normalizeLedgerRail(value.rail, 'spend reservation rail'); + if (rail instanceof Error) return rail; + if (!this.isHexBytes(value.user, 32)) return new Error('Invalid spend reservation user.'); + if (!this.isHexBytes(value.provider, 32)) return new Error('Invalid spend reservation provider.'); + if (!this.isHexBytes(value.enclave_id, 32)) return new Error('Invalid spend reservation enclave.'); + if (!this.isHexBytes(value.enclave_pubkey, 32)) { + return new Error('Invalid spend reservation enclave public key.'); + } + if (typeof value.model_id !== 'string' || + value.model_id.length === 0 || + value.model_id.length > 256) { + return new Error('Invalid spend reservation model id.'); + } + if (!Number.isSafeInteger(value.price_ver) || value.price_ver < 1) { + return new Error('Invalid spend reservation price version.'); + } + if (!Number.isSafeInteger(value.rules_ver) || value.rules_ver < 1) { + return new Error('Invalid spend reservation rules version.'); + } + if (!Number.isSafeInteger(value.served_ctx) || value.served_ctx < 0) { + return new Error('Invalid spend reservation served context.'); + } + const modalityError = this.validateModalitySet( + value.required_modalities, + 'spend reservation required_modalities' + ); + if (modalityError) return modalityError; + const requiredSpecialities = value.required_specialities ?? {}; + const specialitiesError = this.validateSpecialitySelection( + requiredSpecialities, + 'spend reservation required_specialities' + ); + if (specialitiesError) return specialitiesError; + const normalizedSpecialities = Object.fromEntries( + Object.entries(requiredSpecialities).sort(([left], [right]) => compareCodepoint(left, right)) + ); + const activeTable = value.ctx_bracket_table_ver === null || value.ctx_bracket_table_ver === undefined + ? null + : await this.ctxBracketTableAt(value.at); + if (activeTable instanceof Error) return activeTable; + if (activeTable && value.ctx_bracket_table_ver !== activeTable.ver) { + return new Error('Spend reservation context bracket table is not active.'); + } + const ctxMeta = await this.normalizeCtxBracketEvidenceForEnclave( + value.enclave_id.toLowerCase(), + value.served_ctx, + value.ctx_bracket, + value.ctx_bracket_table_ver, + activeTable, + 'spend reservation' + ); + if (ctxMeta instanceof Error) return ctxMeta; + const maxSpendAu = this.normalizeAu(value.max_spend_au, 'spend reservation max spend', { allowZero: false }); + if (maxSpendAu instanceof Error) { + return new Error('Invalid spend reservation max spend.'); + } + if (!this.isHexBytes(value.provider_sig, 64)) { + return new Error('Invalid spend reservation provider signature.'); + } + const voucher = await this.normalizeSpendVoucherForReserve(value.voucher); + if (voucher instanceof Error) return voucher; + if (voucher.session_id !== value.session_id.toLowerCase()) { + return new Error('Spend reservation voucher session mismatch.'); + } + if (voucher.reservation_id !== value.reservation_id.toLowerCase()) { + return new Error('Spend reservation voucher reservation mismatch.'); + } + if (voucher.reservation_expires_after_epoch !== value.reservation_expires_after_epoch || + voucher.reservation_receipt_grace_epochs !== value.reservation_receipt_grace_epochs) { + return new Error('Spend reservation voucher expiry policy mismatch.'); + } + if (voucher.billing_epoch !== value.epoch) { + return new Error('Spend reservation voucher billing epoch mismatch.'); + } + if (voucher.user !== value.user.toLowerCase()) { + return new Error('Spend reservation voucher user mismatch.'); + } + if (voucher.provider !== value.provider.toLowerCase()) { + return new Error('Spend reservation voucher provider mismatch.'); + } + if (voucher.payout_revision !== value.payout_revision.toLowerCase()) { + return new Error('Spend reservation voucher payout revision mismatch.'); + } + if (voucher.rail !== rail) return new Error('Spend reservation voucher rail mismatch.'); + if (voucher.enclave_id !== value.enclave_id.toLowerCase()) { + return new Error('Spend reservation voucher enclave mismatch.'); + } + if (voucher.model_id !== value.model_id) { + return new Error('Spend reservation voucher model mismatch.'); + } + if (voucher.price_ver !== value.price_ver) { + return new Error('Spend reservation voucher price mismatch.'); + } + if (voucher.body.served_ctx !== value.served_ctx) { + return new Error('Spend reservation voucher served context mismatch.'); + } + if (stableJson(voucher.body.required_modalities) !== stableJson(value.required_modalities)) { + return new Error('Spend reservation voucher required modalities mismatch.'); + } + if (stableJson(voucher.body.required_specialities) !== stableJson(normalizedSpecialities)) { + return new Error('Spend reservation voucher required specialities mismatch.'); + } + let workflow = null; + if (hasOwn(value, 'workflow')) { + workflow = this.normalizeWorkflowBinding( + value.workflow, + 'spend reservation workflow', + voucher.body.locked_rate_map + ); + if (workflow instanceof Error) return workflow; + if (stableJson(workflow) !== stableJson(value.workflow)) { + return new Error('Spend reservation workflow must be canonical.'); + } + } + if (stableJson(voucher.body.workflow ?? null) !== stableJson(workflow)) { + return new Error('Spend reservation voucher workflow mismatch.'); + } + if (voucher.body.ctx_bracket !== value.ctx_bracket) { + return new Error('Spend reservation voucher context bracket mismatch.'); + } + if (voucher.body.ctx_bracket_table_ver !== value.ctx_bracket_table_ver) { + return new Error('Spend reservation voucher context bracket table version mismatch.'); + } + if (voucher.rules_ver !== value.rules_ver) { + return new Error('Spend reservation voucher rules version mismatch.'); + } + if (this.compareAu(voucher.max_spend_au, maxSpendAu) !== 0) { + return new Error('Spend reservation voucher max spend mismatch.'); + } + const reservationParams = await this.activeParamsAt(value.at, [ + 'reservation_max_lifetime_epochs', + 'reservation_receipt_grace_epochs', + ]); + if ( + value.epoch > Number.MAX_SAFE_INTEGER - reservationParams.reservation_max_lifetime_epochs || + value.reservation_expires_after_epoch !== + value.epoch + reservationParams.reservation_max_lifetime_epochs || + value.reservation_receipt_grace_epochs !== + reservationParams.reservation_receipt_grace_epochs + ) { + return new Error('Spend reservation expiry policy does not match active parameters.'); + } + return { + op: 'spend_reserve_targeted', + payout_revision: value.payout_revision, + contract_version: CONTRACT_VERSION, + session_id: value.session_id.toLowerCase(), + reservation_id: value.reservation_id.toLowerCase(), + reservation_expires_after_epoch: value.reservation_expires_after_epoch, + reservation_receipt_grace_epochs: value.reservation_receipt_grace_epochs, + epoch: value.epoch, + at: value.at, + rail, + user: value.user.toLowerCase(), + provider: value.provider.toLowerCase(), + enclave_id: value.enclave_id.toLowerCase(), + enclave_pubkey: value.enclave_pubkey.toLowerCase(), + model_id: value.model_id, + price_ver: value.price_ver, + locked_rate_map: voucher.body.locked_rate_map, + locked_per_req_au: voucher.body.locked_per_req_au, + locked_min_session_au: voucher.body.locked_min_session_au, + served_ctx: value.served_ctx, + required_modalities: value.required_modalities.slice(), + required_specialities: normalizedSpecialities, + ...(workflow ? { workflow } : {}), + ctx_bracket: ctxMeta.ctx_bracket, + ctx_bracket_table_ver: ctxMeta.ctx_bracket_table_ver, + rules_ver: value.rules_ver, + max_spend_au: maxSpendAu, + voucher: { ...cloneValue(voucher.body), user_sig: voucher.user_sig }, + voucher_body: voucher.body, + provider_sig: value.provider_sig.toLowerCase(), + }; + } + + + + async normalizeSpendReserveValue(value) { + if (CONTRACT_VERSION >= 17) { + return new Error('Aggregate spend reservations are disabled; use spend_reserve_targeted.'); + } + const reserveFields = [ + 'op', + 'contract_version', + 'session_id', + 'epoch', + 'at', + 'rail', + 'user', + 'provider', + 'enclave_id', + 'price_ver', + 'rules_ver', + 'served_ctx', + 'required_modalities', + 'ctx_bracket', + 'ctx_bracket_table_ver', + 'max_spend_au', + 'voucher', + 'provider_sig', + ]; + if (hasOwn(value, 'required_specialities')) reserveFields.push('required_specialities'); + const shapeError = this.validateExactObjectKeys( + value, + reserveFields, + 'spend reservation feature' + ); + if (shapeError) return shapeError; + if (value.op !== 'spend_reserve') return new Error('Invalid spend reservation op.'); + if (value.contract_version !== CONTRACT_VERSION) return new Error('Invalid spend reservation contract version.'); + if (!this.isHexBytes(value.session_id, 32)) return new Error('Invalid spend reservation session id.'); + if (!Number.isSafeInteger(value.epoch) || value.epoch < 1) return new Error('Invalid spend reservation epoch.'); + if (!Number.isSafeInteger(value.at) || value.at < 0) return new Error('Invalid spend reservation timestamp.'); + const rail = this.normalizeLedgerRail(value.rail, 'spend reservation rail'); + if (rail instanceof Error) return rail; + if (!this.isHexBytes(value.user, 32)) return new Error('Invalid spend reservation user.'); + if (!this.isHexBytes(value.provider, 32)) return new Error('Invalid spend reservation provider.'); + if (!this.isHexBytes(value.enclave_id, 32)) return new Error('Invalid spend reservation enclave.'); + if (!Number.isSafeInteger(value.price_ver) || value.price_ver < 1) { + return new Error('Invalid spend reservation price version.'); + } + if (!Number.isSafeInteger(value.rules_ver) || value.rules_ver < 1) { + return new Error('Invalid spend reservation rules version.'); + } + if (!Number.isSafeInteger(value.served_ctx) || value.served_ctx < 0) { + return new Error('Invalid spend reservation served context.'); + } + const modalityError = this.validateModalitySet( + value.required_modalities, + 'spend reservation required_modalities' + ); + if (modalityError) return modalityError; + const requiredSpecialities = value.required_specialities ?? {}; + const specialitiesError = this.validateSpecialitySelection( + requiredSpecialities, + 'spend reservation required_specialities' + ); + if (specialitiesError) return specialitiesError; + const normalizedSpecialities = Object.fromEntries( + Object.entries(requiredSpecialities).sort(([left], [right]) => compareCodepoint(left, right)) + ); + const activeTable = value.ctx_bracket_table_ver === null || value.ctx_bracket_table_ver === undefined + ? null + : await this.ctxBracketTableAt(value.at); + if (activeTable instanceof Error) return activeTable; + if (activeTable && value.ctx_bracket_table_ver !== activeTable.ver) { + return new Error('Spend reservation context bracket table is not active.'); + } + const ctxMeta = await this.normalizeCtxBracketEvidenceForEnclave( + value.enclave_id.toLowerCase(), + value.served_ctx, + value.ctx_bracket, + value.ctx_bracket_table_ver, + activeTable, + 'spend reservation' + ); + if (ctxMeta instanceof Error) return ctxMeta; + const maxSpendAu = this.normalizeAu(value.max_spend_au, 'spend reservation max spend', { allowZero: false }); + if (maxSpendAu instanceof Error) { + return new Error('Invalid spend reservation max spend.'); + } + if (!this.isHexBytes(value.provider_sig, 64)) { + return new Error('Invalid spend reservation provider signature.'); + } + const voucher = await this.normalizeSpendVoucherForReserve(value.voucher); + if (voucher instanceof Error) return voucher; + if (voucher.session_id !== value.session_id.toLowerCase()) { + return new Error('Spend reservation voucher session mismatch.'); + } + if (voucher.rail !== rail) return new Error('Spend reservation voucher rail mismatch.'); + if (voucher.enclave_id !== value.enclave_id.toLowerCase()) { + return new Error('Spend reservation voucher enclave mismatch.'); + } + if (voucher.price_ver !== value.price_ver) { + return new Error('Spend reservation voucher price mismatch.'); + } + if (voucher.body.served_ctx !== value.served_ctx) { + return new Error('Spend reservation voucher served context mismatch.'); + } + if (stableJson(voucher.body.required_modalities) !== stableJson(value.required_modalities)) { + return new Error('Spend reservation voucher required modalities mismatch.'); + } + if (stableJson(voucher.body.required_specialities) !== stableJson(normalizedSpecialities)) { + return new Error('Spend reservation voucher required specialities mismatch.'); + } + let workflow = null; + if (hasOwn(value, 'workflow')) { + workflow = this.normalizeWorkflowBinding( + value.workflow, + 'spend reservation workflow', + voucher.body.locked_rate_map + ); + if (workflow instanceof Error) return workflow; + if (stableJson(workflow) !== stableJson(value.workflow)) { + return new Error('Spend reservation workflow must be canonical.'); + } + } + if (stableJson(voucher.body.workflow ?? null) !== stableJson(workflow)) { + return new Error('Spend reservation voucher workflow mismatch.'); + } + if (voucher.body.ctx_bracket !== value.ctx_bracket) { + return new Error('Spend reservation voucher context bracket mismatch.'); + } + if (voucher.body.ctx_bracket_table_ver !== value.ctx_bracket_table_ver) { + return new Error('Spend reservation voucher context bracket table version mismatch.'); + } + if (this.compareAu(voucher.max_spend_au, maxSpendAu) !== 0) { + return new Error('Spend reservation voucher max spend mismatch.'); + } + return { + op: 'spend_reserve', + contract_version: CONTRACT_VERSION, + session_id: value.session_id.toLowerCase(), + epoch: value.epoch, + at: value.at, + rail, + user: value.user.toLowerCase(), + provider: value.provider.toLowerCase(), + enclave_id: value.enclave_id.toLowerCase(), + price_ver: value.price_ver, + locked_rate_map: voucher.body.locked_rate_map, + locked_per_req_au: voucher.body.locked_per_req_au, + locked_min_session_au: voucher.body.locked_min_session_au, + served_ctx: value.served_ctx, + required_modalities: value.required_modalities.slice(), + required_specialities: normalizedSpecialities, + ...(workflow ? { workflow } : {}), + ctx_bracket: ctxMeta.ctx_bracket, + ctx_bracket_table_ver: ctxMeta.ctx_bracket_table_ver, + rules_ver: value.rules_ver, + max_spend_au: maxSpendAu, + voucher: { + session_id: voucher.body.session_id, + billing_id: voucher.body.billing_id, + billing_attempt: voucher.body.billing_attempt, + billing_prior_usage: voucher.body.billing_prior_usage, + billing_prior_au_owed_cum: voucher.body.billing_prior_au_owed_cum, + rail: voucher.body.rail, + enclave_id: voucher.body.enclave_id, + price_ver: voucher.body.price_ver, + locked_rate_map: voucher.body.locked_rate_map, + locked_per_req_au: voucher.body.locked_per_req_au, + locked_min_session_au: voucher.body.locked_min_session_au, + served_ctx: voucher.body.served_ctx, + required_modalities: voucher.body.required_modalities, + required_specialities: voucher.body.required_specialities, + ...(voucher.body.workflow ? { workflow: voucher.body.workflow } : {}), + ctx_bracket: voucher.body.ctx_bracket, + ctx_bracket_table_ver: voucher.body.ctx_bracket_table_ver, + max_spend_au: voucher.body.max_spend_au, + checkpoint_every: voucher.body.checkpoint_every, + user_sig: voucher.user_sig, + }, + voucher_body: voucher.body, + provider_sig: value.provider_sig.toLowerCase(), + }; + } + + billingReservationIdentity(normalized) { + return { + billing_id: normalized.voucher_body.billing_id, + billing_attempt: normalized.voucher_body.billing_attempt, + billing_epoch: normalized.epoch, + reservation_id: normalized.voucher_body.reservation_id, + reservation_expires_after_epoch: normalized.reservation_expires_after_epoch, + reservation_receipt_grace_epochs: normalized.reservation_receipt_grace_epochs, + session_id: normalized.session_id, + user: normalized.user, + provider: normalized.provider, + rail: normalized.rail, + payout_revision: normalized.payout_revision, + }; + } + + async validateExistingBillingReservation(normalized) { + const identity = this.billingReservationIdentity(normalized); + const anchor = await this.get(this.receiptBillingKey(identity.billing_id)); + if (!anchor || + anchor.type !== 'receipt_billing_anchor' || + anchor.billing_id !== identity.billing_id || + anchor.user !== identity.user || + anchor.rail !== identity.rail || + anchor.epoch !== identity.billing_epoch || + !Number.isSafeInteger(anchor.latest_attempt) || + anchor.latest_attempt < identity.billing_attempt) { + return new Error('Billing reservation anchor is missing or inconsistent.'); + } + const reservation = await this.get(this.receiptReservationKey(identity.reservation_id)); + if (!reservation || + reservation.type !== 'receipt_reservation_identity' || + reservation.status !== 'active' || + stableJson({ + billing_id: reservation.billing_id, + billing_attempt: reservation.billing_attempt, + billing_epoch: reservation.billing_epoch, + reservation_id: reservation.reservation_id, + reservation_expires_after_epoch: reservation.reservation_expires_after_epoch, + reservation_receipt_grace_epochs: reservation.reservation_receipt_grace_epochs, + session_id: reservation.session_id, + user: reservation.user, + provider: reservation.provider, + rail: reservation.rail, + payout_revision: reservation.payout_revision, + }) !== stableJson(identity)) { + return new Error('Reservation identity is missing or inconsistent.'); + } + return null; + } + + async prepareBillingReservation(normalized, key) { + const identity = this.billingReservationIdentity(normalized); + const anchorKey = this.receiptBillingKey(identity.billing_id); + const existingAnchor = await this.get(anchorKey); + let nextAnchor; + if (existingAnchor === null) { + if (identity.billing_attempt !== 0) { + return new Error('Initial billing reservation attempt must be zero.'); + } + nextAnchor = { + type: 'receipt_billing_anchor', + billing_id: identity.billing_id, + user: identity.user, + rail: identity.rail, + epoch: identity.billing_epoch, + latest_attempt: 0, + created_at: key, + updated_at: key, + }; + } else { + if (existingAnchor.type !== 'receipt_billing_anchor' || + existingAnchor.billing_id !== identity.billing_id || + existingAnchor.user !== identity.user || + existingAnchor.rail !== identity.rail || + existingAnchor.epoch !== identity.billing_epoch || + !Number.isSafeInteger(existingAnchor.latest_attempt) || + existingAnchor.latest_attempt < 0) { + return new Error('Billing id cannot move across user, rail, or epoch.'); + } + if (identity.billing_attempt === existingAnchor.latest_attempt) { + const currentHead = await this.get( + this.receiptHeadKey(identity.billing_id, identity.billing_attempt) + ); + if (currentHead) { + return new Error('Billing attempt must advance exactly once.'); + } + const hold = await this.normalizeTargetedSpendHoldRecord( + (await this.get(this.targetedSpendHoldKey(identity.user, identity.rail))) ?? null, + identity.user, + identity.rail + ); + if (hold instanceof Error) return hold; + const billingAttemptIndex = this.normalizeTargetedSpendReservationIndexRecord( + await this.get( + this.targetedSpendBillingAttemptKey( + identity.user, + identity.rail, + identity.billing_id, + identity.billing_attempt + ) + ), + { + user: identity.user, + rail: identity.rail, + billingId: identity.billing_id, + billingAttempt: identity.billing_attempt, + } + ); + if (billingAttemptIndex instanceof Error) return billingAttemptIndex; + let activeLegacyReservation = false; + for (const session of hold.sessions) { + if (session.billing_id !== identity.billing_id || + session.billing_attempt !== identity.billing_attempt) { + continue; + } + const reservation = await this.get(this.receiptReservationKey(session.reservation_id)); + if (!reservation || + reservation.type !== 'receipt_reservation_identity' || + reservation.status === 'active') { + activeLegacyReservation = true; + break; + } + } + if (activeLegacyReservation || billingAttemptIndex !== null) { + return new Error('Billing attempt already has an active reservation.'); + } + nextAnchor = { + ...existingAnchor, + updated_at: key, + }; + } else { + if (identity.billing_attempt !== existingAnchor.latest_attempt + 1) { + return new Error('Billing attempt must advance exactly once.'); + } + const priorHead = await this.get( + this.receiptHeadKey(identity.billing_id, identity.billing_attempt - 1) + ); + if (!priorHead || + priorHead.type !== 'canonical_receipt_head' || + priorHead.billing_id !== identity.billing_id || + priorHead.billing_attempt !== identity.billing_attempt - 1) { + return new Error('Higher billing attempt requires the prior canonical receipt head.'); + } + if (stableJson(normalized.voucher_body.billing_prior_usage) !== + stableJson(priorHead.receipt.body.usage) || + this.compareAu( + normalized.voucher_body.billing_prior_au_owed_cum, + priorHead.receipt.body.au_owed_cum + ) !== 0) { + return new Error('Higher billing attempt does not exactly chain from the prior attempt.'); + } + nextAnchor = { + ...existingAnchor, + latest_attempt: identity.billing_attempt, + updated_at: key, + }; + } + } + + const reservationKey = this.receiptReservationKey(identity.reservation_id); + if ((await this.get(reservationKey)) !== null) { + return new Error('Reservation id is already in use.'); + } + return { + anchor_key: anchorKey, + anchor: nextAnchor, + reservation_key: reservationKey, + reservation: { + type: 'receipt_reservation_identity', + ...identity, + status: 'active', + closed_at: null, + close_record_key: null, + recorded_at: key, + }, + }; + } + + async normalizeSpendHoldRecord(record, user, rail, epoch, { targeted = false } = {}) { + if (!record) { + const empty = { + user, + rail, + denom: PRICE_DENOMINATION, + reserved_au: ZERO_AU, + balance_au_at_last_reserve: null, + sessions: [], + updated_at: null, + }; + return targeted + ? { type: 'targeted_spend_hold', ...empty } + : { ...empty, epoch }; + } + if (typeof record !== 'object' || Array.isArray(record)) { + return new Error('Spend hold record must be an object.'); + } + if (record.user !== user || record.rail !== rail || + (targeted + ? record.type !== 'targeted_spend_hold' || hasOwn(record, 'epoch') + : record.epoch !== epoch)) { + return new Error('Spend hold record key mismatch.'); + } + if (record.denom !== PRICE_DENOMINATION) return new Error('Spend hold denomination mismatch.'); + const reservedAu = this.normalizeAu(record.reserved_au, 'spend hold reserved amount'); + if (reservedAu instanceof Error) { + return new Error('Invalid spend hold reserved amount.'); + } + if (!Array.isArray(record.sessions)) return new Error('Spend hold sessions must be an array.'); + let targetedReservedAu = ZERO_AU; + for (const session of record.sessions) { + if (!session || typeof session !== 'object' || Array.isArray(session)) { + return new Error('Invalid spend hold session.'); + } + if (!this.isHexBytes(session.session_id, 32)) return new Error('Invalid spend hold session id.'); + if (!this.isHexBytes(session.billing_id, 32)) return new Error('Invalid spend hold billing id.'); + if (!Number.isSafeInteger(session.billing_attempt) || session.billing_attempt < 0) { + return new Error('Invalid spend hold billing attempt.'); + } + if (!Number.isSafeInteger(session.billing_epoch) || + session.billing_epoch < 1 || + (!targeted && session.billing_epoch !== epoch)) { + return new Error('Invalid spend hold billing epoch.'); + } + if (!this.isHexBytes(session.reservation_id, 32)) { + return new Error('Invalid spend hold reservation id.'); + } + if (!Number.isSafeInteger(session.reservation_expires_after_epoch) || + session.reservation_expires_after_epoch <= session.billing_epoch || + !Number.isSafeInteger(session.reservation_receipt_grace_epochs) || + session.reservation_receipt_grace_epochs < 0) { + return new Error('Invalid spend hold reservation expiry policy.'); + } + if (session.user !== user || session.rail !== rail) { + return new Error('Spend hold session user or rail mismatch.'); + } + if (!this.isHexBytes(session.provider, 32)) return new Error('Invalid spend hold provider.'); + if (!this.isHexBytes(session.payout_revision, 32)) { + return new Error('Invalid spend hold payout revision.'); + } + if (!this.isHexBytes(session.enclave_id, 32)) return new Error('Invalid spend hold enclave.'); + if (!this.isHexBytes(session.enclave_pubkey, 32)) { + return new Error('Invalid spend hold enclave public key.'); + } + if (typeof session.model_id !== 'string' || + session.model_id.length === 0 || + session.model_id.length > 256) { + return new Error('Invalid spend hold model id.'); + } + const billingPriorUsage = this.normalizeReceiptUsage(session.billing_prior_usage); + if (billingPriorUsage instanceof Error || + stableJson(billingPriorUsage) !== stableJson(session.billing_prior_usage)) { + return new Error('Invalid spend hold billing prior usage.'); + } + const billingPriorAuOwedCum = this.normalizeAu( + session.billing_prior_au_owed_cum, + 'spend hold billing prior cumulative amount' + ); + if (billingPriorAuOwedCum instanceof Error) { + return new Error('Invalid spend hold billing prior cumulative amount.'); + } + if (!Number.isSafeInteger(session.price_ver) || session.price_ver < 1) { + return new Error('Invalid spend hold price version.'); + } + if (!Number.isSafeInteger(session.rules_ver) || session.rules_ver < 1) { + return new Error('Invalid spend hold rules version.'); + } + const lockedRateMap = this.normalizeLockedRateMap( + session.locked_rate_map, + 'spend hold locked_rate_map' + ); + if (lockedRateMap instanceof Error) return lockedRateMap; + if (stableJson(lockedRateMap) !== stableJson(session.locked_rate_map)) { + return new Error('Spend hold locked_rate_map must be canonical.'); + } + const lockedPerReqAu = this.normalizeAu(session.locked_per_req_au, 'spend hold locked per-request price'); + if (lockedPerReqAu instanceof Error) { + return new Error('Invalid spend hold locked per-request price.'); + } + const lockedMinSessionAu = this.normalizeAu(session.locked_min_session_au, 'spend hold locked minimum session price'); + if (lockedMinSessionAu instanceof Error) { + return new Error('Invalid spend hold locked minimum session price.'); + } + if (!Number.isSafeInteger(session.served_ctx) || session.served_ctx < 0) { + return new Error('Invalid spend hold served context.'); + } + const modalityError = this.validateModalitySet( + session.required_modalities, + 'spend hold required_modalities' + ); + if (modalityError) return modalityError; + const specialitiesError = this.validateSpecialitySelection( + session.required_specialities ?? {}, + 'spend hold required_specialities' + ); + if (specialitiesError) return specialitiesError; + if (hasOwn(session, 'workflow')) { + const workflow = this.normalizeWorkflowBinding( + session.workflow, + 'spend hold workflow', + lockedRateMap + ); + if (workflow instanceof Error) return workflow; + if (stableJson(workflow) !== stableJson(session.workflow)) { + return new Error('Spend hold workflow must be canonical.'); + } + } + const table = session.ctx_bracket_table_ver === null || session.ctx_bracket_table_ver === undefined + ? null + : await this.ctxBracketTableByVersion(session.ctx_bracket_table_ver); + if (table instanceof Error) return table; + const ctxMeta = await this.normalizeCtxBracketEvidenceForEnclave( + session.enclave_id, + session.served_ctx, + session.ctx_bracket, + session.ctx_bracket_table_ver, + table, + 'spend hold' + ); + if (ctxMeta instanceof Error) return ctxMeta; + const maxSpendAu = this.normalizeAu(session.max_spend_au, 'spend hold max spend', { allowZero: false }); + if (maxSpendAu instanceof Error) { + return new Error('Invalid spend hold max spend.'); + } + if (targeted) { + targetedReservedAu = this.safeAddAu(targetedReservedAu, maxSpendAu); + if (targetedReservedAu instanceof Error) return targetedReservedAu; + } + if (!this.isHexBytes(session.voucher_hash, 32)) return new Error('Invalid spend hold voucher hash.'); + } + if (targeted && this.compareAu(targetedReservedAu, reservedAu) !== 0) { + return new Error('Targeted spend hold reserved amount does not equal its sessions.'); + } + return { + ...record, + reserved_au: reservedAu, + sessions: record.sessions.map((session) => ({ + ...session, + billing_prior_usage: this.normalizeReceiptUsage(session.billing_prior_usage), + billing_prior_au_owed_cum: this.normalizeAu( + session.billing_prior_au_owed_cum, + 'spend hold billing prior cumulative amount' + ), + locked_per_req_au: this.normalizeAu(session.locked_per_req_au, 'spend hold locked per-request price'), + locked_min_session_au: this.normalizeAu(session.locked_min_session_au, 'spend hold locked minimum session price'), + required_modalities: session.required_modalities.slice(), + required_specialities: cloneValue(session.required_specialities ?? {}), + ...(hasOwn(session, 'workflow') ? { + workflow: this.normalizeWorkflowBinding( + session.workflow, + 'spend hold workflow', + this.normalizeLockedRateMap(session.locked_rate_map, 'spend hold locked_rate_map') + ), + } : {}), + max_spend_au: this.normalizeAu(session.max_spend_au, 'spend hold max spend', { allowZero: false }), + })), + }; + } + + async normalizeTargetedSpendHoldRecord(record, user, rail) { + return await this.normalizeSpendHoldRecord( + record, + user, + rail, + null, + { targeted: true } + ); + } + + normalizePendingReservationDebitTotals(entries) { + const out = new Map(); + if (entries === null || entries === undefined) return out; + if (!Array.isArray(entries)) return new Error('Pending reservation debits must be an array.'); + for (const entry of entries) { + if (!entry || typeof entry !== 'object' || Array.isArray(entry)) { + return new Error('Invalid pending reservation debit entry.'); + } + const rail = this.normalizeLedgerRail(entry.rail, 'pending reservation debit rail'); + if (rail instanceof Error) return rail; + if (!this.isSafeKeyPart(entry.user)) return new Error('Invalid pending reservation debit user.'); + const au = this.normalizeAu(entry.au, 'pending reservation debit amount', { allowZero: false }); + if (au instanceof Error) return new Error('Invalid pending reservation debit amount.'); + const key = stableJson([rail, entry.user]); + if (out.has(key)) return new Error('Duplicate pending reservation debit entry.'); + out.set(key, { rail, user: entry.user, au }); + } + return out; + } + + reservationDebitTotalEntries(map) { + if (!map) return []; + return this.sortedRailRecords(map, 'user').map((entry) => ({ + rail: entry.rail, + user: entry.user, + au: entry.au, + })); + } + + nextReservationDebitTotals(applyState, epoch, page, debitMap) { + const base = page === 0 + ? new Map() + : this.normalizePendingReservationDebitTotals(applyState.pending_reserved_debits); + if (base instanceof Error) return base; + if (page > 0 && applyState.pending_epoch === epoch && !Array.isArray(applyState.pending_reserved_debits)) { + return new Error('Pending reservation debit state missing for paged epoch apply.'); + } + const out = new Map(base); + for (const debit of debitMap.values()) { + const key = stableJson([debit.rail, debit.user]); + const current = out.get(key) ?? { rail: debit.rail, user: debit.user, au: ZERO_AU }; + const nextAu = this.safeAddAu(current.au, debit.au); + if (nextAu instanceof Error) return nextAu; + out.set(key, { ...current, au: nextAu }); + } + return out; + } + + async validateEpochDebitReservations(epoch, debitTotals) { + for (const debit of debitTotals.values()) { + const hold = await this.normalizeSpendHoldRecord( + (await this.get(this.spendHoldKey(debit.user, debit.rail, epoch))) ?? null, + debit.user, + debit.rail, + epoch + ); + if (hold instanceof Error) return hold; + if (this.compareAu(hold.reserved_au, debit.au) < 0) { + return new Error('Epoch debit exceeds reserved spend hold.'); + } + } + return null; + } + + async requireBoundCanaryProbe(value, auditor) { + const catalogError = await this.requirePublishedCanarySet(value.canary_set); + if (catalogError) return catalogError; + + const enclave = await this.get(`enclave/${value.enclave_id}`); + if (!enclave) return new Error('Canary probe enclave not found.'); + const approvedBinaryHashes = this.normalizeApprovedBinaryHashes( + enclave.binary_hash, + enclave.approved_binary_hashes + ); + if (!approvedBinaryHashes.includes(value.binary_hash.toLowerCase())) { + return new Error('Canary probe binary_hash is not approved for enclave.'); + } + + const challengeError = await this.requireCanaryChallenge(value, auditor); + if (challengeError) return challengeError; + + if (!this.verifyProbeResultSignature(auditor, value)) { + return new Error('Invalid canary auditor signature.'); + } + return null; + } + + async requirePublishedCanarySet(canarySet) { + const catalog = await this.get('catalog/current'); + if (!catalog || catalog.status !== 'active') { + return new Error('Published catalog required for canary probe.'); + } + if (!Array.isArray(catalog.canaries) || !catalog.canaries.some((entry) => entry.set_id === canarySet)) { + return new Error('Canary set is not published in the active catalog.'); + } + return null; + } + + async requireCanaryChallenge(value, auditor) { + if (value.epoch !== value.challenge_epoch + 1) { + return new Error('Canary probe epoch must immediately follow its challenge epoch.'); + } + const anchor = await this.canaryChallengeAnchor(value.challenge_epoch); + if (!anchor) return new Error('Canary challenge epoch is not anchored.'); + if (anchor.apply_hash !== value.challenge_apply_hash.toLowerCase()) { + return new Error('Canary challenge apply hash mismatch.'); + } + const catalog = await this.get('catalog/current'); + const canary = catalog?.canaries?.find((entry) => entry.set_id === value.canary_set); + if (!canary) return new Error('Published canary challenge set not found.'); + const expectedSeed = await this.opaqueHash('mayhem-canary-challenge-v1', { + challenge_epoch: value.challenge_epoch, + challenge_apply_hash: anchor.apply_hash, + probe_epoch: value.epoch, + auditor, + provider: value.provider, + enclave_id: value.enclave_id, + canary_set: value.canary_set, + catalog_hash: catalog.catalog_hash, + }); + if (expectedSeed !== value.challenge_seed.toLowerCase()) { + return new Error('Canary challenge seed mismatch.'); + } + const selectedIndex = Number(BigInt(`0x${expectedSeed}`) % BigInt(canary.prompt_ids.length)); + if (value.canary_prompt_id !== canary.prompt_ids[selectedIndex]) { + return new Error('Canary prompt does not match the unpredictable challenge selection.'); + } + return null; + } + + validateDisputeOpen(value) { + const rail = this.normalizeLedgerRail(value.rail, 'dispute rail'); + if (rail instanceof Error) return rail; + if (!this.isHexBytes(value.session_id, 32)) return new Error('Invalid dispute session id.'); + if (!this.isSafeKeyPart(value.reason)) return new Error('Invalid dispute reason.'); + for (const key of ['provider', 'enclave_id']) { + if (!this.isHexBytes(value[key], 32)) return new Error(`Invalid dispute ${key}.`); + } + for (const key of ['counterparty']) { + if (value[key] !== undefined && !this.isSafeKeyPart(value[key])) { + return new Error(`Invalid dispute ${key}.`); + } + } + if (value.evidence !== undefined) { + const bytes = b4a.from(stableJson(value.evidence)).byteLength; + if (bytes > DISPUTE_EVIDENCE_MAX_BYTES) { + return new Error('Dispute evidence bundle is too large.'); + } + } + return null; + } + + validateEpochApplyShape(value) { + const arrays = [ + ['debits', value.debits], + ['earnings', value.earnings], + ]; + if (value.market_usage !== undefined) arrays.push(['market_usage', value.market_usage]); + if (value.earning_finals !== undefined) arrays.push(['earning_finals', value.earning_finals]); + for (const [name, entries] of arrays) { + if (!Array.isArray(entries)) return new Error(`Epoch apply ${name} must be an array.`); + } + return null; + } + + epochApplyPage(value) { + if (!hasOwn(value, 'page')) return 0; + if (!Number.isSafeInteger(value.page) || value.page < 0) { + return new Error('Invalid epochApply page.'); + } + return value.page; + } + + epochApplyLastPage(value) { + if (!hasOwn(value, 'last_page')) return !hasOwn(value, 'page'); + if (typeof value.last_page !== 'boolean') { + return new Error('Invalid epochApply last_page.'); + } + return value.last_page; + } + + isEpochApplyPagePositionReplay(applyState, epoch, page, lastPage) { + if (applyState.last_page !== page) return false; + if (lastPage) { + return applyState.updated_epoch === epoch && (applyState.pending_epoch ?? null) === null; + } + return ( + applyState.pending_epoch === epoch && + applyState.pending_next_page === page + 1 + ); + } + + isIdempotentEpochApplyPage(applyState, epoch, page, lastPage, applyHash) { + if (applyState.last_apply_hash !== applyHash) return false; + return this.isEpochApplyPagePositionReplay(applyState, epoch, page, lastPage); + } + + validateEpochApplyPageOrder(applyState, epoch, page) { + const pendingEpoch = applyState.pending_epoch ?? null; + if (page === 0) { + if (pendingEpoch !== null) return new Error('Guardian monotonic epoch invariant failed: pending epoch apply page exists.'); + if (epoch <= applyState.updated_epoch) return new Error('Guardian monotonic epoch invariant failed.'); + if (epoch !== applyState.updated_epoch + 1) { + return new Error('Guardian monotonic epoch invariant failed: epoch apply must be contiguous.'); + } + return null; + } + if (pendingEpoch !== epoch) { + return new Error('Guardian monotonic epoch invariant failed: epoch apply page has no pending predecessor.'); + } + if (epoch !== applyState.updated_epoch + 1) { + return new Error('Guardian monotonic epoch invariant failed: epoch apply must be contiguous.'); + } + if (applyState.pending_next_page !== page) { + return new Error('Guardian monotonic epoch invariant failed: epoch apply page is not contiguous.'); + } + if (!this.isHexBytes(applyState.last_apply_hash, 32)) { + return new Error('Guardian monotonic epoch invariant failed: pending epoch apply hash missing.'); + } + return null; + } + + async validateEpochCadenceTime(applyState, epoch, page, settledAt, epochSeconds) { + if (!Number.isSafeInteger(settledAt) || settledAt < 0 || + !Number.isSafeInteger(epochSeconds) || epochSeconds < 1) { + return new Error('Invalid canonical epoch settlement time.'); + } + if (page > 0) { + if (applyState.pending_epoch !== epoch || + applyState.pending_settlement_unix !== settledAt) { + return new Error('Paged epoch apply settlement time changed between pages.'); + } + return null; + } + if (applyState.updated_epoch === 0) return null; + const priorSettlementUnix = await this.priorEpochSettlementUnix(applyState); + if (priorSettlementUnix instanceof Error) return priorSettlementUnix; + if (priorSettlementUnix > Number.MAX_SAFE_INTEGER - epochSeconds) { + return new Error('Canonical epoch settlement time overflow.'); + } + if (settledAt < priorSettlementUnix + epochSeconds) { + return new Error('Epoch settlement cadence has not matured.'); + } + return null; + } + + async priorEpochSettlementUnix(applyState) { + if (Number.isSafeInteger(applyState.last_settlement_unix) && + applyState.last_settlement_unix >= 0) { + return applyState.last_settlement_unix; + } + const epoch = applyState.updated_epoch; + if (!Number.isSafeInteger(epoch) || epoch < 1 || + !this.isHexBytes(applyState.last_apply_hash, 32)) { + return new Error('Prior canonical epoch settlement identity is invalid.'); + } + const seal = await this.get(`epoch/seal/${epoch}`); + if (seal !== null) { + if (seal.type !== 'epoch_empty_seal' || + seal.epoch !== epoch || + seal.seal_hash !== applyState.last_apply_hash || + !Number.isSafeInteger(seal.at) || + seal.at < 0) { + return new Error('Prior canonical empty-seal settlement identity is invalid.'); + } + const expectedSealHash = await this.epochEmptySealHash( + this.epochEmptySealHashValue(seal) + ); + if (expectedSealHash !== seal.seal_hash) { + return new Error('Prior canonical empty-seal settlement hash is invalid.'); + } + return seal.at; + } + const commit = await this.get(`epoch/commit/${epoch}`); + if (!commit || + commit.type !== 'epoch_commit' || + commit.epoch !== epoch || + !Number.isSafeInteger(commit.at) || + commit.at < 0 || + !this.isHexBytes(commit.commit_hash, 32)) { + return new Error('Prior canonical epoch commit settlement identity is invalid.'); + } + const expectedCommitHash = await this.epochCommitHash({ + epoch, + epoch_seconds: commit.epoch_seconds, + roots: commit.roots, + totals: commit.totals, + }); + if (expectedCommitHash !== commit.commit_hash || + (applyState.last_receipt_commit_hash != null && + applyState.last_receipt_commit_hash !== commit.commit_hash)) { + return new Error('Prior canonical epoch commit settlement hash is invalid.'); + } + const usageRoot = await this.get(`ev/use/${epoch}`); + if (!usageRoot || + usageRoot.type !== 'usage_root' || + usageRoot.epoch !== epoch || + usageRoot.ts !== commit.at || + usageRoot.merkle_root !== commit.roots?.use) { + return new Error('Prior canonical epoch apply evidence is missing.'); + } + return commit.at; + } + + nextEpochApplyState({ + applyState, + epoch, + page, + lastPage, + applyHash, + epochSeconds, + settledAt, + reservationDebitTotals = null, + receiptApply = null, + }) { + const base = { + ...applyState, + updated_at: this.tx, + last_apply_previous_hash: applyState.last_apply_hash ?? null, + last_apply_hash: applyHash, + last_epoch_seconds: epochSeconds, + ...(receiptApply ? { + last_receipt_index_count: receiptApply.index_count, + last_receipt_index_revision: receiptApply.index_revision, + last_receipt_index_page_count: receiptApply.index_page_count, + last_receipt_index_updated_at: receiptApply.index_updated_at, + last_receipt_commit_hash: receiptApply.commit_hash, + last_receipt_allocation_count: receiptApply.allocation_count, + last_receipt_provider_count: receiptApply.provider_count, + last_receipt_market_count: receiptApply.market_count, + last_receipt_earn_cum_au: receiptApply.earn_cum_au, + last_receipt_fee_au: receiptApply.fee_au, + last_receipt_burn_au: receiptApply.burn_au, + } : {}), + }; + if (lastPage) { + return { + ...base, + updated_epoch: epoch, + last_settlement_unix: settledAt, + pending_epoch: null, + pending_next_page: 0, + pending_settlement_unix: null, + pending_reserved_debits: null, + ...(receiptApply ? { + pending_receipt_index_count: null, + pending_receipt_index_revision: null, + pending_receipt_index_page_count: null, + pending_receipt_index_updated_at: null, + pending_receipt_commit_hash: null, + pending_receipt_allocation_count: null, + pending_receipt_provider_count: null, + pending_receipt_market_count: null, + pending_receipt_earn_cum_au: null, + pending_receipt_fee_au: null, + pending_receipt_burn_au: null, + } : {}), + last_page: page, + }; + } + return { + ...base, + pending_epoch: epoch, + pending_next_page: page + 1, + pending_settlement_unix: settledAt, + pending_reserved_debits: this.reservationDebitTotalEntries(reservationDebitTotals), + ...(receiptApply ? { + pending_receipt_index_count: receiptApply.index_count, + pending_receipt_index_revision: receiptApply.index_revision, + pending_receipt_index_page_count: receiptApply.index_page_count, + pending_receipt_index_updated_at: receiptApply.index_updated_at, + pending_receipt_commit_hash: receiptApply.commit_hash, + pending_receipt_allocation_count: receiptApply.allocation_count, + pending_receipt_provider_count: receiptApply.provider_count, + pending_receipt_market_count: receiptApply.market_count, + pending_receipt_earn_cum_au: receiptApply.earn_cum_au, + pending_receipt_fee_au: receiptApply.fee_au, + pending_receipt_burn_au: receiptApply.burn_au, + } : {}), + updated_epoch: applyState.updated_epoch, + last_page: page, + }; + } + + validateEpochApplyFeatureValue(value) { + if (CONTRACT_VERSION >= 17) { + return new Error('Aggregate epoch_apply is disabled; use receipt-bound apply_targeted_epoch.'); + } + if (!value || typeof value !== 'object' || Array.isArray(value)) { + return new Error('epochApply feature value must be an object.'); + } + const required = ['op', 'epoch', 'at', 'debits', 'earnings']; + const allowed = new Set([...required, 'market_usage', 'roots', 'totals', 'page', 'last_page']); + const unknown = Object.keys(value).filter((key) => !allowed.has(key)).sort(); + if (unknown.length > 0) { + return new Error(`epochApply feature does not accept fields: ${unknown.join(', ')}.`); + } + for (const key of required) { + if (!hasOwn(value, key)) return new Error(`epochApply feature is missing ${key}.`); + } + if (value.op !== 'epoch_apply') return new Error('Invalid epochApply feature op.'); + if (!Number.isSafeInteger(value.epoch) || value.epoch < 1) { + return new Error('Invalid epochApply feature epoch.'); + } + if (!Number.isSafeInteger(value.at) || value.at < 0) { + return new Error('Invalid epochApply feature timestamp.'); + } + const page = this.epochApplyPage(value); + if (page instanceof Error) return page; + const lastPage = this.epochApplyLastPage(value); + if (lastPage instanceof Error) return lastPage; + return this.validateEpochApplyShape(value); + } + + async normalizeCommitTargetedEpochPageZeroFeatureValue(value) { + if (!value || typeof value !== 'object' || Array.isArray(value)) { + return new Error('Commit-plus-page-zero feature value must be an object.'); + } + const required = [ + 'op', + 'epoch', + 'at', + 'epoch_commit_hash', + 'receipt_index', + 'debits', + 'earnings', + 'allocations', + 'last_page', + 'roots', + 'totals', + ]; + const allowed = new Set([ + ...required, + 'earning_finals', + 'market_usage', + 'supersedes_commit_hash', + ]); + const unknown = Object.keys(value).filter((key) => !allowed.has(key)).sort(); + if (unknown.length > 0) { + return new Error(`Commit-plus-page-zero feature does not accept fields: ${unknown.join(', ')}.`); + } + for (const field of required) { + if (!hasOwn(value, field)) { + return new Error(`Commit-plus-page-zero feature is missing ${field}.`); + } + } + if (value.op !== 'commit_apply_targeted_epoch_page0') { + return new Error('Invalid commit-plus-page-zero feature op.'); + } + if (hasOwn(value, 'supersedes_commit_hash') && + (!this.isHexBytes(value.supersedes_commit_hash, 32) || + value.supersedes_commit_hash !== value.supersedes_commit_hash.toLowerCase())) { + return new Error('Invalid superseded epoch commit hash.'); + } + const roots = this.normalizeEpochRoots(value.roots); + if (roots instanceof Error) return roots; + const totals = this.normalizeEpochTotals(value.totals); + if (totals instanceof Error) return totals; + if (totals.price_count !== 0) { + return new Error('Receipt settlement page zero cannot carry market price derivations.'); + } + const emptyPriceRoot = await this.priceDerivationRoot([]); + if (roots.price !== emptyPriceRoot) { + return new Error('Receipt settlement price root must be the canonical empty root.'); + } + const targetedValue = { + op: 'apply_targeted_epoch', + epoch: value.epoch, + at: value.at, + epoch_commit_hash: value.epoch_commit_hash, + receipt_index: value.receipt_index, + debits: value.debits, + earnings: value.earnings, + allocations: value.allocations, + ...(hasOwn(value, 'earning_finals') ? { earning_finals: value.earning_finals } : {}), + ...(hasOwn(value, 'market_usage') ? { market_usage: value.market_usage } : {}), + page: 0, + last_page: value.last_page, + }; + const normalized = await this.normalizeTargetedEpochFeatureValue(targetedValue); + if (normalized instanceof Error) return normalized; + return { + epoch: value.epoch, + at: value.at, + roots, + totals, + receipt_index: normalized.ledger_value.receipt_index, + epoch_commit_hash: value.epoch_commit_hash, + supersedes_commit_hash: value.supersedes_commit_hash ?? null, + targeted_value: targetedValue, + normalized, + }; + } + + async prepareTargetedEpochCommitTransition(prepared, applyState, featureKey) { + const currentReceiptIndex = this.normalizeReceiptEpochIndexMetadata( + await this.get(this.receiptEpochIndexKey(prepared.epoch)), + prepared.epoch + ); + if (currentReceiptIndex instanceof Error) return currentReceiptIndex; + if (stableJson(currentReceiptIndex) !== stableJson(prepared.receipt_index)) { + return new Error('Canonical receipt epoch index changed before commit-plus-page-zero.'); + } + if (prepared.totals.use_count !== currentReceiptIndex.count) { + return new Error('Epoch commit receipt count must match the canonical receipt index.'); + } + const params = await this.activeParamsAt(prepared.at, ['challenge_epochs', 'epoch_seconds']); + const commitHash = await this.epochCommitHash({ + epoch: prepared.epoch, + epoch_seconds: params.epoch_seconds, + roots: prepared.roots, + totals: prepared.totals, + }); + if (commitHash !== prepared.epoch_commit_hash) { + return new Error('Commit-plus-page-zero epoch commit hash is invalid.'); + } + const key = `epoch/commit/${prepared.epoch}`; + const existing = await this.get(key); + if (existing?.commit_hash === commitHash) { + if ( + existing.type !== 'epoch_commit' || + existing.status !== 'provisional' || + existing.apply_mode !== 'targeted_receipt_pages_v1' || + existing.epoch !== prepared.epoch || + existing.at !== prepared.at || + stableJson(existing.roots) !== stableJson(prepared.roots) || + stableJson(existing.totals) !== stableJson(prepared.totals) + ) { + return new Error('Existing epoch commit does not match commit-plus-page-zero.'); + } + return { key, record: existing, archive: null, write: false }; + } + if (existing !== null) { + if ( + existing.type !== 'epoch_commit' || + existing.status !== 'provisional' || + existing.epoch !== prepared.epoch || + !this.isHexBytes(existing.commit_hash, 32) || + existing.commit_hash !== existing.commit_hash.toLowerCase() || + (existing.replacement_count !== undefined && + (!Number.isSafeInteger(existing.replacement_count) || existing.replacement_count < 0)) + ) { + return new Error('Only a canonical provisional epoch commit can be superseded.'); + } + if (prepared.supersedes_commit_hash !== existing.commit_hash) { + return new Error('Commit-plus-page-zero must identify the current provisional commit.'); + } + if (applyState.updated_epoch !== prepared.epoch - 1 || + (applyState.pending_epoch ?? null) !== null) { + return new Error('Epoch commit cannot be superseded after targeted apply has started.'); + } + for (const evidenceKey of ['use', 'earn', 'fee', 'price']) { + if (await this.get(`ev/${evidenceKey}/${prepared.epoch}`)) { + return new Error('Epoch commit cannot be superseded after settlement evidence exists.'); + } + } + if (!Number.isSafeInteger(existing.totals?.use_count) || + prepared.totals.use_count <= existing.totals.use_count) { + return new Error('Replacement epoch commit must strictly extend the canonical receipt count.'); + } + if (!Number.isSafeInteger(existing.at) || prepared.at < existing.at) { + return new Error('Replacement epoch commit timestamp cannot precede the superseded commit.'); + } + } else if (prepared.supersedes_commit_hash !== null) { + return new Error('Epoch commit has nothing to supersede.'); + } + const record = { + type: 'epoch_commit', + epoch: prepared.epoch, + epoch_seconds: params.epoch_seconds, + apply_mode: 'targeted_receipt_pages_v1', + roots: prepared.roots, + totals: prepared.totals, + status: 'provisional', + challenge_epochs: params.challenge_epochs, + provisional_until_epoch: prepared.epoch + params.challenge_epochs, + commit_hash: commitHash, + submitted_by: this.address, + submitted_at: featureKey, + at: prepared.at, + ...(existing ? { + supersedes: existing.commit_hash, + replacement_count: (existing.replacement_count ?? 0) + 1, + } : {}), + }; + const archive = existing ? { + key: `epoch/commit/superseded/${prepared.epoch}/${existing.commit_hash}`, + value: { + ...existing, + status: 'superseded', + superseded_by: commitHash, + superseded_at: featureKey, + }, + } : null; + return { key, record, archive, write: true }; + } + + async normalizeTargetedEpochFeatureValue(value) { + if (!value || typeof value !== 'object' || Array.isArray(value)) { + return new Error('Targeted epoch feature value must be an object.'); + } + const required = [ + 'op', + 'epoch', + 'at', + 'epoch_commit_hash', + 'receipt_index', + 'debits', + 'earnings', + 'allocations', + 'page', + 'last_page', + ]; + const allowed = new Set([...required, 'market_usage', 'earning_finals']); + const unknown = Object.keys(value).filter((key) => !allowed.has(key)).sort(); + if (unknown.length > 0) { + return new Error(`Targeted epoch feature does not accept fields: ${unknown.join(', ')}.`); + } + for (const key of required) { + if (!hasOwn(value, key)) return new Error(`Targeted epoch feature is missing ${key}.`); + } + if (value.op !== 'apply_targeted_epoch') { + return new Error('Invalid targeted epoch feature op.'); + } + if (!this.isHexBytes(value.epoch_commit_hash, 32) || + value.epoch_commit_hash !== value.epoch_commit_hash.toLowerCase()) { + return new Error('Invalid targeted epoch commit hash.'); + } + const receiptIndex = this.normalizeReceiptEpochIndexMetadata( + value.receipt_index, + value.epoch + ); + if (receiptIndex instanceof Error) return receiptIndex; + if (stableJson(receiptIndex) !== stableJson(value.receipt_index)) { + return new Error('Targeted epoch receipt index snapshot must be canonical.'); + } + if (!Array.isArray(value.earnings)) { + return new Error('Targeted epoch earnings must be an array.'); + } + if (!Array.isArray(value.allocations) || value.allocations.length === 0) { + return new Error('Targeted epoch allocations must be a non-empty array.'); + } + const allocations = []; + const allocationSessions = new Set(); + for (const entry of value.allocations) { + const entryError = this.validateExactObjectKeys( + entry, + [ + 'session_id', + 'billing_id', + 'billing_attempt', + 'billing_epoch', + 'receipt_seq', + 'receipt_hash', + 'user', + 'rail', + 'provider', + 'payout_revision', + 'au', + ], + 'targeted epoch allocation' + ); + if (entryError) return entryError; + const rail = this.normalizeLedgerRail(entry.rail, 'targeted epoch allocation rail'); + if (rail instanceof Error) return rail; + if (!this.isHexBytes(entry.session_id, 32) || + !this.isHexBytes(entry.billing_id, 32) || + !this.isHexBytes(entry.receipt_hash, 32) || + !this.isHexBytes(entry.user, 32) || + !this.isHexBytes(entry.provider, 32) || + !this.isHexBytes(entry.payout_revision, 32)) { + return new Error('Invalid targeted epoch allocation identity.'); + } + if (!Number.isSafeInteger(entry.billing_attempt) || entry.billing_attempt < 0 || + !Number.isSafeInteger(entry.billing_epoch) || entry.billing_epoch < 1 || + !Number.isSafeInteger(entry.receipt_seq) || entry.receipt_seq < 0) { + return new Error('Invalid targeted epoch allocation receipt position.'); + } + const attemptIdentity = `${entry.billing_id}:${entry.billing_attempt}`; + if (allocationSessions.has(entry.session_id) || + allocationSessions.has(attemptIdentity)) { + return new Error('Duplicate targeted epoch receipt allocation.'); + } + allocationSessions.add(entry.session_id); + allocationSessions.add(attemptIdentity); + const au = this.normalizeAu( + entry.au, + 'targeted epoch allocation amount', + { allowZero: false } + ); + if (au instanceof Error) return au; + allocations.push({ + session_id: entry.session_id, + billing_id: entry.billing_id, + billing_attempt: entry.billing_attempt, + billing_epoch: entry.billing_epoch, + receipt_seq: entry.receipt_seq, + receipt_hash: entry.receipt_hash, + user: entry.user, + rail, + provider: entry.provider, + payout_revision: entry.payout_revision, + au, + }); + } + allocations.sort((left, right) => ( + PROVIDER_PAYOUT_BINDING_RAIL_ORDER.indexOf(left.rail) - + PROVIDER_PAYOUT_BINDING_RAIL_ORDER.indexOf(right.rail) || + compareCodepoint(left.user, right.user) || + left.billing_epoch - right.billing_epoch || + compareCodepoint(left.billing_id, right.billing_id) || + left.billing_attempt - right.billing_attempt || + compareCodepoint(left.session_id, right.session_id) + )); + if (stableJson(allocations) !== stableJson(value.allocations)) { + return new Error('Targeted epoch allocations must be canonical.'); + } + const aggregated = new Map(); + const providerRails = new Map(); + for (const entry of value.earnings) { + const entryError = this.validateExactObjectKeys( + entry, + ['rail', 'provider', 'gross_au', 'payout_revision'], + 'targeted epoch earning' + ); + if (entryError) return entryError; + const rail = this.normalizeLedgerRail(entry.rail, 'targeted epoch earning rail'); + if (rail instanceof Error) return rail; + if (!PROVIDER_PAYOUT_BINDING_RAILS.has(rail)) { + return new Error('Targeted epoch earning rail has no payout binding path.'); + } + if (!this.isHexBytes(entry.provider, 32) || + entry.provider !== entry.provider.toLowerCase()) { + return new Error('Invalid targeted epoch earning provider.'); + } + if (!this.isHexBytes(entry.payout_revision, 32) || + entry.payout_revision !== entry.payout_revision.toLowerCase()) { + return new Error('Invalid targeted epoch payout revision.'); + } + const grossAu = this.normalizeAu( + entry.gross_au, + 'targeted epoch gross earning', + { allowZero: false } + ); + if (grossAu instanceof Error) return grossAu; + const providerRailKey = stableJson([rail, entry.provider]); + const selected = providerRails.get(providerRailKey); + if (selected && selected !== entry.payout_revision) { + return new Error('Targeted epoch provider rail cannot substitute payout revisions.'); + } + providerRails.set(providerRailKey, entry.payout_revision); + const key = stableJson([rail, entry.provider, entry.payout_revision]); + const current = aggregated.get(key) ?? { + rail, + provider: entry.provider, + gross_au: ZERO_AU, + payout_revision: entry.payout_revision, + }; + const next = this.safeAddAu(current.gross_au, grossAu); + if (next instanceof Error) return next; + aggregated.set(key, { ...current, gross_au: next }); + } + const targetedEarnings = Array.from(aggregated.values()).sort((left, right) => ( + PROVIDER_PAYOUT_BINDING_RAIL_ORDER.indexOf(left.rail) - + PROVIDER_PAYOUT_BINDING_RAIL_ORDER.indexOf(right.rail) || + compareCodepoint(left.provider, right.provider) || + compareCodepoint(left.payout_revision, right.payout_revision) + )); + let earningFinals = null; + if (hasOwn(value, 'earning_finals')) { + if (value.last_page !== true || !Array.isArray(value.earning_finals) || + value.earning_finals.length === 0) { + return new Error('Targeted epoch earning_finals require a non-empty final page.'); + } + earningFinals = []; + const identities = new Set(); + for (const entry of value.earning_finals) { + const entryError = this.validateExactObjectKeys( + entry, + ['rail', 'provider', 'gross_au', 'net_au', 'cumulative_au'], + 'targeted epoch final earning' + ); + if (entryError) return entryError; + const rail = this.normalizeLedgerRail(entry.rail, 'targeted epoch final earning rail'); + if (rail instanceof Error) return rail; + if (!this.isHexBytes(entry.provider, 32) || + entry.provider !== entry.provider.toLowerCase()) { + return new Error('Invalid targeted epoch final earning provider.'); + } + const identity = stableJson([rail, entry.provider]); + if (identities.has(identity)) { + return new Error('Duplicate targeted epoch final earning provider.'); + } + identities.add(identity); + const grossAu = this.normalizeAu( + entry.gross_au, + 'targeted epoch final gross earning', + { allowZero: false } + ); + const netAu = this.normalizeAu( + entry.net_au, + 'targeted epoch final net earning', + { allowZero: false } + ); + const cumulativeAu = this.normalizeAu( + entry.cumulative_au, + 'targeted epoch final cumulative earning', + { allowZero: false } + ); + if (grossAu instanceof Error || netAu instanceof Error || cumulativeAu instanceof Error) { + return new Error('Invalid targeted epoch final earning amount.'); + } + if (this.compareAu(netAu, grossAu) > 0 || this.compareAu(cumulativeAu, netAu) < 0) { + return new Error('Targeted epoch final earning totals are inconsistent.'); + } + earningFinals.push({ + rail, + provider: entry.provider, + gross_au: grossAu, + net_au: netAu, + cumulative_au: cumulativeAu, + }); + } + earningFinals.sort((left, right) => ( + PROVIDER_PAYOUT_BINDING_RAIL_ORDER.indexOf(left.rail) - + PROVIDER_PAYOUT_BINDING_RAIL_ORDER.indexOf(right.rail) || + compareCodepoint(left.provider, right.provider) + )); + if (stableJson(earningFinals) !== stableJson(value.earning_finals)) { + return new Error('Targeted epoch final earnings must be canonical.'); + } + } + const { + allocations: _allocations, + op: _op, + earnings: _earnings, + earning_finals: _earningFinals, + ...ledgerFields + } = value; + const ledgerValue = { + ...ledgerFields, + receipt_index: receiptIndex, + earnings: targetedEarnings.map(({ payout_revision: _revision, ...earning }) => earning), + ...(earningFinals ? { earning_finals: earningFinals } : {}), + }; + if (!Number.isSafeInteger(ledgerValue.epoch) || ledgerValue.epoch < 1) { + return new Error('Invalid targeted epoch.'); + } + if (!Number.isSafeInteger(ledgerValue.at) || ledgerValue.at < 0) { + return new Error('Invalid targeted epoch timestamp.'); + } + const page = this.epochApplyPage(ledgerValue); + if (page instanceof Error) return page; + const lastPage = this.epochApplyLastPage(ledgerValue); + if (lastPage instanceof Error) return lastPage; + const ledgerError = this.validateEpochApplyShape(ledgerValue); + if (ledgerError) return ledgerError; + return { + ledger_value: ledgerValue, + allocations, + targeted_earnings: targetedEarnings, + earning_finals: earningFinals, + revision_bindings: targetedEarnings.map((earning) => ({ + provider: earning.provider, + rail: earning.rail, + revision: earning.payout_revision, + })), + }; + } + + async validateProviderPayoutBindingIntent(intent, { currentState = true } = {}) { + const shapeError = this.validateExactObjectKeys( + intent, + [ + 'op', + 'network', + 'admin', + 'bootstrap', + 'context_revision', + 'provider', + 'rail', + 'currency', + 'chain_id', + 'target', + 'target_wallet', + 'target_signature', + 'previous_revision', + 'payment_config_version', + 'nonce', + 'expires_after_epoch', + ], + 'provider payout binding intent' + ); + if (shapeError) return shapeError; + if (intent.op !== 'bind_provider_payout') { + return new Error('Invalid provider payout binding intent op.'); + } + for (const field of ['admin', 'bootstrap', 'provider', 'nonce']) { + if (!this.isHexBytes(intent[field], 32) || intent[field] !== intent[field].toLowerCase()) { + return new Error(`Invalid provider payout binding ${field}.`); + } + } + if (!this.isHexBytes(intent.context_revision, 32) || + intent.context_revision !== intent.context_revision.toLowerCase()) { + return new Error('Invalid provider payout binding context revision.'); + } + if (!this.isSafeKeyPart(intent.network)) { + return new Error('Invalid provider payout binding network.'); + } + if (!PROVIDER_PAYOUT_BINDING_RAILS.has(intent.rail)) { + return new Error('Invalid provider payout binding rail.'); + } + if (intent.rail !== 'fiat' && intent.currency !== null) { + return new Error('TAP/TNK payout binding currency must be null.'); + } + if ( + intent.previous_revision !== null && + (!this.isHexBytes(intent.previous_revision, 32) || + intent.previous_revision !== intent.previous_revision.toLowerCase()) + ) { + return new Error('Invalid previous provider payout binding revision.'); + } + if (!Number.isSafeInteger(intent.payment_config_version) || + intent.payment_config_version < 1) { + return new Error('Invalid provider payout payment config version.'); + } + if (!Number.isSafeInteger(intent.expires_after_epoch) || + intent.expires_after_epoch < 1) { + return new Error('Invalid provider payout binding expiry epoch.'); + } + + if (intent.rail === 'fiat') { + const currency = this.normalizeFiatCurrency(intent.currency); + if (currency instanceof Error || + currency !== intent.currency || + intent.chain_id !== null || + intent.target_wallet !== null || + intent.target_signature !== null || + typeof intent.target !== 'string' || + !/^acct_[A-Za-z0-9._-]+$/.test(intent.target)) { + return new Error('Invalid verified Stripe payout target.'); + } + } else if (intent.rail === 'tap') { + if (!Number.isSafeInteger(intent.chain_id) || intent.chain_id < 1) { + return new Error('Invalid TAP payout binding chain id.'); + } + if (!this.isEthHexBytes(intent.target, 20) || + intent.target !== intent.target.toLowerCase()) { + return new Error('Invalid TAP payout target.'); + } + if (intent.target_wallet !== null) { + return new Error('TAP payout binding target_wallet must be null.'); + } + if (!this.isEthHexBytes(intent.target_signature, 65) || + intent.target_signature !== intent.target_signature.toLowerCase()) { + return new Error('Invalid TAP payout target ownership signature.'); + } + } else { + if (intent.chain_id !== null) { + return new Error('TNK payout binding chain_id must be null.'); + } + if (!this.isSafeKeyPart(intent.target) || intent.target !== intent.target.toLowerCase()) { + return new Error('Invalid TNK payout target.'); + } + if (!this.isHexBytes(intent.target_wallet, 32) || + intent.target_wallet !== intent.target_wallet.toLowerCase()) { + return new Error('Invalid TNK payout target wallet.'); + } + if (!this.isHexBytes(intent.target_signature, 64) || + intent.target_signature !== intent.target_signature.toLowerCase()) { + return new Error('Invalid TNK payout target ownership signature.'); + } + const address = this.msbAddressForPublicKey(intent.target_wallet, intent.network); + if (address instanceof Error) return address; + if (intent.target !== address) { + return new Error('TNK payout target does not match target wallet.'); + } + } + if (!currentState) return null; + + const admin = await this.get('admin'); + if (intent.admin !== admin) { + return new Error('Provider payout binding admin does not match canonical admin.'); + } + const payments = await this.get('payments/current'); + if (!payments || payments.set_by !== admin || payments.set_by_role !== 'admin') { + return new Error('Canonical payment configuration required.'); + } + if (intent.payment_config_version !== payments.ver) { + return new Error('Provider payout binding payment config version is stale.'); + } + if (!Array.isArray(payments.rails) || !payments.rails.includes(intent.rail)) { + return new Error('Provider payout binding rail is not enabled.'); + } + if (intent.network !== payments.tnk?.network) { + return new Error('Provider payout binding network is not canonical.'); + } + + if (intent.rail === 'fiat') { + if (!payments.fiat?.payout_currencies?.includes(intent.currency)) { + return new Error('Invalid verified Stripe payout target.'); + } + const verification = await this.providerStripePayoutVerificationForTarget( + intent.provider, + intent.target + ); + if (!verification || + verification.type !== 'stripe_payout_verification' || + verification.provider !== intent.provider || + verification.target !== intent.target || + verification.currency !== intent.currency || + verification.context_revision !== intent.context_revision || + verification.payment_config_version !== intent.payment_config_version || + verification.details_submitted !== true || + verification.payouts_enabled !== true || + verification.transfers_enabled !== true || + verification.verified_by !== admin || + verification.verified_by_role !== 'admin') { + return new Error('Current ready provider-scoped Stripe verification required.'); + } + return null; + } + + if (intent.rail === 'tap') { + if (intent.chain_id !== payments.tap?.chain_id) { + return new Error('TAP payout binding chain id is not canonical.'); + } + return null; + } + return null; + } + + validateDepositTnkIntent(intent) { + const fields = [ + 'op', + 'memo_hash', + 'treasury_address', + 'tnk_e18', + 'quoted_au', + 'rate_tnk_usd_au', + 'rate_source', + ]; + if (hasOwn(intent, 'rate_ts')) fields.push('rate_ts'); + if (hasOwn(intent, 'rate_record_key')) fields.push('rate_record_key'); + const shapeError = this.validateExactObjectKeys( + intent, + fields, + 'deposit TNK intent' + ); + if (shapeError) return shapeError; + if (intent.op !== 'deposit_tnk') return new Error('Invalid deposit TNK intent op.'); + if (!this.isSafeKeyPart(intent.memo_hash)) return new Error('Invalid deposit memo hash.'); + if (!this.isSafeKeyPart(intent.treasury_address)) return new Error('Invalid TNK treasury address.'); + const quotedAu = this.normalizeAu(intent.quoted_au, 'TNK quoted credit', { allowZero: false }); + if (quotedAu instanceof Error) { + return new Error('Invalid TNK quoted credit.'); + } + const rate = this.normalizeAu(intent.rate_tnk_usd_au, 'TNK quoted rate', { allowZero: false }); + if (rate instanceof Error) { + return new Error('Invalid TNK quoted rate.'); + } + if (!this.isSafeKeyPart(intent.rate_source)) return new Error('Invalid TNK rate source.'); + const hasRateTs = hasOwn(intent, 'rate_ts'); + const hasRateRecordKey = hasOwn(intent, 'rate_record_key'); + if (hasRateTs !== hasRateRecordKey) { + return new Error('TNK rate timestamp and record key must be paired.'); + } + if (hasRateTs && + (!Number.isSafeInteger(intent.rate_ts) || intent.rate_ts < 0)) { + return new Error('Invalid TNK rate timestamp.'); + } + if (hasRateRecordKey) { + const prefix = `rate/tnk/${intent.rate_ts}/`; + if (typeof intent.rate_record_key !== 'string' || + !intent.rate_record_key.startsWith(prefix) || + !this.isHexBytes(intent.rate_record_key.slice(prefix.length), 32)) { + return new Error('Invalid TNK rate record key.'); + } + } + const tnkE18 = this.parseTnkE18(intent.tnk_e18); + if (tnkE18 instanceof Error) return tnkE18; + return null; + } + + guardianValidateBalanceRecord(record, user = null, rail = null) { + if (!record || typeof record !== 'object' || Array.isArray(record)) { + return new Error('Guardian non-negative balance invariant failed.'); + } + if (rail !== null && record.rail !== rail) { + return new Error('Guardian balance rail invariant failed.'); + } + if (record.denom !== PRICE_DENOMINATION) { + return new Error('Guardian balance denomination invariant failed.'); + } + if (user !== null && record.user !== user) { + return new Error('Guardian balance owner invariant failed.'); + } + if (this.normalizeAu(record.au, 'balance au') instanceof Error) { + return new Error('Guardian non-negative balance invariant failed.'); + } + if (!Number.isSafeInteger(record.updated_epoch) || record.updated_epoch < 0) { + return new Error('Guardian balance epoch invariant failed.'); + } + if (record.rail === 'tap') { + const scopeError = this.guardianValidateTapScope(record, ['au'], 'balance'); + if (scopeError) return scopeError; + } + return null; + } + + guardianValidateEarningRecord(record, provider = null, rail = null) { + if (!record || typeof record !== 'object' || Array.isArray(record)) { + return new Error('Guardian non-negative earnings invariant failed.'); + } + if (rail !== null && record.rail !== rail) { + return new Error('Guardian earnings rail invariant failed.'); + } + if (record.denom !== PRICE_DENOMINATION) { + return new Error('Guardian earnings denomination invariant failed.'); + } + if (provider !== null && record.provider !== provider) { + return new Error('Guardian earnings owner invariant failed.'); + } + for (const key of ['total_au', 'held_au', 'paid_cum_au']) { + if (this.normalizeAu(record[key], `earning ${key}`) instanceof Error) { + return new Error('Guardian non-negative earnings invariant failed.'); + } + } + if (!Number.isSafeInteger(record.updated_epoch) || record.updated_epoch < 0) { + return new Error('Guardian earnings epoch invariant failed.'); + } + const payableAu = this.safeSubAu(record.total_au, record.held_au); + if (payableAu instanceof Error) return payableAu; + if (this.compareAu(record.held_au, record.total_au) > 0 || this.compareAu(record.paid_cum_au, payableAu) > 0) { + return new Error('Guardian earnings conservation invariant failed.'); + } + if (record.holdbacks !== undefined) { + const holdbacks = this.normalizeHoldbackBuckets(record); + if (holdbacks instanceof Error) return holdbacks; + const heldAu = this.holdbackBucketTotal(holdbacks); + if (heldAu instanceof Error) return heldAu; + if (this.compareAu(heldAu, record.held_au) !== 0) { + return new Error('Guardian earnings conservation invariant failed.'); + } + } + if (record.rail === 'tap') { + const scopeError = this.guardianValidateTapScope( + record, + ['total_au', 'held_au', 'paid_cum_au'], + 'earning' + ); + if (scopeError) return scopeError; + } + return null; + } + + guardianValidatePayoutLiabilityRecord( + record, + provider = null, + rail = null, + revision = null + ) { + if (!record || + typeof record !== 'object' || + Array.isArray(record) || + record.type !== 'provider_payout_liability') { + return new Error('Guardian payout liability shape invariant failed.'); + } + if (!PROVIDER_PAYOUT_BINDING_RAILS.has(record.rail) || + (rail !== null && record.rail !== rail)) { + return new Error('Guardian payout liability rail invariant failed.'); + } + if (!this.isHexBytes(record.provider, 32) || + (provider !== null && record.provider !== provider)) { + return new Error('Guardian payout liability owner invariant failed.'); + } + if (!this.isHexBytes(record.revision, 32) || + (revision !== null && record.revision !== revision)) { + return new Error('Guardian payout liability revision invariant failed.'); + } + if (!this.isSafeKeyPart(record.target)) { + return new Error('Guardian payout liability target invariant failed.'); + } + if (record.rail === 'fiat') { + if (!this.isSafeKeyPart(record.currency) || record.chain_id !== null) { + return new Error('Guardian payout liability fiat scope invariant failed.'); + } + } else if (record.rail === 'tap') { + if (record.currency !== null || + !Number.isSafeInteger(record.chain_id) || + record.chain_id < 1 || + !this.isEthHexBytes(record.target, 20)) { + return new Error('Guardian payout liability TAP scope invariant failed.'); + } + } else if (record.currency !== null || record.chain_id !== null) { + return new Error('Guardian payout liability TNK scope invariant failed.'); + } + for (const key of ['total_au', 'held_au', 'paid_cum_au']) { + if (this.normalizeAu(record[key], `payout liability ${key}`) instanceof Error) { + return new Error('Guardian non-negative payout liability invariant failed.'); + } + } + if (!Number.isSafeInteger(record.updated_epoch) || record.updated_epoch < 0) { + return new Error('Guardian payout liability epoch invariant failed.'); + } + const payableAu = this.safeSubAu(record.total_au, record.held_au); + if (payableAu instanceof Error || + this.compareAu(record.held_au, record.total_au) > 0 || + this.compareAu(record.paid_cum_au, payableAu) > 0) { + return new Error('Guardian payout liability conservation invariant failed.'); + } + const holdbacks = this.normalizeHoldbackBuckets(record); + if (holdbacks instanceof Error) return holdbacks; + const heldAu = this.holdbackBucketTotal(holdbacks); + if (heldAu instanceof Error || + this.compareAu(heldAu, record.held_au) !== 0) { + return new Error('Guardian payout liability conservation invariant failed.'); + } + return null; + } + + guardianValidateFeeRecord(record, rail = null) { + if (!record || typeof record !== 'object' || Array.isArray(record)) { + return new Error('Guardian fee conservation invariant failed.'); + } + if (rail !== null && record.rail !== rail) { + return new Error('Guardian fee rail invariant failed.'); + } + if (record.denom !== PRICE_DENOMINATION) { + return new Error('Guardian fee denomination invariant failed.'); + } + for (const key of ['cum_au', 'swept_cum_au']) { + if (this.normalizeAu(record[key], `fee ${key}`) instanceof Error) { + return new Error('Guardian fee conservation invariant failed.'); + } + } + if (!Number.isSafeInteger(record.updated_epoch) || record.updated_epoch < 0) { + return new Error('Guardian monotonic epoch invariant failed.'); + } + if (this.compareAu(record.swept_cum_au, record.cum_au) > 0) { + return new Error('Guardian fee conservation invariant failed.'); + } + const settledCumAu = record.settled_cum_au ?? record.cum_au; + if ( + this.normalizeAu(settledCumAu, 'fee settled cumulative amount') instanceof Error || + this.compareAu(settledCumAu, record.cum_au) < 0 + ) { + return new Error('Guardian conservation invariant failed.'); + } + if (record.rail === 'tap') { + const scopeError = this.guardianValidateTapScope( + record, + ['cum_au', 'swept_cum_au', 'settled_cum_au'], + 'fee' + ); + if (scopeError) return scopeError; + } + return null; + } + + guardianValidateBurnRecord(record, rail = null) { + if (!record || typeof record !== 'object' || Array.isArray(record)) { + return new Error('Guardian burn conservation invariant failed.'); + } + if (rail !== null && record.rail !== rail) { + return new Error('Guardian burn rail invariant failed.'); + } + if (record.denom !== PRICE_DENOMINATION) { + return new Error('Guardian burn denomination invariant failed.'); + } + if (this.normalizeAu(record.cum_au, 'burn cumulative amount') instanceof Error) { + return new Error('Guardian burn conservation invariant failed.'); + } + if (!Number.isSafeInteger(record.updated_epoch) || record.updated_epoch < 0) { + return new Error('Guardian monotonic epoch invariant failed.'); + } + const expectedBps = record.rail === 'tap' ? TAP_BURN_BPS : 0; + if (record.burn_bps !== expectedBps) { + return new Error('Guardian burn policy invariant failed.'); + } + if (record.rail === 'tap') { + const scopeError = this.guardianValidateTapScope(record, ['cum_au'], 'burn'); + if (scopeError) return scopeError; + } + return null; + } + + guardianCheckEpochApply({ + epoch, + applyState, + feeRecords, + burnRecords, + debitTotal, + debitRailTotals, + feeDeltaByRail, + burnDeltaByRail, + providerDeltaTotal, + nextFeeRecords, + nextBurnRecords, + balances, + earnings, + }) { + if (!applyState || !Number.isSafeInteger(applyState.updated_epoch) || applyState.updated_epoch < 0) { + return new Error('Guardian monotonic epoch invariant failed.'); + } + if (epoch <= applyState.updated_epoch || epoch !== applyState.updated_epoch + 1) { + return new Error('Guardian monotonic epoch invariant failed.'); + } + let feeDeltaAu = ZERO_AU; + let burnDeltaAu = ZERO_AU; + for (const rail of PROVIDER_ACCEPTED_RAIL_ORDER) { + const fee = feeRecords.get(rail); + const feeError = this.guardianValidateFeeRecord(fee, rail); + if (feeError) return feeError; + const nextFee = nextFeeRecords.get(rail); + const nextFeeError = this.guardianValidateFeeRecord(nextFee, rail); + if (nextFeeError) return nextFeeError; + const railFeeDelta = feeDeltaByRail.get(rail) ?? ZERO_AU; + feeDeltaAu = this.safeAddAu(feeDeltaAu, railFeeDelta); + if (feeDeltaAu instanceof Error) return feeDeltaAu; + const expectedFeeCumAu = this.safeAddAu(fee.cum_au, railFeeDelta); + if (expectedFeeCumAu instanceof Error) return expectedFeeCumAu; + if (this.compareAu(nextFee.cum_au, expectedFeeCumAu) !== 0) { + return new Error('Guardian fee conservation invariant failed.'); + } + const burn = burnRecords.get(rail); + const burnError = this.guardianValidateBurnRecord(burn, rail); + if (burnError) return burnError; + const nextBurn = nextBurnRecords.get(rail); + const nextBurnError = this.guardianValidateBurnRecord(nextBurn, rail); + if (nextBurnError) return nextBurnError; + const railBurnDelta = burnDeltaByRail.get(rail) ?? ZERO_AU; + burnDeltaAu = this.safeAddAu(burnDeltaAu, railBurnDelta); + if (burnDeltaAu instanceof Error) return burnDeltaAu; + const expectedBurnCumAu = this.safeAddAu(burn.cum_au, railBurnDelta); + if (expectedBurnCumAu instanceof Error) return expectedBurnCumAu; + if (this.compareAu(nextBurn.cum_au, expectedBurnCumAu) !== 0) { + return new Error('Guardian burn conservation invariant failed.'); + } + } + const providerAndFeeAu = this.safeAddAu(providerDeltaTotal, feeDeltaAu); + if (providerAndFeeAu instanceof Error) return providerAndFeeAu; + const grossDeltaTotal = this.safeAddAu(providerAndFeeAu, burnDeltaAu); + if (grossDeltaTotal instanceof Error) return grossDeltaTotal; + if (this.compareAu(grossDeltaTotal, debitTotal) !== 0) { + return new Error('Guardian conservation invariant failed.'); + } + + for (const balance of balances.values()) { + const balanceError = this.guardianValidateBalanceRecord(balance, balance.user, balance.rail); + if (balanceError) return balanceError; + } + for (const earning of earnings.values()) { + const earningError = this.guardianValidateEarningRecord(earning, earning.provider, earning.rail); + if (earningError) return earningError; + } + + const nextSettledCumByRail = new Map(); + for (const rail of PROVIDER_ACCEPTED_RAIL_ORDER) { + const fee = feeRecords.get(rail); + const nextFee = nextFeeRecords.get(rail); + const priorSettledCumAu = fee.settled_cum_au ?? fee.cum_au; + const nextSettledCumAu = this.safeAddAu(priorSettledCumAu, debitRailTotals.get(rail) ?? ZERO_AU); + if (nextSettledCumAu instanceof Error) return nextSettledCumAu; + if (this.compareAu(nextFee.cum_au, nextSettledCumAu) > 0) { + return new Error('Guardian conservation invariant failed.'); + } + nextSettledCumByRail.set(rail, nextSettledCumAu); + } + return { ok: true, next_settled_cum_by_rail: nextSettledCumByRail }; + } + + lockedEarningEpochs(params) { + return Math.max(params.holdback_epochs ?? 0, params.challenge_epochs ?? 0); + } + + providerLockedEarningEpochs(provider, params) { + const normalHoldback = params.holdback_epochs ?? 0; + const newProviderHoldback = params.new_provider_holdback_epochs ?? normalHoldback; + const threshold = params.probation_successful_sessions ?? 0; + const successfulSessions = provider?.probation?.successful_sessions ?? 0; + if ( + !Number.isSafeInteger(normalHoldback) || + !Number.isSafeInteger(newProviderHoldback) || + !Number.isSafeInteger(threshold) || + !Number.isSafeInteger(successfulSessions) || + normalHoldback < 0 || + newProviderHoldback < 0 || + threshold < 0 || + successfulSessions < 0 + ) { + return new Error('Invalid provider holdback probation state.'); + } + const providerHoldback = successfulSessions < threshold + ? Math.max(normalHoldback, newProviderHoldback) + : normalHoldback; + return Math.max(providerHoldback, params.challenge_epochs ?? 0); + } + + async recordCanaryProbePass(value, auditor) { + const key = `probe/pass/${value.provider}/${value.epoch}`; + const current = await this.get(key); + const auditors = current?.auditors ?? []; + const probes = current?.probes ?? []; + if (!Array.isArray(auditors) || !Array.isArray(probes) || auditors.length !== probes.length) { + return new Error('Invalid canary pass record.'); + } + if (auditors.includes(auditor)) { + return new Error('Auditor already supplied a canary pass for this provider epoch.'); + } + const next = [...probes, { + auditor, + probe_id: value.probe_id, + evidence_hash: value.evidence_hash, + challenge_seed: value.challenge_seed, + }].sort((left, right) => compareCodepoint(left.auditor, right.auditor)); + const record = { + provider: value.provider, + epoch: value.epoch, + pass_count: next.length, + auditors: next.map((entry) => entry.auditor), + probes: next, + last_probe_id: value.probe_id, + last_evidence_hash: value.evidence_hash ?? null, + updated_at: this.tx, + }; + await this.put(key, record); + return record; + } + + async probeGateForEarning(provider, earning, params) { + const holdbackBps = params.canary_probe_holdback_bps ?? 0; + const requiredPasses = params.canary_probe_release_min_passes ?? 0; + if (holdbackBps <= 0 || requiredPasses <= 0) return null; + if (!Number.isSafeInteger(holdbackBps) || holdbackBps < 0 || holdbackBps > 10_000) { + return new Error('Invalid canary probe holdback bps.'); + } + if (!Number.isSafeInteger(requiredPasses) || requiredPasses < 2) { + return new Error('Invalid canary probe release threshold.'); + } + const holdbacks = this.normalizeHoldbackBuckets(earning); + if (holdbacks instanceof Error) return holdbacks; + const passedEpochs = new Set(); + for (const epoch of [...new Set(holdbacks.map((bucket) => bucket.epoch))]) { + const passRecord = await this.get(`probe/pass/${provider}/${epoch}`); + const distinctAuditors = new Set(passRecord?.auditors ?? []); + let activeAuditors = 0; + for (const auditor of distinctAuditors) { + if ((await this.get(`auditor/${auditor}`))?.status === 'active') activeAuditors += 1; + } + if ( + distinctAuditors.size === (passRecord?.pass_count ?? 0) && + activeAuditors >= requiredPasses + ) { + passedEpochs.add(epoch); + } + } + return { + holdback_bps: holdbackBps, + required_passes: requiredPasses, + passed_epochs: passedEpochs, + }; + } + + normalizeHoldbackBuckets(record) { + if (!Array.isArray(record.holdbacks)) { + const heldAu = this.normalizeAu(record.held_au, 'earning held amount'); + if (heldAu instanceof Error) { + return new Error('Guardian non-negative earnings invariant failed.'); + } + if (this.isZeroAu(heldAu)) return []; + return [{ + epoch: Number.isSafeInteger(record.updated_epoch) ? record.updated_epoch : 0, + au: heldAu, + }]; + } + + const byEpoch = new Map(); + for (const bucket of record.holdbacks) { + if (!bucket || typeof bucket !== 'object' || Array.isArray(bucket)) { + return new Error('Guardian earnings conservation invariant failed.'); + } + if (!Number.isSafeInteger(bucket.epoch) || bucket.epoch < 0) { + return new Error('Guardian monotonic epoch invariant failed.'); + } + const bucketAu = this.normalizeAu(bucket.au, 'holdback bucket amount', { allowZero: false }); + if (bucketAu instanceof Error) { + return new Error('Guardian non-negative earnings invariant failed.'); + } + const lockedEpochs = hasOwn(bucket, 'locked_epochs') ? bucket.locked_epochs : null; + if (lockedEpochs !== null && (!Number.isSafeInteger(lockedEpochs) || lockedEpochs < 0)) { + return new Error('Guardian monotonic epoch invariant failed.'); + } + const key = `${bucket.epoch}:${bucket.probe_gate === true ? 'probe' : 'time'}:${lockedEpochs ?? 'default'}`; + const next = this.safeAddAu(byEpoch.get(key)?.au ?? ZERO_AU, bucketAu); + if (next instanceof Error) return next; + byEpoch.set(key, { + epoch: bucket.epoch, + au: next, + probe_gate: bucket.probe_gate === true, + ...(lockedEpochs !== null ? { locked_epochs: lockedEpochs } : {}), + }); + } + return Array.from(byEpoch.values()) + .sort((a, b) => ( + a.epoch - b.epoch || + (a.locked_epochs ?? -1) - (b.locked_epochs ?? -1) || + Number(a.probe_gate) - Number(b.probe_gate) + )) + .map((bucket) => ( + bucket.probe_gate + ? { + epoch: bucket.epoch, + au: bucket.au, + probe_gate: true, + ...(hasOwn(bucket, 'locked_epochs') ? { locked_epochs: bucket.locked_epochs } : {}), + } + : { + epoch: bucket.epoch, + au: bucket.au, + ...(hasOwn(bucket, 'locked_epochs') ? { locked_epochs: bucket.locked_epochs } : {}), + } + )); + } + + holdbackBucketTotal(holdbacks) { + let total = ZERO_AU; + for (const bucket of holdbacks) { + const next = this.safeAddAu(total, bucket.au); + if (next instanceof Error) return next; + total = next; + } + return total; + } + + refreshEarningHoldback(record, currentEpoch, lockedEpochs, probeGate = null, disputeGate = false) { + if (!Number.isSafeInteger(currentEpoch) || currentEpoch < 0) { + return new Error('Guardian monotonic epoch invariant failed.'); + } + if (!Number.isSafeInteger(lockedEpochs) || lockedEpochs < 0) { + return new Error('Guardian monotonic epoch invariant failed.'); + } + const holdbacks = this.normalizeHoldbackBuckets(record); + if (holdbacks instanceof Error) return holdbacks; + const kept = []; + for (const bucket of holdbacks) { + const bucketLockedEpochs = hasOwn(bucket, 'locked_epochs') ? bucket.locked_epochs : lockedEpochs; + if (!Number.isSafeInteger(bucketLockedEpochs) || bucketLockedEpochs < 0) { + return new Error('Guardian monotonic epoch invariant failed.'); + } + if (disputeGate || bucket.epoch + bucketLockedEpochs > currentEpoch) { + kept.push(bucket); + continue; + } + if (!probeGate) continue; + if (probeGate.passed_epochs.has(bucket.epoch)) continue; + if (bucket.probe_gate === true) { + kept.push(bucket); + continue; + } + const gatedAu = this.safeMulDivAu(bucket.au, probeGate.holdback_bps, 10_000); + if (gatedAu instanceof Error) return gatedAu; + if (this.compareAu(gatedAu, ZERO_AU) > 0) { + kept.push({ + epoch: bucket.epoch, + au: gatedAu, + probe_gate: true, + ...(hasOwn(bucket, 'locked_epochs') ? { locked_epochs: bucket.locked_epochs } : {}), + }); + } + } + const heldAu = this.holdbackBucketTotal(kept); + if (heldAu instanceof Error) return heldAu; + return { + ...record, + held_au: heldAu, + holdbacks: kept, + last_holdback_release_epoch: currentEpoch, + }; + } + + slashHoldbackBuckets(holdbacks, slashAu) { + const slashAmountAu = this.normalizeAu(slashAu, 'slash amount'); + if (slashAmountAu instanceof Error) { + return new Error('Guardian non-negative earnings invariant failed.'); + } + if (this.isZeroAu(slashAmountAu)) return holdbacks; + + let remaining = slashAmountAu; + const kept = []; + for (let idx = holdbacks.length - 1; idx >= 0; idx -= 1) { + const bucket = holdbacks[idx]; + if (this.isZeroAu(remaining)) { + kept.push(bucket); + continue; + } + if (this.compareAu(bucket.au, remaining) <= 0) { + remaining = this.safeSubAu(remaining, bucket.au); + if (remaining instanceof Error) return remaining; + continue; + } + const reducedAu = this.safeSubAu(bucket.au, remaining); + if (reducedAu instanceof Error) return reducedAu; + kept.push({ + ...bucket, + epoch: bucket.epoch, + au: reducedAu, + }); + remaining = ZERO_AU; + } + if (!this.isZeroAu(remaining)) return new Error('Guardian earnings conservation invariant failed.'); + return kept.reverse(); + } + + slashAmount(heldAu, slashBps) { + if (this.normalizeAu(heldAu, 'held amount') instanceof Error) { + return new Error('Guardian non-negative earnings invariant failed.'); + } + if (!Number.isSafeInteger(slashBps) || slashBps < 0 || slashBps > 10_000) { + return new Error('Invalid slash bps.'); + } + return this.safeMulDivAu(heldAu, slashBps, 10_000); + } + + providerActiveEnclaves(provider) { + if (!provider || !Array.isArray(provider.enclaves)) return []; + return [...new Set(provider.enclaves.filter((enclaveId) => this.isSafeKeyPart(enclaveId)))].sort(); + } + + providerEnclavesWith(provider, enclaveId) { + const enclaves = this.providerActiveEnclaves(provider); + if (!enclaves.includes(enclaveId)) enclaves.push(enclaveId); + return enclaves.sort(); + } + + providerEnclavesWithout(provider, enclaveId) { + return this.providerActiveEnclaves(provider).filter((activeEnclaveId) => activeEnclaveId !== enclaveId); + } + + enclaveActiveProviders(enclave) { + if (!enclave || !Array.isArray(enclave.providers)) return []; + return [...new Set(enclave.providers.filter((providerId) => this.isSafeKeyPart(providerId)))].sort(); + } + + enclaveProvidersWith(enclave, providerId) { + const providers = this.enclaveActiveProviders(enclave); + if (!providers.includes(providerId)) providers.push(providerId); + return providers.sort(); + } + + enclaveProvidersWithout(enclave, providerId) { + return this.enclaveActiveProviders(enclave).filter((activeProviderId) => activeProviderId !== providerId); + } + + roomServingEntries(room) { + if (!room || !Array.isArray(room.serves)) return []; + const entries = new Map(); + for (const entry of room.serves) { + if (!entry || typeof entry !== 'object' || Array.isArray(entry)) continue; + if (!this.isSafeKeyPart(entry.provider) || !this.isSafeKeyPart(entry.enclave_id)) continue; + entries.set(JSON.stringify([entry.provider, entry.enclave_id]), { + provider: entry.provider, + enclave_id: entry.enclave_id, + }); + } + return Array.from(entries.values()).sort((a, b) => ( + compareCodepoint(a.provider, b.provider) || compareCodepoint(a.enclave_id, b.enclave_id) + )); + } + + roomServesWith(room, providerId, enclaveId) { + const entries = this.roomServingEntries(room); + if (!entries.some((entry) => entry.provider === providerId && entry.enclave_id === enclaveId)) { + entries.push({ provider: providerId, enclave_id: enclaveId }); + } + return entries.sort((a, b) => ( + compareCodepoint(a.provider, b.provider) || compareCodepoint(a.enclave_id, b.enclave_id) + )); + } + + roomServesWithout(room, providerId, enclaveId = null) { + return this.roomServingEntries(room).filter((entry) => ( + entry.provider !== providerId || (enclaveId !== null && entry.enclave_id !== enclaveId) + )); + } + + async tombstoneRoomServes(roomId, entries, evidenceHash) { + if (!this.isSafeKeyPart(roomId)) return new Error('Invalid room id.'); + const tombstones = []; + for (const entry of entries) { + const tombstone = await this.tombstoneRoomServing( + roomId, + entry.provider, + entry.enclave_id, + evidenceHash + ); + if (tombstone instanceof Error) return tombstone; + tombstones.push(tombstone); + } + return tombstones; + } + + async tombstoneRoomServing(roomId, providerId, enclaveId, evidenceHash) { + if (!this.isSafeKeyPart(roomId)) return new Error('Invalid room id.'); + if (!this.isSafeKeyPart(providerId)) return new Error('Invalid provider id.'); + if (!this.isSafeKeyPart(enclaveId)) return new Error('Invalid enclave id.'); + + const roomServeKey = `roomserve/${roomId}/${providerId}/${enclaveId}`; + const roomServing = await this.get(roomServeKey); + const roomKey = `room/${roomId}`; + const room = await this.get(roomKey); + const servingKey = `serve/${providerId}/${enclaveId}`; + const serving = await this.get(servingKey); + + if (room) { + await this.put(roomKey, { + ...room, + serves: this.roomServesWithout(room, providerId, enclaveId), + serves_updated_at: this.tx, + }); + } + if (serving) { + await this.put(servingKey, { + ...serving, + rooms: Array.isArray(serving.rooms) + ? serving.rooms.filter((activeRoomId) => activeRoomId !== roomId) + : [], + updated_at: this.tx, + }); + } + if (!roomServing || roomServing.status !== 'active') { + return { + room_id: roomId, + provider: providerId, + enclave_id: enclaveId, + roomserve_tombstoned: false, + }; + } + + await this.put(roomServeKey, { + ...roomServing, + status: 'tombstoned', + updated_at: this.tx, + tombstoned_at: this.tx, + tombstone_reason_hash: evidenceHash, + }); + return { + room_id: roomId, + provider: providerId, + enclave_id: enclaveId, + roomserve_tombstoned: true, + }; + } + + async tombstoneEnclaveProviders(enclaveId, providerIds, evidenceHash) { + if (!this.isSafeKeyPart(enclaveId)) return new Error('Invalid enclave id.'); + const tombstones = []; + for (const providerId of [...new Set(providerIds)].sort()) { + const tombstone = await this.tombstoneProviderEnclave(providerId, enclaveId, evidenceHash); + if (tombstone instanceof Error) return tombstone; + tombstones.push(tombstone); + } + return tombstones; + } + + async tombstoneProviderEnclaves(providerId, enclaveIds, evidenceHash) { + if (!this.isSafeKeyPart(providerId)) return new Error('Invalid provider id.'); + const tombstones = []; + for (const enclaveId of [...new Set(enclaveIds)].sort()) { + const tombstone = await this.tombstoneProviderEnclave(providerId, enclaveId, evidenceHash); + if (tombstone instanceof Error) return tombstone; + tombstones.push(tombstone); + } + return tombstones; + } + + async tombstoneProviderEnclave(providerId, enclaveId, evidenceHash) { + if (!this.isSafeKeyPart(providerId)) return new Error('Invalid provider id.'); + if (!enclaveId) { + return { + provider: providerId, + enclave_id: null, + serve_tombstoned: false, + rooms_tombstoned: [], + }; + } + if (!this.isSafeKeyPart(enclaveId)) return new Error('Invalid enclave id.'); + + const serveKey = `serve/${providerId}/${enclaveId}`; + const serving = await this.get(serveKey); + if (!serving) { + return { + provider: providerId, + enclave_id: enclaveId, + serve_tombstoned: false, + rooms_tombstoned: [], + }; + } + + const rooms = Array.isArray(serving.rooms) ? serving.rooms.slice().sort() : []; + const tombstonedRooms = []; + for (const roomId of rooms) { + const tombstone = await this.tombstoneRoomServing(roomId, providerId, enclaveId, evidenceHash); + if (tombstone instanceof Error) return tombstone; + if (tombstone.roomserve_tombstoned) tombstonedRooms.push(roomId); + } + + await this.put(serveKey, { + ...serving, + status: 'tombstoned', + rooms: [], + updated_at: this.tx, + tombstoned_at: this.tx, + tombstone_reason_hash: evidenceHash, + }); + const provider = await this.get(`prov/${providerId}`); + if (provider) { + await this.put(`prov/${providerId}`, { + ...provider, + enclaves: this.providerEnclavesWithout(provider, enclaveId), + updated_at: this.tx, + }); + } + const enclave = await this.get(`enclave/${enclaveId}`); + if (enclave) { + await this.put(`enclave/${enclaveId}`, { + ...enclave, + providers: this.enclaveProvidersWithout(enclave, providerId), + updated_at: this.tx, + }); + } + return { + provider: providerId, + enclave_id: enclaveId, + serve_tombstoned: true, + rooms_tombstoned: tombstonedRooms, + }; + } + + async applyProviderSlash({ + providerId, + source, + reason, + evidenceHash, + epoch, + at, + slashBps, + beneficiary = null, + enclaveId = null, + probeId = null, + eventId = null, + banProvider = false, + tombstoneEnclave = false, + }) { + if (!this.isSafeKeyPart(providerId)) return new Error('Invalid provider id.'); + if (beneficiary !== null && !this.isSafeKeyPart(beneficiary)) { + return new Error('Invalid slash beneficiary.'); + } + + const providerKey = `prov/${providerId}`; + const provider = await this.get(providerKey); + if (!provider) return new Error('Provider not found.'); + + const updatedEarnings = new Map(); + const beneficiaryBalances = new Map(); + const updatedFees = new Map(); + const railSlashRecords = []; + let heldBeforeAu = ZERO_AU; + let heldAfterAu = ZERO_AU; + let forfeitedAu = ZERO_AU; + let reporterAu = ZERO_AU; + let treasuryAu = ZERO_AU; + + for (const rail of PROVIDER_ACCEPTED_RAIL_ORDER) { + const earning = await this.earningRecord(providerId, rail); + if (earning instanceof Error) return earning; + const earningError = this.guardianValidateEarningRecord(earning, providerId, rail); + if (earningError) return earningError; + const holdbacks = this.normalizeHoldbackBuckets(earning); + if (holdbacks instanceof Error) return holdbacks; + + const railForfeitedAu = this.slashAmount(earning.held_au, slashBps); + if (railForfeitedAu instanceof Error) return railForfeitedAu; + const railReporterAu = beneficiary === null ? ZERO_AU : this.safeMulDivAu(railForfeitedAu, 1, 2); + if (railReporterAu instanceof Error) return railReporterAu; + const railTreasuryAu = this.safeSubAu(railForfeitedAu, railReporterAu); + if (railTreasuryAu instanceof Error) return railTreasuryAu; + const remainingHoldbacks = this.slashHoldbackBuckets(holdbacks, railForfeitedAu); + if (remainingHoldbacks instanceof Error) return remainingHoldbacks; + const railHeldAfterAu = this.safeSubAu(earning.held_au, railForfeitedAu); + if (railHeldAfterAu instanceof Error) return railHeldAfterAu; + const railTotalAu = this.safeSubAu(earning.total_au, railForfeitedAu); + if (railTotalAu instanceof Error) return railTotalAu; + const slashedCumAu = this.safeAddAu(earning.slashed_cum_au ?? ZERO_AU, railForfeitedAu); + if (slashedCumAu instanceof Error) return slashedCumAu; + + heldBeforeAu = this.safeAddAu(heldBeforeAu, earning.held_au); + if (heldBeforeAu instanceof Error) return heldBeforeAu; + heldAfterAu = this.safeAddAu(heldAfterAu, railHeldAfterAu); + if (heldAfterAu instanceof Error) return heldAfterAu; + forfeitedAu = this.safeAddAu(forfeitedAu, railForfeitedAu); + if (forfeitedAu instanceof Error) return forfeitedAu; + reporterAu = this.safeAddAu(reporterAu, railReporterAu); + if (reporterAu instanceof Error) return reporterAu; + treasuryAu = this.safeAddAu(treasuryAu, railTreasuryAu); + if (treasuryAu instanceof Error) return treasuryAu; + + if (this.compareAu(earning.total_au, ZERO_AU) > 0 || this.compareAu(railForfeitedAu, ZERO_AU) > 0) { + const updatedEarning = { + ...earning, + rail, + total_au: railTotalAu, + held_au: railHeldAfterAu, + holdbacks: remainingHoldbacks, + slashed_cum_au: slashedCumAu, + last_slash_at: this.tx, + updated_at: this.tx, + }; + const updatedEarningError = this.guardianValidateEarningRecord(updatedEarning, providerId, rail); + if (updatedEarningError) return updatedEarningError; + updatedEarnings.set(rail, updatedEarning); + } + + if (this.compareAu(railReporterAu, ZERO_AU) > 0) { + const currentBalance = await this.balanceRecord(beneficiary, rail); + if (currentBalance instanceof Error) return currentBalance; + const balanceError = this.guardianValidateBalanceRecord(currentBalance, beneficiary, rail); + if (balanceError) return balanceError; + const nextAu = this.safeAddAu(currentBalance.au, railReporterAu); + if (nextAu instanceof Error) return nextAu; + beneficiaryBalances.set(rail, { + ...currentBalance, + rail, + au: nextAu, + updated_epoch: Math.max(currentBalance.updated_epoch, epoch), + updated_at: this.tx, + }); + } + + if (this.compareAu(railTreasuryAu, ZERO_AU) > 0) { + const fee = await this.feeCumRecord(rail); + if (fee instanceof Error) return fee; + const feeError = this.guardianValidateFeeRecord(fee, rail); + if (feeError) return feeError; + const cumAu = this.safeAddAu(fee.cum_au, railTreasuryAu); + if (cumAu instanceof Error) return cumAu; + const settledCumAu = this.safeAddAu(fee.settled_cum_au ?? fee.cum_au, railTreasuryAu); + if (settledCumAu instanceof Error) return settledCumAu; + const updatedFee = { + ...fee, + rail, + cum_au: cumAu, + settled_cum_au: settledCumAu, + updated_epoch: Math.max(fee.updated_epoch, epoch), + updated_at: this.tx, + last_slash_at: this.tx, + }; + const updatedFeeError = this.guardianValidateFeeRecord(updatedFee, rail); + if (updatedFeeError) return updatedFeeError; + updatedFees.set(rail, updatedFee); + } + + if (this.compareAu(earning.held_au, ZERO_AU) > 0 || this.compareAu(railForfeitedAu, ZERO_AU) > 0) { + railSlashRecords.push({ + rail, + held_before_au: earning.held_au, + held_after_au: railHeldAfterAu, + forfeited_au: railForfeitedAu, + beneficiary_au: railReporterAu, + treasury_au: railTreasuryAu, + }); + } + } + + const tombstone = tombstoneEnclave + ? await this.tombstoneProviderEnclave(providerId, enclaveId, evidenceHash) + : { + enclave_id: enclaveId, + serve_tombstoned: false, + rooms_tombstoned: [], + }; + if (tombstone instanceof Error) return tombstone; + const banTombstones = banProvider + ? await this.tombstoneProviderEnclaves( + providerId, + this.providerActiveEnclaves(provider).filter((activeEnclaveId) => activeEnclaveId !== enclaveId), + evidenceHash + ) + : []; + if (banTombstones instanceof Error) return banTombstones; + + const slash = { + type: 'slash', + provider: providerId, + source, + reason, + evidence_hash: evidenceHash, + epoch, + at, + tx: this.tx, + slashed_by: this.address, + beneficiary, + enclave_id: enclaveId, + probe_id: probeId, + event_id: eventId, + slash_bps: slashBps, + held_before_au: heldBeforeAu, + held_after_au: heldAfterAu, + forfeited_au: forfeitedAu, + beneficiary_au: reporterAu, + treasury_au: treasuryAu, + rails: railSlashRecords, + tombstone, + ban_tombstones: banTombstones, + provider_banned: banProvider, + }; + slash.slash_hash = await this.opaqueHash('mayhem-slash-v1', slash); + + const updatedProvider = banProvider + ? { + ...provider, + status: 'banned', + enclaves: [], + tombstoned_enclaves: [tombstone, ...banTombstones] + .filter((entry) => entry.enclave_id) + .map((entry) => entry.enclave_id), + banned_at: provider.banned_at ?? this.tx, + banned_by: provider.banned_by ?? this.address, + ban_reason_hash: provider.ban_reason_hash ?? evidenceHash, + updated_at: this.tx, + } + : { + ...provider, + enclaves: tombstone.serve_tombstoned + ? this.providerEnclavesWithout(provider, tombstone.enclave_id) + : this.providerActiveEnclaves(provider), + updated_at: this.tx, + }; + + for (const [rail, updatedEarning] of updatedEarnings) { + await this.put(this.earningKey(providerId, rail), updatedEarning); + } + for (const [rail, beneficiaryBalance] of beneficiaryBalances) { + await this.put(this.balanceKey(beneficiary, rail), beneficiaryBalance); + } + for (const [rail, updatedFee] of updatedFees) { + await this.put(this.feeCumKey(rail), updatedFee); + } + await this.put(providerKey, updatedProvider); + await this.put(`ev/slash/${providerId}/${this.tx}`, slash); + return slash; + } + + appendHoldbackBucket(holdbacks, epoch, au, lockedEpochs = null) { + if (!Number.isSafeInteger(epoch) || epoch < 0) { + return new Error('Guardian monotonic epoch invariant failed.'); + } + const amount = this.normalizeAu(au, 'holdback amount', { allowZero: false }); + if (amount instanceof Error) { + return new Error('Guardian non-negative earnings invariant failed.'); + } + if (lockedEpochs !== null && (!Number.isSafeInteger(lockedEpochs) || lockedEpochs < 0)) { + return new Error('Guardian monotonic epoch invariant failed.'); + } + const normalized = this.normalizeHoldbackBuckets({ holdbacks }); + if (normalized instanceof Error) return normalized; + const gated = normalized.filter((bucket) => bucket.probe_gate === true); + const byEpoch = new Map( + normalized + .filter((bucket) => bucket.probe_gate !== true) + .map((bucket) => [ + `${bucket.epoch}:${hasOwn(bucket, 'locked_epochs') ? bucket.locked_epochs : 'default'}`, + bucket, + ]) + ); + const key = `${epoch}:${lockedEpochs ?? 'default'}`; + const current = byEpoch.get(key); + const next = this.safeAddAu(current?.au ?? ZERO_AU, amount); + if (next instanceof Error) return next; + byEpoch.set(key, { + epoch, + au: next, + ...(lockedEpochs !== null ? { locked_epochs: lockedEpochs } : {}), + }); + return [ + ...Array.from(byEpoch.values()) + .map((bucket) => ({ + epoch: bucket.epoch, + au: bucket.au, + ...(hasOwn(bucket, 'locked_epochs') ? { locked_epochs: bucket.locked_epochs } : {}), + })), + ...gated, + ] + .sort((a, b) => ( + a.epoch - b.epoch || + (a.locked_epochs ?? -1) - (b.locked_epochs ?? -1) || + Number(a.probe_gate === true) - Number(b.probe_gate === true) + )); + } + + normalizeEpochRoots(value) { + if (!value || typeof value !== 'object' || Array.isArray(value)) { + return new Error('Epoch roots must be an object.'); + } + const keys = Object.keys(value).sort(); + if ( + keys.length !== EPOCH_ROOT_KEYS.length || + keys.some((key, idx) => key !== EPOCH_ROOT_KEYS.slice().sort()[idx]) + ) { + return new Error('Epoch roots must include dep, use, earn, fee, and price.'); + } + const roots = {}; + for (const key of EPOCH_ROOT_KEYS) { + const root = value[key]; + if (typeof root !== 'string' || !/^[0-9a-fA-F]{64}$/.test(root)) { + return new Error(`Invalid epoch ${key} root.`); + } + roots[key] = root.toLowerCase(); + } + return roots; + } + + normalizeEpochTotals(value) { + if (!value || typeof value !== 'object' || Array.isArray(value)) { + return new Error('Epoch totals must be an object.'); + } + const expected = EPOCH_TOTAL_KEYS.slice().sort(); + const keys = Object.keys(value).sort(); + if (keys.length !== expected.length || keys.some((key, idx) => key !== expected[idx])) { + return new Error('Epoch totals have an invalid shape.'); + } + const totals = {}; + for (const key of EPOCH_TOTAL_KEYS) { + const total = value[key]; + if (EPOCH_TOTAL_MONEY_KEYS.has(key)) { + const au = this.normalizeAu(total, `epoch total ${key}`); + if (au instanceof Error) { + return new Error(`Invalid epoch total ${key}.`); + } + totals[key] = au; + continue; + } + if (!Number.isSafeInteger(total) || total < 0) { + return new Error(`Invalid epoch total ${key}.`); + } + totals[key] = total; + } + return totals; + } + + canonicalUsageUnit(unit) { + switch (unit) { + case 'in': + case 'in_tokens': + case 'input': + case 'input_tokens': + case 'prompt_tokens': + case 'input_token': + return 'input_token'; + case 'cached_input': + case 'cached_inputs': + case 'cached_input_tokens': + case 'cached_prompt_tokens': + case 'cached_tokens': + case 'cached_input_token': + return 'cached_input_token'; + case 'out': + case 'out_tokens': + case 'output': + case 'output_tokens': + case 'completion_tokens': + case 'output_token': + return 'output_token'; + case 'images': + case 'image': + return 'image'; + case 'steps': + case 'step': + return 'step'; + default: + return unit; + } + } + + normalizeReceiptUsage(usageSource) { + if (!usageSource || typeof usageSource !== 'object' || Array.isArray(usageSource)) { + return new Error('Fraud proof receipt usage must be an object.'); + } + const usage = {}; + for (const [rawUnit, count] of Object.entries(usageSource)) { + if (typeof rawUnit !== 'string' || rawUnit.length === 0 || rawUnit.length > 64) { + return new Error('Invalid receipt usage unit.'); + } + if (!Number.isSafeInteger(count) || count < 0) { + return new Error('Invalid receipt usage count.'); + } + if (count === 0) continue; + const unit = this.canonicalUsageUnit(rawUnit); + if (!this.isSafeKeyPart(unit)) return new Error('Invalid receipt usage unit.'); + const next = this.safeAddCount(usage[unit] ?? 0, count, 'receipt usage count'); + if (next instanceof Error) return next; + usage[unit] = next; + } + return Object.fromEntries(Object.entries(usage).sort(([left], [right]) => compareCodepoint(left, right))); + } + + normalizeWorkflowBinding(source, label, rateMap = null) { + const shapeError = this.validateExactObjectKeys( + source, + ['endpoint_family', 'graph_hash', 'runtime_id', 'outcome_class', 'quoted_usage'], + label + ); + if (shapeError) return shapeError; + if (!this.isSafeKeyPart(source.endpoint_family)) { + return new Error(`${label} endpoint_family is invalid.`); + } + if (!this.isHexBytes(source.graph_hash, 32)) { + return new Error(`${label} graph_hash is invalid.`); + } + if (!this.isSafeKeyPart(source.runtime_id)) { + return new Error(`${label} runtime_id is invalid.`); + } + if (!this.isSafeKeyPart(source.outcome_class)) { + return new Error(`${label} outcome_class is invalid.`); + } + const quotedUsage = this.normalizeReceiptUsage(source.quoted_usage); + if (quotedUsage instanceof Error || Object.keys(quotedUsage).length === 0) { + return new Error(`${label} quoted_usage is invalid.`); + } + if (rateMap !== null) { + const quotedAu = this.usageAuForRateMap(rateMap, quotedUsage); + if (quotedAu instanceof Error) return new Error(`${label} quoted_usage is not priced by the locked rate_map.`); + } + return { + endpoint_family: source.endpoint_family, + graph_hash: source.graph_hash.toLowerCase(), + runtime_id: source.runtime_id, + outcome_class: source.outcome_class, + quoted_usage: quotedUsage, + }; + } + + normalizeWorkflowOutputBinding(source, label) { + const shapeError = this.validateExactObjectKeys( + source, + ['output_modalities', 'metrics'], + label + ); + if (shapeError) return shapeError; + const modalityError = this.validateModalitySet(source.output_modalities, `${label} output_modalities`); + if (modalityError) return modalityError; + if (!source.metrics || typeof source.metrics !== 'object' || Array.isArray(source.metrics)) { + return new Error(`${label} metrics must be an object.`); + } + const metricEntries = Object.entries(source.metrics); + if (metricEntries.length === 0 || metricEntries.length > 32) { + return new Error(`${label} metrics must contain between 1 and 32 entries.`); + } + const metrics = {}; + for (const [key, count] of metricEntries) { + if (!this.isSafeKeyPart(key)) return new Error(`${label} metric key is invalid.`); + if (!Number.isSafeInteger(count) || count <= 0) { + return new Error(`${label} metric count is invalid.`); + } + metrics[key] = count; + } + return { + output_modalities: source.output_modalities.slice(), + metrics: Object.fromEntries( + Object.entries(metrics).sort(([left], [right]) => compareCodepoint(left, right)) + ), + }; + } + + normalizeReceiptUsageAttribution(source) { + if (source === undefined || source === null) return {}; + if (!source || typeof source !== 'object' || Array.isArray(source)) { + return new Error('Receipt usage attribution must be an object.'); + } + const allowed = new Set([ + 'context_input_tokens', + 'reasoning_output_tokens', + 'vision_input_tokens', + 'audio_input_tokens', + ]); + const normalized = {}; + for (const [axis, count] of Object.entries(source)) { + if (!allowed.has(axis)) return new Error(`Unsupported receipt usage attribution ${axis}.`); + if (!Number.isSafeInteger(count) || count < 0) { + return new Error('Invalid receipt usage attribution count.'); + } + if (count > 0) normalized[axis] = count; + } + return Object.fromEntries( + Object.entries(normalized).sort(([left], [right]) => compareCodepoint(left, right)) + ); + } + + async normalizeReceiptEnvelope(value, options = {}) { + const targetSchemaVersion = options.targetSchemaVersion ?? SESSION_RECEIPT_SCHEMA_VERSION; + if (!value || typeof value !== 'object' || Array.isArray(value)) { + return new Error('Fraud proof receipt must be an object.'); + } + const receipt = value.receipt ?? value; + if (!receipt || typeof receipt !== 'object' || Array.isArray(receipt)) { + return new Error('Fraud proof receipt must be an object.'); + } + const bodySource = receipt.body ?? receipt; + if (!bodySource || typeof bodySource !== 'object' || Array.isArray(bodySource)) { + return new Error('Fraud proof receipt body must be an object.'); + } + const body = { + schema_version: bodySource.schema_version, + session_id: bodySource.session_id, + billing_id: bodySource.billing_id, + billing_attempt: bodySource.billing_attempt, + billing_prior_usage: cloneValue(bodySource.billing_prior_usage), + billing_prior_au_owed_cum: bodySource.billing_prior_au_owed_cum, + billing_epoch: bodySource.billing_epoch, + reservation_id: bodySource.reservation_id, + reservation_expires_after_epoch: bodySource.reservation_expires_after_epoch, + reservation_receipt_grace_epochs: bodySource.reservation_receipt_grace_epochs, + payout_revision: bodySource.payout_revision, + seq: bodySource.seq, + final: bodySource.final, + rail: bodySource.rail, + user: bodySource.user, + provider: bodySource.provider, + enclave_id: bodySource.enclave_id, + model_id: bodySource.model_id, + price_ver: bodySource.price_ver, + locked_rate_map: cloneValue(bodySource.locked_rate_map), + rules_ver: bodySource.rules_ver, + usage: cloneValue(bodySource.usage), + au_owed_cum: bodySource.au_owed_cum, + prompt_hash: bodySource.prompt_hash, + ts: bodySource.ts, + }; + if (hasOwn(bodySource, 'usage_attribution')) { + body.usage_attribution = cloneValue(bodySource.usage_attribution); + } + if (hasOwn(bodySource, 'locked_per_req_au')) body.locked_per_req_au = bodySource.locked_per_req_au; + if (hasOwn(bodySource, 'locked_min_session_au')) body.locked_min_session_au = bodySource.locked_min_session_au; + if (hasOwn(bodySource, 'served_ctx')) body.served_ctx = bodySource.served_ctx; + if (hasOwn(bodySource, 'ctx_bracket')) body.ctx_bracket = bodySource.ctx_bracket; + if (hasOwn(bodySource, 'ctx_bracket_table_ver')) { + body.ctx_bracket_table_ver = bodySource.ctx_bracket_table_ver; + } + if (hasOwn(bodySource, 'workflow')) { + body.workflow = cloneValue(bodySource.workflow); + } + if (hasOwn(bodySource, 'workflow_output')) { + body.workflow_output = cloneValue(bodySource.workflow_output); + } + + if (body.schema_version !== targetSchemaVersion) { + return new Error('Unsupported receipt schema version.'); + } + + const bodyError = await this.validateReceiptBody(body, targetSchemaVersion); + if (bodyError) return bodyError; + + const envelope = { + body, + enclave_sig: receipt.enclave_sig ?? value.enclave_sig, + user_sig: receipt.user_sig ?? value.user_sig, + enclave_pubkey: receipt.enclave_pubkey ?? value.enclave_pubkey ?? bodySource.enclave_pubkey ?? null, + }; + if (!this.isHexBytes(envelope.enclave_sig, 64)) return new Error('Invalid enclave receipt signature.'); + if (!this.isHexBytes(envelope.user_sig, 64)) return new Error('Invalid user receipt signature.'); + if (!this.isHexBytes(envelope.enclave_pubkey, 32)) { + return new Error('Invalid enclave receipt public key.'); + } + envelope.enclave_pubkey = envelope.enclave_pubkey.toLowerCase(); + return envelope; + } + + async validateReceiptBody(body, expectedSchemaVersion = SESSION_RECEIPT_SCHEMA_VERSION) { + if (body.schema_version !== expectedSchemaVersion) { + return new Error('Unsupported receipt schema version.'); + } + for (const field of ['session_id', 'user', 'provider', 'enclave_id', 'model_id', 'prompt_hash']) { + if (typeof body[field] !== 'string' || body[field].length === 0 || body[field].length > 256) { + return new Error(`Invalid receipt ${field}.`); + } + } + if (!this.isHexBytes(body.billing_id, 32)) return new Error('Invalid receipt billing id.'); + if (!Number.isSafeInteger(body.billing_attempt) || body.billing_attempt < 0) { + return new Error('Invalid receipt billing attempt.'); + } + if (!Number.isSafeInteger(body.billing_epoch) || body.billing_epoch < 1) { + return new Error('Invalid receipt billing epoch.'); + } + if (!this.isHexBytes(body.reservation_id, 32)) { + return new Error('Invalid receipt reservation id.'); + } + if (!Number.isSafeInteger(body.reservation_expires_after_epoch) || + body.reservation_expires_after_epoch <= body.billing_epoch || + !Number.isSafeInteger(body.reservation_receipt_grace_epochs) || + body.reservation_receipt_grace_epochs < 0) { + return new Error('Invalid receipt reservation expiry policy.'); + } + if (!this.isHexBytes(body.payout_revision, 32)) { + return new Error('Invalid receipt payout revision.'); + } + const billingPriorUsage = this.normalizeReceiptUsage(body.billing_prior_usage); + if (billingPriorUsage instanceof Error) return billingPriorUsage; + if (stableJson(billingPriorUsage) !== stableJson(body.billing_prior_usage)) { + return new Error('Receipt billing prior usage must be canonical.'); + } + const billingPriorAuOwedCum = this.normalizeAu( + body.billing_prior_au_owed_cum, + 'receipt billing prior cumulative amount' + ); + if (billingPriorAuOwedCum instanceof Error) { + return new Error('Invalid receipt billing prior cumulative amount.'); + } + if ( + body.billing_attempt === 0 && + (Object.keys(billingPriorUsage).length > 0 || !this.isZeroAu(billingPriorAuOwedCum)) + ) { + return new Error('Initial receipt billing attempt must have an empty baseline.'); + } + if (this.isZeroAu(billingPriorAuOwedCum) && Object.keys(billingPriorUsage).length > 0) { + return new Error('Receipt billing prior usage requires a prior cumulative amount.'); + } + if (!this.isHexBytes(body.user, 32)) return new Error('Invalid receipt user public key.'); + if (!this.isHexBytes(body.provider, 32)) return new Error('Invalid receipt provider public key.'); + const rail = this.normalizeLedgerRail(body.rail, 'receipt rail'); + if (rail instanceof Error) return rail; + if (body.rail !== rail) return new Error('Receipt rail must be canonical.'); + if (!Number.isSafeInteger(body.seq) || body.seq < 0) return new Error('Invalid receipt sequence.'); + if (typeof body.final !== 'boolean') return new Error('Invalid receipt final flag.'); + if (!Number.isSafeInteger(body.price_ver) || body.price_ver < 1) { + return new Error('Invalid receipt price version.'); + } + const lockedRateMap = this.normalizeLockedRateMap(body.locked_rate_map, 'receipt locked_rate_map'); + if (lockedRateMap instanceof Error) return lockedRateMap; + if (stableJson(lockedRateMap) !== stableJson(body.locked_rate_map)) { + return new Error('Receipt locked_rate_map must be canonical.'); + } + const lockedPerReqAu = this.normalizeAu(body.locked_per_req_au, 'receipt locked per-request price'); + if (lockedPerReqAu instanceof Error) { + return new Error('Invalid receipt locked per-request price.'); + } + const lockedMinSessionAu = this.normalizeAu(body.locked_min_session_au, 'receipt locked minimum session price'); + if (lockedMinSessionAu instanceof Error) { + return new Error('Invalid receipt locked minimum session price.'); + } + if (!Number.isSafeInteger(body.served_ctx) || body.served_ctx < 0) { + return new Error('Invalid receipt served context.'); + } + const table = body.ctx_bracket_table_ver === null || body.ctx_bracket_table_ver === undefined + ? null + : await this.ctxBracketTableByVersion(body.ctx_bracket_table_ver); + if (table instanceof Error) return table; + const ctxMeta = await this.normalizeCtxBracketEvidenceForEnclave( + body.enclave_id, + body.served_ctx, + body.ctx_bracket, + body.ctx_bracket_table_ver, + table, + 'receipt' + ); + if (ctxMeta instanceof Error) return ctxMeta; + if (!Number.isSafeInteger(body.rules_ver) || body.rules_ver < 1) { + return new Error('Invalid receipt rules version.'); + } + let workflow = null; + if (hasOwn(body, 'workflow')) { + workflow = this.normalizeWorkflowBinding(body.workflow, 'receipt workflow', lockedRateMap); + if (workflow instanceof Error) return workflow; + if (stableJson(workflow) !== stableJson(body.workflow)) { + return new Error('Receipt workflow must be canonical.'); + } + } + if (hasOwn(body, 'workflow_output')) { + if (!workflow) return new Error('Receipt workflow_output requires workflow.'); + const workflowOutput = this.normalizeWorkflowOutputBinding( + body.workflow_output, + 'receipt workflow_output' + ); + if (workflowOutput instanceof Error) return workflowOutput; + if (stableJson(workflowOutput) !== stableJson(body.workflow_output)) { + return new Error('Receipt workflow_output must be canonical.'); + } + } else if (workflow) { + return new Error('Receipt workflow requires workflow_output.'); + } + const usage = this.normalizeReceiptUsage(body.usage); + if (usage instanceof Error) return usage; + if (stableJson(usage) !== stableJson(body.usage)) { + return new Error('Receipt usage must be canonical.'); + } + const usageAttribution = this.normalizeReceiptUsageAttribution(body.usage_attribution); + if (usageAttribution instanceof Error) return usageAttribution; + if (stableJson(usageAttribution) !== stableJson(body.usage_attribution ?? {})) { + return new Error('Receipt usage attribution must be canonical.'); + } + // Rendered context telemetry is not a billable usage axis. It may exceed + // canonical input units, but cannot exceed the signed served context. + if ((usageAttribution.context_input_tokens ?? 0) > body.served_ctx) { + return new Error('Receipt context attribution exceeds served context.'); + } + if ((usageAttribution.reasoning_output_tokens ?? 0) > (usage.output_token ?? 0)) { + return new Error('Receipt reasoning attribution exceeds billed output tokens.'); + } + if ((usageAttribution.vision_input_tokens ?? 0) > (usage.input_token ?? 0)) { + return new Error('Receipt vision attribution exceeds billed input tokens.'); + } + if ((usageAttribution.audio_input_tokens ?? 0) > (usage.input_token ?? 0)) { + return new Error('Receipt audio attribution exceeds billed input tokens.'); + } + const auOwedCum = this.normalizeAu(body.au_owed_cum, 'receipt cumulative amount'); + if (auOwedCum instanceof Error) { + return new Error('Invalid receipt cumulative amount.'); + } + const lockedAu = this.logicalCumulativeAuForLockedTerms( + body.locked_rate_map, + lockedPerReqAu, + lockedMinSessionAu, + billingPriorUsage, + billingPriorAuOwedCum, + usage + ); + if (lockedAu instanceof Error) return lockedAu; + if (this.compareAu(auOwedCum, lockedAu) !== 0) { + return new Error('Receipt cumulative amount does not match locked price terms.'); + } + if (!Number.isSafeInteger(body.ts) || body.ts < 0) return new Error('Invalid receipt timestamp.'); + return null; + } + + verifyReceiptEnvelope(envelope) { + const signedBody = envelope.body; + if (!signedBody || typeof signedBody !== 'object' || Array.isArray(signedBody)) return false; + const enclaveKey = envelope.enclave_pubkey ?? ( + this.isHexBytes(signedBody.enclave_id, 32) ? signedBody.enclave_id : null + ); + if (!enclaveKey) return false; + const message = receiptMessage(signedBody); + return ( + verifyEd25519Hex(envelope.enclave_sig, message, enclaveKey) && + verifyEd25519Hex(envelope.user_sig, message, signedBody.user) + ); + } + + receiptLeafEnvelope(envelope) { + return { + body: cloneValue(envelope.body), + enclave_sig: envelope.enclave_sig, + user_sig: envelope.user_sig, + }; + } + + async usageLeafHash(envelope) { + return await this.opaqueHash('mayhem-usage-leaf-v1', this.receiptLeafEnvelope(envelope)); + } + + async fraudProofHash(value) { + return await this.opaqueHash('mayhem-fraud-proof-v1', value); + } + + async validateOverCreditFraudProof(commit, receipt) { + if (commit.status === 'void') return new Error('Epoch commit is already void.'); + if (this.value.proof_epoch > commit.provisional_until_epoch) { + return new Error('Epoch commit challenge window has closed.'); + } + if (commit.totals.use_count !== 1) { + return new Error('Over-credit proof requires a single committed receipt.'); + } + + const previousAu = this.normalizeAu(this.value.previous_au_owed_cum ?? ZERO_AU, 'previous receipt amount'); + if (previousAu instanceof Error) { + return new Error('Invalid previous receipt amount.'); + } + const claimedCum = this.normalizeAu(this.value.claimed_au_owed_cum, 'claimed receipt amount'); + if (claimedCum instanceof Error) return new Error('Invalid claimed receipt amount.'); + if (this.compareAu(previousAu, receipt.body.au_owed_cum) > 0 || this.compareAu(previousAu, claimedCum) > 0) { + return new Error('Previous receipt amount exceeds cumulative amount.'); + } + const actualAu = this.safeSubAu(receipt.body.au_owed_cum, previousAu); + if (actualAu instanceof Error) return actualAu; + const claimedAu = this.safeSubAu(claimedCum, previousAu); + if (claimedAu instanceof Error) return claimedAu; + if (this.compareAu(claimedAu, actualAu) <= 0) return new Error('Receipt does not contradict committed usage.'); + if (this.compareAu(commit.totals.use_au, claimedAu) !== 0) { + return new Error('Fraud proof claimed amount does not match committed usage total.'); + } + + const claimedReceipt = { + ...receipt, + body: { + ...receipt.body, + au_owed_cum: claimedCum, + }, + }; + const claimedUseRoot = await this.usageLeafHash(claimedReceipt); + if (commit.roots.use !== claimedUseRoot) { + return new Error('Fraud proof does not match committed usage root.'); + } + + return { + actual_au: actualAu, + claimed_au: claimedAu, + receipt_hash: await this.usageLeafHash(receipt), + }; + } + + priceTermsSnapshot(record) { + return { + ver: record.ver, + ...(record.ctx_bracket ? { ctx_bracket: record.ctx_bracket } : {}), + ...(record.ctx_bracket_table_ver ? { ctx_bracket_table_ver: record.ctx_bracket_table_ver } : {}), + rate_map: cloneValue(record.rate_map), + per_req_au: record.per_req_au, + min_session_au: record.min_session_au, + }; + } + + priceDerivationLeafValue(derivation) { + const { + derivation_hash: _derivationHash, + price_root: _priceRoot, + updated_at: _updatedAt, + ...leaf + } = derivation; + return leaf; + } + + priceDerivationFromMarketUpdate(update, { epoch, at, epochSeconds = null, usageRoot = null } = {}) { + const record = update.record; + const market = record.market; + if (!record || !market || typeof market !== 'object') { + return new Error('Market price update is missing derivation data.'); + } + return { + type: 'price_derivation', + schema_version: 2, + epoch, + at, + epoch_seconds: epochSeconds, + enclave_id: record.enclave_id, + ...(record.ctx_bracket ? { + ctx_bracket: record.ctx_bracket, + ctx_bracket_table_ver: record.ctx_bracket_table_ver, + } : {}), + model_id: record.model_id, + denom: record.denom, + price_ver: record.ver, + price_source: record.price_source, + usage: { + usage_root: usageRoot, + settled_usage: cloneValue(market.settled_usage), + calibrated_work_ps: market.calibrated_work_ps, + active_demand_au: market.active_demand_au, + session_count: market.session_count, + ...(record.ctx_bracket ? { + ctx_bracket: record.ctx_bracket, + ctx_bracket_table_ver: record.ctx_bracket_table_ver, + } : {}), + }, + controller: { + source: market.source, + active_supply: market.active_supply, + activity_basis: market.activity_basis, + activity_vector: cloneValue(market.activity_vector), + previous_activity_vector: cloneValue(market.previous_activity_vector), + previous_activity_rate: market.previous_activity_rate, + previous_ema_activity_vector: cloneValue(market.previous_ema_activity_vector), + ema_activity_vector: cloneValue(market.ema_activity_vector), + momentum_bps: market.momentum_bps, + activity_rate: market.activity_rate, + previous_ema_activity_rate: market.previous_ema_activity_rate, + ema_activity_rate: market.ema_activity_rate, + activity_initialized: market.activity_initialized, + calibration_hash: market.calibration_hash, + calibration: cloneValue(market.calibration), + modelref_ver: market.modelref_ver, + multiplier_bps: market.multiplier_bps, + frozen: market.frozen, + frozen_reason: market.frozen_reason, + constants: cloneValue(market.constants), + }, + seed_price: this.priceTermsSnapshot(record.seed), + previous_price: { + ver: market.previous_price_ver, + rate_map: cloneValue(market.previous_rate_map), + per_req_au: market.previous_per_req_au, + min_session_au: market.previous_min_session_au, + }, + desired_price: { + rate_map: cloneValue(market.desired_rate_map), + per_req_au: market.desired_per_req_au, + min_session_au: market.desired_min_session_au, + }, + result_price: this.priceTermsSnapshot(record), + }; + } + + async priceDerivationsFromMarketUpdates(updates, context = {}) { + const derivations = []; + const sorted = updates.slice().sort((left, right) => ( + compareCodepoint(left.enclave_id, right.enclave_id) || + compareCodepoint(left.ctx_bracket ?? '', right.ctx_bracket ?? '') + )); + for (const update of sorted) { + const derivation = this.priceDerivationFromMarketUpdate(update, context); + if (derivation instanceof Error) return derivation; + const derivationHash = await this.priceDerivationLeafHash(derivation); + update.derivation_hash = derivationHash; + derivations.push({ + ...derivation, + derivation_hash: derivationHash, + }); + } + const priceRoot = await this.priceDerivationRoot(derivations); + for (const derivation of derivations) { + derivation.price_root = priceRoot; + } + return derivations; + } + + async priceDerivationLeafHash(derivation) { + return await this.opaqueHash('mayhem-price-derivation-leaf-v1', this.priceDerivationLeafValue(derivation)); + } + + async merkleRoot(kind, leaves) { + if (leaves.length === 0) return await this.opaqueHash(`mayhem-${kind}-empty-root-v1`, {}); + let level = leaves.slice().sort(); + while (level.length > 1) { + const next = []; + for (let idx = 0; idx < level.length; idx += 2) { + const left = level[idx]; + const right = idx + 1 < level.length ? level[idx + 1] : left; + next.push(await this.opaqueHash(`mayhem-${kind}-node-v1`, { left, right })); + } + level = next; + } + return level[0]; + } + + async priceDerivationRoot(derivations) { + const leaves = []; + for (const derivation of derivations) { + leaves.push(await this.priceDerivationLeafHash(derivation)); + } + return await this.merkleRoot('price', leaves); + } + + normalizePriceProofUsage(value) { + const usageMap = this.aggregateMarketUsageEntries([value]); + if (usageMap instanceof Error) return usageMap; + const entries = this.mapMarketUsageEntriesForHash(usageMap); + if (entries.length !== 1) return new Error('Price derivation proof requires one market usage entry.'); + return entries[0]; + } + + async prepareCommittedActivityEvidence({ epoch, at, epochSeconds, roots, totals }) { + if (totals.price_count === 0) return null; + if (totals.price_count !== 1) { + return new Error('Nonempty activity price commitments require one market; use bounded receipt pages for larger settlements.'); + } + const index = this.normalizeReceiptEpochIndexMetadata( + await this.get(this.receiptEpochIndexKey(epoch)), epoch); + if (index instanceof Error) return index; + if (index.count > 128 || index.count !== totals.use_count) { + return new Error('Activity price commitment requires at most 128 canonical receipts; use bounded receipt pages.'); + } + const freeze = await this.validateFrozenEpoch(epoch, at, index); + if (freeze) return freeze; + const markets = new Map(); const leaves = []; const seen = new Set(); + for (let page = 0; page < index.page_count; page++) { + const record = await this.get(this.receiptEpochPageKey(epoch, page)); + if (record?.type !== 'canonical_receipt_epoch_page' || record.epoch !== epoch || + record.page !== page || !Array.isArray(record.identities)) { + return new Error('Activity commitment receipt page is invalid.'); + } + for (const identity of record.identities) { + const identityKey = `${identity.billing_id}/${identity.billing_attempt}`; + if (seen.has(identityKey)) return new Error('Activity commitment duplicates a canonical receipt.'); + seen.add(identityKey); + const head = await this.get(this.receiptHeadKey(identity.billing_id, identity.billing_attempt)); + if (head?.type !== 'canonical_receipt_head' || head.epoch !== epoch || + head.settlement_epoch !== epoch || head.settlement_ready !== true || + head.billing_id !== identity.billing_id || head.billing_attempt !== identity.billing_attempt) { + return new Error('Activity commitment requires canonical final receipt heads.'); + } + const body = head.receipt.body; + const marketKey = this.priceMarketKey(body.enclave_id, body.ctx_bracket ?? null); + const row = markets.get(marketKey) ?? { + enclave_id: body.enclave_id, + ...(body.ctx_bracket ? { ctx_bracket: body.ctx_bracket, + ctx_bracket_table_ver: body.ctx_bracket_table_ver } : {}), + demand_au: '0', session_count: 0, providers: new Set(), settled_usage: {}, + }; + const increment = this.incrementalSettledUsage(body); + if (increment instanceof Error) return increment; + row.settled_usage = this.addSettledUsage(row.settled_usage, increment); + row.demand_au = this.safeAddAu(row.demand_au, head.incremental_au); + if (row.settled_usage instanceof Error || row.demand_au instanceof Error) { + return new Error('Activity commitment work overflow.'); + } + row.session_count++; row.providers.add(head.provider); + markets.set(marketKey, row); + leaves.push(await this.usageLeafHash(head.receipt)); + } + } + if (seen.size !== index.count || markets.size !== 1 || leaves.some((leaf) => leaf instanceof Error)) { + return new Error('Activity price commitment must cover exactly one complete canonical receipt market.'); + } + if (await this.merkleRoot('use', leaves) !== roots.use) { + return new Error('Activity price commitment usage root differs from canonical signed receipts.'); + } + const canonical = new Map(); const usage = new Map(); + for (const [key, row] of markets) { + const { providers, settled_usage, ...publicRow } = row; + publicRow.provider_count = providers.size; + if (publicRow.demand_au !== totals.use_au) return new Error('Activity commitment gross total mismatch.'); + usage.set(key, publicRow); + canonical.set(key, { ...publicRow, settled_usage }); + } + const updates = await this.computeMarketPriceUpdates(usage, { + epoch, at, epochSeconds, canonicalActivity: canonical, includeDormant: false, + }); + if (updates instanceof Error) return updates; + const derivations = await this.priceDerivationsFromMarketUpdates(updates, + { epoch, at, epochSeconds, usageRoot: roots.use }); + if (derivations instanceof Error) return derivations; + return { price_usage: this.mapMarketUsageEntriesForHash(usage)[0], + derivation: derivations[0], expected_price_root: await this.priceDerivationRoot(derivations) }; + } + + async validatePriceDerivationFraudProof(commit) { + if (commit.status === 'void') return new Error('Epoch commit is already void.'); + if (this.value.proof_epoch > commit.provisional_until_epoch) { + return new Error('Epoch commit challenge window has closed.'); + } + const evidence = commit.expected_activity_evidence; + if (commit.totals.price_count !== 1 || !evidence) { + return new Error('Price activity proof requires canonical work evidence pinned by its commitment.'); + } + const priceUsage = this.normalizePriceProofUsage(this.value.price_usage); + if (priceUsage instanceof Error) return priceUsage; + if (stableJson(priceUsage) !== stableJson(evidence.price_usage)) { + return new Error('Price activity proof usage differs from canonical commitment evidence.'); + } + const expected = await this.priceDerivationRoot([evidence.derivation]); + if (expected !== evidence.expected_price_root) return new Error('Pinned activity evidence is inconsistent.'); + if (expected === commit.roots.price) return new Error('Price activity proof does not contradict committed price root.'); + return { price_usage: priceUsage, enclave_id: priceUsage.enclave_id, + ...(priceUsage.ctx_bracket ? { ctx_bracket: priceUsage.ctx_bracket, + ctx_bracket_table_ver: priceUsage.ctx_bracket_table_ver } : {}), + expected_price_root: expected, committed_price_root: commit.roots.price, + price_derivation_hash: evidence.derivation.derivation_hash, + price_derivation: cloneValue(evidence.derivation) }; + } + + async validateEpochApplyTotals({ + epoch, + roots, + totals, + debitTotal, + feeDeltaAu, + nextFeeCum, + burnDeltaAu, + nextBurnCum, + providerCount, + earnCumTotal, + epochSeconds, + priceDerivations, + }) { + const commit = await this.get(`epoch/commit/${epoch}`); + if (!commit) return new Error('Epoch commit required before applying evidence roots.'); + if (commit.status === 'void') return new Error('Epoch commit is void.'); + if (commit.epoch_seconds !== epochSeconds) { + return new Error('Epoch apply epoch_seconds does not match committed epoch timing.'); + } + if ( + stableJson(commit.roots) !== stableJson(roots) || + stableJson(commit.totals) !== stableJson(totals) + ) { + return new Error('Epoch apply roots do not match committed roots.'); + } + if (this.compareAu(totals.use_au, debitTotal) !== 0) return new Error('Epoch usage total does not match debits.'); + if (this.compareAu(totals.earn_au, earnCumTotal) !== 0) { + return new Error('Epoch earn total does not match cumulative provider earnings.'); + } + if (this.compareAu(totals.fee_au, feeDeltaAu) !== 0) return new Error('Epoch fee total does not match computed fee.'); + if (this.compareAu(totals.fee_cum_au, nextFeeCum) !== 0) { + return new Error('Epoch cumulative fee total does not match fee state.'); + } + if (this.compareAu(totals.burn_au, burnDeltaAu) !== 0) { + return new Error('Epoch burn total does not match computed TAP burn.'); + } + if (this.compareAu(totals.burn_cum_au, nextBurnCum) !== 0) { + return new Error('Epoch cumulative burn total does not match burn state.'); + } + if (totals.provider_count !== providerCount) { + return new Error('Epoch provider count does not match earnings.'); + } + if (totals.price_count !== priceDerivations.length) { + return new Error('Epoch price derivation count does not match market updates.'); + } + const priceRoot = await this.priceDerivationRoot(priceDerivations); + if (roots.price !== priceRoot) { + return new Error('Epoch price root does not match recomputed price derivations.'); + } + + const depositRoot = await this.get(`ev/dep/${epoch}`); + if (depositRoot) { + if (depositRoot.type !== 'deposit_root') return new Error('Invalid deposit evidence root.'); + if ( + depositRoot.merkle_root !== roots.dep || + depositRoot.count !== totals.dep_count || + this.compareAu(depositRoot.au_total, totals.dep_au) !== 0 + ) { + return new Error('Committed deposit root does not match deposit evidence.'); + } + } + for (const key of ['use', 'earn', 'fee', 'price']) { + if ((await this.get(`ev/${key}/${epoch}`)) !== null) { + return new Error(`Epoch ${key} evidence root already exists.`); + } + } + return null; + } + + async validatePagedEpochCommitEvidence({ + commit, + feeCumAu, + burnCumAu, + priceDerivations, + }) { + if (commit.totals.price_count !== 0) { + return new Error('Paged receipt settlement cannot finalize market price derivations.'); + } + const emptyPriceRoot = await this.priceDerivationRoot([]); + if (commit.roots.price !== emptyPriceRoot) { + return new Error('Paged receipt settlement requires the canonical empty price root.'); + } + if (this.compareAu(commit.totals.fee_cum_au, feeCumAu) !== 0) { + return new Error('Epoch cumulative fee total does not match paged settlement state.'); + } + if (this.compareAu(commit.totals.burn_cum_au, burnCumAu) !== 0) { + return new Error('Epoch cumulative burn total does not match paged settlement state.'); + } + const depositRoot = await this.get(`ev/dep/${commit.epoch}`); + if (depositRoot && ( + depositRoot.type !== 'deposit_root' || + depositRoot.merkle_root !== commit.roots.dep || + depositRoot.count !== commit.totals.dep_count || + this.compareAu(depositRoot.au_total, commit.totals.dep_au) !== 0 + )) { + return new Error('Committed deposit root does not match deposit evidence.'); + } + for (const key of ['use', 'earn', 'fee', 'price']) { + if ((await this.get(`ev/${key}/${commit.epoch}`)) !== null) { + return new Error(`Epoch ${key} evidence root already exists.`); + } + } + if ((await this.get(`market/price/${commit.epoch}`)) !== null) { + return new Error('Bounded market price evidence already exists.'); + } + if (!Array.isArray(priceDerivations)) { + return new Error('Bounded market price derivations are invalid.'); + } + return null; + } + + async writeBoundedMarketPriceEvidence({ + epoch, + at, + epochSeconds, + usageRoot, + derivations, + }) { + const root = await this.priceDerivationRoot(derivations); + await this.put(`market/price/${epoch}`, { + type: 'bounded_market_price_root', + epoch, + epoch_seconds: epochSeconds, + usage_root: usageRoot, + price_root: root, + price_count: derivations.length, + ts: at, + updated_at: this.tx, + }); + for (const derivation of derivations) { + const suffix = derivation.ctx_bracket + ? `${derivation.enclave_id}/${derivation.ctx_bracket}` + : derivation.enclave_id; + await this.put(`market/price/${epoch}/${suffix}`, { + ...derivation, + price_root: root, + updated_at: this.tx, + }); + } + } + + async writePriceDerivationEvidence({ epoch, at, epoch_seconds, root, count, derivations }) { + await this.put(`ev/price/${epoch}`, { + type: 'price_root', + epoch, + epoch_seconds, + merkle_root: root, + price_count: count, + ts: at, + updated_at: this.tx, + }); + for (const derivation of derivations) { + const key = derivation.ctx_bracket + ? `ev/price/${epoch}/${derivation.enclave_id}/${derivation.ctx_bracket}` + : `ev/price/${epoch}/${derivation.enclave_id}`; + await this.put(key, { + ...derivation, + price_root: root, + updated_at: this.tx, + }); + } + } + + async writeEpochEvidenceRoots({ + epoch, + at, + epoch_seconds, + roots, + totals, + feeDeltaAu, + feeCumAu, + burnDeltaAu, + burnCumAu, + priceDerivations, + }) { + if ((await this.get(`ev/dep/${epoch}`)) === null) { + await this.put(`ev/dep/${epoch}`, { + type: 'deposit_root', + epoch, + epoch_seconds, + merkle_root: roots.dep, + count: totals.dep_count, + au_total: totals.dep_au, + ts: at, + updated_at: this.tx, + }); + } + await this.put(`ev/use/${epoch}`, { + type: 'usage_root', + epoch, + epoch_seconds, + merkle_root: roots.use, + sessions: totals.use_count, + au_total: totals.use_au, + providers: totals.provider_count, + ts: at, + updated_at: this.tx, + }); + await this.put(`ev/earn/${epoch}`, { + type: 'earn_root', + epoch, + epoch_seconds, + merkle_root: roots.earn, + provider_count: totals.provider_count, + au_cum_total: totals.earn_au, + ts: at, + updated_at: this.tx, + }); + await this.put(`ev/fee/${epoch}`, { + type: 'fee_root', + epoch, + epoch_seconds, + merkle_root: roots.fee, + au_fee_epoch: feeDeltaAu, + au_fee_cum: feeCumAu, + au_burn_epoch: burnDeltaAu, + au_burn_cum: burnCumAu, + tap_burn_bps: TAP_BURN_BPS, + sweep_msb_tx_hash: null, + ts: at, + updated_at: this.tx, + }); + await this.writePriceDerivationEvidence({ + epoch, + at, + epoch_seconds, + root: roots.price, + count: totals.price_count, + derivations: priceDerivations, + }); + } + + aggregateLedgerEntries(entries, idKey, amountKey, label) { + const out = new Map(); + for (const entry of entries) { + if (!entry || typeof entry !== 'object' || Array.isArray(entry)) { + return new Error(`Invalid ${label} entry.`); + } + const id = entry[idKey]; + const au = this.normalizeAu(entry[amountKey], `${label} amount`, { allowZero: false }); + if (au instanceof Error) return new Error(`Invalid ${label} amount.`); + if (!this.isSafeKeyPart(id)) return new Error(`Invalid ${label} ${idKey}.`); + const next = this.safeAddAu(out.get(id) ?? ZERO_AU, au); + if (next instanceof Error) return next; + out.set(id, next); + } + return out; + } + + aggregateRailLedgerEntries(entries, idKey, amountKey, label) { + const out = new Map(); + for (const entry of entries) { + if (!entry || typeof entry !== 'object' || Array.isArray(entry)) { + return new Error(`Invalid ${label} entry.`); + } + const rail = this.normalizeLedgerRail(entry.rail, `${label} rail`); + if (rail instanceof Error) return rail; + const id = entry[idKey]; + const au = this.normalizeAu(entry[amountKey], `${label} amount`, { allowZero: false }); + if (au instanceof Error) return new Error(`Invalid ${label} amount.`); + if (!this.isSafeKeyPart(id)) return new Error(`Invalid ${label} ${idKey}.`); + const key = stableJson([rail, id]); + const current = out.get(key) ?? { rail, [idKey]: id, [amountKey]: ZERO_AU }; + const next = this.safeAddAu(current[amountKey], au); + if (next instanceof Error) return next; + out.set(key, { ...current, [amountKey]: next }); + } + return out; + } + + aggregateMarketUsageEntries(entries) { + const out = new Map(); + for (const entry of entries) { + if (!entry || typeof entry !== 'object' || Array.isArray(entry)) { + return new Error('Invalid market usage entry.'); + } + const allowed = new Set([ + 'enclave_id', + 'ctx_bracket', + 'ctx_bracket_table_ver', + 'demand_au', + 'session_count', + 'provider_count', + ]); + const unknown = Object.keys(entry).filter((key) => !allowed.has(key)).sort(); + if (unknown.length > 0) { + return new Error(`market usage entry does not accept fields: ${unknown.join(', ')}.`); + } + for (const key of ['enclave_id', 'demand_au', 'session_count', 'provider_count']) { + if (!hasOwn(entry, key)) return new Error(`market usage entry is missing ${key}.`); + } + const enclaveId = entry.enclave_id; + if (!this.isSafeKeyPart(enclaveId)) return new Error('Invalid market usage enclave_id.'); + const ctxBracket = entry.ctx_bracket ?? null; + if (ctxBracket !== null && !this.isSafeKeyPart(ctxBracket)) { + return new Error('Invalid market usage context bracket.'); + } + if ( + hasOwn(entry, 'ctx_bracket_table_ver') && + (!Number.isSafeInteger(entry.ctx_bracket_table_ver) || entry.ctx_bracket_table_ver < 1) + ) { + return new Error('Invalid market usage context bracket table version.'); + } + const demandEntryAu = this.normalizeAu(entry.demand_au, 'market usage demand', { allowZero: false }); + if (demandEntryAu instanceof Error) { + return new Error('Invalid market usage demand.'); + } + if (!Number.isSafeInteger(entry.session_count) || entry.session_count <= 0) { + return new Error('Invalid market usage session_count.'); + } + if (!Number.isSafeInteger(entry.provider_count) || entry.provider_count <= 0) { + return new Error('Invalid market usage provider_count.'); + } + const key = this.priceMarketKey(enclaveId, ctxBracket); + const current = out.get(key) ?? { + enclave_id: enclaveId, + ...(ctxBracket ? { ctx_bracket: ctxBracket } : {}), + ...(entry.ctx_bracket_table_ver ? { ctx_bracket_table_ver: entry.ctx_bracket_table_ver } : {}), + demand_au: ZERO_AU, + session_count: 0, + provider_count: 0, + }; + if ((current.ctx_bracket_table_ver ?? null) !== (entry.ctx_bracket_table_ver ?? current.ctx_bracket_table_ver ?? null)) { + return new Error('Market usage context bracket table version mismatch.'); + } + const demandAu = this.safeAddAu(current.demand_au, demandEntryAu); + if (demandAu instanceof Error) return demandAu; + const sessionCount = this.safeAddCount(current.session_count, entry.session_count, 'market usage session_count'); + if (sessionCount instanceof Error) return sessionCount; + const providerCount = this.safeAddCount(current.provider_count, entry.provider_count, 'market usage provider_count'); + if (providerCount instanceof Error) return providerCount; + out.set(key, { + enclave_id: enclaveId, + ...(ctxBracket ? { ctx_bracket: ctxBracket } : {}), + ...(entry.ctx_bracket_table_ver ? { ctx_bracket_table_ver: entry.ctx_bracket_table_ver } : {}), + demand_au: demandAu, + session_count: sessionCount, + provider_count: providerCount, + }); + } + return out; + } + + sumAu(entries) { + let sum = ZERO_AU; + for (const [, au] of entries) { + const next = this.safeAddAu(sum, au); + if (next instanceof Error) return next; + sum = next; + } + return sum; + } + + sumRailAu(entries, amountKey) { + let sum = ZERO_AU; + for (const entry of entries.values()) { + const next = this.safeAddAu(sum, entry[amountKey]); + if (next instanceof Error) return next; + sum = next; + } + return sum; + } + + sumMarketDemandAu(entries) { + let sum = ZERO_AU; + for (const entry of entries.values()) { + const next = this.safeAddAu(sum, entry.demand_au); + if (next instanceof Error) return next; + sum = next; + } + return sum; + } + + usageAuForRateMap(rateMap, usage) { + const rates = this.rateMapByUnit(rateMap); + const priced = []; + for (const [unit, count] of Object.entries(usage ?? {})) { + if (!Number.isSafeInteger(count) || count < 0) return new Error('Invalid receipt usage count.'); + if (count === 0) continue; + const rate = rates.get(unit); + if (!rate) return new Error(`Receipt locked_rate_map missing usage unit ${unit}.`); + const perUnitAu = this.parseAu(rate.per_unit_au, 'locked rate per_unit_au', { allowZero: false }); + if (perUnitAu instanceof Error) { + return new Error('Invalid locked rate per_unit_au.'); + } + if (!Number.isSafeInteger(rate.granularity) || rate.granularity <= 0) { + return new Error('Invalid locked rate granularity.'); + } + priced.push({ + count: BigInt(count), + perUnitAu, + granularity: BigInt(rate.granularity), + }); + } + if (priced.length === 0) return ZERO_AU; + const sameGranularity = priced.every((entry) => entry.granularity === priced[0].granularity); + if (sameGranularity) { + const raw = priced.reduce((sum, entry) => sum + entry.count * entry.perUnitAu, 0n); + return this.canonicalAu(this.ceilDivBigInt(raw, priced[0].granularity)); + } + let total = 0n; + for (const entry of priced) { + total += this.ceilDivBigInt(entry.count * entry.perUnitAu, entry.granularity); + } + return this.canonicalAu(total); + } + + usageAuForLockedTerms(rateMap, perReqAu, minSessionAu, usage) { + const normalizedPerReqAu = this.normalizeAu(perReqAu, 'locked per-request price'); + if (normalizedPerReqAu instanceof Error) return new Error('Invalid locked per-request price.'); + const normalizedMinSessionAu = this.normalizeAu(minSessionAu, 'locked minimum session price'); + if (normalizedMinSessionAu instanceof Error) return new Error('Invalid locked minimum session price.'); + const usageAu = this.usageAuForRateMap(rateMap, usage); + if (usageAu instanceof Error) return usageAu; + const subtotal = this.safeAddAu(usageAu, normalizedPerReqAu); + if (subtotal instanceof Error) return subtotal; + return this.maxAu(subtotal, normalizedMinSessionAu); + } + + receiptUsageDelta(previous, current) { + const delta = {}; + for (const [unit, previousCount] of Object.entries(previous)) { + const currentCount = current[unit] ?? 0; + if (currentCount < previousCount) return new Error('Receipt cumulative usage regressed.'); + } + for (const [unit, currentCount] of Object.entries(current)) { + const count = currentCount - (previous[unit] ?? 0); + if (count > 0) delta[unit] = count; + } + return delta; + } + + logicalCumulativeAuForLockedTerms( + rateMap, + perReqAu, + minSessionAu, + priorUsage, + priorAuOwedCum, + currentUsage + ) { + const delta = this.receiptUsageDelta(priorUsage, currentUsage); + if (delta instanceof Error) return delta; + const increment = this.isZeroAu(priorAuOwedCum) + ? this.usageAuForLockedTerms(rateMap, perReqAu, minSessionAu, delta) + : this.usageAuForRateMap(rateMap, delta); + if (increment instanceof Error) return increment; + return this.safeAddAu(priorAuOwedCum, increment); + } + + ceilDivBigInt(value, divisor) { + if (value <= 0n) return 0n; + return (value + divisor - 1n) / divisor; + } + + railTotals(entries, amountKey) { + const out = new Map(PROVIDER_ACCEPTED_RAIL_ORDER.map((rail) => [rail, ZERO_AU])); + for (const entry of entries.values()) { + const next = this.safeAddAu(out.get(entry.rail) ?? ZERO_AU, entry[amountKey]); + if (next instanceof Error) return next; + out.set(entry.rail, next); + } + return out; + } + + assertMatchingRailTotals(left, right) { + for (const rail of PROVIDER_ACCEPTED_RAIL_ORDER) { + if (this.compareAu(left.get(rail) ?? ZERO_AU, right.get(rail) ?? ZERO_AU) !== 0) { + return new Error('Epoch debits must equal gross provider earnings per rail.'); + } + } + return null; + } + + safeAddAu(a, b) { + const left = this.parseAu(a, 'au value'); + if (left instanceof Error) return left; + const right = this.parseAu(b, 'au value'); + if (right instanceof Error) return right; + return this.canonicalAu(left + right); + } + + safeSubAu(a, b) { + const left = this.parseAu(a, 'au value'); + if (left instanceof Error) return left; + const right = this.parseAu(b, 'au value'); + if (right instanceof Error) return right; + if (right > left) return new Error('au value underflow.'); + return this.canonicalAu(left - right); + } + + safeMulDivAu(a, b, divisor) { + const amount = this.parseAu(a, 'au value'); + if (amount instanceof Error) return amount; + if ( + !Number.isSafeInteger(b) || + !Number.isSafeInteger(divisor) || + b < 0 || + divisor <= 0 + ) { + return new Error('Invalid au multiplier.'); + } + return this.canonicalAu((amount * BigInt(b)) / BigInt(divisor)); + } + + providerSettlementPageDelta({ grossAu, priorGrossAu, rail, feeBps }) { + const nextGrossAu = this.safeAddAu(priorGrossAu, grossAu); + if (nextGrossAu instanceof Error) return nextGrossAu; + const priorFeeAu = this.safeMulDivAu(priorGrossAu, feeBps, 10_000); + const nextFeeAu = this.safeMulDivAu(nextGrossAu, feeBps, 10_000); + if (priorFeeAu instanceof Error || nextFeeAu instanceof Error) { + return new Error('Provider settlement fee overflow.'); + } + const feeAu = this.safeSubAu(nextFeeAu, priorFeeAu); + if (feeAu instanceof Error) return feeAu; + const priorBurnAu = rail === 'tap' + ? this.safeMulDivAu(priorGrossAu, TAP_BURN_BPS, 10_000) + : ZERO_AU; + const nextBurnAu = rail === 'tap' + ? this.safeMulDivAu(nextGrossAu, TAP_BURN_BPS, 10_000) + : ZERO_AU; + if (priorBurnAu instanceof Error || nextBurnAu instanceof Error) { + return new Error('Provider settlement burn overflow.'); + } + const burnAu = this.safeSubAu(nextBurnAu, priorBurnAu); + if (burnAu instanceof Error) return burnAu; + const providerAu = this.safeSubAu( + this.safeSubAu(grossAu, feeAu), + burnAu + ); + if (providerAu instanceof Error) return providerAu; + return { + fee_au: feeAu, + burn_au: burnAu, + provider_au: providerAu, + }; + } + + safeAddCount(a, b, label = 'count') { + if (!Number.isSafeInteger(a) || !Number.isSafeInteger(b) || a < 0 || b < 0) { + return new Error(`Invalid ${label}.`); + } + const next = a + b; + if (!Number.isSafeInteger(next)) return new Error(`${label} overflow.`); + return next; + } + + parseAu(value, label = 'au value', { allowZero = true } = {}) { + if (typeof value === 'bigint') { + if (value < 0n || (!allowZero && value === 0n)) return new Error(`${label} must be positive.`); + return value; + } + if (typeof value !== 'string' || !/^(0|[1-9][0-9]*)$/.test(value)) { + return new Error(`${label} must be a canonical decimal string.`); + } + const parsed = BigInt(value); + if (!allowZero && parsed === 0n) return new Error(`${label} must be positive.`); + return parsed; + } + + normalizeAu(value, label = 'au value', options = {}) { + const parsed = this.parseAu(value, label, options); + if (parsed instanceof Error) return parsed; + return this.canonicalAu(parsed); + } + + canonicalAu(value) { + if (typeof value !== 'bigint' || value < 0n) return new Error('Invalid au value.'); + return value.toString(); + } + + compareAu(a, b) { + const left = this.parseAu(a, 'au value'); + if (left instanceof Error) return NaN; + const right = this.parseAu(b, 'au value'); + if (right instanceof Error) return NaN; + return left === right ? 0 : (left < right ? -1 : 1); + } + + maxAu(a, b) { + return this.compareAu(a, b) >= 0 ? this.normalizeAu(a) : this.normalizeAu(b); + } + + isZeroAu(value) { + return this.compareAu(value, ZERO_AU) === 0; + } + + sortedMapEntries(map) { + return Array.from(map.entries()).sort(([a], [b]) => compareCodepoint(a, b)); + } + + sortedRailRecords(map, idKey) { + return Array.from(map.values()).sort((a, b) => { + const railOrder = + PROVIDER_ACCEPTED_RAIL_ORDER.indexOf(a.rail) - PROVIDER_ACCEPTED_RAIL_ORDER.indexOf(b.rail); + if (railOrder !== 0) return railOrder; + return compareCodepoint(a[idKey], b[idKey]); + }); + } + + mapEntriesForHash(map, idKey, amountKey) { + return this.sortedMapEntries(map).map(([id, au]) => ({ + [idKey]: id, + [amountKey]: au, + })); + } + + mapRailEntriesForHash(map, idKey, amountKey) { + return this.sortedRailRecords(map, idKey).map((entry) => ({ + rail: entry.rail, + [idKey]: entry[idKey], + [amountKey]: entry[amountKey], + })); + } + + mapMarketUsageEntriesForHash(map) { + return Array.from(map.values()) + .sort((left, right) => ( + compareCodepoint(left.enclave_id, right.enclave_id) || + compareCodepoint(left.ctx_bracket ?? '', right.ctx_bracket ?? '') + )) + .map((entry) => ({ + enclave_id: entry.enclave_id, + ...(entry.ctx_bracket ? { ctx_bracket: entry.ctx_bracket } : {}), + ...(entry.ctx_bracket_table_ver ? { ctx_bracket_table_ver: entry.ctx_bracket_table_ver } : {}), + demand_au: entry.demand_au, + session_count: entry.session_count, + provider_count: entry.provider_count, + })); + } + + async balanceRecord(user, rail) { + const normalizedRail = this.normalizeLedgerRail(rail, 'balance rail'); + if (normalizedRail instanceof Error) return normalizedRail; + const tap = normalizedRail === 'tap' + ? await this.canonicalTapPaymentConfig({ optional: true }) + : null; + if (tap instanceof Error) return tap; + const current = await this.get(this.balanceKey(user, normalizedRail)); + const scoped = tap === null || ( + current?.chain_id === tap.chain_id && + current?.pool_address === tap.pool_address + ) ? current : null; + return scoped ?? { + user, + rail: normalizedRail, + denom: PRICE_DENOMINATION, + au: ZERO_AU, + updated_epoch: 0, + updated_at: null, + ...(tap ?? {}), + }; + } + + async earningRecord(provider, rail) { + const normalizedRail = this.normalizeLedgerRail(rail, 'earning rail'); + if (normalizedRail instanceof Error) return normalizedRail; + const tap = normalizedRail === 'tap' + ? await this.canonicalTapPaymentConfig({ optional: true }) + : null; + if (tap instanceof Error) return tap; + const current = await this.get(this.earningKey(provider, normalizedRail)); + const scoped = tap === null || ( + current?.chain_id === tap.chain_id && + current?.pool_address === tap.pool_address + ) ? current : null; + return scoped ?? { + provider, + rail: normalizedRail, + denom: PRICE_DENOMINATION, + total_au: ZERO_AU, + held_au: ZERO_AU, + paid_cum_au: ZERO_AU, + updated_epoch: 0, + updated_at: null, + ...(tap ?? {}), + }; + } + + async feeCumRecord(rail) { + const normalizedRail = this.normalizeLedgerRail(rail, 'fee rail'); + if (normalizedRail instanceof Error) return normalizedRail; + const tap = normalizedRail === 'tap' + ? await this.canonicalTapPaymentConfig({ optional: true }) + : null; + if (tap instanceof Error) return tap; + const current = await this.get(this.feeCumKey(normalizedRail)); + const scoped = tap === null || ( + current?.chain_id === tap.chain_id && + current?.pool_address === tap.pool_address + ) ? current : null; + return scoped ?? { + rail: normalizedRail, + denom: PRICE_DENOMINATION, + cum_au: ZERO_AU, + swept_cum_au: ZERO_AU, + updated_epoch: 0, + updated_at: null, + last_apply_hash: null, + last_fee_bps: null, + ...(tap ?? {}), + }; + } + + async burnCumRecord(rail) { + const normalizedRail = this.normalizeLedgerRail(rail, 'burn rail'); + if (normalizedRail instanceof Error) return normalizedRail; + const tap = normalizedRail === 'tap' + ? await this.canonicalTapPaymentConfig({ optional: true }) + : null; + if (tap instanceof Error) return tap; + const current = await this.get(this.burnCumKey(normalizedRail)); + const scoped = tap === null || ( + current?.chain_id === tap.chain_id && + current?.pool_address === tap.pool_address + ) ? current : null; + return scoped ?? { + rail: normalizedRail, + denom: PRICE_DENOMINATION, + cum_au: ZERO_AU, + updated_epoch: 0, + updated_at: null, + last_apply_hash: null, + burn_bps: normalizedRail === 'tap' ? TAP_BURN_BPS : 0, + ...(tap ?? {}), + }; + } + + async epochApplyStateRecord() { + return (await this.get('epoch/apply/state')) ?? { + updated_epoch: 0, + updated_at: null, + last_apply_hash: null, + last_settlement_unix: null, + }; + } + + epochApplyAnchorKey(epoch) { + return `epoch/apply-anchor/${epoch}`; + } + + async prepareEpochApplyAnchor(state) { + if ( + !state || + !Number.isSafeInteger(state.updated_epoch) || + state.updated_epoch < 1 || + !this.isHexBytes(state.last_apply_hash, 32) || + !Number.isSafeInteger(state.last_settlement_unix) || + state.last_settlement_unix < 0 || + (state.pending_epoch ?? null) !== null + ) { + return new Error('Cannot anchor an incomplete epoch apply.'); + } + const key = this.epochApplyAnchorKey(state.updated_epoch); + const record = { + type: 'epoch_apply_anchor', + epoch: state.updated_epoch, + apply_hash: state.last_apply_hash.toLowerCase(), + settlement_unix: state.last_settlement_unix, + applied_at: state.updated_at, + }; + const existing = await this.get(key); + if (existing !== null) { + return stableJson(existing) === stableJson(record) + ? null + : new Error('Epoch apply anchor conflict.'); + } + return { key, record, write: true }; + } + + async rememberEpochApplyAnchor(state) { + const prepared = await this.prepareEpochApplyAnchor(state); + if (prepared instanceof Error) return prepared; + await this.writePreparedAnchor(prepared); + return null; + } + + async epochApplyAnchor(epoch) { + if (!Number.isSafeInteger(epoch) || epoch < 1) { + return new Error('Invalid epoch apply anchor epoch.'); + } + const historical = await this.get(this.epochApplyAnchorKey(epoch)); + if (historical !== null) { + if ( + historical.type !== 'epoch_apply_anchor' || + historical.epoch !== epoch || + !this.isHexBytes(historical.apply_hash, 32) || + !Number.isSafeInteger(historical.settlement_unix) || + historical.settlement_unix < 0 || + typeof historical.applied_at !== 'string' || + historical.applied_at.length === 0 + ) { + return new Error('Epoch apply anchor is invalid.'); + } + return historical; + } + const current = await this.epochApplyStateRecord(); + if ( + current.updated_epoch === epoch && + this.isHexBytes(current.last_apply_hash, 32) && + (current.pending_epoch ?? null) === null + ) { + const settlementUnix = await this.priorEpochSettlementUnix(current); + if (settlementUnix instanceof Error) return settlementUnix; + return { + type: 'epoch_apply_anchor', + epoch, + apply_hash: current.last_apply_hash.toLowerCase(), + settlement_unix: settlementUnix, + applied_at: current.updated_at, + }; + } + return null; + } + + async requireEpochApplyAnchor(epoch, applyHash, label) { + const anchor = await this.epochApplyAnchor(epoch); + if (anchor instanceof Error) return anchor; + if (!anchor || anchor.apply_hash !== applyHash) { + return new Error(`${label} apply hash mismatch.`); + } + return anchor; + } + + async prepareCanaryChallengeAnchor(state) { + if ( + !state || + !Number.isSafeInteger(state.updated_epoch) || + state.updated_epoch < 1 || + !this.isHexBytes(state.last_apply_hash, 32) || + (state.pending_epoch ?? null) !== null + ) { + return null; + } + const key = `epoch/challenge/${state.updated_epoch}`; + const record = { + epoch: state.updated_epoch, + apply_hash: state.last_apply_hash.toLowerCase(), + recorded_at: this.tx, + }; + const existing = await this.get(key); + if (existing !== null) { + if (existing.epoch !== record.epoch || existing.apply_hash !== record.apply_hash) { + return new Error('Canary challenge anchor conflict.'); + } + return { key, record, write: false }; + } + return { key, record, write: true }; + } + + async writePreparedAnchor(prepared) { + if (prepared?.write === true) { + await this.put(prepared.key, prepared.record); + } + } + + async rememberCanaryChallengeAnchor(state) { + const prepared = await this.prepareCanaryChallengeAnchor(state); + if (prepared instanceof Error) return prepared; + await this.writePreparedAnchor(prepared); + return null; + } + + async canaryChallengeAnchor(epoch) { + const historical = await this.get(`epoch/challenge/${epoch}`); + if (historical) return historical; + const current = await this.epochApplyStateRecord(); + if ( + current.updated_epoch === epoch && + this.isHexBytes(current.last_apply_hash, 32) && + (current.pending_epoch ?? null) === null + ) { + return { + epoch, + apply_hash: current.last_apply_hash.toLowerCase(), + recorded_at: current.updated_at, + }; + } + return null; + } + + parseTnkE18(value) { + if (typeof value !== 'string' || !/^[0-9]+$/.test(value)) { + return new Error('tnk_e18 must be a decimal integer string.'); + } + const parsed = BigInt(value); + if (parsed <= 0n) return new Error('tnk_e18 must be positive.'); + return parsed; + } + + parseTapWei(value, { allowZero = false } = {}) { + if (typeof value !== 'string' || !/^[0-9]+$/.test(value)) { + return new Error('tap_wei must be a decimal integer string.'); + } + const parsed = BigInt(value); + if (parsed < 0n || (!allowZero && parsed === 0n)) { + return new Error('tap_wei must be positive.'); + } + return parsed; + } + + normalizeFiatCurrency(value) { + if (typeof value !== 'string') return new Error('Invalid fiat currency.'); + const currency = value.trim().toLowerCase(); + if (!/^[a-z]{3}$/.test(currency)) return new Error('Unsupported fiat currency.'); + return currency; + } + + fiatEvidenceFields() { + if (this.value.fiat_currency === undefined || this.value.fiat_amount_minor === undefined) { + return new Error('Fiat evidence requires fiat_currency and fiat_amount_minor.'); + } + const currency = this.normalizeFiatCurrency(this.value.fiat_currency); + if (currency instanceof Error) return currency; + if ( + !Number.isSafeInteger(this.value.fiat_amount_minor) || + this.value.fiat_amount_minor <= 0 + ) { + return new Error('Invalid fiat amount.'); + } + return { + fiat_currency: currency, + fiat_amount_minor: this.value.fiat_amount_minor, + }; + } + + tnkE18ToAu(tnkE18, tnkUsdAu) { + const rate = this.parseAu(tnkUsdAu, 'TNK/USD atto-rate', { allowZero: false }); + if (rate instanceof Error) return new Error('Invalid TNK/USD atto-rate.'); + // Deposit conversion rounds down so credited AU never exceeds received TNK value. + return this.canonicalAu((tnkE18 * rate) / TNK_E18); + } + + tapWeiToAu(tapWei, tapUsdAu) { + const rate = this.parseAu(tapUsdAu, 'TAP/USD policy rate', { allowZero: false }); + if (rate instanceof Error) return new Error('Invalid TAP/USD policy rate.'); + // Deposit conversion rounds down so credited AU never exceeds received TAP value. + return this.canonicalAu((tapWei * rate) / TAP_WEI); + } + + async requireFreshRate(at) { + const rate = await this.get('rate/latest'); + if (!rate) return new Error('Fresh rate oracle required.'); + const admin = await this.get('admin'); + if (admin === null) return new Error('Fresh rate oracle requires a current admin key.'); + if (rate.posted_by !== admin || rate.posted_by_role !== 'admin') { + return new Error('Fresh rate oracle must be admin-posted.'); + } + if (rate.ts > at) return new Error('Rate oracle timestamp is in the future.'); + const params = await this.activeParamsAt(at, ['rate_staleness_seconds']); + if (at - rate.ts > params.rate_staleness_seconds) { + return new Error('Rate oracle is stale.'); + } + return rate; + } + + validateTnkRateRecord( + rate, + { + tnkUsdAu = null, + source = null, + ts = null, + updatedAt = null, + admin = null, + label = 'TNK rate', + } = {} + ) { + if (!rate || typeof rate !== 'object' || Array.isArray(rate)) { + return new Error(`${label} is missing.`); + } + const normalized = this.normalizeAu( + rate.tnk_usd_au, + `${label} TNK/USD atto-rate`, + { allowZero: false } + ); + if (normalized instanceof Error || normalized !== rate.tnk_usd_au || + rate.denom !== 'tnk_usd_au' || + typeof rate.source !== 'string' || + rate.source.length < 1 || + rate.source.length > 64 || + !Number.isSafeInteger(rate.ts) || + rate.ts < 0 || + typeof rate.updated_at !== 'string' || + !rate.updated_at.startsWith(`rate/tnk/${rate.ts}/`) || + !this.isHexBytes(rate.updated_at.slice(`rate/tnk/${rate.ts}/`.length), 32) || + !this.isHexBytes(rate.posted_by, 32) || + rate.posted_by !== rate.posted_by.toLowerCase() || + rate.posted_by_role !== 'admin') { + return new Error(`${label} is invalid.`); + } + if (admin !== null && rate.posted_by !== admin) { + return new Error(`${label} is not admin-posted.`); + } + if (tnkUsdAu !== null && this.compareAu(rate.tnk_usd_au, tnkUsdAu) !== 0) { + return new Error(`${label} amount mismatch.`); + } + if (source !== null && rate.source !== source) { + return new Error(`${label} source mismatch.`); + } + if (ts !== null && rate.ts !== ts) { + return new Error(`${label} timestamp mismatch.`); + } + if (updatedAt !== null && rate.updated_at !== updatedAt) { + return new Error(`${label} record key mismatch.`); + } + return null; + } + + async currentTnkRateRecord(label = 'Current TNK rate') { + const rate = await this.get('rate/latest'); + const admin = await this.get('admin'); + if (!rate || admin === null) return new Error(`${label} is missing.`); + const rateError = this.validateTnkRateRecord(rate, { admin, label }); + if (rateError) return rateError; + return rate; + } + + async guardianAcceptTnkDepositIntentRate(intent) { + const rate = await this.currentTnkRateRecord('TNK deposit rate'); + if (rate instanceof Error) return rate; + const exactError = this.validateTnkRateRecord(rate, { + tnkUsdAu: intent.rate_tnk_usd_au, + source: intent.rate_source, + label: 'TNK deposit rate', + }); + if (exactError) { + return new Error('TNK deposit rate does not match current oracle.'); + } + if ( + (hasOwn(intent, 'rate_ts') && intent.rate_ts !== rate.ts) || + (hasOwn(intent, 'rate_record_key') && intent.rate_record_key !== rate.updated_at) + ) { + return new Error('TNK deposit rate record is not current.'); + } + return rate; + } + + legacyTnkDepositRateCloseToCurrent(lockedRate, currentRate) { + const locked = this.parseAu(lockedRate, 'legacy TNK deposit locked rate', { allowZero: false }); + const current = this.parseAu(currentRate, 'current TNK deposit rate', { allowZero: false }); + if (locked instanceof Error || current instanceof Error) return false; + const diff = locked > current ? locked - current : current - locked; + const ceiling = locked > current ? locked : current; + return diff * 10_000n <= ceiling * LEGACY_TNK_DEPOSIT_RATE_DRIFT_BPS; + } + + async guardianRequireHistoricalTnkDepositRate(pending, at) { + const oracleValue = { + op: 'rate_oracle', + tnk_usd_au: pending.rate_tnk_usd_au, + source: pending.rate_source, + ts: pending.rate_ts, + }; + const key = await this.rateFeatureKey(oracleValue); + if (key instanceof Error) { + return new Error(`Guardian TNK deposit rate invariant failed: ${key.message}`); + } + if (key !== pending.rate_record_key) { + return new Error('Guardian TNK deposit rate invariant failed: rate record key mismatch.'); + } + const rate = await this.get(key); + const admin = await this.get('admin'); + if (!rate || admin === null) { + return new Error('Guardian TNK deposit rate invariant failed: exact admin-posted oracle history required.'); + } + const rateError = this.validateTnkRateRecord(rate, { + tnkUsdAu: pending.rate_tnk_usd_au, + source: pending.rate_source, + ts: pending.rate_ts, + updatedAt: pending.rate_record_key, + admin, + label: 'Guardian TNK deposit rate', + }); + if (rateError) { + return new Error(`Guardian TNK deposit rate invariant failed: ${rateError.message}`); + } + if (rate.ts > at) { + return new Error('Guardian TNK deposit rate invariant failed: oracle timestamp is in the future.'); + } + return rate; + } + + async guardianRequireTnkDepositRateLock(pending, at) { + const pendingRate = this.normalizeAu( + pending?.rate_tnk_usd_au, + 'pending TNK deposit rate', + { allowZero: false } + ); + if (pendingRate instanceof Error || pendingRate !== pending.rate_tnk_usd_au || + !this.isSafeKeyPart(pending.rate_source)) { + return new Error('Guardian TNK deposit rate invariant failed: pending rate is invalid.'); + } + const hasRateTs = hasOwn(pending, 'rate_ts'); + const hasRateRecordKey = hasOwn(pending, 'rate_record_key'); + if (hasRateTs || hasRateRecordKey) { + if (!hasRateTs || !hasRateRecordKey || + !Number.isSafeInteger(pending.rate_ts) || + pending.rate_ts < 0 || + typeof pending.rate_record_key !== 'string') { + return new Error('Guardian TNK deposit rate invariant failed: pending rate lock is invalid.'); + } + return await this.guardianRequireHistoricalTnkDepositRate(pending, at); + } + + const current = await this.guardianRequireFreshRate(at); + if (current instanceof Error) return current; + if (current.source !== pending.rate_source || + !this.legacyTnkDepositRateCloseToCurrent(pending.rate_tnk_usd_au, current.tnk_usd_au)) { + return new Error('TNK deposit rate does not match pending intent.'); + } + return { + ...current, + tnk_usd_au: pending.rate_tnk_usd_au, + source: pending.rate_source, + legacy_rate_lock: true, + }; + } + + async requireFreshTapRate(at) { + const rate = await this.get('tap/rate/latest'); + if (!rate) return new Error('Fresh TAP rate oracle required.'); + const admin = await this.get('admin'); + if (admin === null) return new Error('Fresh TAP rate oracle requires a current admin key.'); + if (rate.posted_by !== admin || rate.posted_by_role !== 'admin') { + return new Error('Fresh TAP rate oracle must be admin-posted.'); + } + if (rate.ts > at) return new Error('TAP rate oracle timestamp is in the future.'); + const params = await this.activeParamsAt(at, ['rate_staleness_seconds']); + if (at - rate.ts > params.rate_staleness_seconds) { + return new Error('TAP rate oracle is stale.'); + } + return rate; + } + + async guardianRequireFreshRate(at) { + const rate = await this.requireFreshRate(at); + if (rate instanceof Error) { + return new Error(`Guardian rate freshness invariant failed: ${rate.message}`); + } + return rate; + } + + async guardianRequireHistoricalTnkRate(settlement, at) { + const oracleValue = { + op: 'rate_oracle', + tnk_usd_au: settlement.rate_tnk_usd_au, + source: settlement.rate_source, + ts: settlement.rate_ts, + }; + const key = await this.rateFeatureKey(oracleValue); + if (key instanceof Error) { + return new Error(`Guardian TNK settlement rate invariant failed: ${key.message}`); + } + const rate = await this.get(key); + if (!rate) { + return new Error('Guardian TNK settlement rate invariant failed: exact admin-posted oracle history required.'); + } + if ( + rate.denom !== 'tnk_usd_au' || + this.compareAu(rate.tnk_usd_au, settlement.rate_tnk_usd_au) !== 0 || + rate.source !== settlement.rate_source || + rate.ts !== settlement.rate_ts || + rate.updated_at !== key || + rate.posted_by_role !== 'admin' || + !this.isHexBytes(rate.posted_by, 32) + ) { + return new Error('Guardian TNK settlement rate invariant failed: oracle history is invalid.'); + } + if (rate.ts > at) { + return new Error('Guardian TNK settlement rate invariant failed: oracle timestamp is in the future.'); + } + const params = await this.activeParamsAt(at, ['rate_staleness_seconds']); + if (at - rate.ts > params.rate_staleness_seconds) { + return new Error('Guardian TNK settlement rate invariant failed: oracle is stale.'); + } + return rate; + } + + async guardianRequireHistoricalTapRateLock(lock, at) { + const oracleValue = { + op: 'tap_rate_oracle', + tap_usd_au: lock.tap_usd_au, + source: lock.source, + ts: lock.rate_ts, + }; + const key = await this.rateFeatureKey(oracleValue); + if (key instanceof Error) { + return new Error(`Guardian TAP settlement rate invariant failed: ${key.message}`); + } + if (key !== lock.rate_record_key) { + return new Error('Guardian TAP settlement rate invariant failed: rate record key mismatch.'); + } + const rate = await this.get(key); + const admin = await this.get('admin'); + if (!rate || admin === null) { + return new Error('Guardian TAP settlement rate invariant failed: exact admin-posted oracle history required.'); + } + if ( + rate.denom !== 'tap_usd_au' || + this.compareAu(rate.tap_usd_au, lock.tap_usd_au) !== 0 || + rate.source !== lock.source || + rate.ts !== lock.rate_ts || + rate.updated_at !== key || + rate.posted_by !== admin || + rate.posted_by !== lock.posted_by || + rate.posted_by_role !== 'admin' + ) { + return new Error('Guardian TAP settlement rate invariant failed: oracle history is invalid.'); + } + if (rate.ts > at) { + return new Error('Guardian TAP settlement rate invariant failed: oracle timestamp is in the future.'); + } + const params = await this.activeParamsAt(at, ['rate_staleness_seconds']); + if (at - rate.ts > params.rate_staleness_seconds) { + return new Error('Guardian TAP settlement rate invariant failed: oracle is stale.'); + } + return rate; + } + + async guardianRequireFreshTapRate(at) { + const rate = await this.requireFreshTapRate(at); + if (rate instanceof Error) { + return new Error(`Guardian TAP rate freshness invariant failed: ${rate.message}`); + } + return rate; + } + + async opaqueHash(domain, value) { + const digest = await blake3(b4a.from(stableJson({ domain, value }))); + return b4a.toString(digest, 'hex'); + } + + async depositLeafHash(value) { + return await this.opaqueHash('mayhem-deposit-leaf-v1', value); + } + + async nextDepositRoot({ epoch, leaf, au, at }) { + const current = await this.get(`ev/dep/${epoch}`); + if (current && current.type !== 'deposit_root') { + return new Error('Invalid deposit evidence root.'); + } + const count = (current?.count ?? 0) + 1; + const auTotal = this.safeAddAu(current?.au_total ?? ZERO_AU, au); + if (auTotal instanceof Error) return auTotal; + const merkleRoot = current + ? await this.opaqueHash('mayhem-deposit-root-v1', { + previous_root: current.merkle_root, + leaf, + count, + }) + : leaf; + return { + type: 'deposit_root', + epoch, + merkle_root: merkleRoot, + count, + au_total: auTotal, + ts: at, + updated_at: this.tx, + }; + } + + async nextDepositReversalRoot({ epoch, leaf, disputedAu, clawbackAu, absorbedAu, at }) { + const current = await this.get(`ev/dep/${epoch}`); + if (current && current.type !== 'deposit_root') { + return new Error('Invalid deposit evidence root.'); + } + const count = (current?.count ?? 0) + 1; + const reversedAuTotal = this.safeAddAu(current?.reversed_au_total ?? ZERO_AU, disputedAu); + if (reversedAuTotal instanceof Error) return reversedAuTotal; + const clawbackAuTotal = this.safeAddAu(current?.clawback_au_total ?? ZERO_AU, clawbackAu); + if (clawbackAuTotal instanceof Error) return clawbackAuTotal; + const networkAbsorbedAuTotal = this.safeAddAu(current?.network_absorbed_au_total ?? ZERO_AU, absorbedAu); + if (networkAbsorbedAuTotal instanceof Error) return networkAbsorbedAuTotal; + const merkleRoot = current + ? await this.opaqueHash('mayhem-deposit-root-v1', { + previous_root: current.merkle_root, + leaf, + count, + }) + : leaf; + return { + ...(current ?? { + type: 'deposit_root', + epoch, + au_total: ZERO_AU, + }), + type: 'deposit_root', + epoch, + merkle_root: merkleRoot, + count, + reversed: true, + reversal_count: (current?.reversal_count ?? 0) + 1, + reversed_au_total: reversedAuTotal, + clawback_au_total: clawbackAuTotal, + network_absorbed_au_total: networkAbsorbedAuTotal, + ts: at, + updated_at: this.tx, + }; + } + + async epochApplyHash(value) { + const digest = await blake3(b4a.from(stableJson({ + domain: 'mayhem-epoch-apply-v1', + value, + }))); + return b4a.toString(digest, 'hex'); + } + + epochEmptySealHashValue(value) { + return { + type: value.type, + epoch: value.epoch, + at: value.at, + epoch_seconds: value.epoch_seconds, + previous_apply_hash: value.previous_apply_hash ?? null, + reason_hash: value.reason_hash, + sealed_by: value.sealed_by, + sealed_by_role: value.sealed_by_role, + totals: value.totals, + ...(value.market_price_root !== undefined ? { + market_price_root: value.market_price_root, + market_price_count: value.market_price_count, + } : {}), + }; + } + + async epochEmptySealHash(value) { + return await this.opaqueHash('mayhem-epoch-empty-seal-v1', this.epochEmptySealHashValue(value)); + } + + async epochApplyFeatureKey(value) { + const shapeError = this.validateEpochApplyFeatureValue(value); + if (shapeError) return shapeError; + const digest = await blake3(b4a.from(stableJson({ + domain: 'mayhem-epoch-apply-feature-v1', + value, + }))); + return `epoch/apply/${value.epoch}/${b4a.toString(digest, 'hex')}`; + } + + async tapAccountBindingFeatureKey(value) { + const normalized = this.normalizeTapAccountBinding(value); + if (normalized instanceof Error) return normalized; + const digest = await blake3(b4a.from(tapAccountBindingMessage(normalized))); + return `tap_account/${normalized.user}/${b4a.toString(digest, 'hex')}`; + } + + tapAccountBindingKey(user, chainId, poolAddress) { + return `tap/account/${chainId}/${poolAddress.toLowerCase()}/${user}`; + } + + tapAccountAddressKey(ethereumAddress, chainId, poolAddress) { + return `tap/account-by-address/${chainId}/${poolAddress.toLowerCase()}/${ethereumAddress.toLowerCase()}`; + } + + tapDepositIdentity(value) { + return [ + value.chain_id, + value.pool_address.toLowerCase(), + value.eth_tx_hash.toLowerCase(), + value.log_index, + value.block_hash.toLowerCase(), + ].join('/'); + } + + validateTapDepositIdentity(value) { + for (const key of ['chain_id', 'pool_address', 'eth_tx_hash', 'log_index', 'block_hash']) { + if (!hasOwn(value, key)) return new Error(`TAP deposit is missing ${key}.`); + } + if (!Number.isSafeInteger(value.chain_id) || value.chain_id < 1) { + return new Error('Invalid TAP chain id.'); + } + if (!this.isEthHexBytes(value.pool_address, 20)) return new Error('Invalid TAP pool address.'); + if (!this.isEthHexBytes(value.eth_tx_hash, 32)) return new Error('Invalid Ethereum tx hash.'); + if (!Number.isSafeInteger(value.log_index) || value.log_index < 0) { + return new Error('Invalid TAP deposit log index.'); + } + if (!this.isEthHexBytes(value.block_hash, 32)) return new Error('Invalid TAP deposit block hash.'); + return null; + } + + async depositFeatureKey(value) { + if (!value || typeof value !== 'object' || Array.isArray(value)) { + return new Error('Deposit feature value must be an object.'); + } + const digest = b4a.toString( + await blake3(b4a.from(stableJson({ + domain: 'mayhem-deposit-feature-v1', + value, + }))), + 'hex' + ); + if (value.op === 'deposit_tnk' && value.intent) { + const intentError = this.validateDepositTnkIntent(value.intent); + if (intentError) return intentError; + return `dep/tnk-intent/${value.intent.memo_hash}/${digest}`; + } + if (value.op === 'tnk_deposit') { + if (!this.isSafeKeyPart(value.memo_hash)) return new Error('Invalid deposit memo hash.'); + return `dep/tnk/${value.memo_hash}/${digest}`; + } + if (value.op === 'tap_deposit') { + const validationError = this.validateTapDepositIdentity(value); + if (validationError) return validationError; + return `dep/tap/${this.tapDepositIdentity(value)}`; + } + if (value.op === 'tap_deposit_reversal') { + const validationError = this.validateTapDepositReversalValue(value); + if (validationError) return validationError; + return `dep/tap/${this.tapDepositIdentity(value)}/reversal`; + } + if (value.op === 'fiat_deposit') { + if (!this.isSafeKeyPart(value.ext_ref_hash)) return new Error('Invalid external reference hash.'); + return `dep/fiat/${value.ext_ref_hash}`; + } + if (value.op === 'fiat_chargeback') { + if (!this.isSafeKeyPart(value.ext_ref_hash)) return new Error('Invalid external reference hash.'); + if (!this.isSafeKeyPart(value.dispute_ref_hash)) return new Error('Invalid dispute reference hash.'); + return `dep/fiat/${value.ext_ref_hash}/chargeback/${value.dispute_ref_hash}`; + } + return new Error('Unsupported deposit feature op.'); + } + + async rateFeatureKey(value) { + if (!value || typeof value !== 'object' || Array.isArray(value)) { + return new Error('Rate feature value must be an object.'); + } + let kind; + if (value.op === 'rate_oracle') { + const shapeError = this.validateRateOracleValue(value); + if (shapeError) return shapeError; + kind = 'tnk'; + } else if (value.op === 'tap_rate_oracle') { + const shapeError = this.validateTapRateOracleValue(value); + if (shapeError) return shapeError; + kind = 'tap'; + } else { + return new Error('Unsupported rate feature op.'); + } + this._mayhemApplyStage = 'rate:key:hash'; + const digest = b4a.toString( + await blake3(b4a.from(stableJson({ + domain: 'mayhem-rate-feature-v1', + value, + }))), + 'hex' + ); + this._mayhemApplyStage = 'rate:key:hashed'; + return `rate/${kind}/${value.ts}/${digest}`; + } + + async targetedPayoutPreparationFeatureKey(value) { + const normalized = await this.normalizeTargetedPayoutPreparationValue(value); + if (normalized instanceof Error) return normalized; + const digest = b4a.toString( + await blake3(b4a.from(stableJson({ + domain: 'mayhem-targeted-payout-preparation-feature-v1', + value: normalized, + }))), + 'hex' + ); + return ( + `payout/preparation-submit/${normalized.rail}/` + + `${normalized.economic_op_id}/${digest}` + ); + } + + async targetedPayoutEpochFeatureKey(value) { + const normalized = await this.normalizeTargetedPayoutEpochValue(value); + if (normalized instanceof Error) return normalized; + const digest = b4a.toString( + await blake3(b4a.from(stableJson({ + domain: 'mayhem-targeted-payout-epoch-feature-v1', + value: normalized, + }))), + 'hex' + ); + return `payout/epoch-plan-submit/${normalized.rail}/${normalized.epoch}/${digest}`; + } + + async targetedFiatAttemptFeatureKey(value) { + const normalized = await this.normalizePrepareTargetedFiatAttemptValue(value); + if (normalized instanceof Error) return normalized; + const digest = b4a.toString( + await blake3(b4a.from(stableJson({ + domain: 'mayhem-targeted-fiat-attempt-feature-v1', + value: normalized, + }))), + 'hex' + ); + return ( + `payout/attempt-submit/fiat/${normalized.economic_op_id}/` + + `${normalized.attempt_id}/${digest}` + ); + } + + async finalizeTargetedFiatAttemptFeatureKey(value) { + const normalized = this.normalizeFinalizeTargetedFiatAttemptValue(value); + if (normalized instanceof Error) return normalized; + const digest = b4a.toString( + await blake3(b4a.from(stableJson({ + domain: 'mayhem-finalize-targeted-fiat-attempt-feature-v1', + value: normalized, + }))), + 'hex' + ); + return ( + `payout/attempt-finalize/fiat/${normalized.economic_op_id}/` + + `${normalized.attempt_id}/${digest}` + ); + } + + async targetedTnkOutputFeatureKey(value) { + const normalized = this.normalizeTargetedTnkOutputSettlementValue(value); + if (normalized instanceof Error) return normalized; + const digest = b4a.toString( + await blake3(b4a.from(stableJson({ + domain: 'mayhem-targeted-tnk-output-feature-v1', + value: normalized, + }))), + 'hex' + ); + return ( + `settle/targeted/tnk/${normalized.epoch}/output/` + + `${normalized.economic_op_id}/${digest}` + ); + } + + async targetedFiatOutputFeatureKey(value) { + const normalized = this.normalizeTargetedFiatOutputSettlementValue(value); + if (normalized instanceof Error) return normalized; + const digest = b4a.toString( + await blake3(b4a.from(stableJson({ + domain: 'mayhem-targeted-fiat-output-feature-v1', + value: normalized, + }))), + 'hex' + ); + return ( + `settle/targeted/fiat/${normalized.epoch}/output/` + + `${normalized.economic_op_id}/${digest}` + ); + } + + async closeTargetedPayoutEpochFeatureKey(value) { + const normalized = this.normalizeCloseTargetedPayoutEpochValue(value); + if (normalized instanceof Error) return normalized; + const digest = b4a.toString( + await blake3(b4a.from(stableJson({ + domain: 'mayhem-close-targeted-payout-epoch-feature-v1', + value: normalized, + }))), + 'hex' + ); + return `settle/targeted/${normalized.rail}/${normalized.epoch}/close/${digest}`; + } + + async targetedTnkSettlementFeatureKey(value) { + const normalized = this.normalizeTargetedTnkSettlementValue(value); + if (normalized instanceof Error) return normalized; + const digest = b4a.toString( + await blake3(b4a.from(stableJson({ + domain: 'mayhem-targeted-tnk-settlement-feature-v1', + value, + }))), + 'hex' + ); + return `settle/targeted/tnk/${value.epoch}/${digest}`; + } + + async targetedTapSettlementFeatureKey(value) { + const normalized = this.normalizeTargetedTapSettlementValue(value); + if (normalized instanceof Error) return normalized; + const digest = b4a.toString( + await blake3(b4a.from(stableJson({ + domain: 'mayhem-targeted-tap-settlement-feature-v1', + value, + }))), + 'hex' + ); + return `settle/targeted/tap/${value.epoch}/${digest}`; + } + + async targetedFiatSettlementFeatureKey(value) { + const normalized = this.normalizeTargetedFiatSettlementValue(value); + if (normalized instanceof Error) return normalized; + const digest = b4a.toString( + await blake3(b4a.from(stableJson({ + domain: 'mayhem-targeted-fiat-settlement-feature-v2', + value, + }))), + 'hex' + ); + return `settle/targeted/fiat/${value.epoch}/${digest}`; + } + + async fiatDustSweepFeatureKey(value) { + const validationError = this.validateFiatDustSweepValue(value); + if (validationError) return validationError; + const digest = b4a.toString( + await blake3(b4a.from(stableJson({ + domain: 'mayhem-fiat-dust-sweep-feature-v1', + value, + }))), + 'hex' + ); + return `settle/fiat-dust/${value.provider}/${value.epoch}/${digest}`; + } + + async reputationAnchorFeatureKey(value) { + const validationError = this.validateReputationAnchor(value); + if (validationError) return validationError; + const digest = b4a.toString( + await blake3(b4a.from(stableJson({ + domain: 'mayhem-reputation-anchor-feature-v1', + value, + }))), + 'hex' + ); + return `rep/${value.provider}/${value.epoch}/${digest}`; + } + + async tier3MeasurementFeatureKey(value) { + const validationError = this.validateTier3MeasurementBlessValue(value); + if (validationError) return validationError; + const digest = b4a.toString( + await blake3(b4a.from(stableJson({ + domain: 'mayhem-tier3-measurement-feature-v1', + value, + }))), + 'hex' + ); + return `tier3/measurement/${value.platform}/${digest}`; + } + + async epochCommitHash(value) { + const digest = await blake3(b4a.from(stableJson({ + domain: 'mayhem-epoch-commit-v1', + value, + }))); + return b4a.toString(digest, 'hex'); + } + + probePass(value, params) { + if (value.probe_kind === 'uptime_tick') return true; + return value.match_bps >= params.canary_match_min_bps; + } + + async requireAuditorEligibility(auditor, atSeconds) { + const rep = await this.get(`rep/${auditor}`); + if (!rep) return new Error('Auditor reputation snapshot required.'); + const params = await this.activeParamsAt(atSeconds, [ + 'auditor_min_reputation_bps', + 'auditor_min_age_seconds', + ]); + if (rep.provenance_violation === true) return new Error('Auditor has a provenance violation.'); + if ((rep.r_bps ?? 0) < params.auditor_min_reputation_bps) { + return new Error('Auditor reputation too low.'); + } + const sinceSeconds = rep.probation?.since_seconds ?? 0; + if (atSeconds - sinceSeconds < params.auditor_min_age_seconds) { + return new Error('Auditor account age too low.'); + } + return null; + } + + async appendReputationEvent(event) { + if (!this.isSafeKeyPart(event.event_id)) return new Error('Invalid reputation event id.'); + const key = `ev/rep/${event.provider}/${event.event_id}`; + if ((await this.get(key)) !== null) return new Error('Reputation event already recorded.'); + + const headKey = `ev/rep/head/${event.provider}`; + const currentHead = await this.get(headKey); + const body = { + ...event, + paid_au: event.paid_au !== null && event.paid_au !== undefined + ? this.normalizeAu(event.paid_au, 'reputation paid amount') + : null, + max_spend_au: event.max_spend_au !== null && event.max_spend_au !== undefined + ? this.normalizeAu(event.max_spend_au, 'reputation max spend') + : null, + evidence_hash: event.evidence_hash ?? null, + recorded_at: this.tx, + recorded_by: this.address, + }; + const head = await this.reputationEventHead(currentHead?.head ?? null, body); + const foldKey = `ev/rep/fold/${event.provider}`; + const fold = this.advanceReputationFold(await this.get(foldKey), body, head); + if (fold instanceof Error) return fold; + const record = { + ...body, + head, + }; + const headRecord = { + provider: event.provider, + head, + count: (currentHead?.count ?? 0) + 1, + updated_at: this.tx, + }; + + await this.put(key, record); + await this.put(headKey, headRecord); + await this.put(foldKey, fold); + return record; + } + + parseSignedDecimal(value, label) { + if (typeof value !== 'string' || !/^(0|-?[1-9][0-9]*)$/.test(value)) { + return new Error(`${label} must be a canonical signed decimal string.`); + } + return BigInt(value); + } + + roundSignedRatio(value, divisor) { + if (typeof value !== 'bigint' || typeof divisor !== 'bigint' || divisor <= 0n) { + return new Error('Invalid signed ratio.'); + } + const negative = value < 0n; + const absolute = negative ? -value : value; + const rounded = (absolute + divisor / 2n) / divisor; + return negative ? -rounded : rounded; + } + + quantizePositiveReputation(value, scale, label) { + const scaled = value * Number(scale); + if (!Number.isFinite(scaled) || scaled < 0 || !Number.isSafeInteger(Math.floor(scaled + 0.5))) { + return new Error(`Invalid ${label}.`); + } + return BigInt(Math.floor(scaled + 0.5)); + } + + decayReputationRawNano(rawNano, fromSeconds, toSeconds) { + if ( + typeof rawNano !== 'bigint' || + !Number.isSafeInteger(fromSeconds) || + !Number.isSafeInteger(toSeconds) || + fromSeconds < 0 || + toSeconds < fromSeconds + ) { + return new Error('Invalid reputation decay range.'); + } + if (fromSeconds === toSeconds || rawNano === 0n) return rawNano; + const decay = 2 ** (-(toSeconds - fromSeconds) / REPUTATION_HALF_LIFE_SECONDS); + const decayPico = this.quantizePositiveReputation( + decay, + REPUTATION_DECAY_PICO_SCALE, + 'reputation decay' + ); + if (decayPico instanceof Error) return decayPico; + return this.roundSignedRatio( + rawNano * decayPico, + REPUTATION_DECAY_PICO_SCALE + ); + } + + reputationEventRawNano(event) { + let scoreQuarters = null; + let weightedAu = null; + if (event.kind === 'session_ok') { + scoreQuarters = 4n; + weightedAu = event.paid_au; + } else if (event.kind === 'session_partial') { + scoreQuarters = 1n; + weightedAu = event.paid_au; + } else if (event.kind === 'session_fail') { + scoreQuarters = -16n; + weightedAu = event.max_spend_au; + } + if (scoreQuarters !== null) { + const amount = this.parseAu(weightedAu, 'reputation weighted amount'); + if (amount instanceof Error) return amount; + const weight = Math.log10(1 + Number(amount)); + const weightNano = this.quantizePositiveReputation( + weight, + REPUTATION_RAW_NANO_SCALE, + 'reputation paid weight' + ); + if (weightNano instanceof Error) return weightNano; + return this.roundSignedRatio(weightNano * scoreQuarters, 4n); + } + const fixedScores = { + probe_ok: 500_000_000n, + probe_fail: -6_000_000_000n, + uptime_tick: 100_000_000n, + underdelivery: -6_000_000_000n, + dispute_lost: -20_000_000_000n, + provenance_violation: 0n, + }; + return fixedScores[event.kind] ?? new Error('Unsupported reputation event kind.'); + } + + advanceReputationFold(current, event, eventsHead) { + if (!Number.isSafeInteger(event.at) || event.at < 0) { + return new Error('Invalid reputation event time.'); + } + if (!Number.isSafeInteger(event.epoch) || event.epoch < 0) { + return new Error('Invalid reputation event epoch.'); + } + let rawNano = 0n; + let foldAt = event.at; + let successfulSessions = 0; + let provenanceViolation = false; + let eventCount = 0; + let maxEpoch = 0; + if (current !== null) { + rawNano = this.parseSignedDecimal(current.raw_nano, 'reputation raw_nano'); + if (rawNano instanceof Error) return rawNano; + if ( + !Number.isSafeInteger(current.at) || current.at < 0 || + !Number.isSafeInteger(current.successful_sessions) || current.successful_sessions < 0 || + !Number.isSafeInteger(current.event_count) || current.event_count < 0 || + !Number.isSafeInteger(current.max_epoch) || current.max_epoch < 0 || + typeof current.provenance_violation !== 'boolean' + ) { + return new Error('Invalid reputation fold state.'); + } + foldAt = Math.max(current.at, event.at); + rawNano = this.decayReputationRawNano(rawNano, current.at, foldAt); + if (rawNano instanceof Error) return rawNano; + successfulSessions = current.successful_sessions; + provenanceViolation = current.provenance_violation; + eventCount = current.event_count; + maxEpoch = current.max_epoch; + } + let contribution = this.reputationEventRawNano(event); + if (contribution instanceof Error) return contribution; + contribution = this.decayReputationRawNano(contribution, event.at, foldAt); + if (contribution instanceof Error) return contribution; + const nextSuccessfulSessions = this.safeAddCount( + successfulSessions, + event.kind === 'session_ok' ? 1 : 0, + 'successful reputation session count' + ); + if (nextSuccessfulSessions instanceof Error) return nextSuccessfulSessions; + const nextEventCount = this.safeAddCount(eventCount, 1, 'reputation event count'); + if (nextEventCount instanceof Error) return nextEventCount; + return { + provider: event.provider, + raw_nano: (rawNano + contribution).toString(), + at: foldAt, + max_epoch: Math.max(maxEpoch, event.epoch), + successful_sessions: nextSuccessfulSessions, + provenance_violation: provenanceViolation || event.kind === 'provenance_violation', + event_count: nextEventCount, + events_head: eventsHead, + updated_at: this.tx, + }; + } + + reputationFoldAt(fold, foldedAt) { + if (!Number.isSafeInteger(foldedAt) || foldedAt < fold.at) { + return new Error('Reputation folded_at precedes the latest event.'); + } + const rawNano = this.parseSignedDecimal(fold.raw_nano, 'reputation raw_nano'); + if (rawNano instanceof Error) return rawNano; + const decayed = this.decayReputationRawNano(rawNano, fold.at, foldedAt); + if (decayed instanceof Error) return decayed; + const rawMilliValue = this.roundSignedRatio(decayed, REPUTATION_RAW_NANO_PER_MILLI); + if (rawMilliValue instanceof Error) return rawMilliValue; + const rawMilli = Number(rawMilliValue); + if (!Number.isSafeInteger(rawMilli)) return new Error('Reputation raw_milli overflow.'); + const raw = rawMilli / 1_000; + const r = 1 / (1 + Math.exp(-raw / REPUTATION_KAPPA)); + const rBps = Math.floor(r * 10_000 + 0.5); + if (!Number.isSafeInteger(rBps) || rBps < 0 || rBps > 10_000) { + return new Error('Invalid folded reputation score.'); + } + return { + raw_milli: rawMilli, + r_bps: rBps, + successful_sessions: fold.successful_sessions, + provenance_violation: fold.provenance_violation, + }; + } + + isSafeKeyPart(value) { + return typeof value === 'string' && /^[a-zA-Z0-9._:-]{1,128}$/.test(value); + } + + isSafeModelId(value) { + return typeof value === 'string' && + /^[a-zA-Z0-9._:@/+~-]{1,256}$/.test(value) && + !value.startsWith('/') && + !value.endsWith('/') && + !value.includes('//'); + } + + isSafeHuggingFaceRepo(value) { + if (typeof value !== 'string') return false; + const parts = value.split('/'); + return parts.length === 2 && + parts.every((part) => this.isSafeHuggingFaceComponent(part)); + } + + isSafeHuggingFaceComponent(value) { + return typeof value === 'string' && + /^[A-Za-z0-9][A-Za-z0-9._-]{0,95}$/.test(value) && + !value.endsWith('.') && + !value.endsWith('-') && + !value.includes('..') && + !value.includes('--'); + } + + isSafeHuggingFacePath(value) { + return typeof value === 'string' && + value.length > 0 && + !value.startsWith('/') && + !value.startsWith('\\') && + !value.includes('\\') && + !value.includes('?') && + !value.includes('#') && + !value.includes('%') && + !/[\x00-\x1f\x7f]/.test(value) && + value.split('/').every((part) => this.isSafeHuggingFacePathSegment(part)); + } + + isSafeHuggingFacePathSegment(value) { + return typeof value === 'string' && + value.length > 0 && + value !== '.' && + value !== '..' && + /^[A-Za-z0-9._+-]+$/.test(value); + } + + isHttpsUrl(value) { + if (typeof value !== 'string' || value.length === 0 || value.length > 512) return false; + try { + const parsed = new URL(value); + return parsed.protocol === 'https:' && !!parsed.hostname; + } catch { + return false; + } + } + + isPinnedHuggingFaceResolveUrl(value) { + return this.pinnedHuggingFaceResolveRevision(value) !== null; + } + + pinnedHuggingFaceResolveRevision(value) { + if (!this.isHttpsUrl(value)) return null; + const parsed = new URL(value); + if (parsed.hostname !== 'huggingface.co') return null; + const parts = parsed.pathname.split('/').filter(Boolean); + const resolveIndex = parts.indexOf('resolve'); + if (resolveIndex < 0 || resolveIndex + 2 >= parts.length) return null; + return this.isHexBytes(parts[resolveIndex + 1], 20) ? parts[resolveIndex + 1] : null; + } + + isSafeExternalRef(value) { + return typeof value === 'string' && /^[a-zA-Z0-9._:-]{1,256}$/.test(value); + } + + normalizeProviderKybValue(value) { + if (!this.isHexBytes(value.provider, 32)) return new Error('Invalid provider id.'); + const legalName = value.legal_name.trim(); + if (!legalName || /[\x00-\x1f\x7f]/.test(legalName)) { + return new Error('Invalid provider KYB legal name.'); + } + const jurisdiction = value.jurisdiction.trim().toUpperCase(); + if (!/^[A-Z0-9._:-]{1,64}$/.test(jurisdiction)) { + return new Error('Invalid provider KYB jurisdiction.'); + } + const proofHash = value.proof_hash.toLowerCase(); + if (!this.isHexBytes(proofHash, 32)) return new Error('Invalid provider KYB proof hash.'); + const kybRef = value.kyb_ref.trim(); + if (!this.isSafeExternalRef(kybRef)) return new Error('Invalid provider KYB reference.'); + const schemaVersion = value.schema_version ?? 1; + if (!Number.isInteger(schemaVersion) || schemaVersion < 1) { + return new Error('Invalid provider KYB schema version.'); + } + const adminSig = value.admin_sig.toLowerCase(); + if (!this.isHexBytes(adminSig, 64)) return new Error('Invalid provider KYB admin signature.'); + return { + provider: value.provider.toLowerCase(), + legal_name: legalName, + jurisdiction, + proof_hash: proofHash, + kyb_ref: kybRef, + verified_at: value.verified_at, + schema_version: schemaVersion, + admin_sig: adminSig, + }; + } + + isHexBytes(value, bytes) { + return typeof value === 'string' && + value.length === bytes * 2 && + /^[0-9a-fA-F]+$/.test(value); + } + + isEthHexBytes(value, bytes) { + return typeof value === 'string' && + value.length === bytes * 2 + 2 && + /^0x[0-9a-fA-F]+$/.test(value); + } + + async reputationEventHead(previousHead, event) { + const payload = stableJson({ + domain: 'mayhem-reputation-event-v1', + previous_head: previousHead, + event, + }); + const digest = await blake3(b4a.from(payload)); + return b4a.toString(digest, 'hex'); + } + + verifyConsentSignature(sender, ver, hash, sig) { + const verify = this.protocol?.peer?.wallet?.verify; + if (typeof verify !== 'function') return false; + return verify.call(this.protocol.peer.wallet, sig, consentMessage(ver, hash), sender) === true; + } + + verifyProviderLifecycleSignature(provider, intent, sig) { + const verify = this.protocol?.peer?.wallet?.verify; + if (typeof verify !== 'function') return false; + return verify.call(this.protocol.peer.wallet, sig, providerLifecycleIntentMessage(intent), provider) === true; + } + + verifyProviderPayoutBindingSignature(provider, intent, signature) { + const verify = this.protocol?.peer?.wallet?.verify; + if (typeof verify !== 'function') return false; + return verify.call( + this.protocol.peer.wallet, + signature, + providerPayoutBindingMessage(intent), + provider + ) === true; + } + + verifyProviderPayoutTargetBindingSignature(intent) { + if (intent.rail === 'fiat') return intent.target_signature === null; + if (intent.rail === 'tnk') { + const verify = this.protocol?.peer?.wallet?.verify; + if (typeof verify !== 'function') return false; + return verify.call( + this.protocol.peer.wallet, + intent.target_signature, + providerPayoutTargetBindingMessage(intent), + intent.target_wallet + ) === true; + } + if (intent.rail !== 'tap') return false; + try { + const bytes = b4a.from(intent.target_signature.slice(2), 'hex'); + let recovery = bytes[64]; + if (recovery === 27 || recovery === 28) recovery -= 27; + if (recovery !== 0 && recovery !== 1) return false; + const signature = secp256k1.Signature + .fromCompact(bytes.subarray(0, 64)) + .addRecoveryBit(recovery); + if (signature.hasHighS()) return false; + const publicKey = signature + .recoverPublicKey( + ethereumPersonalMessageHash(providerPayoutTargetBindingMessage(intent)) + ) + .toRawBytes(false); + return ethereumAddressFromPublicKey(publicKey) === intent.target; + } catch { + return false; + } + } + + verifyDepositTnkSignature(sender, intent, sig) { + const verify = this.protocol?.peer?.wallet?.verify; + if (typeof verify !== 'function') return false; + return verify.call(this.protocol.peer.wallet, sig, depositTnkIntentMessage(intent), sender) === true; + } + + verifyTapAccountUserSignature(value) { + const verify = this.protocol?.peer?.wallet?.verify; + if (typeof verify !== 'function') return false; + return verify.call( + this.protocol.peer.wallet, + value.user_sig, + tapAccountBindingMessage(value), + value.user + ) === true; + } + + verifyTapAccountEthereumSignature(value) { + try { + const bytes = b4a.from(value.ethereum_sig.slice(2), 'hex'); + let recovery = bytes[64]; + if (recovery === 27 || recovery === 28) recovery -= 27; + if (recovery !== 0 && recovery !== 1) return false; + const signature = secp256k1.Signature + .fromCompact(bytes.subarray(0, 64)) + .addRecoveryBit(recovery); + if (signature.hasHighS()) return false; + const publicKey = signature + .recoverPublicKey(ethereumPersonalMessageHash(tapAccountBindingMessage(value))) + .toRawBytes(false); + return ethereumAddressFromPublicKey(publicKey) === value.ethereum_address.toLowerCase(); + } catch { + return false; + } + } + + verifySpendVoucherSignature(user, body, sig) { + const verify = this.protocol?.peer?.wallet?.verify; + if (typeof verify !== 'function') return false; + return verify.call(this.protocol.peer.wallet, sig, spendVoucherMessage(body), user) === true; + } + + verifySpendReservationSignature(provider, value) { + const verify = this.protocol?.peer?.wallet?.verify; + if (typeof verify !== 'function') return false; + return verify.call(this.protocol.peer.wallet, value.provider_sig, spendReservationMessage(value), provider) === true; + } + + verifyProbeResultSignature(auditor, value) { + const verify = this.protocol?.peer?.wallet?.verify; + if (typeof verify !== 'function') return false; + return verify.call(this.protocol.peer.wallet, value.auditor_sig, probeResultMessage(value, auditor), auditor) === true; + } + + async verifyProviderKybSignature(value) { + const admin = await this.get('admin'); + const verify = this.protocol?.peer?.wallet?.verify; + if (typeof admin !== 'string' || typeof verify !== 'function') return false; + return verify.call(this.protocol.peer.wallet, value.admin_sig, providerKybMessage(value), admin) === true; + } +} + +export default MayhemContract; diff --git a/intercom/contract/history/v27.js b/intercom/contract/history/v27.js new file mode 100644 index 00000000..2e639066 --- /dev/null +++ b/intercom/contract/history/v27.js @@ -0,0 +1,25585 @@ +import b4a from 'b4a'; +import { blake3 } from '@tracsystems/blake3'; +import { keccak256 } from 'ethereum-cryptography/keccak'; +import { secp256k1 } from 'ethereum-cryptography/secp256k1'; +import { Contract } from 'trac-peer'; +import { consumeCanonicalReplayContext } from 'trac-peer/src/base/canonical-replay.js'; +import PeerWallet from 'trac-wallet'; +import ContractV23 from './v23.js'; +import ContractV24 from './v24.js'; +import ContractV25 from './v25.js'; +import ContractV26 from './v26.js'; + +export const CONTRACT_VERSION = 27; +// Recovery is limited to receipt evidence already signed by v23-v26 +// participants. New prior-version operations are not admitted; +// separately authenticated canonical replay does not constitute new admission. +const RECOVERABLE_RECEIPT_CONTRACT_VERSIONS = new Set([23, 24, 25, 26]); +const SIGNING_MESSAGE_VERSION = 2; +const CURRENT_RULES_KEY = 'rules/current'; +const PROVIDER_ACCEPTED_RAILS = new Set(['fiat', 'tap', 'tnk']); +const PROVIDER_ACCEPTED_RAIL_ORDER = Object.freeze(['fiat', 'tap', 'tnk']); +const PROVIDER_RAIL_SCHEMA_VERSION = 1; +const PROVIDER_PAYOUT_BINDING_RAILS = new Set(['fiat', 'tap', 'tnk']); +const PROVIDER_PAYOUT_BINDING_RAIL_ORDER = Object.freeze(['fiat', 'tap', 'tnk']); +const PROVIDER_PAYOUT_BINDING_CONTEXT_CURRENT_KEY = 'payout/context/current'; +export const PAYOUT_INTENT_MAX_EXPIRY_EPOCHS_DEFAULT = 10_000; +const PAYOUT_PARAM_DEFINITIONS = Object.freeze({ + payout_intent_max_expiry_epochs: { + default: PAYOUT_INTENT_MAX_EXPIRY_EPOCHS_DEFAULT, + min: 1, + max: 1_000_000, + }, +}); +const FIAT_DEPOSIT_RAILS = new Set(['stripe']); +const REQUIRED_FIAT_PAYOUT_CURRENCIES = Object.freeze(['eur', 'gbp', 'usd']); +const PRICE_DENOMINATION = 'au_usd'; +const RATE_SOURCES = new Set(['gate-spot', 'mexc-spot']); +const LEGACY_TNK_DEPOSIT_RATE_DRIFT_BPS = 100n; +const CATALOG_SOURCE_KINDS = new Set(['https', 'huggingface']); +const CATALOG_RUNTIME_STATUSES = new Set(['blessed', 'deprecated', 'revoked']); +const CATALOG_OUTCOME_CLASS_STATUSES = new Set(['active', 'deprecated', 'revoked']); +const PROVIDER_LIFECYCLE_OPS = new Set([ + 'register_provider', + 'join_enclave', + 'leave_enclave', + 'join_room', + 'leave_room', + 'set_provider_rails', +]); +const DAY_SECONDS = 24 * 60 * 60; +const DEFAULT_PRICE_RATE_LIMIT_SECONDS = 6 * 60 * 60; +const DEFAULT_MARKET_PRICE_TARGET_UTILIZATION_BPS = 8_500; +const DEFAULT_MARKET_PRICE_EMA_ALPHA_BPS = 2_500; +const DEFAULT_MARKET_PRICE_GAIN_BPS = 5_000; +const DEFAULT_MARKET_PRICE_MAX_STEP_BPS = 1_000; +const MARKET_UTILIZATION_LOW_BPS = 2_000; +const MARKET_UTILIZATION_HIGH_BPS = 8_000; +const MARKET_UTILIZATION_STEP_BPS = 1_000; +const DEFAULT_MARKET_PRICE_COLD_START_MIN_PROVIDERS = 2; +const ZERO_AU = '0'; +const ONE_USD_AU = '1000000000000000000'; +const FIVE_MILLI_USD_AU = '5000000000000000'; +const DEFAULT_MARKET_PRICE_PROVIDER_EPOCH_TARGET_AU = ONE_USD_AU; +const DEFAULT_MARKET_PRICE_MAX_UTILIZATION_BPS = 50_000; +const DEFAULT_MARKET_PRICE_BELOW_TARGET_DISCOUNT_BPS = 2_500; +const DEFAULT_MARKET_PRICE_ABOVE_TARGET_SLOPE_BPS = 15_000; +const DEFAULT_PARAM_ACTIVATION_DELAY_SECONDS = DAY_SECONDS; +const PROBATION_SECONDS = 7 * DAY_SECONDS; +const DEFAULT_FRAUD_SLASH_BPS = 10_000; +const DEFAULT_DISPUTE_LOST_SLASH_BPS = 2_000; +const MAX_OPERATOR_FEE_BPS = 1_500; +const MAX_LAUNCH_ENCLAVE_ATTESTATION_TIER = 3; +const DEFAULT_DISPUTE_DEPOSIT_AU = ONE_USD_AU; +const DEFAULT_DISPUTE_TIMEOUT_EPOCHS = 168; +const DEFAULT_MAX_OPEN_DISPUTES_PER_OPENER = 8; +const DEFAULT_DISPUTE_OPENER_FAULT_FORFEIT_BPS = 2_500; +const DEFAULT_MAX_APPLY_BATCH = 2_000; +const DEFAULT_MAX_MARKET_USAGE_ENTRIES = 5_000; +const DEFAULT_MAX_TAP_SETTLEMENT_OUTPUTS = 5_000; +const DEFAULT_MAX_TNK_SETTLEMENT_OUTPUTS = 5_000; +const DEFAULT_MAX_FIAT_SETTLEMENT_OUTPUTS = 5_000; +const MIN_TAP_CONFIRMATION_DEPTH = 12; +const TAP_OPERATOR_BPS = 1_500; +const TAP_BURN_BPS = 1_000; +const DISPUTE_EVIDENCE_MAX_BYTES = 4_096; +const FRAUD_PROOF_MAX_BYTES = 4_096; +export const SESSION_RECEIPT_SCHEMA_VERSION = 12; +export const SPEND_VOUCHER_SCHEMA_VERSION = 11; +const RECEIPT_EPOCH_INDEX_PAGE_SIZE = 128; +const CTX_BRACKET_TABLE_VERSION = 1; +const CTX_BRACKETS = Object.freeze([ + { id: 'le8k', max_ctx: 8_192 }, + { id: 'le32k', max_ctx: 32_768 }, + { id: 'le128k', max_ctx: 131_072 }, + { id: 'le256k', max_ctx: 262_144 }, + { id: 'gt256k', max_ctx: null }, +]); +const TNK_E18 = 1_000_000_000_000_000_000n; +const TAP_WEI = 1_000_000_000_000_000_000n; +const USD_AU = 1_000_000_000_000_000_000n; +const USD_CENT_AU = 10_000_000_000_000_000n; +const TAP_DEPOSIT_EVENT_SIGNATURE = '0xe1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d3c460751c2402c5c5cc9109c'; +const TAP_DEPOSIT_WATCHER_ID = 'tap-deposit-watcher-v1'; +const MSB_TRANSFER_EVIDENCE_VERSION = 1; +const STRIPE_TRANSFER_EVIDENCE_VERSION = 2; +const REPUTATION_HALF_LIFE_SECONDS = 14 * DAY_SECONDS; +const REPUTATION_KAPPA = 25; +const REPUTATION_RAW_NANO_SCALE = 1_000_000_000n; +const REPUTATION_DECAY_PICO_SCALE = 1_000_000_000_000n; +const REPUTATION_RAW_NANO_PER_MILLI = 1_000_000n; +const PARAM_DEFINITIONS = Object.freeze({ + probation_successful_sessions: { default: 50, min: 0, max: 1_000_000 }, + probation_seconds: { default: PROBATION_SECONDS, min: 0, max: 365 * 24 * 60 * 60 }, + probation_max_concurrent_sessions_per_user: { default: 2, min: 1, max: 1_000_000 }, + probation_price_max_bps: { default: 10_000, min: 0, max: 1_000_000 }, + probation_weight_bps: { default: 5_000, min: 0, max: 10_000 }, + auditor_min_reputation_bps: { default: 8_000, min: 0, max: 10_000 }, + auditor_min_age_seconds: { default: 30 * DAY_SECONDS, min: 0, max: 10 * 365 * DAY_SECONDS }, + canary_match_min_bps: { default: 9_000, min: 0, max: 10_000 }, + canary_probe_holdback_bps: { default: 0, min: 0, max: 10_000 }, + canary_probe_release_min_passes: { default: 2, min: 0, max: 1_000_000 }, + probe_reward_au: { default: FIVE_MILLI_USD_AU, min: ZERO_AU, money: true }, + uptime_tick_seconds: { default: 6 * 60 * 60, min: 60, max: 30 * DAY_SECONDS }, + fraud_slash_bps: { default: DEFAULT_FRAUD_SLASH_BPS, min: 0, max: 10_000 }, + dispute_lost_slash_bps: { default: DEFAULT_DISPUTE_LOST_SLASH_BPS, min: 0, max: 10_000 }, + new_provider_holdback_epochs: { default: 168, min: 0, max: 1_000_000 }, + holdback_epochs: { default: 24, min: 0, max: 1_000_000 }, + min_tier_notice_epochs: { default: 24, min: 1, max: 1_000_000 }, + fee_bps: { default: 1_500, min: 0, max: MAX_OPERATOR_FEE_BPS }, + dispute_deposit_au: { default: DEFAULT_DISPUTE_DEPOSIT_AU, min: '1', money: true }, + dispute_timeout_epochs: { default: DEFAULT_DISPUTE_TIMEOUT_EPOCHS, min: 1, max: 1_000_000 }, + max_open_disputes_per_opener: { default: DEFAULT_MAX_OPEN_DISPUTES_PER_OPENER, min: 1, max: 1_000 }, + dispute_opener_fault_forfeit_bps: { default: DEFAULT_DISPUTE_OPENER_FAULT_FORFEIT_BPS, min: 1, max: 9_999 }, + payout_min_au: { default: ONE_USD_AU, min: ZERO_AU, money: true }, + price_min_bps: { default: 2_500, min: 2_500, max: 40_000 }, + price_max_bps: { default: 40_000, min: 2_500, max: 40_000 }, + price_rate_limit_seconds: { default: DEFAULT_PRICE_RATE_LIMIT_SECONDS, min: 0, max: 365 * DAY_SECONDS }, + market_target_utilization_bps: { default: DEFAULT_MARKET_PRICE_TARGET_UTILIZATION_BPS, min: 1, max: 9_999, deprecated: true }, + market_ema_alpha_bps: { default: DEFAULT_MARKET_PRICE_EMA_ALPHA_BPS, min: 1, max: 10_000, deprecated: true }, + market_gain_bps: { default: DEFAULT_MARKET_PRICE_GAIN_BPS, min: 1, max: 10_000, deprecated: true }, + market_max_step_bps: { default: DEFAULT_MARKET_PRICE_MAX_STEP_BPS, min: 1, max: 10_000, deprecated: true }, + market_cold_start_min_providers: { default: DEFAULT_MARKET_PRICE_COLD_START_MIN_PROVIDERS, min: 0, max: 1_000_000, deprecated: true }, + market_provider_epoch_target_au: { default: DEFAULT_MARKET_PRICE_PROVIDER_EPOCH_TARGET_AU, min: '1', money: true, deprecated: true }, + market_max_utilization_bps: { default: DEFAULT_MARKET_PRICE_MAX_UTILIZATION_BPS, min: 1, max: 1_000_000, deprecated: true }, + market_below_target_discount_bps: { default: DEFAULT_MARKET_PRICE_BELOW_TARGET_DISCOUNT_BPS, min: 0, max: 10_000, deprecated: true }, + market_above_target_slope_bps: { default: DEFAULT_MARKET_PRICE_ABOVE_TARGET_SLOPE_BPS, min: 0, max: 1_000_000, deprecated: true }, + epoch_seconds: { default: 3_600, min: 60, max: 86_400 }, + reservation_max_lifetime_epochs: { default: 24, min: 1, max: 1_000_000 }, + reservation_receipt_grace_epochs: { default: 6, min: 0, max: 1_000_000 }, + rate_staleness_seconds: { default: 45 * 60, min: 60, max: 86_400 }, + rules_grace_seconds: { default: 14 * 24 * 60 * 60, min: 0, max: 365 * 24 * 60 * 60 }, + challenge_epochs: { default: 6, min: 0, max: 1_000_000 }, + max_apply_batch: { default: DEFAULT_MAX_APPLY_BATCH, min: 1, max: Number.MAX_SAFE_INTEGER }, + max_market_usage_entries: { default: DEFAULT_MAX_MARKET_USAGE_ENTRIES, min: 0, max: Number.MAX_SAFE_INTEGER }, + max_tap_settlement_outputs: { default: DEFAULT_MAX_TAP_SETTLEMENT_OUTPUTS, min: 1, max: Number.MAX_SAFE_INTEGER }, + max_tnk_settlement_outputs: { default: DEFAULT_MAX_TNK_SETTLEMENT_OUTPUTS, min: 1, max: Number.MAX_SAFE_INTEGER }, + max_fiat_settlement_outputs: { default: DEFAULT_MAX_FIAT_SETTLEMENT_OUTPUTS, min: 1, max: Number.MAX_SAFE_INTEGER }, + param_activation_delay_seconds: { default: DEFAULT_PARAM_ACTIVATION_DELAY_SECONDS, min: 0, max: 30 * DAY_SECONDS }, +}); +const EPOCH_ADMIN_PARAM_KEYS = Object.freeze([ + 'probation_successful_sessions', + 'probation_seconds', + 'probation_max_concurrent_sessions_per_user', + 'probation_price_max_bps', + 'probation_weight_bps', + 'auditor_min_reputation_bps', + 'auditor_min_age_seconds', + 'canary_match_min_bps', + 'canary_probe_holdback_bps', + 'canary_probe_release_min_passes', + 'probe_reward_au', + 'uptime_tick_seconds', + 'fraud_slash_bps', + 'dispute_lost_slash_bps', + 'new_provider_holdback_epochs', + 'holdback_epochs', + 'min_tier_notice_epochs', + 'fee_bps', + 'dispute_deposit_au', + 'dispute_timeout_epochs', + 'max_open_disputes_per_opener', + 'dispute_opener_fault_forfeit_bps', + 'payout_min_au', + 'price_min_bps', + 'price_max_bps', + 'price_rate_limit_seconds', + 'epoch_seconds', + 'reservation_max_lifetime_epochs', + 'reservation_receipt_grace_epochs', + 'rate_staleness_seconds', + 'rules_grace_seconds', + 'challenge_epochs', + 'max_apply_batch', + 'max_market_usage_entries', + 'max_tap_settlement_outputs', + 'max_tnk_settlement_outputs', + 'max_fiat_settlement_outputs', + 'param_activation_delay_seconds', +]); +const REPUTATION_EVENT_KINDS = new Set([ + 'session_ok', + 'session_partial', + 'session_fail', + 'probe_ok', + 'probe_fail', + 'uptime_tick', + 'underdelivery', + 'dispute_lost', + 'provenance_violation', +]); +const PROBE_KINDS = new Set(['canary', 'uptime_tick']); +const PROBE_VERIFICATION_METHODS = new Set([ + 'token_fingerprint', + 'context_needle', + 'seed_perceptual_hash', + 'embedding_cosine', + 'transcript_match', + 'audio_fingerprint', + 'attestation_of_compute', +]); +const AUDITOR_SLASH_REASONS = new Set(['collusion', 'false_report']); +const BAN_TARGET_TYPES = new Set(['provider', 'device', 'fingerprint', 'committer', 'kyb']); +const FRAUD_PROOF_REASONS = new Set(['over_credit', 'price_derivation']); +const DISPUTE_OUTCOMES = new Set(['provider_fault', 'opener_fault', 'no_fault']); +const DISPUTE_DEPOSIT_ACTIONS = new Set(['refund', 'forfeit', 'partial_forfeit']); +const EPOCH_ROOT_KEYS = ['dep', 'use', 'earn', 'fee', 'price']; +const EPOCH_TOTAL_KEYS = [ + 'dep_count', + 'dep_au', + 'use_count', + 'use_au', + 'provider_count', + 'earn_au', + 'fee_au', + 'fee_cum_au', + 'burn_au', + 'burn_cum_au', + 'price_count', +]; +const EPOCH_TOTAL_MONEY_KEYS = new Set([ + 'dep_au', + 'use_au', + 'earn_au', + 'fee_au', + 'fee_cum_au', + 'burn_au', + 'burn_cum_au', +]); +const ENCLAVE_UPDATE_FIELDS = [ + 'att_tier', + 'binary_hash', + 'approved_binary_hashes', + 'launch_measurements', + 'caps', +]; +const ENCLAVE_ARTIFACT_SIDECARS_MAX = 64; +const ENCLAVE_APPROVED_BINARY_HASHES_MAX = 64; +const TIER3_MEASUREMENT_MAX_NAMES = 32; +const TIER3_MEASUREMENT_MAX_VALUES = 128; +const ENCLAVE_BACKEND_PATTERN = /^[a-z][a-z0-9]*(?:[.-][a-z0-9]+)*$/; +const ENCLAVE_BACKEND_MAX_LENGTH = 64; +const ENCLAVE_QUANT_BUCKETS = new Set([ + 'unknown', + 'fp32', + 'fp16', + 'bf16', + 'fp8', + 'nvfp4', + 'int8', + 'int4', + 'int2', + 'int1', + 'fp64', + 'tf32', + 'mxfp8', + 'mxfp6', + 'mxfp4', + 'fp6', + 'fp4', + 'nf4', +]); +const ENCLAVE_QUANT_BUCKET_PATTERN = /^(?:unknown|binary|ternary|tf32|(?:mxfp|nvfp|uint|int|fp|bf|nf)[1-9][0-9]?(?:-[a-z0-9]+)*)$/; +const ENCLAVE_QUANT_BUCKET_MAX_LENGTH = 32; +const DEFAULT_MODEL_CLASS = 'text-generation'; +const MODEL_CLASSES = new Set([ + DEFAULT_MODEL_CLASS, + 'embedding', + 'image-generation', + 'video-generation', + 'tts', + 'stt', + 'audio-generation', + 'music-generation', + 'workflow', +]); +const RATE_MAP_MAX_ENTRIES = 16; +const MODEL_CLASS_RATE_UNITS = Object.freeze({ + [DEFAULT_MODEL_CLASS]: new Set([ + 'input_token', + 'cached_input_token', + 'output_token', + ]), + embedding: new Set(['input_token', 'embedding']), + 'image-generation': new Set(['image', 'step']), + 'video-generation': new Set(['video_second', 'frame']), + tts: new Set(['input_character', 'audio_second']), + stt: new Set(['audio_second']), + 'audio-generation': new Set(['input_character', 'audio_second']), + 'music-generation': new Set(['input_character', 'audio_second']), + workflow: new Set([ + 'megapixel_step', + 'megapixel', + // Exact integer pixel-frames (width * height * frames * artifact_count). + // Video workflow classes price in this unit; `megapixel_step` rounds each + // frame up to a whole megapixel and is image-only. + 'pixel_frame', + 'compute_second', + 'audio_second', + 'input_character', + 'frame', + 'image', + 'step', + 'video_second', + ]), +}); +const CAP_OUTPUT_MODALITIES = new Set(['text', 'embedding', 'image', 'video', 'audio']); +const ENCLAVE_MODALITIES = new Set(['text', 'embedding', 'image', 'video', 'audio']); +const MODEL_CLASS_OUTPUT_MODALITIES = Object.freeze({ + [DEFAULT_MODEL_CLASS]: new Set(['text']), + embedding: new Set(['embedding']), + 'image-generation': new Set(['image']), + 'video-generation': new Set(['video', 'audio']), + tts: new Set(['audio']), + stt: new Set(['text']), + 'audio-generation': new Set(['audio']), + 'music-generation': new Set(['audio']), + workflow: new Set(['image', 'video', 'audio']), +}); +const ENCLAVE_ARTIFACT_ROOT_KIND = 'blake3_merkle_v1'; +const ENCLAVE_CAP_BOOLEAN_FIELDS = [ + 'chat', + 'tools', + 'json', + 'embeddings', + 'vision', + 'image', + 'video', + 'audio', +]; +const ENCLAVE_CAP_INTEGER_FIELDS = [ + 'ctx', + 'ctx_max', + 'tp_degree', + 'max_batch_size', + 'max_num_tokens', + 'kv_bytes_per_token', + 'vllm_gpu_memory_utilization_pct', + 'max_image_width', + 'max_image_height', + 'max_image_steps', + 'max_video_width', + 'max_video_height', + 'max_video_frames', + 'max_video_seconds', + 'max_audio_seconds', + 'sample_rate_hz', +]; +const ENCLAVE_CAP_STRING_FIELDS = [ + 'vllm_dtype', +]; +const ENCLAVE_CAP_FIELDS = new Set([ + ...ENCLAVE_CAP_BOOLEAN_FIELDS, + ...ENCLAVE_CAP_INTEGER_FIELDS, + ...ENCLAVE_CAP_STRING_FIELDS, + 'output_modality', + 'output_modalities', + 'modality_set', + 'speciality_levels', +]); +const ROOM_POLICY_FIELDS = new Set([ + 'region_hint', + 'canary_set', + 'min_reputation', + 'max_price_mult', +]); + +export const signingMessageVersions = () => [SIGNING_MESSAGE_VERSION]; +export const consentMessage = (ver, hash, signingVersion = SIGNING_MESSAGE_VERSION) => { + if (signingVersion !== SIGNING_MESSAGE_VERSION) { + throw new Error(`Unsupported signing message version: ${signingVersion}`); + } + return JSON.stringify({ + domain: 'mayhem-consent', + signing_version: SIGNING_MESSAGE_VERSION, + rules_ver: ver, + rules_hash: hash, + }); +}; +export const providerLifecycleIntentMessage = (intent, signingVersion = SIGNING_MESSAGE_VERSION) => { + if (signingVersion !== SIGNING_MESSAGE_VERSION) { + throw new Error(`Unsupported signing message version: ${signingVersion}`); + } + return JSON.stringify({ + domain: 'mayhem-provider-lifecycle', + signing_version: SIGNING_MESSAGE_VERSION, + intent: stableValue(intent), + }); +}; +export const providerPayoutTargetBindingEvidence = (intent) => ({ + admin: intent.admin, + bootstrap: intent.bootstrap, + chain_id: intent.chain_id, + context_revision: intent.context_revision, + currency: intent.currency, + expires_after_epoch: intent.expires_after_epoch, + network: intent.network, + nonce: intent.nonce, + payment_config_version: intent.payment_config_version, + previous_revision: intent.previous_revision, + provider: intent.provider, + rail: intent.rail, + target: intent.target, + target_wallet: intent.target_wallet, +}); +export const providerPayoutTargetBindingMessage = (intent) => + `mayhem-provider-payout-target-binding-v1${stableJson( + providerPayoutTargetBindingEvidence(intent) + )}`; +export const providerPayoutBindingMessage = (intent) => + `mayhem-provider-payout-binding-v1${stableJson(intent)}`; +export const stripePayoutProcessorEvidence = (value) => ({ + account_id: value.account_id, + account_type: value.account_type, + country: value.country, + currency: value.currency, + mode: value.mode, + provider: value.provider, +}); +export const stripePayoutProcessorRevision = async (value) => { + const digest = await blake3(b4a.from(stableJson({ + domain: 'mayhem-stripe-payout-processor-evidence-v1', + evidence: stripePayoutProcessorEvidence(value), + }))); + return b4a.toString(digest, 'hex'); +}; +export const stripePayoutVerificationFeatureKey = async (value) => { + const digest = await blake3(b4a.from(stableJson({ + domain: 'mayhem-stripe-payout-verification-feature-v1', + value, + }))); + return `payout/stripe-verified/${value.provider}/${b4a.toString(digest, 'hex')}`; +}; +export const depositTnkIntentMessage = (intent) => + `mayhem-deposit-tnk-intent-v1${stableJson(intent)}`; +export const tapAccountBindingEvidence = (value) => ({ + user: value.user, + ethereum_address: value.ethereum_address.toLowerCase(), + chain_id: value.chain_id, + pool_address: value.pool_address.toLowerCase(), +}); +export const tapAccountBindingMessage = (value) => + `mayhem-tap-account-bind-v1${stableJson(tapAccountBindingEvidence(value))}`; +const canonicalSpendVoucherBody = (body) => { + if (!body || typeof body !== 'object' || Array.isArray(body)) return body; + const canonical = { + schema_version: body.schema_version, + session_id: body.session_id, + billing_id: body.billing_id, + billing_attempt: body.billing_attempt, + billing_prior_usage: body.billing_prior_usage, + billing_prior_au_owed_cum: body.billing_prior_au_owed_cum, + billing_epoch: body.billing_epoch, + reservation_id: body.reservation_id, + reservation_expires_after_epoch: body.reservation_expires_after_epoch, + reservation_receipt_grace_epochs: body.reservation_receipt_grace_epochs, + user: body.user, + provider: body.provider, + payout_revision: body.payout_revision, + rail: body.rail, + enclave_id: body.enclave_id, + model_id: body.model_id, + price_ver: body.price_ver, + locked_rate_map: body.locked_rate_map, + locked_per_req_au: body.locked_per_req_au, + locked_min_session_au: body.locked_min_session_au, + served_ctx: body.served_ctx, + }; + if (Array.isArray(body.required_modalities) && body.required_modalities.length > 0) { + canonical.required_modalities = body.required_modalities; + } + if (body.required_specialities && typeof body.required_specialities === 'object' && + !Array.isArray(body.required_specialities) && Object.keys(body.required_specialities).length > 0) { + canonical.required_specialities = body.required_specialities; + } + if (hasOwn(body, 'workflow')) canonical.workflow = canonicalWorkflowBinding(body.workflow); + if (hasOwn(body, 'ctx_bracket')) canonical.ctx_bracket = body.ctx_bracket; + if (hasOwn(body, 'ctx_bracket_table_ver')) canonical.ctx_bracket_table_ver = body.ctx_bracket_table_ver; + canonical.rules_ver = body.rules_ver; + canonical.max_spend_au = body.max_spend_au; + canonical.checkpoint_every = body.checkpoint_every; + return canonical; +}; +const canonicalReceiptBody = (body) => { + if (!body || typeof body !== 'object' || Array.isArray(body)) return body; + const canonical = { + schema_version: body.schema_version, + session_id: body.session_id, + billing_id: body.billing_id, + billing_attempt: body.billing_attempt, + billing_prior_usage: body.billing_prior_usage, + billing_prior_au_owed_cum: body.billing_prior_au_owed_cum, + billing_epoch: body.billing_epoch, + reservation_id: body.reservation_id, + reservation_expires_after_epoch: body.reservation_expires_after_epoch, + reservation_receipt_grace_epochs: body.reservation_receipt_grace_epochs, + payout_revision: body.payout_revision, + seq: body.seq, + final: body.final, + rail: body.rail, + user: body.user, + provider: body.provider, + enclave_id: body.enclave_id, + model_id: body.model_id, + price_ver: body.price_ver, + locked_rate_map: body.locked_rate_map, + locked_per_req_au: body.locked_per_req_au, + locked_min_session_au: body.locked_min_session_au, + served_ctx: body.served_ctx, + }; + if (hasOwn(body, 'compute_ms')) canonical.compute_ms = body.compute_ms; + if (hasOwn(body, 'capacity_slots')) canonical.capacity_slots = body.capacity_slots; + if (hasOwn(body, 'ctx_bracket')) canonical.ctx_bracket = body.ctx_bracket; + if (hasOwn(body, 'ctx_bracket_table_ver')) canonical.ctx_bracket_table_ver = body.ctx_bracket_table_ver; + canonical.rules_ver = body.rules_ver; + if (hasOwn(body, 'workflow')) canonical.workflow = canonicalWorkflowBinding(body.workflow); + if (hasOwn(body, 'workflow_output')) canonical.workflow_output = canonicalWorkflowOutput(body.workflow_output); + canonical.usage = body.usage; + if (body.usage_attribution && typeof body.usage_attribution === 'object' && + !Array.isArray(body.usage_attribution) && Object.keys(body.usage_attribution).length > 0) { + canonical.usage_attribution = body.usage_attribution; + } + canonical.au_owed_cum = body.au_owed_cum; + canonical.prompt_hash = body.prompt_hash; + canonical.ts = body.ts; + return canonical; +}; +const canonicalWorkflowBinding = (workflow) => { + if (!workflow || typeof workflow !== 'object' || Array.isArray(workflow)) return workflow; + return { + endpoint_family: workflow.endpoint_family, + graph_hash: workflow.graph_hash, + runtime_id: workflow.runtime_id, + outcome_class: workflow.outcome_class, + quoted_usage: stableValue(workflow.quoted_usage), + }; +}; +const canonicalWorkflowOutput = (output) => { + if (!output || typeof output !== 'object' || Array.isArray(output)) return output; + return { + output_modalities: output.output_modalities, + metrics: stableValue(output.metrics), + }; +}; +export const receiptMessage = (body, signingVersion = SIGNING_MESSAGE_VERSION) => { + if (signingVersion !== SIGNING_MESSAGE_VERSION) { + throw new Error(`Unsupported signing message version: ${signingVersion}`); + } + return JSON.stringify({ + domain: 'mayhem-session-receipt', + signing_version: SIGNING_MESSAGE_VERSION, + body: canonicalReceiptBody(body), + }); +}; +export const spendVoucherMessage = (body, signingVersion = SIGNING_MESSAGE_VERSION) => { + if (signingVersion !== SIGNING_MESSAGE_VERSION) { + throw new Error(`Unsupported signing message version: ${signingVersion}`); + } + return JSON.stringify({ + domain: 'mayhem-spend-voucher', + signing_version: SIGNING_MESSAGE_VERSION, + body: canonicalSpendVoucherBody(body), + }); +}; +export const spendReservationEvidence = (value) => { + const voucher = stableValue(value.voucher); + if (voucher?.required_specialities && typeof voucher.required_specialities === 'object' && + !Array.isArray(voucher.required_specialities) && Object.keys(voucher.required_specialities).length === 0) { + delete voucher.required_specialities; + } + const evidence = { + contract_version: value.contract_version, + session_id: value.session_id, + reservation_id: value.reservation_id, + reservation_expires_after_epoch: value.reservation_expires_after_epoch, + reservation_receipt_grace_epochs: value.reservation_receipt_grace_epochs, + epoch: value.epoch, + at: value.at, + rail: value.rail, + user: value.user, + provider: value.provider, + enclave_id: value.enclave_id, + enclave_pubkey: value.enclave_pubkey, + model_id: value.model_id, + price_ver: value.price_ver, + rules_ver: value.rules_ver, + served_ctx: value.served_ctx, + required_modalities: value.required_modalities, + ctx_bracket: value.ctx_bracket, + ctx_bracket_table_ver: value.ctx_bracket_table_ver, + max_spend_au: value.max_spend_au, + voucher, + }; + if (value.required_specialities && typeof value.required_specialities === 'object' && + !Array.isArray(value.required_specialities) && Object.keys(value.required_specialities).length > 0) { + evidence.required_specialities = value.required_specialities; + } + if (value.workflow && typeof value.workflow === 'object' && !Array.isArray(value.workflow)) { + evidence.workflow = canonicalWorkflowBinding(value.workflow); + } + return evidence; +}; +export const spendReservationMessage = (value) => + `mayhem-spend-reservation-v1${stableJson(spendReservationEvidence(value))}`; +export const targetedSpendReservationEvidence = (value) => ({ + payout_revision: value.payout_revision, + reservation: spendReservationEvidence(value), +}); +export const targetedSpendReservationMessage = (value) => + `mayhem-targeted-spend-reservation-v1${stableJson( + targetedSpendReservationEvidence(value) + )}`; +export const recordUsageReceiptEvidence = (value) => ({ + contract_version: value.contract_version, + epoch: value.epoch, + payout_revision: value.payout_revision, + receipt: stableValue(value.receipt), +}); +export const recordUsageReceiptMessage = (value) => + `mayhem-record-usage-receipt-v1${stableJson(recordUsageReceiptEvidence(value))}`; +export const closeUsageReservationEvidence = (value) => ({ + op: value.op, + contract_version: value.contract_version, + billing_epoch: value.billing_epoch, + reservation_id: value.reservation_id, + reservation_expires_after_epoch: value.reservation_expires_after_epoch, + reservation_receipt_grace_epochs: value.reservation_receipt_grace_epochs, + billing_id: value.billing_id, + billing_attempt: value.billing_attempt, + session_id: value.session_id, + user: value.user, + rail: value.rail, + provider: value.provider, + payout_revision: value.payout_revision, + latest_receipt_seq: value.latest_receipt_seq, + latest_receipt_hash: value.latest_receipt_hash, + at: value.at, + reason: value.reason, + actor: value.actor, + actor_role: value.actor_role, +}); +export const closeUsageReservationMessage = (value) => + `mayhem-close-usage-reservation-v1${stableJson(closeUsageReservationEvidence(value))}`; +export const expireUsageReservationMessage = (value) => + `mayhem-expire-usage-reservation-v1${stableJson(closeUsageReservationEvidence(value))}`; +export const payoutPreparationEvidence = (value) => ({ + op: value.op, + contract_version: value.contract_version, + economic_op_id: value.economic_op_id, + rail: value.rail, + epoch: value.epoch, + epoch_apply_hash: value.epoch_apply_hash, + prepared_at: value.prepared_at, + kind: value.kind, + output_index: value.output_index, + payload_hash: value.payload_hash, + payload: stableValue(value.payload), + liability: stableValue(value.liability), + external_effect_ids: value.external_effect_ids, + admin: value.admin, +}); +export const payoutPreparationMessage = (value) => + `mayhem-targeted-payout-preparation-v1${stableJson(payoutPreparationEvidence(value))}`; +export const targetedPayoutControlEvidence = (value) => { + const evidence = { ...value }; + delete evidence.admin_sig; + return stableValue(evidence); +}; +export const targetedPayoutControlMessage = (value) => + `mayhem-targeted-payout-control-v1${stableJson(targetedPayoutControlEvidence(value))}`; +export const probeResultEvidence = (value, auditor) => ({ + auditor, + probe_id: value.probe_id, + probe_kind: value.probe_kind, + provider: value.provider, + enclave_id: value.enclave_id, + binary_hash: value.binary_hash, + canary_set: value.canary_set, + canary_prompt_id: value.canary_prompt_id, + challenge_epoch: value.challenge_epoch, + challenge_apply_hash: value.challenge_apply_hash, + challenge_seed: value.challenge_seed, + verification_method: value.verification_method, + session_receipt_hash: value.session_receipt_hash, + evidence_hash: value.evidence_hash, + match_bps: value.match_bps, + pass: value.pass, + epoch: value.epoch, + at: value.at, +}); +export const probeResultMessage = (value, auditor) => + `mayhem-probe-result-v1${stableJson(probeResultEvidence(value, auditor))}`; +export const providerKybEvidence = (value) => ({ + provider: value.provider, + legal_name: value.legal_name, + jurisdiction: value.jurisdiction, + proof_hash: value.proof_hash, + kyb_ref: value.kyb_ref, + verified_at: value.verified_at, + schema_version: value.schema_version, +}); +export const providerKybMessage = (value) => + `mayhem-provider-kyb-v1${stableJson(providerKybEvidence(value))}`; +export const roomSidechannelName = (roomId) => `mx/room/${roomId}`; +export const deriveRoomId = async (modelId, creator, nonce) => { + const digest = await blake3(b4a.from(`${modelId}${creator}${nonce}`)); + return b4a.toString(digest, 'hex').slice(0, 32); +}; + +const cloneValue = (value) => (value === undefined ? undefined : JSON.parse(JSON.stringify(value))); +const hasOwn = (value, key) => Object.prototype.hasOwnProperty.call(value, key); + +const versionedMayhemOperation = (op) => { + const dispatch = op?.value?.dispatch; + if (!dispatch || typeof dispatch !== 'object' || Array.isArray(dispatch)) { + return { present: false, version: null, operation: op }; + } + + if (op.type === 'feature' && dispatch.type === 'mayhem_feature' && + hasOwn(dispatch, 'contract_version')) { + const normalizedDispatch = { ...dispatch }; + const version = normalizedDispatch.contract_version; + delete normalizedDispatch.contract_version; + return { + present: true, + version, + operation: { + ...op, + value: { ...op.value, dispatch: normalizedDispatch }, + }, + }; + } + + if (op.type === 'tx' && dispatch.value && typeof dispatch.value === 'object' && + !Array.isArray(dispatch.value) && hasOwn(dispatch.value, 'contract_version')) { + const normalizedValue = { ...dispatch.value }; + const version = normalizedValue.contract_version; + delete normalizedValue.contract_version; + return { + present: true, + version, + operation: { + ...op, + value: { + ...op.value, + dispatch: { ...dispatch, value: normalizedValue }, + }, + }, + }; + } + + return { present: false, version: null, operation: op }; +}; + +export const validateMayhemOperationContractVersion = ( + op, + expectedVersion = CONTRACT_VERSION +) => { + if (!Number.isSafeInteger(expectedVersion) || expectedVersion < 1) { + throw new Error('Expected contract version must be a positive safe integer.'); + } + const versioned = versionedMayhemOperation(op); + if (versioned.present && + (!Number.isSafeInteger(versioned.version) || versioned.version !== expectedVersion)) { + const actual = Number.isSafeInteger(versioned.version) + ? versioned.version + : 'invalid'; + throw new Error( + `Contract upgrade required: expected CONTRACT_VERSION ${expectedVersion}, got ${actual}.` + ); + } + return versioned.operation; +}; +const compareCodepoint = (left, right) => { + const a = String(left); + const b = String(right); + if (a < b) return -1; + if (a > b) return 1; + return 0; +}; +const stableValue = (value) => { + if (Array.isArray(value)) return value.map((item) => stableValue(item)); + if (value && typeof value === 'object') { + return Object.fromEntries( + Object.keys(value) + .sort() + .map((key) => [key, stableValue(value[key])]) + ); + } + return value; +}; +const stableJson = (value) => JSON.stringify(stableValue(value)); + +const verifyEd25519Hex = (signature, message, publicKey) => { + const signatureHex = String(signature ?? '').toLowerCase(); + const publicKeyHex = String(publicKey ?? '').toLowerCase(); + const messageBytes = b4a.isBuffer(message) ? message : b4a.from(String(message)); + if (!/^[0-9a-f]{128}$/.test(signatureHex) || + !/^[0-9a-f]{64}$/.test(publicKeyHex) || + messageBytes.length === 0) { + return false; + } + const signatureBytes = b4a.from(signatureHex, 'hex'); + const publicKeyBytes = b4a.from(publicKeyHex, 'hex'); + try { + return PeerWallet.verify(signatureBytes, messageBytes, publicKeyBytes) === true; + } catch (_error) { + return false; + } +}; + +export const adminContractTxSigningValue = ({ + address, + context, + nonce, + prepared_command: preparedCommand, + sim, +}) => stableValue({ + address: String(address ?? '').trim().toLowerCase(), + context: { + contract_version: context?.contract_version, + msb_bootstrap: String(context?.msb_bootstrap ?? '').trim().toLowerCase(), + network_id: context?.network_id, + subnet_bootstrap: String(context?.subnet_bootstrap ?? '').trim().toLowerCase(), + }, + domain: 'mayhem-admin-contract-tx-v1', + nonce: String(nonce ?? '').trim().toLowerCase(), + prepared_command: preparedCommand, + sim: sim === true, +}); + +export const adminContractTxDigest = async (value) => b4a.toString( + await blake3(b4a.from(stableJson(adminContractTxSigningValue(value)), 'utf8')), + 'hex' +); + +const serializableFeatureResult = (value) => { + if (value === undefined) return null; + if (value instanceof Error) { + return { + name: value.name, + message: value.message, + }; + } + try { + return JSON.parse(JSON.stringify(value)); + } catch { + return String(value); + } +}; + +const ethereumPersonalMessageHash = (message) => { + const body = b4a.from(message, 'utf8'); + const prefix = b4a.from(`\x19Ethereum Signed Message:\n${body.length}`, 'utf8'); + return keccak256(b4a.concat([prefix, body])); +}; +const ethereumAddressFromPublicKey = (publicKey) => + `0x${b4a.toString(keccak256(publicKey.subarray(1)).subarray(12), 'hex')}`; + +export const contractParamDefinitions = () => cloneValue(PARAM_DEFINITIONS); +export const contractEpochAdminParamKeys = () => [...EPOCH_ADMIN_PARAM_KEYS]; +export const contractEpochAdminParamDefinitions = () => Object.fromEntries( + EPOCH_ADMIN_PARAM_KEYS.map((key) => [key, cloneValue(PARAM_DEFINITIONS[key])]) +); +export const contractCtxBracketTable = () => ({ + ver: CTX_BRACKET_TABLE_VERSION, + brackets: cloneValue(CTX_BRACKETS), +}); + +const ctxBracketForTokens = (tokens, table = CTX_BRACKETS) => { + if (!Number.isSafeInteger(tokens) || tokens < 0) return null; + const bracket = table.find((entry) => entry.max_ctx === null || tokens <= entry.max_ctx); + return bracket?.id ?? null; +}; + +class MayhemContract extends Contract { + // The base class runs one execution at a time and calls executeQueued() from + // inside that queue, so the per-operation fields set here belong to the call + // that is running and cannot be overwritten by an overlapping one. + async executeQueued(op, storage, consensusContext = null) { + // Admin Feature envelopes temporarily impersonate a command execution. Keep + // the actual consensus operation kind separately for paid-only operations. + this._mayhemExecutionType = op?.type; + try { + const versioned = versionedMayhemOperation(op); + const canonicalReplay = consumeCanonicalReplayContext(consensusContext, op, storage); + const historical = versioned.present && ( + ([23, 24, 25, 26].includes(versioned.version) && canonicalReplay) || + ([24, 25, 26].includes(versioned.version) && + await this.isPreparedCheckpointReplay(op, storage)) + ); + if (historical) { + // Replaying with today's pricing/receipt methods would produce a + // different signed view. Retained implementations preserve the exact + // historical transition, and never participate in new admission. + const Implementation = versioned.version === 23 + ? ContractV23 + : versioned.version === 24 + ? ContractV24 + : versioned.version === 25 + ? ContractV25 + : ContractV26; + this._historicalContracts ??= new Map(); + if (!this._historicalContracts.has(versioned.version)) { + this._historicalContracts.set(versioned.version, new Implementation(this.protocol, this.config)); + } + const previous = this._mayhemReplayStatus; + this._mayhemReplayStatus = { active: true, completed: previous?.completed ?? 0, + contractVersion: versioned.version, canonicalSignedLength: canonicalReplay?.signedLength ?? null }; + try { + const result = await this._historicalContracts.get(versioned.version).execute(op, storage); + this._mayhemReplayStatus.completed++; + return result; + } finally { this._mayhemReplayStatus.active = false; } + } + return await super.executeQueued(validateMayhemOperationContractVersion(op), storage); + } finally { + this._mayhemExecutionType = null; + } + } + + // Compatibility is attached to canonical preparation evidence, never a + // caller-supplied replay flag. TxOperation verifies the original MSB payment + // and exact dispatch hash before entering consensus execution here. + async isPreparedCheckpointReplay(op, storage) { + const dispatch = op?.value?.dispatch; + const value = dispatch?.value; + if (op?.type !== 'tx' || dispatch?.type !== 'stateCheckpoint' || + value?.op !== 'state_checkpoint' || value.contract_version !== 24 || + !Number.isSafeInteger(value.slot) || value.slot < 1 || + !this.isHexBytes(op.key, 32) || !this.isHexBytes(value.snapshot_hash, 32)) return false; + const read = async (key) => (await storage.get(key))?.value ?? null; + const admin = await read('admin'); + const snapshot = await read(`checkpoint/prepared/${value.slot}`); + if (op.value.ipk !== admin || !snapshot || + snapshot.type !== 'state_checkpoint_snapshot' || snapshot.schema_version !== 1 || + snapshot.slot !== value.slot || snapshot.prepared_by !== admin || + snapshot.state?.contract_version !== 24 || snapshot.snapshot_hash !== value.snapshot_hash) return false; + const { snapshot_hash: snapshotHash, ...body } = snapshot; + if (await this.opaqueHash('mayhem-checkpoint-state-v1', snapshot.state) !== snapshot.state_hash || + await this.opaqueHash('mayhem-checkpoint-snapshot-v1', body) !== snapshotHash) return false; + const existing = await read(`checkpoint/slot/${value.slot}`); + if (existing) return existing.tx === op.key && existing.snapshot_hash === snapshotHash && + existing.paid_by === admin; + const preparing = await read('checkpoint/preparing'); + return preparing?.slot === value.slot && preparing.snapshot_hash === snapshotHash; + } + + constructor(protocol, options = {}) { + super(protocol, options); + const self = this; + this._mayhemApplyStage = null; + + this.addFeature('mayhem_feature', async function () { + const result = await self.mayhemFeature(); + await self.recordMayhemFeatureResult(result); + return result; + }); + + this.addSchema('noop', { + value: { + $$strict: true, + $$type: 'object', + op: { type: 'string', min: 1, max: 32 }, + }, + }); + + this.addSchema('gatedNoop', { + value: { + $$strict: true, + $$type: 'object', + op: { type: 'string', min: 1, max: 32 }, + }, + }); + + this.addSchema('readKey', { + value: { + $$strict: true, + $$type: 'object', + op: { type: 'string', min: 1, max: 64 }, + key: { type: 'string', min: 1, max: 256 }, + }, + }); + + this.addSchema('setRules', { + value: { + $$strict: true, + $$type: 'object', + op: { type: 'string', min: 1, max: 64 }, + ver: { type: 'number', integer: true, min: 1 }, + hash: { type: 'string', min: 1, max: 128 }, + }, + }); + + this.addSchema('setParams', { + value: { + $$strict: true, + $$type: 'object', + op: { type: 'string', min: 1, max: 64 }, + submitted_at: { type: 'number', integer: true, min: 0 }, + effective_at: { type: 'number', integer: true, min: 0 }, + values: { type: 'any' }, + }, + }); + + this.addSchema('setPayments', { + value: { + $$strict: true, + $$type: 'object', + op: { type: 'string', min: 1, max: 64 }, + ver: { type: 'number', integer: true, min: 1 }, + fiat: { type: 'any' }, + tap: { type: 'any' }, + tnk: { type: 'any' }, + }, + }); + + this.addSchema('readParams', { + value: { + $$strict: true, + $$type: 'object', + op: { type: 'string', min: 1, max: 64 }, + at: { type: 'number', integer: true, min: 0 }, + keys: { + type: 'array', + max: 64, + items: { type: 'string', min: 1, max: 64 }, + optional: true, + }, + }, + }); + + this.addSchema('setCtxBrackets', { + value: { + $$strict: true, + $$type: 'object', + op: { type: 'string', min: 1, max: 64 }, + submitted_at: { type: 'number', integer: true, min: 0 }, + effective_at: { type: 'number', integer: true, min: 0 }, + brackets: { type: 'array', min: 1, max: 32, items: { type: 'any' } }, + }, + }); + + this.addSchema('readCtxBrackets', { + value: { + $$strict: true, + $$type: 'object', + op: { type: 'string', min: 1, max: 64 }, + at: { type: 'number', integer: true, min: 0, optional: true }, + ver: { type: 'number', integer: true, min: 1, optional: true }, + }, + }); + + this.addSchema('consent', { + value: { + $$strict: true, + $$type: 'object', + op: { type: 'string', min: 1, max: 64 }, + ver: { type: 'number', integer: true, min: 1 }, + hash: { type: 'string', min: 1, max: 128 }, + sig: { type: 'string', min: 1, max: 256 }, + }, + }); + + this.addFunction('registerProvider'); + + this.addSchema('setProviderRails', { + value: { + $$strict: true, + $$type: 'object', + op: { type: 'string', min: 1, max: 64 }, + rails: { + type: 'array', + min: 1, + max: 3, + items: { type: 'string', min: 1, max: 16 }, + }, + }, + }); + + this.addSchema('setProviderKyb', { + value: { + $$strict: true, + $$type: 'object', + op: { type: 'string', min: 1, max: 64 }, + provider: { type: 'string', min: 1, max: 128 }, + legal_name: { type: 'string', min: 1, max: 160 }, + jurisdiction: { type: 'string', min: 1, max: 64 }, + proof_hash: { type: 'string', min: 1, max: 128 }, + kyb_ref: { type: 'string', min: 1, max: 128 }, + verified_at: { type: 'number', integer: true, min: 0 }, + schema_version: { type: 'number', integer: true, min: 1, optional: true }, + admin_sig: { type: 'string', min: 1, max: 256 }, + }, + }); + + this.addSchema('revokeProviderKyb', { + value: { + $$strict: true, + $$type: 'object', + op: { type: 'string', min: 1, max: 64 }, + provider: { type: 'string', min: 1, max: 128 }, + reason_hash: { type: 'string', min: 1, max: 128, optional: true }, + }, + }); + + this.addSchema('banProvider', { + value: { + $$strict: true, + $$type: 'object', + op: { type: 'string', min: 1, max: 64 }, + provider: { type: 'string', min: 1, max: 128 }, + reason_hash: { type: 'string', min: 1, max: 128, optional: true }, + device_key: { type: 'string', min: 1, max: 128, optional: true }, + hardware_fingerprint: { type: 'string', min: 1, max: 128, optional: true }, + }, + }); + + this.addSchema('unban', { + value: { + $$strict: true, + $$type: 'object', + op: { type: 'string', min: 1, max: 64 }, + target_type: { type: 'string', min: 1, max: 32 }, + target: { type: 'string', min: 1, max: 128 }, + reason_hash: { type: 'string', min: 1, max: 128 }, + }, + }); + + this.addSchema('deviceRebind', { + value: { + $$strict: true, + $$type: 'object', + op: { type: 'string', min: 1, max: 64 }, + device_key: { type: 'string', min: 1, max: 128 }, + provider: { type: 'string', min: 1, max: 128 }, + reason_hash: { type: 'string', min: 1, max: 128 }, + }, + }); + + this.addSchema('migrateMarketPricing', { + value: { + $$strict: true, + $$type: 'object', + op: { type: 'string', min: 1, max: 64 }, + at: { type: 'number', integer: true, min: 0 }, + markets: { type: 'array', min: 0, max: 128, items: { type: 'any' } }, + }, + }); + + this.addSchema('setModelRef', { + value: { + $$strict: true, + $$type: 'object', + op: { type: 'string', min: 1, max: 64 }, + model_id: { type: 'string', min: 1, max: 256 }, + model_class: { type: 'string', min: 1, max: 64 }, + rate_map: { type: 'array', min: 1, max: RATE_MAP_MAX_ENTRIES, items: { type: 'any' } }, + source_hash: { type: 'string', min: 1, max: 128, optional: true }, + activity_calibration: { type: 'any', optional: true }, + }, + }); + + this.addSchema('publishCatalog', { + value: { + $$strict: true, + $$type: 'object', + op: { type: 'string', min: 1, max: 64 }, + catalog_id: { type: 'string', min: 1, max: 128 }, + source_kind: { type: 'string', min: 1, max: 32 }, + catalog_url: { type: 'string', min: 1, max: 512 }, + signature_url: { type: 'string', min: 1, max: 512 }, + catalog_hash: { type: 'string', min: 1, max: 128 }, + signature_hash: { type: 'string', min: 1, max: 128 }, + key_id: { type: 'string', min: 1, max: 128 }, + public_key: { type: 'string', min: 1, max: 128 }, + model_count: { type: 'number', integer: true, min: 1 }, + artifact_count: { type: 'number', integer: true, min: 1 }, + canaries: { type: 'array', max: 64, items: { type: 'any' } }, + parts_anchor: { type: 'any', optional: true }, + blessed_runtimes: { type: 'array', max: 32, optional: true, items: { type: 'any' } }, + outcome_classes: { type: 'array', max: 64, optional: true, items: { type: 'any' } }, + }, + }); + + this.addSchema('registerEnclave', { + value: { + $$strict: true, + $$type: 'object', + op: { type: 'string', min: 1, max: 64 }, + enclave_id: { type: 'string', min: 1, max: 128 }, + model_id: { type: 'string', min: 1, max: 256 }, + model_class: { type: 'string', min: 1, max: 64 }, + backend: { type: 'string', min: 1, max: 64 }, + artifact_root: { type: 'string', min: 1, max: 256 }, + artifact_root_kind: { type: 'string', min: 1, max: 64 }, + artifact_source: { type: 'any' }, + artifact_sidecars: { type: 'any', optional: true }, + source_sha256: { type: 'string', min: 1, max: 128, optional: true }, + manifest_hash: { type: 'string', min: 1, max: 128 }, + att_tier: { type: 'number', integer: true, min: 1, max: 4 }, + quant: { type: 'string', min: 1, max: 32, optional: true }, + binary_hash: { type: 'string', min: 1, max: 128 }, + launch_measurements: { type: 'any', optional: true }, + caps: { type: 'any' }, + }, + }); + + this.addSchema('updateEnclave', { + value: { + $$strict: true, + $$type: 'object', + op: { type: 'string', min: 1, max: 64 }, + enclave_id: { type: 'string', min: 1, max: 128 }, + model_class: { type: 'string', min: 1, max: 64, optional: true }, + backend: { type: 'string', min: 1, max: 64, optional: true }, + artifact_root: { type: 'string', min: 1, max: 256, optional: true }, + artifact_root_kind: { type: 'string', min: 1, max: 64, optional: true }, + artifact_source: { type: 'any', optional: true }, + artifact_sidecars: { type: 'any', optional: true }, + source_sha256: { type: 'string', min: 1, max: 128, optional: true }, + manifest_hash: { type: 'string', min: 1, max: 128, optional: true }, + att_tier: { type: 'number', integer: true, min: 1, max: 4, optional: true }, + quant: { type: 'string', min: 1, max: 32, optional: true }, + binary_hash: { type: 'string', min: 1, max: 128, optional: true }, + launch_measurements: { type: 'any', optional: true }, + caps: { type: 'any', optional: true }, + }, + }); + + this.addSchema('setEnclaveMinTier', { + value: { + $$strict: true, + $$type: 'object', + op: { type: 'string', min: 1, max: 64 }, + enclave_id: { type: 'string', min: 1, max: 128 }, + min_att_tier: { type: 'number', integer: true, min: 1, max: 4 }, + submitted_epoch: { type: 'number', integer: true, min: 0 }, + effective_epoch: { type: 'number', integer: true, min: 0 }, + submitted_at: { type: 'number', integer: true, min: 0 }, + reason_hash: { type: 'string', min: 1, max: 128 }, + }, + }); + + this.addSchema('joinEnclave', { + value: { + $$strict: true, + $$type: 'object', + op: { type: 'string', min: 1, max: 64 }, + enclave_id: { type: 'string', min: 1, max: 128 }, + att_tier: { type: 'number', integer: true, min: 1, max: 3 }, + attestation_head: { type: 'string', min: 64, max: 64 }, + hardware_fingerprint: { type: 'string', min: 1, max: 128, optional: true }, + device_key: { type: 'string', min: 1, max: 128, optional: true }, + }, + }); + + this.addSchema('leaveEnclave', { + value: { + $$strict: true, + $$type: 'object', + op: { type: 'string', min: 1, max: 64 }, + enclave_id: { type: 'string', min: 1, max: 128 }, + }, + }); + + this.addSchema('joinRoom', { + value: { + $$strict: true, + $$type: 'object', + op: { type: 'string', min: 1, max: 64 }, + room_id: { type: 'string', min: 1, max: 128 }, + enclave_id: { type: 'string', min: 1, max: 128 }, + }, + }); + + this.addSchema('leaveRoom', { + value: { + $$strict: true, + $$type: 'object', + op: { type: 'string', min: 1, max: 64 }, + room_id: { type: 'string', min: 1, max: 128 }, + enclave_id: { type: 'string', min: 1, max: 128 }, + }, + }); + + this.addSchema('retireEnclave', { + value: { + $$strict: true, + $$type: 'object', + op: { type: 'string', min: 1, max: 64 }, + enclave_id: { type: 'string', min: 1, max: 128 }, + }, + }); + + this.addSchema('openRoom', { + value: { + $$strict: true, + $$type: 'object', + op: { type: 'string', min: 1, max: 64 }, + enclave_id: { type: 'string', min: 1, max: 128 }, + model_id: { type: 'string', min: 1, max: 256, optional: true }, + nonce: { type: 'string', min: 1, max: 128 }, + label: { type: 'string', min: 1, max: 64 }, + policy: { type: 'any' }, + }, + }); + + this.addSchema('closeRoom', { + value: { + $$strict: true, + $$type: 'object', + op: { type: 'string', min: 1, max: 64 }, + room_id: { type: 'string', min: 1, max: 128 }, + }, + }); + + this.addSchema('setPrice', { + value: { + $$strict: true, + $$type: 'object', + op: { type: 'string', min: 1, max: 64 }, + enclave_id: { type: 'string', min: 1, max: 128 }, + rate_map: { type: 'array', min: 1, max: RATE_MAP_MAX_ENTRIES, items: { type: 'any' } }, + per_req_au: { type: 'string', min: 1, max: 80 }, + min_session_au: { type: 'string', min: 1, max: 80 }, + effective_at: { type: 'number', integer: true, min: 0 }, + ctx_bracket: { type: 'string', min: 1, max: 64, optional: true }, + }, + }); + + this.addSchema('readPrice', { + value: { + $$strict: true, + $$type: 'object', + op: { type: 'string', min: 1, max: 64 }, + enclave_id: { type: 'string', min: 1, max: 128 }, + at: { type: 'number', integer: true, min: 0 }, + ctx_bracket: { type: 'string', min: 1, max: 64, optional: true }, + }, + }); + + this.addSchema('recordReputationEvent', { + value: { + $$strict: true, + $$type: 'object', + op: { type: 'string', min: 1, max: 64 }, + provider: { type: 'string', min: 1, max: 128 }, + event_id: { type: 'string', min: 1, max: 128 }, + kind: { type: 'string', min: 1, max: 64 }, + epoch: { type: 'number', integer: true, min: 0 }, + at: { type: 'number', integer: true, min: 0 }, + paid_au: { type: 'string', min: 1, max: 80, optional: true }, + max_spend_au: { type: 'string', min: 1, max: 80, optional: true }, + evidence_hash: { type: 'string', min: 1, max: 128, optional: true }, + beneficiary: { type: 'string', min: 1, max: 128, optional: true }, + enclave_id: { type: 'string', min: 1, max: 128, optional: true }, + }, + }); + + this.addSchema('anchorReputation', { + value: { + $$strict: true, + $$type: 'object', + op: { type: 'string', min: 1, max: 64 }, + provider: { type: 'string', min: 1, max: 128 }, + epoch: { type: 'number', integer: true, min: 0 }, + folded_at: { type: 'number', integer: true, min: 0 }, + events_head: { type: 'string', min: 1, max: 128 }, + r_bps: { type: 'number', integer: true, min: 0, max: 10_000 }, + raw_milli: { type: 'number', integer: true }, + successful_sessions: { type: 'number', integer: true, min: 0 }, + provenance_violation: { type: 'boolean', optional: true }, + }, + }); + + this.addSchema('auditorRegister', { + value: { + $$strict: true, + $$type: 'object', + op: { type: 'string', min: 1, max: 64 }, + auditor: { type: 'string', min: 1, max: 128, optional: true }, + registered_at_seconds: { type: 'number', integer: true, min: 0, optional: true }, + }, + }); + + this.addSchema('auditorSlash', { + value: { + $$strict: true, + $$type: 'object', + op: { type: 'string', min: 1, max: 64 }, + auditor: { type: 'string', min: 64, max: 64 }, + provider: { type: 'string', min: 64, max: 64 }, + probe_id: { type: 'string', min: 1, max: 128 }, + epoch: { type: 'number', integer: true, min: 0 }, + reason: { type: 'string', min: 1, max: 64 }, + evidence_hash: { type: 'string', min: 64, max: 64 }, + at: { type: 'number', integer: true, min: 0 }, + }, + }); + + this.addSchema('probeResult', { + value: { + $$strict: true, + $$type: 'object', + op: { type: 'string', min: 1, max: 64 }, + probe_id: { type: 'string', min: 1, max: 128 }, + probe_kind: { type: 'string', min: 1, max: 64 }, + provider: { type: 'string', min: 1, max: 128 }, + enclave_id: { type: 'string', min: 1, max: 128, optional: true }, + binary_hash: { type: 'string', min: 1, max: 128, optional: true }, + epoch: { type: 'number', integer: true, min: 0 }, + at: { type: 'number', integer: true, min: 0 }, + canary_set: { type: 'string', min: 1, max: 128, optional: true }, + canary_prompt_id: { type: 'string', min: 1, max: 128, optional: true }, + challenge_epoch: { type: 'number', integer: true, min: 0, optional: true }, + challenge_apply_hash: { type: 'string', min: 64, max: 64, optional: true }, + challenge_seed: { type: 'string', min: 64, max: 64, optional: true }, + verification_method: { type: 'string', min: 1, max: 64, optional: true }, + match_bps: { type: 'number', integer: true, min: 0, max: 10_000, optional: true }, + pass: { type: 'boolean', optional: true }, + session_receipt_hash: { type: 'string', min: 1, max: 128, optional: true }, + evidence_hash: { type: 'string', min: 1, max: 128, optional: true }, + auditor_sig: { type: 'string', min: 1, max: 128, optional: true }, + }, + }); + + this.addSchema('prepareStateCheckpoint', { + value: { + $$strict: true, $$type: 'object', + op: { type: 'string', min: 1, max: 64 }, + slot: { type: 'number', integer: true, min: 1 }, + observed_at: { type: 'number', integer: true, min: 0 }, + contract_code_sha256: { type: 'string', min: 64, max: 64 }, + }, + }); + this.addSchema('stateCheckpoint', { + value: { + $$strict: true, $$type: 'object', + op: { type: 'string', min: 1, max: 64 }, + slot: { type: 'number', integer: true, min: 1 }, + snapshot_hash: { type: 'string', min: 64, max: 64 }, + }, + }); + + this.addSchema('epochFreeze', { + value: { + $$strict: true, + $$type: 'object', + op: { type: 'string', min: 1, max: 64 }, + epoch: { type: 'number', integer: true, min: 1 }, + at: { type: 'number', integer: true, min: 0 }, + }, + }); + + this.addSchema('epochCommit', { + value: { + $$strict: true, + $$type: 'object', + op: { type: 'string', min: 1, max: 64 }, + epoch: { type: 'number', integer: true, min: 1 }, + at: { type: 'number', integer: true, min: 0 }, + roots: { type: 'any' }, + totals: { type: 'any' }, + }, + }); + + this.addSchema('epochSealEmpty', { + value: { + $$strict: true, + $$type: 'object', + op: { type: 'string', min: 1, max: 64 }, + epoch: { type: 'number', integer: true, min: 1 }, + at: { type: 'number', integer: true, min: 0 }, + reason_hash: { type: 'string', min: 64, max: 64 }, + }, + }); + + this.addSchema('fraudProof', { + value: { + $$strict: true, + $$type: 'object', + op: { type: 'string', min: 1, max: 64 }, + epoch: { type: 'number', integer: true, min: 1 }, + proof_epoch: { type: 'number', integer: true, min: 1 }, + at: { type: 'number', integer: true, min: 0 }, + reason: { type: 'string', min: 1, max: 64 }, + receipt: { type: 'any', optional: true }, + claimed_au_owed_cum: { type: 'string', min: 1, max: 80, optional: true }, + previous_au_owed_cum: { type: 'string', min: 1, max: 80, optional: true }, + price_usage: { type: 'any', optional: true }, + }, + }); + + this.addSchema('dispute', { + value: { + $$strict: true, + $$type: 'object', + op: { type: 'string', min: 1, max: 64 }, + rail: { type: 'string', min: 1, max: 16 }, + session_id: { type: 'string', min: 64, max: 64 }, + reason: { type: 'string', min: 1, max: 64 }, + provider: { type: 'string', min: 64, max: 64 }, + counterparty: { type: 'string', min: 1, max: 128, optional: true }, + enclave_id: { type: 'string', min: 64, max: 64 }, + epoch: { type: 'number', integer: true, min: 0, optional: true }, + at: { type: 'number', integer: true, min: 0 }, + evidence_hash: { type: 'string', min: 1, max: 128, optional: true }, + evidence: { type: 'any', optional: true }, + }, + }); + + this.addSchema('disputeResolve', { + value: { + $$strict: true, + $$type: 'object', + op: { type: 'string', min: 1, max: 64 }, + dispute_id: { type: 'number', integer: true, min: 1 }, + outcome: { type: 'string', min: 1, max: 64 }, + deposit_action: { type: 'string', min: 1, max: 64 }, + rationale_hash: { type: 'string', min: 1, max: 128 }, + at: { type: 'number', integer: true, min: 0 }, + slash: { type: 'boolean', optional: true }, + beneficiary: { type: 'string', min: 1, max: 128, optional: true }, + }, + }); + + this.addSchema('disputeExpire', { + value: { + $$strict: true, + $$type: 'object', + op: { type: 'string', min: 1, max: 64 }, + dispute_id: { type: 'number', integer: true, min: 1 }, + at: { type: 'number', integer: true, min: 0 }, + }, + }); + + this.addSchema('fiatDeposit', { + value: { + $$strict: true, + $$type: 'object', + op: { type: 'string', min: 1, max: 64 }, + rail: { type: 'string', min: 1, max: 64 }, + who: { type: 'string', min: 1, max: 128 }, + au: { type: 'string', min: 1, max: 80 }, + ext_ref_hash: { type: 'string', min: 1, max: 128 }, + fiat_currency: { type: 'string', min: 3, max: 3 }, + fiat_amount_minor: { type: 'number', integer: true, min: 1, max: Number.MAX_SAFE_INTEGER }, + epoch: { type: 'number', integer: true, min: 1 }, + at: { type: 'number', integer: true, min: 0 }, + }, + }); + + this.addSchema('fiatChargeback', { + value: { + $$strict: true, + $$type: 'object', + op: { type: 'string', min: 1, max: 64 }, + rail: { type: 'string', min: 1, max: 64 }, + who: { type: 'string', min: 1, max: 128 }, + au: { type: 'string', min: 1, max: 80 }, + ext_ref_hash: { type: 'string', min: 1, max: 128 }, + dispute_ref_hash: { type: 'string', min: 1, max: 128 }, + fiat_currency: { type: 'string', min: 3, max: 3 }, + fiat_amount_minor: { type: 'number', integer: true, min: 1, max: Number.MAX_SAFE_INTEGER }, + epoch: { type: 'number', integer: true, min: 1 }, + at: { type: 'number', integer: true, min: 0 }, + }, + }); + + } + + async noop() { + const result = { + ok: true, + op: 'noop', + contract: 'mayhem', + version: CONTRACT_VERSION, + address: this.address, + }; + console.log('mayhem noop', result); + return result; + } + + async gatedNoop() { + const consentError = await this.requireConsent(); + if (consentError) return consentError; + + const result = { + ok: true, + op: 'gatedNoop', + contract: 'mayhem', + version: CONTRACT_VERSION, + address: this.address, + }; + console.log('mayhem gatedNoop', result); + return result; + } + + async readKey() { + const key = this.value?.key; + const value = typeof key === 'string' ? await this.get(key) : null; + console.log('mayhem readKey', key, '=>', value); + return value; + } + + async mayhemFeature() { + this._mayhemLastFeatureResult = undefined; + const rawKey = this.op?.key; + const key = typeof rawKey === 'string' && rawKey.startsWith('mayhem_') + ? rawKey.slice('mayhem_'.length) + : rawKey; + const value = this.value; + if (typeof key !== 'string' || !value || typeof value !== 'object' || Array.isArray(value)) { + return; + } + if (value.op === 'deposit_tnk') { + const result = await this.applyDepositTnkFeature(key, value); + this._mayhemLastFeatureResult = result; + return result; + } + if (value.op === 'consent') { + const result = await this.applyConsentFeature(key, value); + this._mayhemLastFeatureResult = result; + return result; + } + if (value.op === 'provider_lifecycle') { + const result = await this.applyProviderLifecycleFeature(key, value); + this._mayhemLastFeatureResult = result; + return result; + } + if (value.op === 'bind_provider_payout') { + const result = await this.applyProviderPayoutBindingFeature(key, value); + this._mayhemLastFeatureResult = result; + return result; + } + if (value.op === 'spend_reserve_targeted') { + const result = await this.applyTargetedSpendReserveFeature(key, value); + this._mayhemLastFeatureResult = result; + return result; + } + if (value.op === 'record_usage_receipt') { + const result = await this.applyRecordUsageReceiptFeature(key, value); + this._mayhemLastFeatureResult = result; + return result; + } + if (value.op === 'close_usage_reservation') { + const result = await this.applyCloseUsageReservationFeature(key, value); + this._mayhemLastFeatureResult = result; + return result; + } + if (value.op === 'expire_usage_reservation') { + const result = await this.applyCloseUsageReservationFeature(key, value, { + expiry: true, + }); + this._mayhemLastFeatureResult = result; + return result; + } + if (value.op === 'tap_account_bind') { + const result = await this.applyTapAccountBindingFeature(key, value); + this._mayhemLastFeatureResult = result; + return result; + } + const isRateFeature = value.op === 'rate_oracle' || value.op === 'tap_rate_oracle'; + if (isRateFeature) this._mayhemApplyStage = 'rate:require-admin'; + const adminError = await this.requireAdmin(this.address); + if (adminError) { + if (isRateFeature) this._mayhemApplyStage = null; + this._mayhemLastFeatureResult = adminError; + return adminError; + } + if (isRateFeature) this._mayhemApplyStage = 'rate:admin-verified'; + if (value.op === 'admin_contract_tx') { + const result = await this.applyAdminContractTxFeature(key, value); + this._mayhemLastFeatureResult = result; + return result; + } + if (value.op === 'publish_payout_context') { + const result = await this.applyPublishPayoutContextFeature(key, value); + this._mayhemLastFeatureResult = result; + return result; + } + if (value.op === 'schedule_payout_parameter') { + const result = await this.applySchedulePayoutParameterFeature(key, value); + this._mayhemLastFeatureResult = result; + return result; + } + if (value.op === 'verify_stripe_payout') { + const result = await this.applyVerifyStripePayoutFeature(key, value); + this._mayhemLastFeatureResult = result; + return result; + } + if (value.op === 'apply_targeted_epoch') { + const result = await this.applyTargetedEpochFeature(key, value); + this._mayhemLastFeatureResult = result; + return result; + } + if (value.op === 'commit_apply_targeted_epoch_page0') { + const result = await this.applyCommitTargetedEpochPageZeroFeature(key, value); + this._mayhemLastFeatureResult = result; + return result; + } + if (['tnk_deposit', 'tap_deposit', 'tap_deposit_reversal', 'fiat_deposit', 'fiat_chargeback'].includes(value.op)) { + const result = await this.applyDepositCreditFeature(key, value); + this._mayhemLastFeatureResult = result; + return result; + } + if (value.op === 'rate_oracle' || value.op === 'tap_rate_oracle') { + const result = await this.applyRateOracleFeature(key, value); + this._mayhemApplyStage = null; + this._mayhemLastFeatureResult = result; + return result; + } + if (value.op === 'prepare_targeted_payout') { + const result = await this.applyTargetedPayoutPreparationFeature(key, value); + this._mayhemLastFeatureResult = result; + return result; + } + if (value.op === 'prepare_targeted_payout_epoch') { + const result = await this.applyTargetedPayoutEpochFeature(key, value); + this._mayhemLastFeatureResult = result; + return result; + } + if (value.op === 'prepare_targeted_fiat_attempt') { + const result = await this.applyTargetedFiatAttemptFeature(key, value); + this._mayhemLastFeatureResult = result; + return result; + } + if (value.op === 'finalize_targeted_fiat_attempt') { + const result = await this.applyFinalizeTargetedFiatAttemptFeature(key, value); + this._mayhemLastFeatureResult = result; + return result; + } + if (value.op === 'settle_targeted_tap') { + const result = await this.applyTargetedTapSettlementFeature(key, value); + this._mayhemLastFeatureResult = result; + return result; + } + if (value.op === 'settle_targeted_tnk_output') { + const result = await this.applyTargetedTnkOutputFeature(key, value); + this._mayhemLastFeatureResult = result; + return result; + } + if (value.op === 'settle_targeted_fiat_output') { + const result = await this.applyTargetedFiatOutputFeature(key, value); + this._mayhemLastFeatureResult = result; + return result; + } + if (value.op === 'close_targeted_payout_epoch') { + const result = await this.applyCloseTargetedPayoutEpochFeature(key, value); + this._mayhemLastFeatureResult = result; + return result; + } + if (value.op === 'fiat_dust_sweep') { + const result = await this.applyFiatDustSweepFeature(key, value); + this._mayhemLastFeatureResult = result; + return result; + } + if (value.op === 'anchor_reputation') { + const result = await this.applyReputationAnchorFeature(key, value); + this._mayhemLastFeatureResult = result; + return result; + } + if (value.op === 'tier3_bless_measurement') { + const result = await this.applyTier3MeasurementFeature(key, value); + this._mayhemLastFeatureResult = result; + return result; + } + } + + async recordMayhemFeatureResult(result) { + const hash = String(this.op?.hash ?? '').toLowerCase(); + if (!/^[0-9a-f]+$/.test(hash)) return; + const error = result instanceof Error + ? result + : result === undefined + ? new Error('Feature returned no result.') + : result?.ok === false + ? new Error(String(result?.error?.message ?? result?.message ?? 'Feature rejected.')) + : null; + const ok = error === null; + await this.put(`fr/${hash}`, { + type: 'feature_result', + feature_key: this.op?.key ?? null, + hash, + address: this.address ?? null, + status: ok ? 'applied' : 'rejected', + ok, + result: ok ? serializableFeatureResult(result) : null, + error: ok + ? null + : { + name: error.name || 'FeatureRejected', + message: error.message || 'Feature rejected.', + }, + }); + } + + async applyAdminContractTxFeature(key, value) { + const shapeError = this.validateExactObjectKeys( + value, + ['op', 'tx', 'prepared_command', 'address', 'signature', 'nonce', 'sim', 'context'], + 'admin contract transaction feature' + ); + if (shapeError) return shapeError; + if ( + value.op !== 'admin_contract_tx' || + !this.isHexBytes(value.tx, 32) || + !this.isHexBytes(value.address, 32) || + !this.isHexBytes(value.signature, 64) || + !this.isHexBytes(value.nonce, 32) || + typeof value.sim !== 'boolean' || + !value.prepared_command || + typeof value.prepared_command !== 'object' || + Array.isArray(value.prepared_command) || + !value.context || + typeof value.context !== 'object' || + Array.isArray(value.context) + ) { + return new Error('Invalid admin contract transaction feature.'); + } + if (value.sim) { + return new Error('Simulated admin transactions must not be appended.'); + } + if (key !== `admin/contract-tx/${value.tx}`) { + return new Error('Admin contract transaction key does not match its digest.'); + } + const commandShapeError = this.validateExactObjectKeys( + value.prepared_command, + ['type', 'value'], + 'prepared admin command' + ); + if (commandShapeError) return commandShapeError; + const contextShapeError = this.validateExactObjectKeys( + value.context, + ['contract_version', 'msb_bootstrap', 'network_id', 'subnet_bootstrap'], + 'admin contract transaction context' + ); + if (contextShapeError) return contextShapeError; + const peer = this.protocol?.peer; + const configuredBootstrap = peer?.config?.bootstrap; + const subnetBootstrap = b4a.isBuffer(configuredBootstrap) + ? b4a.toString(configuredBootstrap, 'hex') + : typeof configuredBootstrap === 'string' && configuredBootstrap + ? configuredBootstrap.toLowerCase() + : b4a.isBuffer(peer?.base?.key) + ? b4a.toString(peer.base.key, 'hex') + : ''; + const runtimeContext = { + contract_version: CONTRACT_VERSION, + msb_bootstrap: String(peer?.msbClient?.bootstrapHex ?? '').toLowerCase(), + network_id: peer?.msbClient?.networkId, + subnet_bootstrap: subnetBootstrap, + }; + if ( + !Number.isSafeInteger(value.context.contract_version) || + !Number.isSafeInteger(value.context.network_id) || + !this.isHexBytes(value.context.msb_bootstrap, 32) || + !this.isHexBytes(value.context.subnet_bootstrap, 32) || + stableJson(value.context) !== stableJson(runtimeContext) + ) { + return new Error('Admin contract transaction context does not match this contract network.'); + } + const adminError = await this.requireAdmin(value.address); + if (adminError) return adminError; + const expectedTx = await adminContractTxDigest(value); + if (expectedTx !== value.tx) { + return new Error('Invalid admin contract transaction digest.'); + } + const verify = this.protocol?.peer?.wallet?.verify; + if ( + typeof verify !== 'function' || + verify.call( + this.protocol.peer.wallet, + value.signature, + b4a.from(value.tx, 'hex'), + value.address + ) !== true + ) { + return new Error('Invalid admin contract transaction signature.'); + } + + const existing = await this.get(key); + if (existing !== null) return existing; + + const type = value.prepared_command.type; + if ( + typeof type !== 'string' || + (!hasOwn(this.metadata.schemas, type) && !hasOwn(this.metadata.functions, type)) || + typeof this[type] !== 'function' + ) { + return new Error('Admin contract transaction type is not a registered Mayhem command.'); + } + if ( + hasOwn(this.metadata.schemas, type) && + this.check.validateSchema(type, value.prepared_command) !== true + ) { + return new Error('Invalid prepared admin command schema.'); + } + + const applyingRecord = { + ok: false, + op: 'admin_contract_tx', + status: 'applying', + tx: value.tx, + type, + result: null, + error: null, + }; + await this.put(key, applyingRecord); + + const context = { + address: this.address, + isFeature: this.is_feature, + op: this.op, + tx: this.tx, + value: this.value, + }; + let commandResult; + try { + this.address = value.address; + this.is_feature = false; + this.op = value.prepared_command; + this.tx = value.tx; + this.value = value.prepared_command.value; + commandResult = await this[type](); + } catch (error) { + commandResult = error instanceof Error ? error : new Error(String(error)); + } finally { + this.address = context.address; + this.is_feature = context.isFeature; + this.op = context.op; + this.tx = context.tx; + this.value = context.value; + } + const commandError = commandResult === undefined + ? new Error('Admin contract transaction returned no result.') + : commandResult instanceof Error + ? commandResult + : commandResult?.ok === false + ? new Error( + String( + commandResult?.error?.message ?? + commandResult?.message ?? + 'Admin command rejected.' + ) + ) + : null; + if (commandError) { + await this.put(key, { + ...applyingRecord, + status: 'rejected', + error: serializableFeatureResult(commandError), + }); + return commandError; + } + + const record = { + ok: true, + op: 'admin_contract_tx', + status: 'applied', + tx: value.tx, + type, + result: serializableFeatureResult(commandResult), + error: null, + }; + await this.put(key, record); + return record; + } + + async applyConsentFeature(key, value) { + const shapeError = this.validateExactObjectKeys( + value, + ['op', 'sender', 'ver', 'hash', 'sig'], + 'consent feature' + ); + if (shapeError) return; + if (!this.isHexBytes(value.sender, 32)) return; + if (!this.isHexBytes(value.sig, 64)) return; + const rules = await this.currentRules(); + if (!rules || value.ver !== rules.ver || value.hash !== rules.hash) return; + if (key !== `consent/${value.sender}/${value.ver}/${value.hash}`) return; + if (!this.verifyConsentSignature(value.sender, value.ver, value.hash, value.sig)) return; + + const record = { + ver: value.ver, + hash: value.hash, + at: key, + via: 'feature', + }; + await this.put(`consent/${value.sender}`, record); + console.log('mayhem consent feature', { address: value.sender, ...record }); + return { ok: true, op: 'consentFeature', address: value.sender, ...record }; + } + + async applyTapAccountBindingFeature(key, value) { + const normalized = this.normalizeTapAccountBinding(value); + if (normalized instanceof Error) return normalized; + const expectedKey = await this.tapAccountBindingFeatureKey(normalized); + if (expectedKey instanceof Error) return expectedKey; + if (key !== expectedKey) return new Error('Invalid TAP account binding key.'); + + const consentError = await this.requireConsent(normalized.user); + if (consentError) return consentError; + if (!this.verifyTapAccountUserSignature(normalized)) { + return new Error('Invalid TAP account user signature.'); + } + if (!this.verifyTapAccountEthereumSignature(normalized)) { + return new Error('Invalid TAP account Ethereum signature.'); + } + const poolError = await this.requireCanonicalTapPool( + normalized.chain_id, + normalized.pool_address + ); + if (poolError) return poolError; + + const bindingKey = this.tapAccountBindingKey( + normalized.user, + normalized.chain_id, + normalized.pool_address + ); + const addressKey = this.tapAccountAddressKey( + normalized.ethereum_address, + normalized.chain_id, + normalized.pool_address + ); + const existingBinding = await this.get(bindingKey); + if (existingBinding && existingBinding.ethereum_address !== normalized.ethereum_address) { + return new Error('Mayhem wallet is already bound to a different TAP account.'); + } + const existingAddress = await this.get(addressKey); + if (existingAddress && existingAddress.user !== normalized.user) { + return new Error('TAP account is already bound to a different Mayhem wallet.'); + } + + const source = await this.balanceRecord(normalized.ethereum_address, 'tap'); + if (source instanceof Error) return source; + const sourceError = this.guardianValidateBalanceRecord( + source, + normalized.ethereum_address, + 'tap' + ); + if (sourceError) return sourceError; + const target = await this.balanceRecord(normalized.user, 'tap'); + if (target instanceof Error) return target; + const targetError = this.guardianValidateBalanceRecord(target, normalized.user, 'tap'); + if (targetError) return targetError; + const nextAu = this.safeAddAu(target.au, source.au); + if (nextAu instanceof Error) return nextAu; + + const record = { + type: 'tap_account_binding', + user: normalized.user, + ethereum_address: normalized.ethereum_address, + chain_id: normalized.chain_id, + pool_address: normalized.pool_address, + user_sig: normalized.user_sig, + ethereum_sig: normalized.ethereum_sig, + status: 'active', + bound_at: key, + }; + await this.put(bindingKey, record); + await this.put(addressKey, record); + + await this.put(this.balanceKey(normalized.user, 'tap'), { + ...target, + user: normalized.user, + rail: 'tap', + au: nextAu, + updated_epoch: Math.max(target.updated_epoch, source.updated_epoch), + updated_at: key, + ...(!this.isZeroAu(source.au) ? { + last_tap_account_claim_au: source.au, + last_tap_account_claim_from: normalized.ethereum_address, + } : {}), + }); + await this.put(this.balanceKey(normalized.ethereum_address, 'tap'), { + ...source, + au: ZERO_AU, + updated_at: key, + tap_account_bound_to: normalized.user, + tap_account_binding_key: bindingKey, + }); + + return { + ok: true, + op: 'tapAccountBind', + user: normalized.user, + ethereum_address: normalized.ethereum_address, + chain_id: normalized.chain_id, + pool_address: normalized.pool_address, + claimed_au: source.au, + balance_au: nextAu, + idempotent: existingBinding !== null && existingAddress !== null && this.isZeroAu(source.au), + }; + } + + async applyProviderLifecycleFeature(key, value) { + const shapeError = this.validateExactObjectKeys( + value, + ['op', 'intent', 'sig'], + 'provider lifecycle feature' + ); + if (shapeError) return; + const intent = value.intent; + if (!intent || typeof intent !== 'object' || Array.isArray(intent)) return; + const intentError = this.validateProviderLifecycleIntent(intent); + if (intentError) return; + if (!this.isHexBytes(value.sig, 64)) return; + if (!(await this.providerLifecycleFeatureKeys(intent)).includes(key)) return; + if (!this.verifyProviderLifecycleSignature(intent.provider, intent, value.sig)) return; + + switch (intent.op) { + case 'register_provider': + return await this.applyRegisterProvider(intent.provider, key); + case 'join_enclave': + return await this.applyJoinEnclave( + intent.provider, + intent.enclave_id, + key, + intent.att_tier, + intent.attestation_head, + intent.hardware_fingerprint ?? null, + intent.device_key ?? null, + { + served_ctx: intent.served_ctx, + served_modalities: intent.served_modalities, + served_specialities: intent.served_specialities, + ctx_bracket: intent.ctx_bracket, + ctx_bracket_table_ver: intent.ctx_bracket_table_ver, + } + ); + case 'leave_enclave': + return await this.applyLeaveEnclave(intent.provider, intent.enclave_id, key); + case 'join_room': + return await this.applyJoinRoom(intent.provider, intent.room_id, intent.enclave_id, key); + case 'leave_room': + return await this.applyLeaveRoom(intent.provider, intent.room_id, intent.enclave_id, key); + case 'set_provider_rails': + return await this.applySetProviderRails(intent.provider, intent.rails, key); + default: + return; + } + } + + async applyProviderPayoutBindingFeature(key, value) { + const shapeError = this.validateExactObjectKeys( + value, + ['op', 'intent', 'provider_signature'], + 'provider payout binding feature' + ); + if (shapeError) return shapeError; + if (value.op !== 'bind_provider_payout') { + return new Error('Invalid provider payout binding feature op.'); + } + const intentError = await this.validateProviderPayoutBindingIntent( + value.intent, + { currentState: false } + ); + if (intentError) return intentError; + if (!this.isHexBytes(value.provider_signature, 64)) { + return new Error('Invalid provider payout binding signature.'); + } + + const revision = await this.providerPayoutBindingRevision(value.intent); + const expectedKey = this.providerPayoutBindingFeatureKey( + value.intent.rail, + value.intent.provider, + revision + ); + if (key !== expectedKey) return new Error('Invalid provider payout binding key.'); + if (!this.verifyProviderPayoutBindingSignature( + value.intent.provider, + value.intent, + value.provider_signature + )) { + return new Error('Invalid provider payout binding signature.'); + } + if (!this.verifyProviderPayoutTargetBindingSignature(value.intent)) { + return new Error('Invalid provider payout target ownership signature.'); + } + const adminError = await this.requireAdmin(this.address); + if (adminError) return adminError; + + const nonceKey = this.providerPayoutBindingNonceKey( + value.intent.provider, + value.intent.nonce + ); + const nonceRecord = await this.get(nonceKey); + if (nonceRecord) { + if (nonceRecord.revision !== revision) { + return new Error('Provider payout binding nonce already consumed.'); + } + const existing = await this.get(expectedKey); + if (!existing || + existing.type !== 'provider_payout_binding' || + existing.revision !== revision || + existing.provider !== value.intent.provider || + existing.rail !== value.intent.rail || + existing.nonce !== value.intent.nonce || + existing.provider_signature !== value.provider_signature || + existing.target_signature !== value.intent.target_signature) { + return new Error('Provider payout binding nonce record is inconsistent.'); + } + return { + ok: true, + op: 'bindProviderPayout', + provider: value.intent.provider, + rail: value.intent.rail, + revision, + activation_epoch: existing.activation_epoch, + idempotent: true, + }; + } + + const currentIntentError = await this.validateProviderPayoutBindingIntent(value.intent); + if (currentIntentError) return currentIntentError; + const provider = await this.get(`prov/${value.intent.provider}`); + if (!provider || provider.status !== 'active') { + return new Error('Active provider registration required.'); + } + if (!Array.isArray(provider.accepted_rails) || + !provider.accepted_rails.includes(value.intent.rail)) { + return new Error('Provider does not accept payout rail.'); + } + + const applyState = await this.epochApplyStateRecord(); + if ((applyState.pending_epoch ?? null) !== null) { + return new Error('Provider payout binding cannot rotate during a paged epoch apply.'); + } + if (value.intent.expires_after_epoch <= applyState.updated_epoch) { + return new Error('Provider payout binding intent expired.'); + } + const payoutParams = await this.activePayoutParamsAtEpoch(applyState.updated_epoch); + if (payoutParams instanceof Error) return payoutParams; + if (value.intent.expires_after_epoch - applyState.updated_epoch > + payoutParams.payout_intent_max_expiry_epochs) { + return new Error('Provider payout binding expiry is too far in the future.'); + } + + const context = await this.providerPayoutBindingContext(value.intent); + if (context instanceof Error) return context; + const currentContext = await this.get(PROVIDER_PAYOUT_BINDING_CONTEXT_CURRENT_KEY); + if (!currentContext) { + return new Error('Canonical provider payout context is not published.'); + } + const immutableContext = await this.get( + this.providerPayoutContextRecordKey( + value.intent.payment_config_version, + value.intent.context_revision + ) + ); + if (!immutableContext || immutableContext.revision !== value.intent.context_revision) { + return new Error('Referenced immutable provider payout context is not published.'); + } + if ( + currentContext.revision !== context.context_revision || + immutableContext.network !== context.network || + immutableContext.admin !== context.admin || + immutableContext.bootstrap !== context.bootstrap || + immutableContext.payment_config_version !== context.payment_config_version + ) { + return new Error('Provider payout binding canonical context mismatch.'); + } + + const pointerKey = this.providerPayoutBindingPointerKey( + value.intent.provider, + value.intent.rail + ); + const storedPointer = await this.get(pointerKey); + const activeBillingEpoch = applyState.updated_epoch + 1; + const pointer = storedPointer?.pending_revision !== null && + storedPointer?.pending_revision !== undefined && + storedPointer.pending_activation_epoch <= activeBillingEpoch + ? { + ...storedPointer, + current_revision: storedPointer.pending_revision, + pending_revision: null, + pending_activation_epoch: null, + } + : storedPointer; + const latestRevision = pointer?.latest_revision ?? null; + if (value.intent.previous_revision !== latestRevision) { + return new Error('Provider payout binding revision is stale.'); + } + if ((await this.get(expectedKey)) !== null) { + return new Error('Provider payout binding revision already exists.'); + } + + let stripeVerification = null; + if (value.intent.rail === 'fiat') { + stripeVerification = await this.providerStripePayoutVerificationForTarget( + value.intent.provider, + value.intent.target + ); + if (!stripeVerification || + stripeVerification.target !== value.intent.target || + stripeVerification.currency !== value.intent.currency || + stripeVerification.ready !== true) { + return new Error('Current ready provider-scoped Stripe verification required.'); + } + } + + const firstBinding = latestRevision === null; + const activationEpoch = applyState.updated_epoch + (firstBinding ? 1 : 2); + const binding = { + type: 'provider_payout_binding', + revision, + provider: value.intent.provider, + rail: value.intent.rail, + target: value.intent.target, + target_wallet: value.intent.target_wallet, + currency: value.intent.currency, + chain_id: value.intent.chain_id, + stripe_processor_revision: stripeVerification?.processor_revision ?? null, + stripe_verification_revision: stripeVerification?.revision ?? null, + network: value.intent.network, + admin: value.intent.admin, + bootstrap: value.intent.bootstrap, + context_revision: value.intent.context_revision, + payment_config_version: value.intent.payment_config_version, + previous_revision: value.intent.previous_revision, + nonce: value.intent.nonce, + expires_after_epoch: value.intent.expires_after_epoch, + activation_epoch: activationEpoch, + target_signature: value.intent.target_signature, + provider_signature: value.provider_signature, + verified: true, + bound_at: key, + bound_by: this.address, + bound_by_role: 'admin', + }; + const nextPointer = { + provider: value.intent.provider, + rail: value.intent.rail, + latest_revision: revision, + current_revision: firstBinding ? revision : pointer.current_revision, + pending_revision: firstBinding ? null : revision, + pending_activation_epoch: firstBinding ? null : activationEpoch, + updated_at: key, + }; + await this.put(expectedKey, binding); + await this.put(pointerKey, nextPointer); + await this.put(nonceKey, { + provider: value.intent.provider, + rail: value.intent.rail, + nonce: value.intent.nonce, + revision, + expires_after_epoch: value.intent.expires_after_epoch, + consumed_at: key, + }); + return { + ok: true, + op: 'bindProviderPayout', + provider: value.intent.provider, + rail: value.intent.rail, + revision, + activation_epoch: activationEpoch, + idempotent: false, + }; + } + + async applyPublishPayoutContextFeature(key, value) { + const shapeError = this.validateExactObjectKeys( + value, + [ + 'op', + 'network', + 'admin', + 'bootstrap', + 'payment_config_version', + 'payment_config_hash', + ], + 'provider payout context feature' + ); + if (shapeError) return shapeError; + if (value.op !== 'publish_payout_context') { + return new Error('Invalid provider payout context feature op.'); + } + if (!this.isSafeKeyPart(value.network) || + !this.isHexBytes(value.admin, 32) || + value.admin !== value.admin.toLowerCase() || + !this.isHexBytes(value.bootstrap, 32) || + value.bootstrap !== value.bootstrap.toLowerCase() || + !Number.isSafeInteger(value.payment_config_version) || + value.payment_config_version < 1 || + !this.isHexBytes(value.payment_config_hash, 32) || + value.payment_config_hash !== value.payment_config_hash.toLowerCase()) { + return new Error('Invalid provider payout context.'); + } + const admin = await this.get('admin'); + if (value.admin !== admin || this.address !== admin) { + return new Error('Provider payout context requires canonical admin authority.'); + } + const payments = await this.get('payments/current'); + if (!payments || payments.set_by !== admin || payments.set_by_role !== 'admin') { + return new Error('Canonical payment configuration required.'); + } + if (value.network !== payments.tnk?.network || + value.payment_config_version !== payments.ver) { + return new Error('Provider payout context does not match canonical payment configuration.'); + } + const paymentConfigHash = await this.providerPayoutPaymentConfigHash(payments); + if (value.payment_config_hash !== paymentConfigHash) { + return new Error('Provider payout context payment configuration hash mismatch.'); + } + const expectedKey = await this.providerPayoutContextFeatureKey(value); + if (key !== expectedKey) return new Error('Invalid provider payout context feature key.'); + + const record = { + type: 'provider_payout_context', + revision: await this.providerPayoutContextRevision(value), + network: value.network, + admin: value.admin, + bootstrap: value.bootstrap, + payment_config_version: value.payment_config_version, + payment_config_hash: value.payment_config_hash, + published_at: key, + published_by: this.address, + published_by_role: 'admin', + }; + const recordKey = this.providerPayoutContextRecordKey( + value.payment_config_version, + record.revision + ); + if (key !== recordKey) return new Error('Invalid provider payout context record key.'); + const existing = await this.get(recordKey); + if (existing) { + if (stableJson(existing) === stableJson(record)) { + const current = await this.get(PROVIDER_PAYOUT_BINDING_CONTEXT_CURRENT_KEY); + if (current?.revision !== record.revision) { + return new Error('Immutable provider payout context is not current.'); + } + return { + ok: true, + op: 'publishPayoutContext', + context: existing, + idempotent: true, + }; + } + return new Error('Immutable provider payout context record already exists.'); + } + const current = await this.get(PROVIDER_PAYOUT_BINDING_CONTEXT_CURRENT_KEY); + if (current && current.payment_config_version >= value.payment_config_version) { + return new Error('Provider payout context payment config version must increase.'); + } + await this.put(recordKey, record); + await this.put(PROVIDER_PAYOUT_BINDING_CONTEXT_CURRENT_KEY, { + type: 'provider_payout_context_pointer', + payment_config_version: value.payment_config_version, + revision: record.revision, + record_key: recordKey, + updated_at: key, + updated_by: this.address, + updated_by_role: 'admin', + }); + return { + ok: true, + op: 'publishPayoutContext', + context: record, + idempotent: false, + }; + } + + async applySchedulePayoutParameterFeature(key, value) { + const shapeError = this.validateExactObjectKeys( + value, + ['op', 'key', 'value', 'effective_epoch'], + 'payout parameter feature' + ); + if (shapeError) return shapeError; + if (value.op !== 'schedule_payout_parameter' || + !Object.hasOwn(PAYOUT_PARAM_DEFINITIONS, value.key)) { + return new Error('Invalid payout parameter feature.'); + } + const definition = PAYOUT_PARAM_DEFINITIONS[value.key]; + if (!Number.isSafeInteger(value.value) || + value.value < definition.min || + value.value > definition.max || + !Number.isSafeInteger(value.effective_epoch) || + value.effective_epoch < 1) { + return new Error('Invalid payout parameter value or activation epoch.'); + } + const adminError = await this.requireAdmin(this.address); + if (adminError) return adminError; + const applyState = await this.epochApplyStateRecord(); + if (value.effective_epoch <= applyState.updated_epoch) { + return new Error('Payout parameter activation epoch must be in the future.'); + } + const expectedKey = await this.payoutParameterFeatureKey(value); + if (key !== expectedKey) return new Error('Invalid payout parameter feature key.'); + + const recordKey = this.payoutParameterKey(value.key); + const schedule = await this.payoutParameterRecord(value.key); + const current = schedule.pending && + schedule.pending.effective_epoch <= applyState.updated_epoch + ? schedule.pending + : schedule.current; + const pending = schedule.pending && + schedule.pending.effective_epoch > applyState.updated_epoch + ? schedule.pending + : null; + const nextEntry = { + key: value.key, + value: value.value, + effective_epoch: value.effective_epoch, + scheduled_at: key, + scheduled_by: this.address, + scheduled_by_role: 'admin', + }; + if (pending) { + if (stableJson(pending) === stableJson(nextEntry)) { + return { + ok: true, + op: 'schedulePayoutParameter', + key: value.key, + value: value.value, + effective_epoch: value.effective_epoch, + idempotent: true, + }; + } + return new Error('A payout parameter update is already pending.'); + } + await this.put(recordKey, { key: value.key, current, pending: nextEntry }); + return { + ok: true, + op: 'schedulePayoutParameter', + key: value.key, + value: value.value, + effective_epoch: value.effective_epoch, + idempotent: false, + }; + } + + async applyVerifyStripePayoutFeature(key, value) { + const shapeError = this.validateExactObjectKeys( + value, + [ + 'op', + 'provider', + 'account_id', + 'account_type', + 'country', + 'currency', + 'mode', + 'verification_kind', + 'source_provider', + 'processor_revision', + 'previous_verification', + 'details_submitted', + 'payouts_enabled', + 'transfers_enabled', + 'network', + 'admin', + 'bootstrap', + 'context_revision', + 'payment_config_version', + 'request_nonce', + ], + 'Stripe payout verification feature' + ); + if (shapeError) return shapeError; + if (value.op !== 'verify_stripe_payout' || + !this.isHexBytes(value.provider, 32) || + value.provider !== value.provider.toLowerCase() || + typeof value.account_id !== 'string' || + !/^acct_[A-Za-z0-9._-]+$/.test(value.account_id) || + !['express', 'standard', 'custom'].includes(value.account_type) || + typeof value.country !== 'string' || + !/^[A-Z]{2}$/.test(value.country) || + !['adopt', 'onboard', 'status', 'relink'].includes(value.verification_kind) || + (value.verification_kind === 'adopt' && value.account_type !== 'standard') || + (value.source_provider !== null && + (!this.isHexBytes(value.source_provider, 32) || + value.source_provider !== value.source_provider.toLowerCase())) || + (value.verification_kind === 'relink' && + (value.source_provider === null || value.source_provider === value.provider)) || + (value.verification_kind !== 'relink' && value.source_provider !== null) || + !this.isHexBytes(value.processor_revision, 32) || + value.processor_revision !== value.processor_revision.toLowerCase() || + (value.previous_verification !== null && + (!this.isHexBytes(value.previous_verification, 32) || + value.previous_verification !== value.previous_verification.toLowerCase())) || + !['live', 'test'].includes(value.mode) || + typeof value.details_submitted !== 'boolean' || + typeof value.payouts_enabled !== 'boolean' || + typeof value.transfers_enabled !== 'boolean' || + !this.isHexBytes(value.admin, 32) || + value.admin !== value.admin.toLowerCase() || + !this.isHexBytes(value.bootstrap, 32) || + value.bootstrap !== value.bootstrap.toLowerCase() || + !this.isHexBytes(value.context_revision, 32) || + value.context_revision !== value.context_revision.toLowerCase() || + !Number.isSafeInteger(value.payment_config_version) || + value.payment_config_version < 1 || + !this.isHexBytes(value.request_nonce, 32) || + value.request_nonce !== value.request_nonce.toLowerCase()) { + return new Error('Invalid Stripe payout verification.'); + } + const processorRevision = await stripePayoutProcessorRevision(value); + if (value.processor_revision !== processorRevision) { + return new Error('Stripe payout processor evidence revision mismatch.'); + } + const adminError = await this.requireAdmin(this.address); + if (adminError) return adminError; + const admin = await this.get('admin'); + if (value.admin !== admin) { + return new Error('Stripe payout verification admin is not canonical.'); + } + const expectedKey = await stripePayoutVerificationFeatureKey(value); + if (key !== expectedKey) return new Error('Invalid Stripe payout verification key.'); + const record = { + type: 'stripe_payout_verification', + revision: key.split('/').at(-1), + provider: value.provider, + target: value.account_id, + account_type: value.account_type, + country: value.country, + currency: value.currency, + mode: value.mode, + verification_kind: value.verification_kind, + source_provider: value.source_provider, + processor_revision: value.processor_revision, + previous_verification: value.previous_verification, + details_submitted: value.details_submitted, + payouts_enabled: value.payouts_enabled, + transfers_enabled: value.transfers_enabled, + ready: value.details_submitted && + value.payouts_enabled && + value.transfers_enabled, + network: value.network, + admin: value.admin, + bootstrap: value.bootstrap, + context_revision: value.context_revision, + payment_config_version: value.payment_config_version, + request_nonce: value.request_nonce, + verified_at: key, + verified_by: this.address, + verified_by_role: 'admin', + }; + const existing = await this.get(key); + if (existing) { + if (stableJson(existing) !== stableJson(record)) { + return new Error('Stripe payout verification record already exists.'); + } + return { + ok: true, + op: 'verifyStripePayout', + provider: value.provider, + revision: record.revision, + idempotent: true, + }; + } + const nonceKey = `payout/stripe-verified/nonce/${value.provider}/${value.request_nonce}`; + const nonceRecord = await this.get(nonceKey); + if (nonceRecord) { + return new Error('Stripe payout verification request nonce already consumed.'); + } + const currentPointer = await this.get( + `payout/stripe-verified/current/${value.provider}` + ); + if ((currentPointer?.revision ?? null) !== value.previous_verification) { + return new Error('Stripe payout verification revision is stale.'); + } + + const contextPointer = await this.get(PROVIDER_PAYOUT_BINDING_CONTEXT_CURRENT_KEY); + const context = await this.get( + this.providerPayoutContextRecordKey( + value.payment_config_version, + value.context_revision + ) + ); + if (!contextPointer || + contextPointer.revision !== value.context_revision || + !context || + context.network !== value.network || + context.admin !== value.admin || + context.bootstrap !== value.bootstrap || + context.payment_config_version !== value.payment_config_version) { + return new Error('Stripe payout verification context is not current.'); + } + if ((value.network === 'mainnet' && value.mode !== 'live') || + (value.network !== 'mainnet' && value.mode !== 'test')) { + return new Error('Stripe payout verification mode does not match canonical network.'); + } + const payments = await this.get('payments/current'); + if (!payments || + payments.ver !== value.payment_config_version || + payments.set_by !== admin || + payments.set_by_role !== 'admin' || + payments.fiat?.processor !== 'stripe' || + !Array.isArray(payments.fiat.payout_currencies) || + !payments.fiat.payout_currencies.includes(value.currency)) { + return new Error('Stripe payout verification currency is not canonical.'); + } + const provider = await this.get(`prov/${value.provider}`); + if (!provider || provider.status !== 'active' || + !Array.isArray(provider.accepted_rails) || + !provider.accepted_rails.includes('fiat')) { + return new Error('Stripe payout verification requires an active fiat provider.'); + } + await this.put(key, record); + await this.put(nonceKey, { + provider: value.provider, + request_nonce: value.request_nonce, + processor_revision: value.processor_revision, + revision: record.revision, + record_key: key, + consumed_at: key, + }); + const verificationPointer = { + provider: value.provider, + revision: record.revision, + record_key: key, + target: value.account_id, + currency: value.currency, + processor_revision: value.processor_revision, + ready: record.ready, + details_submitted: value.details_submitted, + payouts_enabled: value.payouts_enabled, + transfers_enabled: value.transfers_enabled, + context_revision: value.context_revision, + payment_config_version: value.payment_config_version, + updated_at: key, + updated_by: this.address, + updated_by_role: 'admin', + }; + await this.put(`payout/stripe-verified/current/${value.provider}`, verificationPointer); + await this.put( + this.providerStripePayoutVerificationTargetKey(value.provider, value.account_id), + verificationPointer + ); + return { + ok: true, + op: 'verifyStripePayout', + provider: value.provider, + revision: record.revision, + idempotent: false, + }; + } + + async applySpendReserveFeature(key, value) { + const normalized = await this.normalizeSpendReserveValue(value); + if (normalized instanceof Error) return normalized; + normalized.voucher_hash = await this.opaqueHash('mayhem-spend-voucher-record-v1', normalized.voucher_body); + const expectedKey = await this.spendReservationFeatureKey(normalized); + if (expectedKey instanceof Error) return expectedKey; + if (key !== expectedKey) return; + if (!this.verifySpendVoucherSignature(normalized.user, normalized.voucher_body, normalized.voucher.user_sig)) { + return new Error('Invalid spend voucher signature.'); + } + if (!this.verifySpendReservationSignature(normalized.provider, normalized)) { + return new Error('Invalid spend reservation provider signature.'); + } + + const applyState = await this.epochApplyStateRecord(); + const activeEpoch = (applyState.pending_epoch ?? applyState.updated_epoch) + 1; + if (normalized.epoch !== activeEpoch) { + return new Error('Spend reservation epoch is not the active billing epoch.'); + } + + const provider = await this.get(`prov/${normalized.provider}`); + if (!provider || provider.status !== 'active') return new Error('Provider registration required.'); + if (!Array.isArray(provider.accepted_rails) || !provider.accepted_rails.includes(normalized.rail)) { + return new Error('Provider does not accept payment rail.'); + } + const enclave = await this.get(`enclave/${normalized.enclave_id}`); + if (!enclave || enclave.status !== 'active') return new Error('Admin enclave is not active.'); + const enclaveError = this.requireAdminCreatedEnclave(enclave); + if (enclaveError) return enclaveError; + const serve = await this.get(`serve/${normalized.provider}/${normalized.enclave_id}`); + if (!serve || serve.status !== 'active') { + return new Error('Provider is not actively serving this admin enclave.'); + } + const serveTermsError = this.validateCommittedServeTerms(serve, normalized); + if (serveTermsError) return serveTermsError; + const priceCtxBracket = normalized.ctx_bracket ?? null; + const priceError = await this.requireCurrentAdminPrice(normalized.enclave_id, priceCtxBracket); + if (priceError) return priceError; + const lockedPrice = await this.get( + this.priceRecordKey(normalized.enclave_id, normalized.price_ver, priceCtxBracket) + ); + if (!lockedPrice || lockedPrice.denom !== PRICE_DENOMINATION) { + return new Error('Spend reservation locked price version is not known.'); + } + if (lockedPrice.set_by_role !== 'admin') { + return new Error('Spend reservation locked price is not admin-set.'); + } + if ((lockedPrice.ctx_bracket ?? null) !== priceCtxBracket) { + return new Error('Spend reservation locked price context bracket mismatch.'); + } + if ((lockedPrice.ctx_bracket_table_ver ?? null) !== (normalized.ctx_bracket_table_ver ?? null)) { + return new Error('Spend reservation locked price context bracket table mismatch.'); + } + if (stableJson(normalized.locked_rate_map) !== stableJson(lockedPrice.rate_map)) { + return new Error('Spend reservation locked rate_map does not match price version.'); + } + if (this.compareAu(normalized.locked_per_req_au, lockedPrice.per_req_au ?? ZERO_AU) !== 0) { + return new Error('Spend reservation locked per_req_au does not match price version.'); + } + if (this.compareAu(normalized.locked_min_session_au, lockedPrice.min_session_au ?? ZERO_AU) !== 0) { + return new Error('Spend reservation locked min_session_au does not match price version.'); + } + const rules = await this.currentRules(); + if (!rules || rules.ver !== normalized.rules_ver) { + return new Error('Spend reservation rules version is not current.'); + } + + const balance = await this.balanceRecord(normalized.user, normalized.rail); + if (balance instanceof Error) return balance; + const balanceError = this.guardianValidateBalanceRecord(balance, normalized.user, normalized.rail); + if (balanceError) return balanceError; + + const holdKey = this.spendHoldKey(normalized.user, normalized.rail, normalized.epoch); + const hold = await this.normalizeSpendHoldRecord( + (await this.get(holdKey)) ?? null, + normalized.user, + normalized.rail, + normalized.epoch + ); + if (hold instanceof Error) return hold; + const existing = hold.sessions.find((session) => session.session_id === normalized.session_id); + if (existing) { + if ( + existing.provider !== normalized.provider || + existing.enclave_id !== normalized.enclave_id || + existing.price_ver !== normalized.price_ver || + stableJson(existing.locked_rate_map) !== stableJson(normalized.locked_rate_map) || + this.compareAu(existing.locked_per_req_au, normalized.locked_per_req_au) !== 0 || + this.compareAu(existing.locked_min_session_au, normalized.locked_min_session_au) !== 0 || + existing.served_ctx !== normalized.served_ctx || + stableJson(existing.required_modalities) !== stableJson(normalized.required_modalities) || + stableJson(existing.required_specialities) !== stableJson(normalized.required_specialities) || + stableJson(existing.workflow ?? null) !== stableJson(normalized.workflow ?? null) || + existing.ctx_bracket !== normalized.ctx_bracket || + existing.ctx_bracket_table_ver !== normalized.ctx_bracket_table_ver || + this.compareAu(existing.max_spend_au, normalized.max_spend_au) !== 0 || + existing.voucher_hash !== normalized.voucher_hash + ) { + return new Error('Spend reservation session already exists with different terms.'); + } + const availableAu = this.compareAu(hold.reserved_au, balance.au) >= 0 + ? ZERO_AU + : this.safeSubAu(balance.au, hold.reserved_au); + if (availableAu instanceof Error) return availableAu; + return { + ok: true, + op: 'spendReserve', + session_id: normalized.session_id, + epoch: normalized.epoch, + rail: normalized.rail, + user: normalized.user, + provider: normalized.provider, + reserved_au: hold.reserved_au, + available_au: availableAu, + idempotent: true, + }; + } + + const nextReservedAu = this.safeAddAu(hold.reserved_au, normalized.max_spend_au); + if (nextReservedAu instanceof Error) return nextReservedAu; + if (this.compareAu(nextReservedAu, balance.au) > 0) { + return new Error('Insufficient unreserved credit balance.'); + } + const session = { + session_id: normalized.session_id, + provider: normalized.provider, + enclave_id: normalized.enclave_id, + price_ver: normalized.price_ver, + locked_rate_map: normalized.locked_rate_map, + locked_per_req_au: normalized.locked_per_req_au, + locked_min_session_au: normalized.locked_min_session_au, + served_ctx: normalized.served_ctx, + required_modalities: normalized.required_modalities.slice(), + required_specialities: cloneValue(normalized.required_specialities), + ...(normalized.workflow ? { workflow: cloneValue(normalized.workflow) } : {}), + ctx_bracket: normalized.ctx_bracket, + ctx_bracket_table_ver: normalized.ctx_bracket_table_ver, + rules_ver: normalized.rules_ver, + max_spend_au: normalized.max_spend_au, + voucher_hash: normalized.voucher_hash, + feature_key: key, + reserved_at: normalized.at, + recorded_at: this.tx, + }; + const nextHold = { + ...hold, + balance_au_at_last_reserve: balance.au, + reserved_au: nextReservedAu, + sessions: [...hold.sessions, session].sort((a, b) => compareCodepoint(a.session_id, b.session_id)), + updated_at: this.tx, + }; + await this.put(holdKey, nextHold); + const availableAu = this.safeSubAu(balance.au, nextHold.reserved_au); + if (availableAu instanceof Error) return availableAu; + return { + ok: true, + op: 'spendReserve', + session_id: normalized.session_id, + epoch: normalized.epoch, + rail: normalized.rail, + user: normalized.user, + provider: normalized.provider, + reserved_au: nextHold.reserved_au, + available_au: availableAu, + idempotent: false, + }; + } + + async applyTargetedSpendReserveFeature(key, value) { + const normalized = await this.normalizeTargetedSpendReserveValue(value); + if (normalized instanceof Error) return normalized; + normalized.voucher_hash = await this.opaqueHash( + 'mayhem-spend-voucher-record-v1', + normalized.voucher_body + ); + const binding = await this.providerPayoutBindingForEpoch( + normalized.provider, + normalized.rail, + value.payout_revision, + normalized.epoch, + { requireCurrentReadiness: true } + ); + if (binding instanceof Error) return binding; + const expectedKey = await this.targetedSpendReservationFeatureKey(value); + if (expectedKey instanceof Error) return expectedKey; + if (key !== expectedKey) return new Error('Invalid targeted spend reservation key.'); + if (!this.verifySpendVoucherSignature( + normalized.user, + normalized.voucher_body, + normalized.voucher.user_sig + )) { + return new Error('Invalid spend voucher signature.'); + } + const verify = this.protocol?.peer?.wallet?.verify; + if (typeof verify !== 'function' || + verify.call( + this.protocol.peer.wallet, + normalized.provider_sig, + targetedSpendReservationMessage({ + ...normalized, + payout_revision: value.payout_revision, + }), + normalized.provider + ) !== true) { + return new Error('Invalid targeted spend reservation provider signature.'); + } + + const applyState = await this.epochApplyStateRecord(); + const activeEpoch = (applyState.pending_epoch ?? applyState.updated_epoch) + 1; + if (normalized.epoch !== activeEpoch) { + return new Error('Spend reservation epoch is not the active billing epoch.'); + } + + const provider = await this.get(`prov/${normalized.provider}`); + if (!provider || provider.status !== 'active') return new Error('Provider registration required.'); + if (!Array.isArray(provider.accepted_rails) || !provider.accepted_rails.includes(normalized.rail)) { + return new Error('Provider does not accept payment rail.'); + } + const enclave = await this.get(`enclave/${normalized.enclave_id}`); + if (!enclave || enclave.status !== 'active') return new Error('Admin enclave is not active.'); + const enclaveError = this.requireAdminCreatedEnclave(enclave); + if (enclaveError) return enclaveError; + if (enclave.model_id !== normalized.model_id) { + return new Error('Spend reservation model does not match admin enclave.'); + } + const serve = await this.get(`serve/${normalized.provider}/${normalized.enclave_id}`); + if (!serve || serve.status !== 'active') { + return new Error('Provider is not actively serving this admin enclave.'); + } + const serveTermsError = this.validateCommittedServeTerms(serve, normalized); + if (serveTermsError) return serveTermsError; + const priceCtxBracket = normalized.ctx_bracket ?? null; + const priceError = await this.requireCurrentAdminPrice(normalized.enclave_id, priceCtxBracket); + if (priceError) return priceError; + const lockedPrice = await this.get( + this.priceRecordKey(normalized.enclave_id, normalized.price_ver, priceCtxBracket) + ); + if (!lockedPrice || lockedPrice.denom !== PRICE_DENOMINATION) { + return new Error('Spend reservation locked price version is not known.'); + } + if (lockedPrice.set_by_role !== 'admin') { + return new Error('Spend reservation locked price is not admin-set.'); + } + if ((lockedPrice.ctx_bracket ?? null) !== priceCtxBracket) { + return new Error('Spend reservation locked price context bracket mismatch.'); + } + if ((lockedPrice.ctx_bracket_table_ver ?? null) !== (normalized.ctx_bracket_table_ver ?? null)) { + return new Error('Spend reservation locked price context bracket table mismatch.'); + } + if (stableJson(normalized.locked_rate_map) !== stableJson(lockedPrice.rate_map)) { + return new Error('Spend reservation locked rate_map does not match price version.'); + } + if (this.compareAu(normalized.locked_per_req_au, lockedPrice.per_req_au ?? ZERO_AU) !== 0) { + return new Error('Spend reservation locked per_req_au does not match price version.'); + } + if (this.compareAu(normalized.locked_min_session_au, lockedPrice.min_session_au ?? ZERO_AU) !== 0) { + return new Error('Spend reservation locked min_session_au does not match price version.'); + } + const rules = await this.currentRules(); + if (!rules || rules.ver !== normalized.rules_ver) { + return new Error('Spend reservation rules version is not current.'); + } + + const balance = await this.balanceRecord(normalized.user, normalized.rail); + if (balance instanceof Error) return balance; + const balanceError = this.guardianValidateBalanceRecord(balance, normalized.user, normalized.rail); + if (balanceError) return balanceError; + + const accounting = await this.targetedSpendAccountingState( + normalized.user, + normalized.rail + ); + if (accounting instanceof Error) return accounting; + const reservationState = await this.targetedSpendReservationState( + normalized.user, + normalized.rail, + normalized.reservation_id, + normalized.session_id + ); + if (reservationState instanceof Error) return reservationState; + const legacySessionById = accounting.hold.sessions.find( + (session) => session.session_id === normalized.session_id + ); + const sessionIndexKey = this.targetedSpendSessionIndexKey( + normalized.user, + normalized.rail, + normalized.session_id + ); + const sessionIndex = this.normalizeTargetedSpendReservationIndexRecord( + await this.get(sessionIndexKey), + { + user: normalized.user, + rail: normalized.rail, + sessionId: normalized.session_id, + } + ); + if (sessionIndex instanceof Error) return sessionIndex; + const billingAttemptKey = this.targetedSpendBillingAttemptKey( + normalized.user, + normalized.rail, + normalized.voucher_body.billing_id, + normalized.voucher_body.billing_attempt + ); + const billingAttemptIndex = this.normalizeTargetedSpendReservationIndexRecord( + await this.get(billingAttemptKey), + { + user: normalized.user, + rail: normalized.rail, + billingId: normalized.voucher_body.billing_id, + billingAttempt: normalized.voucher_body.billing_attempt, + } + ); + if (billingAttemptIndex instanceof Error) return billingAttemptIndex; + if (sessionIndex !== null && sessionIndex.reservation_id !== normalized.reservation_id) { + return new Error('Targeted spend reservation session already exists with different terms.'); + } + if (billingAttemptIndex !== null && + billingAttemptIndex.reservation_id !== normalized.reservation_id) { + return new Error('Billing attempt already has an active reservation.'); + } + const existing = reservationState.kind !== 'missing' + ? reservationState.session + : legacySessionById ?? null; + if (existing) { + if ( + existing.billing_id !== normalized.voucher_body.billing_id || + existing.billing_attempt !== normalized.voucher_body.billing_attempt || + existing.billing_epoch !== normalized.epoch || + existing.reservation_id !== normalized.voucher_body.reservation_id || + existing.reservation_expires_after_epoch !== normalized.reservation_expires_after_epoch || + existing.reservation_receipt_grace_epochs !== normalized.reservation_receipt_grace_epochs || + existing.user !== normalized.user || + existing.rail !== normalized.rail || + existing.provider !== normalized.provider || + existing.payout_revision !== value.payout_revision || + existing.enclave_id !== normalized.enclave_id || + existing.enclave_pubkey !== normalized.enclave_pubkey || + existing.model_id !== normalized.model_id || + existing.price_ver !== normalized.price_ver || + stableJson(existing.billing_prior_usage) !== + stableJson(normalized.voucher_body.billing_prior_usage) || + this.compareAu( + existing.billing_prior_au_owed_cum, + normalized.voucher_body.billing_prior_au_owed_cum + ) !== 0 || + stableJson(existing.locked_rate_map) !== stableJson(normalized.locked_rate_map) || + this.compareAu(existing.locked_per_req_au, normalized.locked_per_req_au) !== 0 || + this.compareAu(existing.locked_min_session_au, normalized.locked_min_session_au) !== 0 || + existing.served_ctx !== normalized.served_ctx || + stableJson(existing.required_modalities) !== stableJson(normalized.required_modalities) || + stableJson(existing.required_specialities) !== stableJson(normalized.required_specialities) || + stableJson(existing.workflow ?? null) !== stableJson(normalized.workflow ?? null) || + existing.ctx_bracket !== normalized.ctx_bracket || + existing.ctx_bracket_table_ver !== normalized.ctx_bracket_table_ver || + existing.rules_ver !== normalized.rules_ver || + this.compareAu(existing.max_spend_au, normalized.max_spend_au) !== 0 || + existing.voucher_hash !== normalized.voucher_hash || + existing.payout_revision !== value.payout_revision + ) { + return new Error('Targeted spend reservation session already exists with different terms.'); + } + const billingError = await this.validateExistingBillingReservation(normalized); + if (billingError) return billingError; + const availableAu = this.compareAu(accounting.total_reserved_au, balance.au) >= 0 + ? ZERO_AU + : this.safeSubAu(balance.au, accounting.total_reserved_au); + if (availableAu instanceof Error) return availableAu; + return { + ok: true, + op: 'spendReserveTargeted', + session_id: normalized.session_id, + epoch: normalized.epoch, + rail: normalized.rail, + user: normalized.user, + provider: normalized.provider, + payout_revision: value.payout_revision, + reserved_au: accounting.total_reserved_au, + available_au: availableAu, + idempotent: true, + }; + } + + const billingReservation = await this.prepareBillingReservation(normalized, key); + if (billingReservation instanceof Error) return billingReservation; + const nextSummaryReservedAu = this.safeAddAu( + accounting.summary.reserved_au, + normalized.max_spend_au + ); + if (nextSummaryReservedAu instanceof Error) return nextSummaryReservedAu; + const nextTotalReservedAu = this.safeAddAu( + accounting.legacy_reserved_au, + nextSummaryReservedAu + ); + if (nextTotalReservedAu instanceof Error) return nextTotalReservedAu; + if (this.compareAu(nextTotalReservedAu, balance.au) > 0) { + return new Error('Insufficient unreserved credit balance.'); + } + const session = { + session_id: normalized.session_id, + billing_id: normalized.voucher_body.billing_id, + billing_attempt: normalized.voucher_body.billing_attempt, + billing_prior_usage: cloneValue(normalized.voucher_body.billing_prior_usage), + billing_prior_au_owed_cum: normalized.voucher_body.billing_prior_au_owed_cum, + billing_epoch: normalized.epoch, + reservation_id: normalized.voucher_body.reservation_id, + reservation_expires_after_epoch: normalized.reservation_expires_after_epoch, + reservation_receipt_grace_epochs: normalized.reservation_receipt_grace_epochs, + user: normalized.user, + rail: normalized.rail, + provider: normalized.provider, + payout_revision: value.payout_revision, + enclave_id: normalized.enclave_id, + enclave_pubkey: normalized.enclave_pubkey, + model_id: normalized.model_id, + price_ver: normalized.price_ver, + locked_rate_map: normalized.locked_rate_map, + locked_per_req_au: normalized.locked_per_req_au, + locked_min_session_au: normalized.locked_min_session_au, + served_ctx: normalized.served_ctx, + required_modalities: normalized.required_modalities.slice(), + required_specialities: cloneValue(normalized.required_specialities), + ...(normalized.workflow ? { workflow: cloneValue(normalized.workflow) } : {}), + ctx_bracket: normalized.ctx_bracket, + ctx_bracket_table_ver: normalized.ctx_bracket_table_ver, + rules_ver: normalized.rules_ver, + max_spend_au: normalized.max_spend_au, + voucher_hash: normalized.voucher_hash, + feature_key: key, + reserved_at: normalized.at, + recorded_at: this.tx, + }; + const sessionKey = this.targetedSpendSessionKey( + normalized.user, + normalized.rail, + normalized.reservation_id + ); + const nextSummary = { + ...accounting.summary, + balance_au_at_last_reserve: balance.au, + reserved_au: nextSummaryReservedAu, + updated_at: this.tx, + }; + const indexRecord = this.targetedSpendReservationIndexRecord( + session, + sessionKey, + this.tx + ); + await this.put( + this.targetedSpendSummaryKey(normalized.user, normalized.rail), + nextSummary + ); + await this.put(sessionKey, this.targetedSpendSessionRecord(session, this.tx)); + await this.put(sessionIndexKey, indexRecord); + await this.put(billingAttemptKey, indexRecord); + await this.put(billingReservation.anchor_key, billingReservation.anchor); + await this.put(billingReservation.reservation_key, billingReservation.reservation); + const availableAu = this.safeSubAu(balance.au, nextTotalReservedAu); + if (availableAu instanceof Error) return availableAu; + return { + ok: true, + op: 'spendReserveTargeted', + session_id: normalized.session_id, + epoch: normalized.epoch, + rail: normalized.rail, + user: normalized.user, + provider: normalized.provider, + payout_revision: value.payout_revision, + reserved_au: nextTotalReservedAu, + available_au: availableAu, + idempotent: false, + }; + } + + receiptAttemptTerms(body) { + return { + schema_version: body.schema_version, + session_id: body.session_id, + billing_id: body.billing_id, + billing_attempt: body.billing_attempt, + billing_prior_usage: body.billing_prior_usage, + billing_prior_au_owed_cum: body.billing_prior_au_owed_cum, + billing_epoch: body.billing_epoch, + reservation_id: body.reservation_id, + reservation_expires_after_epoch: body.reservation_expires_after_epoch, + reservation_receipt_grace_epochs: body.reservation_receipt_grace_epochs, + payout_revision: body.payout_revision, + rail: body.rail, + user: body.user, + provider: body.provider, + enclave_id: body.enclave_id, + model_id: body.model_id, + price_ver: body.price_ver, + locked_rate_map: body.locked_rate_map, + locked_per_req_au: body.locked_per_req_au, + locked_min_session_au: body.locked_min_session_au, + served_ctx: body.served_ctx, + capacity_slots: body.capacity_slots, + ctx_bracket: body.ctx_bracket, + ctx_bracket_table_ver: body.ctx_bracket_table_ver, + rules_ver: body.rules_ver, + workflow: body.workflow ?? null, + prompt_hash: body.prompt_hash, + }; + } + + receiptUsageIsMonotonic(previous, next) { + const units = new Set([...Object.keys(previous), ...Object.keys(next)]); + for (const unit of units) { + const before = previous[unit] ?? 0; + const after = next[unit] ?? 0; + if (!Number.isSafeInteger(before) || + !Number.isSafeInteger(after) || + after < before) { + return false; + } + } + return true; + } + + async normalizeRecordUsageReceiptValue(value) { + const shapeError = this.validateExactObjectKeys( + value, + ['op', 'contract_version', 'epoch', 'payout_revision', 'receipt', 'provider_sig'], + 'record usage receipt feature' + ); + if (shapeError) return shapeError; + if (value.op !== 'record_usage_receipt') { + return new Error('Invalid record usage receipt op.'); + } + if (value.contract_version !== CONTRACT_VERSION && + !RECOVERABLE_RECEIPT_CONTRACT_VERSIONS.has(value.contract_version)) { + return new Error('Invalid record usage receipt contract version.'); + } + if (!Number.isSafeInteger(value.epoch) || value.epoch < 1) { + return new Error('Invalid record usage receipt epoch.'); + } + if (!this.isHexBytes(value.payout_revision, 32) || + value.payout_revision !== value.payout_revision.toLowerCase()) { + return new Error('Invalid record usage receipt payout revision.'); + } + if (!this.isHexBytes(value.provider_sig, 64)) { + return new Error('Invalid record usage receipt provider signature.'); + } + const receiptShapeError = this.validateExactObjectKeys( + value.receipt, + ['body', 'enclave_sig', 'enclave_pubkey', 'user_sig'], + 'record usage receipt envelope' + ); + if (receiptShapeError) return receiptShapeError; + const targetSchemaVersion = value.contract_version === CONTRACT_VERSION + ? SESSION_RECEIPT_SCHEMA_VERSION + : 11; + const receipt = await this.normalizeReceiptEnvelope(value.receipt, { + targetSchemaVersion, + }); + if (receipt instanceof Error) return receipt; + const canonicalReceipt = { + body: canonicalReceiptBody(receipt.body), + enclave_sig: receipt.enclave_sig.toLowerCase(), + enclave_pubkey: receipt.enclave_pubkey.toLowerCase(), + user_sig: receipt.user_sig.toLowerCase(), + }; + if (stableJson(value.receipt) !== stableJson(canonicalReceipt)) { + return new Error('Record usage receipt envelope must be canonical.'); + } + if (receipt.body.billing_epoch !== value.epoch) { + return new Error('Record usage receipt outer epoch does not match signed receipt.'); + } + if (receipt.body.payout_revision !== value.payout_revision) { + return new Error('Record usage receipt outer payout revision does not match signed receipt.'); + } + return { + op: 'record_usage_receipt', + // The outer version participates in the provider signature and feature key. + // Never rewrite retained v23 evidence while executing under a v24 dispatch. + contract_version: value.contract_version, + epoch: value.epoch, + payout_revision: value.payout_revision, + receipt: canonicalReceipt, + provider_sig: value.provider_sig.toLowerCase(), + }; + } + + async recordUsageReceiptFeatureKey(value) { + const normalized = await this.normalizeRecordUsageReceiptValue(value); + if (normalized instanceof Error) return normalized; + const digest = await blake3(b4a.from(stableJson({ + domain: 'mayhem-record-usage-receipt-feature-v1', + evidence: recordUsageReceiptEvidence(normalized), + }))); + const body = normalized.receipt.body; + return ( + `receipt/submit/${body.billing_epoch}/${body.billing_id}/` + + `${body.billing_attempt}/${body.seq}/${b4a.toString(digest, 'hex')}` + ); + } + + normalizeCloseUsageReservationValue(value, { expiry = false } = {}) { + const shapeError = this.validateExactObjectKeys( + value, + [ + 'op', + 'contract_version', + 'billing_epoch', + 'reservation_id', + 'reservation_expires_after_epoch', + 'reservation_receipt_grace_epochs', + 'billing_id', + 'billing_attempt', + 'session_id', + 'user', + 'rail', + 'provider', + 'payout_revision', + 'latest_receipt_seq', + 'latest_receipt_hash', + 'at', + 'reason', + 'actor', + 'actor_role', + 'actor_sig', + ], + 'close usage reservation feature' + ); + if (shapeError) return shapeError; + const rail = this.normalizeLedgerRail(value.rail, 'close usage reservation rail'); + if (rail instanceof Error) return rail; + const expectedOp = expiry ? 'expire_usage_reservation' : 'close_usage_reservation'; + const expectedRole = expiry ? 'user' : 'provider'; + if (value.op !== expectedOp || + value.contract_version !== CONTRACT_VERSION || + !Number.isSafeInteger(value.billing_epoch) || + value.billing_epoch < 1 || + !this.isHexBytes(value.reservation_id, 32) || + !Number.isSafeInteger(value.reservation_expires_after_epoch) || + value.reservation_expires_after_epoch <= value.billing_epoch || + !Number.isSafeInteger(value.reservation_receipt_grace_epochs) || + value.reservation_receipt_grace_epochs < 0 || + !this.isHexBytes(value.billing_id, 32) || + !Number.isSafeInteger(value.billing_attempt) || + value.billing_attempt < 0 || + !this.isHexBytes(value.session_id, 32) || + !this.isHexBytes(value.user, 32) || + !this.isHexBytes(value.provider, 32) || + !this.isHexBytes(value.payout_revision, 32) || + !Number.isSafeInteger(value.at) || + value.at < 0 || + !this.isSafeKeyPart(value.reason) || + !this.isHexBytes(value.actor, 32) || + value.actor_role !== expectedRole || + !this.isHexBytes(value.actor_sig, 64)) { + return new Error('Invalid close usage reservation feature.'); + } + const expectedActor = expiry ? value.user : value.provider; + if (value.actor !== expectedActor) { + return new Error('Close usage reservation actor does not match its role.'); + } + const hasReceipt = value.latest_receipt_seq !== null || + value.latest_receipt_hash !== null; + if ( + hasReceipt + ? (!Number.isSafeInteger(value.latest_receipt_seq) || + value.latest_receipt_seq < 0 || + !this.isHexBytes(value.latest_receipt_hash, 32)) + : value.latest_receipt_seq !== null || value.latest_receipt_hash !== null + ) { + return new Error('Invalid close usage reservation receipt head.'); + } + const normalized = { + op: expectedOp, + contract_version: CONTRACT_VERSION, + billing_epoch: value.billing_epoch, + reservation_id: value.reservation_id.toLowerCase(), + reservation_expires_after_epoch: value.reservation_expires_after_epoch, + reservation_receipt_grace_epochs: value.reservation_receipt_grace_epochs, + billing_id: value.billing_id.toLowerCase(), + billing_attempt: value.billing_attempt, + session_id: value.session_id.toLowerCase(), + user: value.user.toLowerCase(), + rail, + provider: value.provider.toLowerCase(), + payout_revision: value.payout_revision.toLowerCase(), + latest_receipt_seq: value.latest_receipt_seq, + latest_receipt_hash: value.latest_receipt_hash?.toLowerCase() ?? null, + at: value.at, + reason: value.reason, + actor: value.actor.toLowerCase(), + actor_role: value.actor_role, + actor_sig: value.actor_sig.toLowerCase(), + }; + if (stableJson(normalized) !== stableJson(value)) { + return new Error('Close usage reservation feature must be canonical.'); + } + return normalized; + } + + async closeUsageReservationFeatureKey(value, { expiry = false } = {}) { + const normalized = this.normalizeCloseUsageReservationValue(value, { expiry }); + if (normalized instanceof Error) return normalized; + const digest = await blake3(b4a.from(stableJson({ + domain: expiry + ? 'mayhem-expire-usage-reservation-feature-v1' + : 'mayhem-close-usage-reservation-feature-v1', + evidence: closeUsageReservationEvidence(normalized), + actor_sig: normalized.actor_sig, + }))); + return ( + `receipt/${expiry ? 'expire' : 'close'}/${normalized.billing_epoch}/` + + `${normalized.reservation_id}/` + + `${b4a.toString(digest, 'hex')}` + ); + } + + async nextReceiptEpochIndex(epoch, billingId, billingAttempt) { + if (await this.get(`epoch/freeze/${epoch}`)) { + return new Error('Cannot append receipts to a frozen settlement epoch.'); + } + const indexKey = this.receiptEpochIndexKey(epoch); + const existingIndex = await this.get(indexKey); + const index = existingIndex ?? { + type: 'canonical_receipt_epoch_index', + epoch, + count: 0, + page_size: RECEIPT_EPOCH_INDEX_PAGE_SIZE, + page_count: 0, + revision: 0, + updated_at: null, + }; + const normalizedIndex = this.normalizeReceiptEpochIndexMetadata(index, epoch, { + allowEmpty: true, + }); + if (normalizedIndex instanceof Error) return normalizedIndex; + if (index.count >= Number.MAX_SAFE_INTEGER) { + return new Error('Canonical receipt epoch count overflow.'); + } + const page = Math.floor(index.count / RECEIPT_EPOCH_INDEX_PAGE_SIZE); + const pageKey = this.receiptEpochPageKey(epoch, page); + const existingPage = await this.get(pageKey); + const pageRecord = existingPage ?? { + type: 'canonical_receipt_epoch_page', + epoch, + page, + identities: [], + }; + if (pageRecord.type !== 'canonical_receipt_epoch_page' || + pageRecord.epoch !== epoch || + pageRecord.page !== page || + !Array.isArray(pageRecord.identities) || + Object.keys(pageRecord).sort().join(',') !== 'epoch,identities,page,type' || + pageRecord.identities.length !== index.count % RECEIPT_EPOCH_INDEX_PAGE_SIZE || + pageRecord.identities.length >= RECEIPT_EPOCH_INDEX_PAGE_SIZE) { + return new Error('Canonical receipt epoch page is invalid or full.'); + } + const identities = pageRecord.identities; + if ( + identities.length >= RECEIPT_EPOCH_INDEX_PAGE_SIZE) { + return new Error('Canonical receipt epoch page is invalid or full.'); + } + const identity = { billing_id: billingId, billing_attempt: billingAttempt }; + if (identities.some((entry) => + entry?.billing_id === billingId && entry?.billing_attempt === billingAttempt + )) { + return new Error('Canonical receipt billing attempt is already indexed.'); + } + return { + index_key: indexKey, + index: { + ...index, + count: index.count + 1, + page_count: Math.max(index.page_count, page + 1), + }, + page_key: pageKey, + page: { + ...pageRecord, + identities: [...identities, identity], + }, + position: index.count, + }; + } + + normalizeReceiptEpochIndexMetadata(value, epoch, { allowEmpty = false } = {}) { + const shapeError = this.validateExactObjectKeys( + value, + ['type', 'epoch', 'count', 'page_size', 'page_count', 'revision', 'updated_at'], + 'canonical receipt epoch metadata' + ); + if (shapeError) return shapeError; + if (value.type !== 'canonical_receipt_epoch_index' || value.epoch !== epoch) { + return new Error('Canonical receipt epoch metadata identity is invalid.'); + } + if (!Number.isSafeInteger(value.count) || value.count < 0 || + value.page_size !== RECEIPT_EPOCH_INDEX_PAGE_SIZE || + !Number.isSafeInteger(value.page_count) || value.page_count < 0 || + !Number.isSafeInteger(value.revision) || value.revision < value.count) { + return new Error('Canonical receipt epoch metadata counters are invalid.'); + } + const expectedPageCount = value.count === 0 + ? 0 + : Math.ceil(value.count / RECEIPT_EPOCH_INDEX_PAGE_SIZE); + if (value.page_count !== expectedPageCount) { + return new Error('Canonical receipt epoch metadata page count is invalid.'); + } + if (value.count === 0) { + if (!allowEmpty || value.revision !== 0 || value.updated_at !== null) { + return new Error('Canonical receipt epoch metadata cannot be empty.'); + } + } else if (typeof value.updated_at !== 'string' || value.updated_at.length === 0) { + return new Error('Canonical receipt epoch metadata updated_at is invalid.'); + } + return { + type: value.type, + epoch: value.epoch, + count: value.count, + page_size: value.page_size, + page_count: value.page_count, + revision: value.revision, + updated_at: value.updated_at, + }; + } + + async receiptSettlementEpoch(applyState) { + const base = applyState.pending_epoch ?? applyState.updated_epoch; + if (!Number.isSafeInteger(base) || base < 0 || base >= Number.MAX_SAFE_INTEGER) { + return new Error('Receipt settlement epoch overflow.'); + } + const cursor = await this.get('receipt/ingress'); + if (cursor === null) return base + 1; + if (cursor.type !== 'receipt_ingress' || + !Number.isSafeInteger(cursor.next_epoch) || cursor.next_epoch < 1 || + !Number.isSafeInteger(cursor.activated_epoch) || cursor.activated_epoch < 1 || + cursor.activated_epoch >= cursor.next_epoch || + cursor.next_epoch > base + 2) { + return new Error('Canonical receipt ingress cursor is invalid.'); + } + return Math.max(base + 1, cursor.next_epoch); + } + + async prepareStateCheckpoint() { + const adminError = await this.requireAdmin(); + if (adminError) return adminError; + const shapeError = this.validateExactCommandValue( + ['op', 'slot', 'observed_at', 'contract_code_sha256'], 'prepare_state_checkpoint' + ); + if (shapeError) return shapeError; + const { slot, observed_at: observedAt, contract_code_sha256: codeHash } = this.value; + if (!Number.isSafeInteger(slot) || slot < 1 || !Number.isSafeInteger(slot * 30) || + !Number.isSafeInteger(observedAt) || observedAt < slot * 30 || + !this.isHexBytes(codeHash, 32) || codeHash !== codeHash.toLowerCase()) { + return new Error('Invalid checkpoint slot, observation time or release hash.'); + } + const key = `checkpoint/prepared/${slot}`; + const existing = await this.get(key); + if (existing !== null) { + return { ok: true, op: 'prepareStateCheckpoint', idempotent: true, snapshot: existing }; + } + const previous = await this.get('checkpoint/current'); + const preparing = await this.get('checkpoint/preparing'); + if (preparing && preparing.slot !== slot && preparing.slot > (previous?.slot ?? 0)) { + return new Error('An earlier checkpoint preparation is still awaiting its paid transaction.'); + } + if (previous && (slot !== previous.slot + 1 || observedAt < previous.observed_at)) { + return new Error('Checkpoint preparation must follow the last paid slot and observation time.'); + } + const applyState = await this.epochApplyStateRecord(); + const ingress = await this.receiptSettlementEpoch(applyState); + if (ingress instanceof Error) return ingress; + const completed = applyState.updated_epoch > 0 + ? await this.epochApplyAnchor(applyState.updated_epoch) : null; + if (completed instanceof Error) return completed; + if (applyState.updated_epoch > 0 && completed === null) { + return new Error('Completed settlement anchor is unavailable for checkpoint.'); + } + const catalog = await this.get('catalog/current'); + const state = { + completed_settlement: completed, + pending_apply: applyState.pending_epoch == null ? null : { + epoch: applyState.pending_epoch, next_page: applyState.pending_next_page, + apply_hash: applyState.last_apply_hash, + }, + receipt_ingress_epoch: ingress, + open_receipt_index: await this.get(this.receiptEpochIndexKey(ingress)), + frozen_receipts: await this.get(`epoch/freeze/${applyState.updated_epoch + 1}`), + catalog_hash: catalog?.catalog_hash ?? null, + catalog_version: catalog?.ver ?? catalog?.version ?? null, + contract_version: CONTRACT_VERSION, + // The writer supplies its startup-verified release manifest digest. This + // attests the publisher's code identity; canonical state is read here. + contract_code_sha256: codeHash, + }; + const stateHash = await this.opaqueHash('mayhem-checkpoint-state-v1', state); + const body = { + type: 'state_checkpoint_snapshot', schema_version: 1, slot, + scheduled_at: slot * 30, observed_at: observedAt, + previous_tx: previous?.tx ?? null, + state, state_hash: stateHash, + no_change: previous?.state_hash === stateHash, + prepared_by: this.address, + }; + const snapshot = { + ...body, snapshot_hash: await this.opaqueHash('mayhem-checkpoint-snapshot-v1', body), + }; + await this.put(key, snapshot); + await this.put('checkpoint/preparing', { slot, snapshot_hash: snapshot.snapshot_hash }); + return { ok: true, op: 'prepareStateCheckpoint', idempotent: false, snapshot }; + } + + async stateCheckpoint() { + if (this._mayhemExecutionType !== 'tx' || this.isFeature() || !this.isHexBytes(this.tx, 32)) { + return new Error('State checkpoints require a paid MSB transaction; free admin Features are forbidden.'); + } + const adminError = await this.requireAdmin(); + if (adminError) return adminError; + const shapeError = this.validateExactCommandValue( + ['op', 'slot', 'snapshot_hash'], 'state_checkpoint' + ); + if (shapeError) return shapeError; + const { slot, snapshot_hash: snapshotHash } = this.value; + if (!Number.isSafeInteger(slot) || slot < 1 || !this.isHexBytes(snapshotHash, 32)) { + return new Error('Invalid paid checkpoint identity.'); + } + const key = `checkpoint/slot/${slot}`; + const existing = await this.get(key); + if (existing !== null) { + return existing.tx === this.tx && existing.snapshot_hash === snapshotHash + ? { ok: true, op: 'stateCheckpoint', idempotent: true, checkpoint: existing } + : new Error('Checkpoint slot already has a paid transaction.'); + } + const snapshot = await this.get(`checkpoint/prepared/${slot}`); + if (!snapshot || snapshot.type !== 'state_checkpoint_snapshot' || + snapshot.slot !== slot || snapshot.snapshot_hash !== snapshotHash || + snapshot.prepared_by !== this.address) { + return new Error('Matching canonical checkpoint snapshot required.'); + } + const previous = await this.get('checkpoint/current'); + if (snapshot.previous_tx !== (previous?.tx ?? null) || + (previous && slot !== previous.slot + 1)) { + return new Error('Paid checkpoint predecessor does not match canonical history.'); + } + const checkpoint = { + type: 'paid_state_checkpoint', schema_version: 1, slot, + scheduled_at: snapshot.scheduled_at, observed_at: snapshot.observed_at, + snapshot_hash: snapshotHash, state_hash: snapshot.state_hash, + no_change: snapshot.no_change, previous_tx: snapshot.previous_tx, + tx: this.tx, paid_by: this.address, + }; + await this.put(key, checkpoint); + await this.put('checkpoint/current', checkpoint); + return { ok: true, op: 'stateCheckpoint', idempotent: false, checkpoint }; + } + + async epochFreeze() { + const adminError = await this.requireAdmin(); + if (adminError) return adminError; + const shapeError = this.validateExactCommandValue(['op', 'epoch', 'at'], 'epoch_freeze'); + if (shapeError) return shapeError; + const { epoch, at } = this.value; + if (!Number.isSafeInteger(epoch) || epoch < 1 || epoch >= Number.MAX_SAFE_INTEGER || + !Number.isSafeInteger(at) || at < 0) { + return new Error('Invalid epoch freeze identity or timestamp.'); + } + const key = `epoch/freeze/${epoch}`; + const existing = await this.get(key); + if (existing !== null) { + return { ok: true, op: 'epochFreeze', idempotent: true, freeze: existing }; + } + const applyState = await this.epochApplyStateRecord(); + const orderError = this.validateEpochApplyPageOrder(applyState, epoch, 0); + if (orderError) return orderError; + const params = await this.activeParamsAt(at, ['epoch_seconds']); + const cadenceError = await this.validateEpochCadenceTime( + applyState, epoch, 0, at, params.epoch_seconds + ); + if (cadenceError) return cadenceError; + if (at < epoch * params.epoch_seconds) { + return new Error('Epoch freeze is not active until the epoch window ends.'); + } + const ingress = await this.receiptSettlementEpoch(applyState); + if (ingress instanceof Error) return ingress; + if (ingress !== epoch) return new Error('Epoch freeze must close the open receipt batch.'); + const metadata = this.normalizeReceiptEpochIndexMetadata( + (await this.get(this.receiptEpochIndexKey(epoch))) ?? { + type: 'canonical_receipt_epoch_index', epoch, count: 0, + page_size: RECEIPT_EPOCH_INDEX_PAGE_SIZE, page_count: 0, + revision: 0, updated_at: null, + }, epoch, { allowEmpty: true } + ); + if (metadata instanceof Error) return metadata; + const body = { + type: 'epoch_receipt_freeze', epoch, at, epoch_seconds: params.epoch_seconds, + previous_apply_hash: applyState.last_apply_hash ?? null, + receipt_index: metadata, frozen_by: this.address, + }; + const freeze = { + ...body, freeze_hash: await this.opaqueHash('mayhem-epoch-receipt-freeze-v1', body), + frozen_at: this.tx, + }; + const cursor = await this.get('receipt/ingress'); + // Both writes are in the same consensus apply. No external observation or + // receipt append can interleave with this cutoff. + await this.put(key, freeze); + await this.put('receipt/ingress', { + type: 'receipt_ingress', next_epoch: epoch + 1, + activated_epoch: cursor?.activated_epoch ?? epoch, + freeze_hash: freeze.freeze_hash, updated_at: this.tx, + }); + return { ok: true, op: 'epochFreeze', idempotent: false, freeze }; + } + + async validateFrozenEpoch(epoch, at, receiptIndex) { + const cursor = await this.get('receipt/ingress'); + const freeze = await this.get(`epoch/freeze/${epoch}`); + // Legacy already-open/partially-applied epochs remain recoverable. Once + // ingress is activated, every subsequent epoch requires a canonical cutoff. + if (freeze === null) { + return cursor && epoch >= cursor.activated_epoch + ? new Error('Canonical epoch freeze required before settlement.') : null; + } + if (freeze.type !== 'epoch_receipt_freeze' || freeze.epoch !== epoch || + freeze.at !== at || + stableJson(freeze.receipt_index) !== stableJson(receiptIndex)) { + return new Error('Settlement does not match its canonical epoch freeze.'); + } + return null; + } + + async normalizeTargetedSpendSummaryRecord(record, user, rail) { + if (!record) { + return { + type: 'targeted_spend_summary', + user, + rail, + denom: PRICE_DENOMINATION, + reserved_au: ZERO_AU, + balance_au_at_last_reserve: null, + updated_at: null, + }; + } + if (!record || typeof record !== 'object' || Array.isArray(record) || + record.type !== 'targeted_spend_summary' || + record.user !== user || + record.rail !== rail || + record.denom !== PRICE_DENOMINATION) { + return new Error('Targeted spend summary key mismatch.'); + } + const reservedAu = this.normalizeAu(record.reserved_au, 'targeted spend summary reserved amount'); + if (reservedAu instanceof Error) { + return new Error('Invalid targeted spend summary reserved amount.'); + } + let balanceAu = null; + if (record.balance_au_at_last_reserve !== null) { + balanceAu = this.normalizeAu( + record.balance_au_at_last_reserve, + 'targeted spend summary balance amount' + ); + if (balanceAu instanceof Error) { + return new Error('Invalid targeted spend summary balance amount.'); + } + } + if (record.updated_at !== null && + (typeof record.updated_at !== 'string' || record.updated_at.length === 0)) { + return new Error('Invalid targeted spend summary update pointer.'); + } + return { + type: 'targeted_spend_summary', + user, + rail, + denom: PRICE_DENOMINATION, + reserved_au: reservedAu, + balance_au_at_last_reserve: balanceAu, + updated_at: record.updated_at, + }; + } + + async normalizeTargetedSpendLegacyReleaseSummaryRecord(record, user, rail) { + if (!record) { + return { + type: 'targeted_spend_legacy_release_summary', + user, + rail, + denom: PRICE_DENOMINATION, + released_au: ZERO_AU, + updated_at: null, + }; + } + if (!record || typeof record !== 'object' || Array.isArray(record) || + record.type !== 'targeted_spend_legacy_release_summary' || + record.user !== user || + record.rail !== rail || + record.denom !== PRICE_DENOMINATION) { + return new Error('Targeted spend legacy release summary key mismatch.'); + } + const releasedAu = this.normalizeAu( + record.released_au, + 'targeted spend legacy release amount' + ); + if (releasedAu instanceof Error) { + return new Error('Invalid targeted spend legacy release amount.'); + } + if (record.updated_at !== null && + record.updated_at !== undefined && + (typeof record.updated_at !== 'string' || record.updated_at.length === 0)) { + return new Error('Invalid targeted spend legacy release update pointer.'); + } + return { + type: 'targeted_spend_legacy_release_summary', + user, + rail, + denom: PRICE_DENOMINATION, + released_au: releasedAu, + updated_at: record.updated_at ?? null, + }; + } + + async normalizeTargetedSpendSessionRecord(record, user, rail, reservationId = null) { + if (!record) return null; + if (!record || typeof record !== 'object' || Array.isArray(record) || + record.type !== 'targeted_spend_session') { + return new Error('Targeted spend session record must be a session object.'); + } + const session = { ...record }; + delete session.type; + delete session.updated_at; + const hold = await this.normalizeTargetedSpendHoldRecord({ + type: 'targeted_spend_hold', + user, + rail, + denom: PRICE_DENOMINATION, + reserved_au: session.max_spend_au, + balance_au_at_last_reserve: null, + sessions: [session], + updated_at: record.updated_at ?? null, + }, user, rail); + if (hold instanceof Error) return hold; + const normalized = hold.sessions[0]; + if (normalized.user !== user || + normalized.rail !== rail || + (reservationId !== null && normalized.reservation_id !== reservationId)) { + return new Error('Targeted spend session key mismatch.'); + } + if (record.updated_at !== null && + record.updated_at !== undefined && + (typeof record.updated_at !== 'string' || record.updated_at.length === 0)) { + return new Error('Invalid targeted spend session update pointer.'); + } + return { + type: 'targeted_spend_session', + ...normalized, + updated_at: record.updated_at ?? null, + }; + } + + targetedSpendSessionRecord(session, updatedAt) { + return { + type: 'targeted_spend_session', + ...session, + updated_at: updatedAt, + }; + } + + normalizeTargetedSpendReservationIndexRecord( + record, + { + user, + rail, + sessionId = null, + billingId = null, + billingAttempt = null, + } + ) { + if (!record) return null; + if (!record || typeof record !== 'object' || Array.isArray(record) || + record.type !== 'targeted_spend_reservation_index' || + record.user !== user || + record.rail !== rail || + (sessionId !== null && record.session_id !== sessionId) || + (billingId !== null && record.billing_id !== billingId) || + (billingAttempt !== null && record.billing_attempt !== billingAttempt) || + !this.isHexBytes(record.session_id, 32) || + !this.isHexBytes(record.billing_id, 32) || + !Number.isSafeInteger(record.billing_attempt) || + record.billing_attempt < 0 || + !this.isHexBytes(record.reservation_id, 32) || + typeof record.session_key !== 'string' || + record.session_key !== + this.targetedSpendSessionKey(user, rail, record.reservation_id) || + (record.updated_at !== null && + record.updated_at !== undefined && + (typeof record.updated_at !== 'string' || record.updated_at.length === 0))) { + return new Error('Targeted spend reservation index is invalid.'); + } + return { + type: 'targeted_spend_reservation_index', + user, + rail, + session_id: record.session_id, + billing_id: record.billing_id, + billing_attempt: record.billing_attempt, + reservation_id: record.reservation_id, + session_key: record.session_key, + updated_at: record.updated_at, + }; + } + + targetedSpendReservationIndexRecord(session, sessionKey, updatedAt) { + return { + type: 'targeted_spend_reservation_index', + user: session.user, + rail: session.rail, + session_id: session.session_id, + billing_id: session.billing_id, + billing_attempt: session.billing_attempt, + reservation_id: session.reservation_id, + session_key: sessionKey, + updated_at: updatedAt, + }; + } + + async targetedSpendAccountingState(user, rail) { + // MAYHEM PATCH: combine legacy aggregate holds with sharded reservation + // state so existing reservations survive the targeted-hold rollout. + const hold = await this.normalizeTargetedSpendHoldRecord( + (await this.get(this.targetedSpendHoldKey(user, rail))) ?? null, + user, + rail + ); + if (hold instanceof Error) return hold; + const summary = await this.normalizeTargetedSpendSummaryRecord( + await this.get(this.targetedSpendSummaryKey(user, rail)), + user, + rail + ); + if (summary instanceof Error) return summary; + const legacyRelease = await this.normalizeTargetedSpendLegacyReleaseSummaryRecord( + await this.get(this.targetedSpendLegacyReleaseSummaryKey(user, rail)), + user, + rail + ); + if (legacyRelease instanceof Error) return legacyRelease; + const legacyReservedAu = this.safeSubAu(hold.reserved_au, legacyRelease.released_au); + if (legacyReservedAu instanceof Error) { + return new Error('Targeted spend legacy release exceeds outstanding holds.'); + } + const totalReservedAu = this.safeAddAu(legacyReservedAu, summary.reserved_au); + if (totalReservedAu instanceof Error) return totalReservedAu; + return { + hold, + summary, + legacy_release: legacyRelease, + legacy_reserved_au: legacyReservedAu, + total_reserved_au: totalReservedAu, + }; + } + + async targetedSpendReservationState(user, rail, reservationId, sessionId) { + // MAYHEM PATCH: prefer sharded targeted reservation records and retain a + // legacy overlay path for reservations opened before the sharded layout. + const sessionKey = this.targetedSpendSessionKey(user, rail, reservationId); + const sessionRecord = await this.normalizeTargetedSpendSessionRecord( + await this.get(sessionKey), + user, + rail, + reservationId + ); + if (sessionRecord instanceof Error) return sessionRecord; + if (sessionRecord !== null) { + if (sessionRecord.session_id !== sessionId) { + return new Error('Targeted spend session id does not match reservation key.'); + } + const summary = await this.normalizeTargetedSpendSummaryRecord( + await this.get(this.targetedSpendSummaryKey(user, rail)), + user, + rail + ); + if (summary instanceof Error) return summary; + return { + kind: 'sharded', + sessionKey, + sessionIndexKey: this.targetedSpendSessionIndexKey(user, rail, sessionId), + billingAttemptKey: this.targetedSpendBillingAttemptKey( + user, + rail, + sessionRecord.billing_id, + sessionRecord.billing_attempt + ), + summary, + session: sessionRecord, + }; + } + const holdKey = this.targetedSpendHoldKey(user, rail); + const hold = await this.normalizeTargetedSpendHoldRecord( + (await this.get(holdKey)) ?? null, + user, + rail + ); + if (hold instanceof Error) return hold; + const session = hold.sessions.find((entry) => + entry.session_id === sessionId && + entry.reservation_id === reservationId + ); + if (!session) return { kind: 'missing', hold }; + const legacySessionKey = this.targetedSpendLegacySessionKey(user, rail, reservationId); + const legacySessionRecord = await this.normalizeTargetedSpendSessionRecord( + await this.get(legacySessionKey), + user, + rail, + reservationId + ); + if (legacySessionRecord instanceof Error) return legacySessionRecord; + const legacyRelease = await this.normalizeTargetedSpendLegacyReleaseSummaryRecord( + await this.get(this.targetedSpendLegacyReleaseSummaryKey(user, rail)), + user, + rail + ); + if (legacyRelease instanceof Error) return legacyRelease; + if (legacySessionRecord !== null) { + if (legacySessionRecord.session_id !== sessionId) { + return new Error('Targeted spend legacy session id does not match reservation key.'); + } + return { + kind: 'legacy_overlay', + holdKey, + hold, + legacySessionKey, + legacyRelease, + session: legacySessionRecord, + }; + } + return { kind: 'legacy', holdKey, hold, legacySessionKey, legacyRelease, session }; + } + + prepareTargetedReservationClosure({ + hold, + session, + reservation, + head, + closeRecordKey, + closedBy, + closedByRole, + at, + reason, + }) { + if (reservation.status !== 'active' || + reservation.closed_at !== null || + reservation.close_record_key !== null) { + return new Error('Targeted reservation is already closed.'); + } + const retainedAu = head?.incremental_au ?? ZERO_AU; + const releasedAu = this.safeSubAu(session.max_spend_au, retainedAu); + if (releasedAu instanceof Error) { + return new Error('Targeted reservation close exceeds its hold.'); + } + const reservedAu = this.safeSubAu(hold.reserved_au, releasedAu); + if (reservedAu instanceof Error) { + return new Error('Targeted reservation close exceeds outstanding holds.'); + } + const sessions = head + ? hold.sessions.map((entry) => ( + entry.reservation_id === session.reservation_id + ? { + ...entry, + max_spend_au: retainedAu, + settlement_ready: true, + closed_at: closeRecordKey, + } + : entry + )) + : hold.sessions.filter((entry) => entry.reservation_id !== session.reservation_id); + const closeRecord = { + type: 'targeted_reservation_close', + reservation_id: session.reservation_id, + billing_id: session.billing_id, + billing_attempt: session.billing_attempt, + billing_epoch: session.billing_epoch, + reservation_expires_after_epoch: session.reservation_expires_after_epoch, + reservation_receipt_grace_epochs: session.reservation_receipt_grace_epochs, + session_id: session.session_id, + user: session.user, + rail: session.rail, + provider: session.provider, + payout_revision: session.payout_revision, + latest_receipt_seq: head?.receipt_seq ?? null, + latest_receipt_hash: head?.receipt_hash ?? null, + retained_au: retainedAu, + released_au: releasedAu, + reason, + closed_by: closedBy, + closed_by_role: closedByRole, + at, + recorded_at: closeRecordKey, + }; + return { + hold: { + ...hold, + reserved_au: reservedAu, + sessions, + updated_at: closeRecordKey, + }, + reservation: { + ...reservation, + status: 'closed', + closed_at: closeRecordKey, + close_record_key: closeRecordKey, + }, + close_record: closeRecord, + }; + } + + prepareShardedTargetedReservationClosure({ + summary, + session, + reservation, + head, + closeRecordKey, + closedBy, + closedByRole, + at, + reason, + }) { + const closure = this.prepareTargetedReservationClosure({ + hold: { + type: 'targeted_spend_hold', + user: session.user, + rail: session.rail, + denom: PRICE_DENOMINATION, + reserved_au: session.max_spend_au, + balance_au_at_last_reserve: summary.balance_au_at_last_reserve, + sessions: [session], + updated_at: summary.updated_at, + }, + session, + reservation, + head, + closeRecordKey, + closedBy, + closedByRole, + at, + reason, + }); + if (closure instanceof Error) return closure; + const nextReservedAu = this.safeSubAu( + summary.reserved_au, + closure.close_record.released_au + ); + if (nextReservedAu instanceof Error) { + return new Error('Targeted reservation close exceeds outstanding sharded holds.'); + } + return { + summary: { + ...summary, + reserved_au: nextReservedAu, + updated_at: closeRecordKey, + }, + session: head ? this.targetedSpendSessionRecord(closure.hold.sessions[0], closeRecordKey) : null, + reservation: closure.reservation, + close_record: closure.close_record, + }; + } + + prepareLegacyTargetedReservationClosure({ + legacyRelease, + hold, + session, + reservation, + head, + closeRecordKey, + closedBy, + closedByRole, + at, + reason, + }) { + // MAYHEM PATCH: close or retain legacy targeted holds deterministically + // while payout epochs consume the new sharded reservation records. + const closure = this.prepareTargetedReservationClosure({ + hold, + session, + reservation, + head, + closeRecordKey, + closedBy, + closedByRole, + at, + reason, + }); + if (closure instanceof Error) return closure; + const nextReleasedAu = this.safeAddAu( + legacyRelease.released_au, + closure.close_record.released_au + ); + if (nextReleasedAu instanceof Error || + this.compareAu(nextReleasedAu, hold.reserved_au) > 0) { + return new Error('Targeted reservation close exceeds legacy outstanding holds.'); + } + const overlaySession = head + ? closure.hold.sessions.find( + (entry) => entry.reservation_id === session.reservation_id + ) + : null; + if (head && !overlaySession) { + return new Error('Targeted reservation close lost its retained legacy session.'); + } + return { + legacy_release: { + ...legacyRelease, + released_au: nextReleasedAu, + updated_at: closeRecordKey, + }, + session: overlaySession + ? this.targetedSpendSessionRecord(overlaySession, closeRecordKey) + : null, + reservation: closure.reservation, + close_record: closure.close_record, + }; + } + + async applyRecordUsageReceiptFeature(key, value) { + const normalized = await this.normalizeRecordUsageReceiptValue(value); + if (normalized instanceof Error) return normalized; + const expectedKey = await this.recordUsageReceiptFeatureKey(normalized); + if (expectedKey instanceof Error) return expectedKey; + if (key !== expectedKey) return new Error('Invalid record usage receipt feature key.'); + const body = normalized.receipt.body; + if (!verifyEd25519Hex( + normalized.provider_sig, + recordUsageReceiptMessage(normalized), + body.provider + )) { + return new Error('Invalid record usage receipt provider signature.'); + } + if (!this.verifyReceiptEnvelope(normalized.receipt)) { + return new Error('Invalid record usage receipt user or enclave signature.'); + } + + const receiptHash = await this.opaqueHash( + 'mayhem-canonical-receipt-v1', + normalized.receipt + ); + const headKey = this.receiptHeadKey(body.billing_id, body.billing_attempt); + const existingHead = await this.get(headKey); + if (existingHead) { + if (existingHead.type !== 'canonical_receipt_head') { + return new Error('Canonical receipt head is invalid.'); + } + if (existingHead.receipt_hash === receiptHash && + stableJson(existingHead.receipt) === stableJson(normalized.receipt)) { + return { + ok: true, + op: 'recordUsageReceipt', + epoch: existingHead.settlement_epoch ?? null, + billing_epoch: body.billing_epoch, + billing_id: body.billing_id, + billing_attempt: body.billing_attempt, + receipt_seq: body.seq, + receipt_hash: receiptHash, + idempotent: true, + }; + } + if ((await this.get(this.receiptConsumedKey(body.billing_id, body.billing_attempt))) !== null) { + return new Error('A consumed canonical receipt head cannot advance.'); + } + if (body.seq <= existingHead.receipt_seq) { + return new Error('Receipt sequence conflicts with the canonical high-water head.'); + } + if (existingHead.receipt.body.final === true || + existingHead.settlement_ready === true) { + return new Error('A finalized canonical receipt head cannot advance.'); + } + if (stableJson(this.receiptAttemptTerms(existingHead.receipt.body)) !== + stableJson(this.receiptAttemptTerms(body))) { + return new Error('Higher receipt sequence changed immutable attempt terms.'); + } + if (!this.receiptUsageIsMonotonic(existingHead.receipt.body.usage, body.usage) || + this.compareAu(body.au_owed_cum, existingHead.receipt.body.au_owed_cum) < 0 || + body.compute_ms < existingHead.receipt.body.compute_ms) { + return new Error('Higher receipt sequence is not monotonic.'); + } + } + + const reservationState = await this.targetedSpendReservationState( + body.user, + body.rail, + body.reservation_id, + body.session_id + ); + if (reservationState instanceof Error) return reservationState; + if (reservationState.kind === 'missing') { + return new Error('Receipt does not match an exact targeted spend hold session.'); + } + const session = reservationState.session; + const sessionTermsMatch = + session.billing_id === body.billing_id && + session.billing_attempt === body.billing_attempt && + session.billing_epoch === body.billing_epoch && + session.reservation_id === body.reservation_id && + session.reservation_expires_after_epoch === body.reservation_expires_after_epoch && + session.reservation_receipt_grace_epochs === body.reservation_receipt_grace_epochs && + session.user === body.user && + session.rail === body.rail && + session.provider === body.provider && + session.payout_revision === body.payout_revision && + session.enclave_id === body.enclave_id && + session.enclave_pubkey === normalized.receipt.enclave_pubkey && + session.model_id === body.model_id && + session.price_ver === body.price_ver && + stableJson(session.billing_prior_usage) === stableJson(body.billing_prior_usage) && + this.compareAu( + session.billing_prior_au_owed_cum, + body.billing_prior_au_owed_cum + ) === 0 && + stableJson(session.locked_rate_map) === stableJson(body.locked_rate_map) && + this.compareAu(session.locked_per_req_au, body.locked_per_req_au) === 0 && + this.compareAu(session.locked_min_session_au, body.locked_min_session_au) === 0 && + session.served_ctx === body.served_ctx && + session.ctx_bracket === body.ctx_bracket && + session.ctx_bracket_table_ver === body.ctx_bracket_table_ver && + session.rules_ver === body.rules_ver && + stableJson(session.workflow ?? null) === stableJson(body.workflow ?? null); + if (!sessionTermsMatch) { + return new Error('Receipt terms do not match the targeted spend hold session.'); + } + + const incrementalAu = this.safeSubAu( + body.au_owed_cum, + body.billing_prior_au_owed_cum + ); + if (incrementalAu instanceof Error || + this.isZeroAu(incrementalAu) || + this.compareAu(incrementalAu, session.max_spend_au) > 0) { + return new Error('Receipt incremental amount is not positive or exceeds its session reservation.'); + } + + const billingAnchor = await this.get(this.receiptBillingKey(body.billing_id)); + if (!billingAnchor || + billingAnchor.type !== 'receipt_billing_anchor' || + billingAnchor.user !== body.user || + billingAnchor.rail !== body.rail || + billingAnchor.epoch !== body.billing_epoch || + billingAnchor.latest_attempt < body.billing_attempt) { + return new Error('Receipt billing anchor is missing or inconsistent.'); + } + if (billingAnchor.latest_attempt > body.billing_attempt) { + return new Error('An older billing attempt cannot advance after a higher attempt exists.'); + } + const reservationKey = this.receiptReservationKey(body.reservation_id); + const reservation = await this.get(reservationKey); + if (!reservation || + reservation.type !== 'receipt_reservation_identity' || + reservation.reservation_id !== body.reservation_id || + reservation.billing_id !== body.billing_id || + reservation.billing_attempt !== body.billing_attempt || + reservation.billing_epoch !== body.billing_epoch || + reservation.reservation_expires_after_epoch !== body.reservation_expires_after_epoch || + reservation.reservation_receipt_grace_epochs !== body.reservation_receipt_grace_epochs || + reservation.session_id !== body.session_id || + reservation.user !== body.user || + reservation.rail !== body.rail || + reservation.provider !== body.provider || + reservation.payout_revision !== body.payout_revision) { + return new Error('Receipt reservation identity is missing or inconsistent.'); + } + if (reservation.status !== 'active' || + reservation.closed_at !== null || + reservation.close_record_key !== null) { + return new Error('A closed targeted reservation cannot advance.'); + } + const applyState = await this.epochApplyStateRecord(); + const settlementEpoch = await this.receiptSettlementEpoch(applyState); + if (settlementEpoch instanceof Error) return settlementEpoch; + if (body.billing_epoch > settlementEpoch) { + return new Error('Receipt billing epoch is in the future.'); + } + const isFinal = body.final === true; + let epochIndex = null; + if (isFinal) { + if (existingHead?.index_position !== null && + existingHead?.index_position !== undefined) { + return new Error('Non-final canonical receipt head was unexpectedly indexed.'); + } + epochIndex = await this.nextReceiptEpochIndex( + settlementEpoch, + body.billing_id, + body.billing_attempt + ); + if (epochIndex instanceof Error) return epochIndex; + } + const head = { + type: 'canonical_receipt_head', + billing_id: body.billing_id, + billing_attempt: body.billing_attempt, + epoch: isFinal ? settlementEpoch : null, + billing_epoch: body.billing_epoch, + settlement_epoch: isFinal ? settlementEpoch : null, + index_position: isFinal ? epochIndex.position : null, + settlement_ready: isFinal, + user: body.user, + rail: body.rail, + provider: body.provider, + payout_revision: body.payout_revision, + session_id: body.session_id, + reservation_id: body.reservation_id, + receipt_seq: body.seq, + receipt_hash: receiptHash, + incremental_au: incrementalAu, + receipt: cloneValue(normalized.receipt), + feature_key: key, + updated_at: key, + }; + let closure = null; + let nextMetadata = null; + if (isFinal) { + const closeRecordKey = this.receiptReservationCloseKey(body.reservation_id); + if ((await this.get(closeRecordKey)) !== null) { + return new Error('Targeted reservation close record already exists.'); + } + closure = reservationState.kind === 'legacy' + ? this.prepareLegacyTargetedReservationClosure({ + legacyRelease: reservationState.legacyRelease, + hold: reservationState.hold, + session, + reservation, + head, + closeRecordKey, + closedBy: body.provider, + closedByRole: 'provider', + at: body.ts, + reason: 'final_receipt', + }) + : this.prepareShardedTargetedReservationClosure({ + summary: reservationState.summary, + session, + reservation, + head, + closeRecordKey, + closedBy: body.provider, + closedByRole: 'provider', + at: body.ts, + reason: 'final_receipt', + }); + if (closure instanceof Error) return closure; + if (epochIndex.index.revision >= Number.MAX_SAFE_INTEGER) { + return new Error('Canonical receipt epoch revision overflow.'); + } + nextMetadata = { + ...epochIndex.index, + revision: epochIndex.index.revision + 1, + updated_at: key, + }; + } + await this.put(headKey, head); + if (isFinal) { + await this.put(epochIndex.page_key, epochIndex.page); + await this.put(epochIndex.index_key, nextMetadata); + if (reservationState.kind === 'legacy') { + await this.put( + this.targetedSpendLegacyReleaseSummaryKey(body.user, body.rail), + closure.legacy_release + ); + await this.put(reservationState.legacySessionKey, closure.session); + } else { + await this.put( + this.targetedSpendSummaryKey(body.user, body.rail), + closure.summary + ); + await this.put(reservationState.sessionKey, closure.session); + } + await this.put(reservationKey, closure.reservation); + await this.put(this.receiptReservationCloseKey(body.reservation_id), closure.close_record); + } + return { + ok: true, + op: 'recordUsageReceipt', + epoch: isFinal ? settlementEpoch : null, + billing_epoch: body.billing_epoch, + billing_id: body.billing_id, + billing_attempt: body.billing_attempt, + receipt_seq: body.seq, + receipt_hash: receiptHash, + idempotent: false, + }; + } + + async applyCloseUsageReservationFeature(key, value, { expiry = false } = {}) { + const normalized = this.normalizeCloseUsageReservationValue(value, { expiry }); + if (normalized instanceof Error) return normalized; + const expectedKey = await this.closeUsageReservationFeatureKey(normalized, { expiry }); + if (expectedKey instanceof Error) return expectedKey; + if (key !== expectedKey) return new Error('Invalid close usage reservation feature key.'); + const verify = this.protocol?.peer?.wallet?.verify; + if (typeof verify !== 'function' || + verify.call( + this.protocol.peer.wallet, + normalized.actor_sig, + expiry + ? expireUsageReservationMessage(normalized) + : closeUsageReservationMessage(normalized), + normalized.actor + ) !== true) { + return new Error('Invalid close usage reservation signature.'); + } + + const closeRecordKey = this.receiptReservationCloseKey(normalized.reservation_id); + const existingClose = await this.get(closeRecordKey); + if (existingClose !== null) { + if (existingClose.feature_key === key) { + return { + ok: true, + op: expiry ? 'expireUsageReservation' : 'closeUsageReservation', + billing_epoch: normalized.billing_epoch, + reservation_id: normalized.reservation_id, + settlement_epoch: existingClose.settlement_epoch, + idempotent: true, + }; + } + return new Error('Targeted reservation close conflicts with its canonical close.'); + } + + const reservationKey = this.receiptReservationKey(normalized.reservation_id); + const reservation = await this.get(reservationKey); + if (!reservation || + reservation.type !== 'receipt_reservation_identity' || + reservation.status !== 'active' || + reservation.closed_at !== null || + reservation.close_record_key !== null || + reservation.reservation_id !== normalized.reservation_id || + reservation.billing_id !== normalized.billing_id || + reservation.billing_attempt !== normalized.billing_attempt || + reservation.billing_epoch !== normalized.billing_epoch || + reservation.reservation_expires_after_epoch !== + normalized.reservation_expires_after_epoch || + reservation.reservation_receipt_grace_epochs !== + normalized.reservation_receipt_grace_epochs || + reservation.session_id !== normalized.session_id || + reservation.user !== normalized.user || + reservation.rail !== normalized.rail || + reservation.provider !== normalized.provider || + reservation.payout_revision !== normalized.payout_revision) { + return new Error('Close usage reservation does not match an active reservation.'); + } + const reservationState = await this.targetedSpendReservationState( + normalized.user, + normalized.rail, + normalized.reservation_id, + normalized.session_id + ); + if (reservationState instanceof Error) return reservationState; + const session = reservationState.kind === 'missing' + ? null + : reservationState.session; + if (!session || + session.billing_id !== normalized.billing_id || + session.billing_attempt !== normalized.billing_attempt || + session.billing_epoch !== normalized.billing_epoch || + session.reservation_expires_after_epoch !== + normalized.reservation_expires_after_epoch || + session.reservation_receipt_grace_epochs !== + normalized.reservation_receipt_grace_epochs || + session.provider !== normalized.provider || + session.payout_revision !== normalized.payout_revision) { + return new Error('Close usage reservation does not match its outstanding hold.'); + } + + const headKey = this.receiptHeadKey( + normalized.billing_id, + normalized.billing_attempt + ); + const existingHead = await this.get(headKey); + if (existingHead === null) { + if (normalized.latest_receipt_seq !== null || + normalized.latest_receipt_hash !== null) { + return new Error('Close usage reservation receipt head does not exist.'); + } + } else { + if (existingHead.type !== 'canonical_receipt_head' || + existingHead.billing_epoch !== normalized.billing_epoch || + existingHead.reservation_id !== normalized.reservation_id || + existingHead.session_id !== normalized.session_id || + existingHead.user !== normalized.user || + existingHead.rail !== normalized.rail || + existingHead.provider !== normalized.provider || + existingHead.payout_revision !== normalized.payout_revision || + existingHead.receipt_seq !== normalized.latest_receipt_seq || + existingHead.receipt_hash !== normalized.latest_receipt_hash) { + return new Error('Close usage reservation does not match the canonical receipt head.'); + } + if (existingHead.settlement_ready === true || + existingHead.receipt?.body?.final === true || + (await this.get( + this.receiptConsumedKey(normalized.billing_id, normalized.billing_attempt) + )) !== null) { + return new Error('Finalized or consumed receipt evidence cannot be closed again.'); + } + } + + const applyState = await this.epochApplyStateRecord(); + const settlementEpoch = await this.receiptSettlementEpoch(applyState); + if (settlementEpoch instanceof Error) return settlementEpoch; + if (normalized.billing_epoch > settlementEpoch) { + return new Error('Close usage reservation billing epoch is in the future.'); + } + if (expiry) { + if ( + normalized.reservation_expires_after_epoch > + Number.MAX_SAFE_INTEGER - normalized.reservation_receipt_grace_epochs || + applyState.updated_epoch < + normalized.reservation_expires_after_epoch + + normalized.reservation_receipt_grace_epochs + ) { + return new Error( + 'Buyer reservation close is not yet past canonical expiry and receipt grace.' + ); + } + } + let head = null; + let epochIndex = null; + let nextMetadata = null; + if (existingHead !== null) { + epochIndex = await this.nextReceiptEpochIndex( + settlementEpoch, + normalized.billing_id, + normalized.billing_attempt + ); + if (epochIndex instanceof Error) return epochIndex; + if (epochIndex.index.revision >= Number.MAX_SAFE_INTEGER) { + return new Error('Canonical receipt epoch revision overflow.'); + } + head = { + ...existingHead, + epoch: settlementEpoch, + settlement_epoch: settlementEpoch, + index_position: epochIndex.position, + settlement_ready: true, + updated_at: key, + }; + nextMetadata = { + ...epochIndex.index, + revision: epochIndex.index.revision + 1, + updated_at: key, + }; + } + const closure = reservationState.kind === 'legacy' + ? this.prepareLegacyTargetedReservationClosure({ + legacyRelease: reservationState.legacyRelease, + hold: reservationState.hold, + session, + reservation, + head, + closeRecordKey, + closedBy: normalized.actor, + closedByRole: normalized.actor_role, + at: normalized.at, + reason: normalized.reason, + }) + : this.prepareShardedTargetedReservationClosure({ + summary: reservationState.summary, + session, + reservation, + head, + closeRecordKey, + closedBy: normalized.actor, + closedByRole: normalized.actor_role, + at: normalized.at, + reason: normalized.reason, + }); + if (closure instanceof Error) return closure; + const closeRecord = { + ...closure.close_record, + settlement_epoch: head?.settlement_epoch ?? null, + actor_sig: normalized.actor_sig, + feature_key: key, + signed_evidence: closeUsageReservationEvidence(normalized), + }; + + if (head !== null) { + await this.put(headKey, head); + await this.put(epochIndex.page_key, epochIndex.page); + await this.put(epochIndex.index_key, nextMetadata); + } + if (reservationState.kind === 'legacy') { + await this.put( + this.targetedSpendLegacyReleaseSummaryKey(normalized.user, normalized.rail), + closure.legacy_release + ); + if (closure.session) { + await this.put(reservationState.legacySessionKey, closure.session); + } else { + await this.del(reservationState.legacySessionKey); + } + } else { + await this.put( + this.targetedSpendSummaryKey(normalized.user, normalized.rail), + closure.summary + ); + if (closure.session) { + await this.put(reservationState.sessionKey, closure.session); + } else { + await this.del(reservationState.sessionKey); + await this.del(reservationState.sessionIndexKey); + await this.del(reservationState.billingAttemptKey); + } + } + await this.put(reservationKey, closure.reservation); + await this.put(closeRecordKey, closeRecord); + return { + ok: true, + op: expiry ? 'expireUsageReservation' : 'closeUsageReservation', + billing_epoch: normalized.billing_epoch, + reservation_id: normalized.reservation_id, + settlement_epoch: head?.settlement_epoch ?? null, + retained_au: closeRecord.retained_au, + released_au: closeRecord.released_au, + idempotent: false, + }; + } + + async applyEpochApplyFeature(key, value) { + const expectedKey = await this.epochApplyFeatureKey(value); + if (expectedKey instanceof Error) return expectedKey; + if (key !== expectedKey) return; + + const previousTx = this.tx; + this.tx = key; + try { + return await this.epochApply(); + } finally { + this.tx = previousTx; + } + } + + async applyTargetedEpochFeature(key, value, options = {}) { + const normalized = options.normalized ?? await this.normalizeTargetedEpochFeatureValue(value); + if (normalized instanceof Error) return normalized; + const expectedKey = options.expectedKey ?? await this.targetedEpochFeatureKey(value); + if (expectedKey instanceof Error) return expectedKey; + if (key !== expectedKey) return new Error('Invalid targeted epoch feature key.'); + + const applyState = await this.epochApplyStateRecord(); + const page = this.epochApplyPage(value); + if (page instanceof Error) return page; + const lastPage = this.epochApplyLastPage(value); + if (lastPage instanceof Error) return lastPage; + const replayPosition = this.isEpochApplyPagePositionReplay( + applyState, + value.epoch, + page, + lastPage + ); + const params = await this.activeParamsAt(value.at, [ + 'fee_bps', + 'probation_successful_sessions', + 'new_provider_holdback_epochs', + 'holdback_epochs', + 'challenge_epochs', + 'canary_probe_holdback_bps', + 'canary_probe_release_min_passes', + ]); + const reservationBindings = await this.validateTargetedEpochReservationBindings( + value, + normalized.targeted_earnings, + key + ); + if (reservationBindings instanceof Error) return reservationBindings; + const allocationUpdates = normalized.allocations.map((allocation) => ({ + key: `payout/allocation/${value.epoch}/${allocation.session_id}`, + value: { + type: 'provider_payout_session_allocation', + epoch: value.epoch, + page, + ...allocation, + feature_key: key, + }, + })); + const consumptionUpdates = normalized.allocations.map((allocation) => ({ + key: this.receiptConsumedKey(allocation.billing_id, allocation.billing_attempt), + value: this.receiptConsumptionRecord(value.epoch, allocation, key), + })); + let hasPreexistingFeatureArtifact = false; + for (const update of allocationUpdates) { + const existing = await this.get(update.key); + if (existing !== null && stableJson(existing) !== stableJson(update.value)) { + return new Error('Targeted epoch session allocation already exists.'); + } + hasPreexistingFeatureArtifact ||= existing !== null; + } + for (const update of consumptionUpdates) { + const existing = await this.get(update.key); + if (existing !== null && stableJson(existing) !== stableJson(update.value)) { + return new Error('Canonical receipt billing attempt is already consumed.'); + } + hasPreexistingFeatureArtifact ||= existing !== null; + } + const epochCommit = options.commitTransition?.record ?? + await this.get(`epoch/commit/${value.epoch}`); + const boundedReceiptSettlement = + epochCommit?.apply_mode === 'targeted_receipt_pages_v1'; + const providerSettlementDeltas = new Map(); + const liabilityUpdates = []; + for (const earning of normalized.targeted_earnings) { + const binding = await this.providerPayoutBindingForEpoch( + earning.provider, + earning.rail, + earning.payout_revision, + value.epoch + ); + if (binding instanceof Error) return binding; + const provider = await this.get(`prov/${earning.provider}`); + if (!provider || provider.status !== 'active') { + return new Error('Targeted epoch provider is not active.'); + } + let priorGrossAu = ZERO_AU; + if (boundedReceiptSettlement) { + const marker = await this.get( + `epoch/earning-provider/${value.epoch}/${earning.rail}/${earning.provider}` + ); + if (marker !== null) { + if (marker.type !== 'epoch_provider_earning' || + marker.epoch !== value.epoch || + marker.rail !== earning.rail || + marker.provider !== earning.provider || + !Number.isSafeInteger(marker.last_page) || + (replayPosition ? marker.last_page !== page : marker.last_page >= page)) { + return new Error('Bounded receipt provider earning marker is inconsistent.'); + } + priorGrossAu = replayPosition + ? this.safeSubAu(marker.gross_au, earning.gross_au) + : this.normalizeAu( + marker.gross_au, + 'bounded receipt prior provider gross earning', + { allowZero: true } + ); + if (priorGrossAu instanceof Error) return priorGrossAu; + } else if (replayPosition) { + return new Error('Bounded receipt provider earning marker is missing on replay.'); + } + } + const settlementDelta = this.providerSettlementPageDelta({ + grossAu: earning.gross_au, + priorGrossAu, + rail: earning.rail, + feeBps: params.fee_bps, + }); + if (settlementDelta instanceof Error) return settlementDelta; + const { + fee_au: feeAu, + burn_au: burnAu, + provider_au: providerAu, + } = settlementDelta; + providerSettlementDeltas.set( + stableJson([earning.rail, earning.provider]), + { + gross_au: earning.gross_au, + ...settlementDelta, + } + ); + + const liabilityKey = this.providerPayoutLiabilityKey( + earning.provider, + earning.rail, + earning.payout_revision + ); + const existing = (await this.get(liabilityKey)) ?? { + type: 'provider_payout_liability', + provider: earning.provider, + rail: earning.rail, + revision: earning.payout_revision, + target: binding.target, + currency: binding.currency, + chain_id: binding.chain_id, + total_au: ZERO_AU, + held_au: ZERO_AU, + paid_cum_au: ZERO_AU, + holdbacks: [], + updated_epoch: 0, + updated_at: null, + }; + if ( + existing.provider !== earning.provider || + existing.rail !== earning.rail || + existing.revision !== earning.payout_revision || + existing.target !== binding.target || + (existing.currency ?? null) !== binding.currency || + (existing.chain_id ?? null) !== binding.chain_id + ) { + return new Error('Provider payout liability binding mismatch.'); + } + const existingLiabilityError = this.guardianValidatePayoutLiabilityRecord( + existing, + earning.provider, + earning.rail, + earning.payout_revision + ); + if (existingLiabilityError) return existingLiabilityError; + const aggregate = await this.earningRecord(earning.provider, earning.rail); + if (aggregate instanceof Error) return aggregate; + const probeGate = await this.probeGateForEarning(earning.provider, aggregate, params); + if (probeGate instanceof Error) return probeGate; + const lockedEarningEpochs = this.providerLockedEarningEpochs(provider, params); + if (lockedEarningEpochs instanceof Error) return lockedEarningEpochs; + const disputeGate = await this.providerHasOpenDispute(earning.provider); + if (disputeGate instanceof Error) return disputeGate; + const refreshed = this.refreshEarningHoldback( + existing, + value.epoch, + lockedEarningEpochs, + probeGate, + disputeGate + ); + if (refreshed instanceof Error) return refreshed; + const totalAu = this.safeAddAu(refreshed.total_au, providerAu); + if (totalAu instanceof Error) return totalAu; + const heldAu = this.safeAddAu(refreshed.held_au, providerAu); + if (heldAu instanceof Error) return heldAu; + const holdbacks = this.appendHoldbackBucket( + refreshed.holdbacks, + value.epoch, + providerAu, + lockedEarningEpochs + ); + if (holdbacks instanceof Error) return holdbacks; + const nextLiability = { + ...refreshed, + total_au: totalAu, + held_au: heldAu, + holdbacks, + updated_epoch: value.epoch, + updated_at: key, + }; + const nextLiabilityError = this.guardianValidatePayoutLiabilityRecord( + nextLiability, + earning.provider, + earning.rail, + earning.payout_revision + ); + if (nextLiabilityError) return nextLiabilityError; + liabilityUpdates.push({ + key: liabilityKey, + value: nextLiability, + snapshot_key: this.providerPayoutEpochSnapshotKey( + value.epoch, + page, + earning.provider, + earning.rail + ), + snapshot: { + type: 'provider_payout_epoch_binding', + epoch: value.epoch, + page, + provider: earning.provider, + rail: earning.rail, + revision: earning.payout_revision, + target: binding.target, + currency: binding.currency, + chain_id: binding.chain_id, + earned_au: providerAu, + feature_key: key, + }, + }); + } + const liabilityIndexUpdates = await this.nextProviderPayoutLiabilityIndexes( + liabilityUpdates, + value.epoch, + key + ); + if (liabilityIndexUpdates instanceof Error) return liabilityIndexUpdates; + for (const update of liabilityUpdates) { + const existing = await this.get(update.snapshot_key); + if (existing !== null && stableJson(existing) !== stableJson(update.snapshot)) { + return new Error('Targeted epoch payout snapshot already exists.'); + } + hasPreexistingFeatureArtifact ||= existing !== null; + } + for (const update of liabilityIndexUpdates) { + const existing = await this.get(update.key); + hasPreexistingFeatureArtifact ||= + existing !== null && stableJson(existing) === stableJson(update.value); + } + if (hasPreexistingFeatureArtifact && !replayPosition) { + return new Error('Targeted epoch feature artifacts require an idempotent page replay.'); + } + + const previousTx = this.tx; + this.tx = key; + let result; + try { + result = await this.targetedEpochApply( + normalized.ledger_value, + normalized.revision_bindings, + normalized.allocations, + { + commitTransition: options.commitTransition ?? null, + providerSettlementDeltas, + canonicalMarketUsage: reservationBindings.market_usage, + } + ); + } finally { + this.tx = previousTx; + } + if (!result || result instanceof Error || result.ok !== true) return result; + if (result.idempotent === true) { + for (const update of allocationUpdates) { + const allocation = await this.get(update.key); + if (!allocation || stableJson(allocation) !== stableJson(update.value)) { + return new Error('Targeted epoch session allocation is missing.'); + } + } + for (const update of liabilityUpdates) { + const snapshot = await this.get(update.snapshot_key); + if (!snapshot || stableJson(snapshot) !== stableJson(update.snapshot)) { + return new Error('Targeted epoch liability snapshot is missing.'); + } + } + for (const update of liabilityIndexUpdates) { + const index = await this.get(update.key); + if (!index || stableJson(index) !== stableJson(update.value)) { + return new Error('Targeted epoch liability index is missing.'); + } + } + for (const update of consumptionUpdates) { + const consumption = await this.get(update.key); + if (!consumption || stableJson(consumption) !== stableJson(update.value)) { + return new Error('Canonical receipt consumption is missing.'); + } + } + return { + ...result, + op: options.commitTransition ? 'commitApplyTargetedEpochPage0' : 'applyTargetedEpoch', + ...(options.commitTransition ? { + commit_hash: options.commitTransition.record.commit_hash, + replaced_commit: false, + } : {}), + }; + } + + for (const update of reservationBindings.hold_updates) { + await this.put(update.key, update.value); + } + for (const update of reservationBindings.summary_updates) { + await this.put(update.key, update.value); + } + for (const update of reservationBindings.legacy_release_updates) { + await this.put(update.key, update.value); + } + for (const deleteKey of reservationBindings.session_deletes) { + await this.del(deleteKey); + } + for (const update of allocationUpdates) { + await this.put(update.key, update.value); + } + for (const update of consumptionUpdates) { + await this.put(update.key, update.value); + } + for (const update of liabilityUpdates) { + await this.put(update.key, update.value); + await this.put(update.snapshot_key, update.snapshot); + const pointerKey = this.providerPayoutBindingPointerKey( + update.value.provider, + update.value.rail + ); + const pointer = await this.get(pointerKey); + if (pointer?.pending_revision === update.value.revision && + pointer.pending_activation_epoch <= value.epoch) { + await this.put(pointerKey, { + ...pointer, + current_revision: update.value.revision, + pending_revision: null, + pending_activation_epoch: null, + updated_at: key, + }); + } + } + for (const update of liabilityIndexUpdates) { + await this.put(update.key, update.value); + } + return { + ...result, + op: options.commitTransition ? 'commitApplyTargetedEpochPage0' : 'applyTargetedEpoch', + ...(options.commitTransition ? { + commit_hash: options.commitTransition.record.commit_hash, + replaced_commit: options.commitTransition.archive !== null, + } : {}), + }; + } + + async applyCommitTargetedEpochPageZeroFeature(key, value) { + const expectedKey = await this.commitTargetedEpochPageZeroFeatureKey(value); + if (expectedKey instanceof Error) return expectedKey; + if (key !== expectedKey) return new Error('Invalid commit-plus-page-zero feature key.'); + const prepared = await this.normalizeCommitTargetedEpochPageZeroFeatureValue(value); + if (prepared instanceof Error) return prepared; + const applyState = await this.epochApplyStateRecord(); + const commitTransition = await this.prepareTargetedEpochCommitTransition( + prepared, + applyState, + key + ); + if (commitTransition instanceof Error) return commitTransition; + return await this.applyTargetedEpochFeature( + key, + prepared.targeted_value, + { + normalized: prepared.normalized, + expectedKey: key, + commitTransition, + } + ); + } + + async applyDepositTnkFeature(key, value) { + const shapeError = this.validateExactObjectKeys( + value, + ['op', 'sender', 'intent', 'sig'], + 'deposit TNK feature' + ); + if (shapeError) return shapeError; + if (value.op !== 'deposit_tnk') return new Error('Invalid deposit TNK feature op.'); + if (!this.isHexBytes(value.sender, 32)) return new Error('Invalid deposit TNK sender.'); + if (!this.isHexBytes(value.sig, 64)) return new Error('Invalid deposit TNK signature.'); + const intentError = this.validateDepositTnkIntent(value.intent); + if (intentError) return intentError; + if (!this.verifyDepositTnkSignature(value.sender, value.intent, value.sig)) { + return new Error('Invalid deposit TNK signature.'); + } + const expectedKey = await this.depositFeatureKey(value); + if (expectedKey instanceof Error) return expectedKey; + if (key !== expectedKey) return; + + const previousAddress = this.address; + const previousTx = this.tx; + const previousValue = this.value; + this.address = value.sender; + this.tx = key; + this.value = value.intent; + try { + return await this.depositTnk(); + } finally { + this.address = previousAddress; + this.tx = previousTx; + this.value = previousValue; + } + } + + async applyDepositCreditFeature(key, value) { + const expectedKey = await this.depositFeatureKey(value); + if (expectedKey instanceof Error) return expectedKey; + if (key !== expectedKey) return; + + const previousTx = this.tx; + this.tx = key; + try { + if (value.op === 'tnk_deposit') return await this.tnkDeposit(); + if (value.op === 'tap_deposit') return await this.tapDeposit(); + if (value.op === 'tap_deposit_reversal') return await this.tapDepositReversal(); + if (value.op === 'fiat_deposit') return await this.fiatDeposit(); + if (value.op === 'fiat_chargeback') return await this.fiatChargeback(); + return; + } finally { + this.tx = previousTx; + } + } + + async applyRateOracleFeature(key, value) { + this._mayhemApplyStage = 'rate:key'; + const expectedKey = await this.rateFeatureKey(value); + if (expectedKey instanceof Error) return expectedKey; + if (key !== expectedKey) return; + + const previousTx = this.tx; + this.tx = key; + try { + this._mayhemApplyStage = 'rate:dispatch'; + if (value.op === 'rate_oracle') return await this.rateOracle(); + if (value.op === 'tap_rate_oracle') return await this.tapRateOracle(); + return; + } finally { + this.tx = previousTx; + } + } + + async normalizeTargetedPayoutPreparationValue(value) { + const shapeError = this.validateExactObjectKeys( + value, + [ + 'op', + 'contract_version', + 'economic_op_id', + 'rail', + 'epoch', + 'epoch_apply_hash', + 'prepared_at', + 'kind', + 'output_index', + 'payload_hash', + 'payload', + 'liability', + 'external_effect_ids', + 'admin', + 'admin_sig', + ], + 'targeted payout preparation' + ); + if (shapeError) return shapeError; + const rail = this.normalizeLedgerRail(value.rail, 'targeted payout preparation rail'); + if (rail instanceof Error) return rail; + if (value.op !== 'prepare_targeted_payout' || + value.contract_version !== CONTRACT_VERSION || + !this.isHexBytes(value.economic_op_id, 32) || + value.economic_op_id !== value.economic_op_id.toLowerCase() || + !Number.isSafeInteger(value.epoch) || + value.epoch < 1 || + !this.isHexBytes(value.epoch_apply_hash, 32) || + value.epoch_apply_hash !== value.epoch_apply_hash.toLowerCase() || + !Number.isSafeInteger(value.prepared_at) || + value.prepared_at < 0 || + !['liability', 'fee', 'tap_root'].includes(value.kind) || + !Number.isSafeInteger(value.output_index) || + value.output_index < 0 || + !this.isHexBytes(value.payload_hash, 32) || + value.payload_hash !== value.payload_hash.toLowerCase() || + !value.payload || + typeof value.payload !== 'object' || + Array.isArray(value.payload) || + !Array.isArray(value.external_effect_ids) || + value.external_effect_ids.length > 2 || + !this.isHexBytes(value.admin, 32) || + value.admin !== value.admin.toLowerCase() || + !this.isHexBytes(value.admin_sig, 64)) { + return new Error('Invalid targeted payout preparation.'); + } + if (b4a.byteLength(stableJson(value.payload)) > 16_384) { + return new Error('Targeted payout preparation payload exceeds 16384 bytes.'); + } + const externalEffectIds = value.external_effect_ids.map((effectId) => + String(effectId).toLowerCase() + ); + if (externalEffectIds.some((effectId) => !this.isHexBytes(effectId, 32)) || + new Set(externalEffectIds).size !== externalEffectIds.length) { + return new Error('Invalid targeted payout preparation external effect ids.'); + } + if ((value.kind === 'tap_root' && rail !== 'tap') || + (value.kind === 'tap_root' && externalEffectIds.length !== 2) || + (value.kind === 'fee' && rail === 'tap') || + (value.kind === 'fee' && rail === 'tnk' && + externalEffectIds.length !== 1) || + (value.kind === 'fee' && rail === 'fiat' && + externalEffectIds.length !== 0) || + (value.kind === 'liability' && rail === 'tnk' && + externalEffectIds.length !== 1) || + (value.kind === 'liability' && ['tap', 'fiat'].includes(rail) && + externalEffectIds.length !== 0)) { + return new Error('Targeted payout preparation kind does not match rail effects.'); + } + let liability = null; + if (value.kind === 'liability') { + const liabilityShapeError = this.validateExactObjectKeys( + value.liability, + [ + 'provider', + 'payout_revision', + 'target', + 'currency', + 'chain_id', + 'paid_cum_au_before', + 'aggregate_paid_cum_au_before', + 'liability_au', + 'paid_au', + ], + 'targeted payout preparation liability' + ); + if (liabilityShapeError) return liabilityShapeError; + if (!this.isHexBytes(value.liability.provider, 32) || + value.liability.provider !== value.liability.provider.toLowerCase() || + !this.isHexBytes(value.liability.payout_revision, 32) || + value.liability.payout_revision !== value.liability.payout_revision.toLowerCase() || + !this.isSafeKeyPart(value.liability.target) || + (value.liability.currency !== null && + this.normalizeFiatCurrency(value.liability.currency) !== value.liability.currency) || + (value.liability.chain_id !== null && + (!Number.isSafeInteger(value.liability.chain_id) || + value.liability.chain_id < 1))) { + return new Error('Invalid targeted payout preparation liability identity.'); + } + const paidCumAuBefore = this.normalizeAu( + value.liability.paid_cum_au_before, + 'targeted payout preparation liability watermark', + { allowZero: true } + ); + const aggregatePaidCumAuBefore = this.normalizeAu( + value.liability.aggregate_paid_cum_au_before, + 'targeted payout preparation aggregate watermark', + { allowZero: true } + ); + const liabilityAu = this.normalizeAu( + value.liability.liability_au, + 'targeted payout preparation liability amount', + { allowZero: false } + ); + const paidAu = this.normalizeAu( + value.liability.paid_au, + 'targeted payout preparation paid amount', + { allowZero: false } + ); + if ([paidCumAuBefore, aggregatePaidCumAuBefore, liabilityAu, paidAu] + .some((entry) => entry instanceof Error) || + this.compareAu(paidAu, liabilityAu) > 0) { + return new Error('Invalid targeted payout preparation liability amount.'); + } + liability = { + provider: value.liability.provider, + payout_revision: value.liability.payout_revision, + target: value.liability.target, + currency: value.liability.currency, + chain_id: value.liability.chain_id, + paid_cum_au_before: paidCumAuBefore, + aggregate_paid_cum_au_before: aggregatePaidCumAuBefore, + liability_au: liabilityAu, + paid_au: paidAu, + }; + } else if (value.liability !== null) { + return new Error('Non-liability payout preparation cannot bind a liability.'); + } + let payload = stableValue(value.payload); + if (rail === 'fiat') { + payload = this.normalizeTargetedFiatPreparationPayload( + value, + payload, + liability + ); + if (payload instanceof Error) return payload; + } else if (rail === 'tnk') { + payload = await this.normalizeTargetedTnkPreparationPayload( + value, + payload, + liability, + externalEffectIds + ); + if (payload instanceof Error) return payload; + } + if (stableJson(payload) !== stableJson(value.payload)) { + return new Error('Targeted payout preparation payload must be canonical.'); + } + const payloadHash = await this.opaqueHash( + 'mayhem-targeted-payout-preparation-payload-v1', + { + economic_op_id: value.economic_op_id, + rail, + epoch: value.epoch, + epoch_apply_hash: value.epoch_apply_hash, + kind: value.kind, + output_index: value.output_index, + payload, + } + ); + if (payloadHash !== value.payload_hash) { + return new Error('Targeted payout preparation payload hash mismatch.'); + } + const normalized = { + op: 'prepare_targeted_payout', + contract_version: CONTRACT_VERSION, + economic_op_id: value.economic_op_id, + rail, + epoch: value.epoch, + epoch_apply_hash: value.epoch_apply_hash, + prepared_at: value.prepared_at, + kind: value.kind, + output_index: value.output_index, + payload_hash: value.payload_hash, + payload, + liability, + external_effect_ids: externalEffectIds, + admin: value.admin, + admin_sig: value.admin_sig.toLowerCase(), + }; + if (stableJson(normalized) !== stableJson(value)) { + return new Error('Targeted payout preparation must be canonical.'); + } + return normalized; + } + + payoutPreparationRecordKey(rail, economicOpId) { + return `payout/preparation/${rail}/${economicOpId}`; + } + + payoutPreparationLiabilityLockKey(rail, liability) { + return ( + `payout/preparation-lock/${rail}/${liability.provider}/` + + `${liability.payout_revision}/${liability.paid_cum_au_before}` + ); + } + + payoutPreparationAggregateTailKey(rail, provider) { + return `payout/preparation-tail/${rail}/${provider}`; + } + + payoutPreparationEffectLockKey(rail, effectId) { + return `payout/preparation-effect/${rail}/${effectId}`; + } + + async validatePayoutPreparationLiability(value) { + const liability = value.liability; + const binding = await this.get( + this.providerPayoutBindingFeatureKey( + value.rail, + liability.provider, + liability.payout_revision + ) + ); + if (!binding || + binding.verified !== true || + binding.provider !== liability.provider || + binding.rail !== value.rail || + binding.revision !== liability.payout_revision || + binding.activation_epoch > value.epoch || + binding.target !== liability.target || + (binding.currency ?? null) !== liability.currency || + (binding.chain_id ?? null) !== liability.chain_id) { + return new Error('Targeted payout preparation requires its immutable payout binding.'); + } + const liabilityKey = this.providerPayoutLiabilityKey( + liability.provider, + value.rail, + liability.payout_revision + ); + const current = await this.get(liabilityKey); + if (!current || + current.provider !== liability.provider || + current.rail !== value.rail || + current.revision !== liability.payout_revision || + current.target !== liability.target || + (current.currency ?? null) !== liability.currency || + (current.chain_id ?? null) !== liability.chain_id || + current.paid_cum_au !== liability.paid_cum_au_before) { + return new Error('Targeted payout preparation liability watermark mismatch.'); + } + const provider = await this.get(`prov/${liability.provider}`); + if (!provider || + (provider.status !== 'active' && provider.status !== 'banned')) { + return new Error('Targeted payout preparation provider status is not payable.'); + } + const params = await this.activeParamsAt(value.prepared_at, [ + 'holdback_epochs', + 'challenge_epochs', + 'canary_probe_holdback_bps', + 'canary_probe_release_min_passes', + ]); + if (params instanceof Error) return params; + const probeGate = await this.probeGateForEarning( + liability.provider, + current, + params + ); + if (probeGate instanceof Error) return probeGate; + const lockedEpochs = this.providerLockedEarningEpochs(provider, params); + if (lockedEpochs instanceof Error) return lockedEpochs; + const disputeGate = await this.providerHasOpenDispute(liability.provider); + if (disputeGate instanceof Error) return disputeGate; + const refreshed = this.refreshEarningHoldback( + current, + value.epoch, + lockedEpochs, + probeGate, + disputeGate + ); + if (refreshed instanceof Error) return refreshed; + const payable = this.safeSubAu( + this.safeSubAu(refreshed.total_au, refreshed.held_au), + refreshed.paid_cum_au + ); + if (payable instanceof Error) return payable; + if (this.compareAu(liability.liability_au, payable) > 0) { + return new Error('Targeted payout preparation exceeds revision liability.'); + } + const earning = await this.earningRecord(liability.provider, value.rail); + if (earning instanceof Error) return earning; + const tailKey = this.payoutPreparationAggregateTailKey(value.rail, liability.provider); + const tail = await this.get(tailKey); + if (tail && + typeof tail.consumed !== 'boolean') { + return new Error('Targeted payout preparation aggregate tail is invalid.'); + } + if (tail?.consumed === true && + tail.paid_cum_au_after !== earning.paid_cum_au) { + return new Error('Consumed payout preparation tail does not match aggregate earnings.'); + } + const expectedAggregateBefore = tail?.consumed === false + ? tail.paid_cum_au_after + : earning.paid_cum_au; + if (liability.aggregate_paid_cum_au_before !== expectedAggregateBefore) { + return new Error('Targeted payout preparation aggregate watermark mismatch.'); + } + const paidCumAuAfter = this.safeAddAu( + liability.aggregate_paid_cum_au_before, + liability.paid_au + ); + if (paidCumAuAfter instanceof Error) return paidCumAuAfter; + return { + liability_key: liabilityKey, + tail_key: tailKey, + paid_cum_au_after: paidCumAuAfter, + }; + } + + async applyTargetedPayoutPreparationFeature(key, value) { + const normalized = await this.normalizeTargetedPayoutPreparationValue(value); + if (normalized instanceof Error) return normalized; + const expectedKey = await this.targetedPayoutPreparationFeatureKey(normalized); + if (expectedKey instanceof Error) return expectedKey; + if (key !== expectedKey) return new Error('Invalid targeted payout preparation key.'); + const admin = await this.get('admin'); + if (normalized.admin !== admin || this.address !== admin) { + return new Error('Targeted payout preparation requires canonical admin authority.'); + } + const verify = this.protocol?.peer?.wallet?.verify; + if (typeof verify !== 'function' || + verify.call( + this.protocol.peer.wallet, + normalized.admin_sig, + payoutPreparationMessage(normalized), + admin + ) !== true) { + return new Error('Invalid targeted payout preparation admin signature.'); + } + const applyAnchor = await this.requireEpochApplyAnchor( + normalized.epoch, + normalized.epoch_apply_hash, + 'Targeted payout preparation' + ); + if (applyAnchor instanceof Error) return applyAnchor; + if (['fiat', 'tnk'].includes(normalized.rail)) { + const plan = await this.get( + this.targetedPayoutEpochPlanRecordKey(normalized.rail, normalized.epoch) + ); + const output = plan?.value?.outputs?.[normalized.output_index]; + if (!plan || + plan.plan_root !== normalized.payload.plan_root || + plan.value.epoch_apply_hash !== normalized.epoch_apply_hash || + output?.economic_op_id !== normalized.economic_op_id || + stableJson(output) !== stableJson(normalized.payload.output)) { + return new Error( + 'Targeted payout preparation does not match its canonical epoch plan.' + ); + } + } + if (normalized.rail === 'tnk') { + const payment = await this.canonicalTnkPaymentConfig(); + if (payment instanceof Error) return payment; + if (normalized.payload.network !== payment.network || + normalized.payload.treasury_from !== payment.treasury_address) { + return new Error( + 'Targeted TNK preparation source does not match payment config.' + ); + } + } + if (normalized.rail === 'tap') { + const params = await this.activeParamsAt(normalized.prepared_at, ['fee_bps']); + const split = this.targetedTapPayoutSplit(params.fee_bps); + if (split instanceof Error) return split; + if (normalized.payload.fee_bps !== split.fee_bps || + normalized.payload.tap_burn_bps !== split.tap_burn_bps || + normalized.payload.provider_share_bps !== split.provider_share_bps) { + return new Error( + 'Targeted TAP preparation does not match the fixed on-chain split.' + ); + } + } + const recordKey = this.payoutPreparationRecordKey( + normalized.rail, + normalized.economic_op_id + ); + const record = { + type: 'targeted_payout_preparation', + ...normalized, + consumed: false, + consumed_by: null, + prepared_at_tx: this.tx, + }; + const existing = await this.get(recordKey); + if (existing !== null) { + if (stableJson(existing) === stableJson(record)) { + return { + ok: true, + op: 'targetedPayoutPreparation', + rail: normalized.rail, + economic_op_id: normalized.economic_op_id, + idempotent: true, + }; + } + return new Error('Targeted payout preparation economic operation already exists.'); + } + let liabilityState = null; + if (normalized.liability !== null) { + liabilityState = await this.validatePayoutPreparationLiability(normalized); + if (liabilityState instanceof Error) return liabilityState; + const lockKey = this.payoutPreparationLiabilityLockKey( + normalized.rail, + normalized.liability + ); + if ((await this.get(lockKey)) !== null) { + return new Error('Targeted payout liability watermark already has a preparation.'); + } + } + for (const effectId of normalized.external_effect_ids) { + if ((await this.get( + this.payoutPreparationEffectLockKey(normalized.rail, effectId) + )) !== null) { + return new Error('Targeted payout external effect id already has a preparation.'); + } + } + if (normalized.liability !== null) { + await this.put( + this.payoutPreparationLiabilityLockKey(normalized.rail, normalized.liability), + { + economic_op_id: normalized.economic_op_id, + rail: normalized.rail, + provider: normalized.liability.provider, + payout_revision: normalized.liability.payout_revision, + paid_cum_au_before: normalized.liability.paid_cum_au_before, + prepared_at: this.tx, + } + ); + await this.put(liabilityState.tail_key, { + economic_op_id: normalized.economic_op_id, + paid_cum_au_before: normalized.liability.aggregate_paid_cum_au_before, + paid_cum_au_after: liabilityState.paid_cum_au_after, + consumed: false, + updated_at: this.tx, + }); + } + for (const effectId of normalized.external_effect_ids) { + await this.put(this.payoutPreparationEffectLockKey(normalized.rail, effectId), { + economic_op_id: normalized.economic_op_id, + effect_id: effectId, + consumed: false, + updated_at: this.tx, + }); + } + await this.put(recordKey, record); + return { + ok: true, + op: 'targetedPayoutPreparation', + rail: normalized.rail, + economic_op_id: normalized.economic_op_id, + idempotent: false, + }; + } + + async requireTargetedPayoutAdminSignature(value, label) { + const admin = await this.get('admin'); + if (value.admin !== admin || this.address !== admin) { + return new Error(`${label} requires canonical admin authority.`); + } + const verify = this.protocol?.peer?.wallet?.verify; + if (typeof verify !== 'function' || + verify.call( + this.protocol.peer.wallet, + value.admin_sig, + targetedPayoutControlMessage(value), + admin + ) !== true) { + return new Error(`Invalid ${label.toLowerCase()} admin signature.`); + } + return null; + } + + targetedPayoutEpochPlanRecordKey(rail, epoch) { + return `payout/epoch-plan/${rail}/${epoch}`; + } + + targetedPayoutOutputRecordKey(rail, epoch, economicOpId) { + return `settle/targeted/${rail}/${epoch}/output/${economicOpId}`; + } + + targetedPayoutEpochCloseRecordKey(rail, epoch) { + return `settle/targeted/${rail}/${epoch}`; + } + + targetedFiatAttemptRecordKey(economicOpId, attemptId) { + return `payout/attempt/fiat/${economicOpId}/${attemptId}`; + } + + targetedFiatAttemptTailKey(economicOpId) { + return `payout/attempt/fiat/${economicOpId}/latest`; + } + + targetedFiatAttemptEffectKey(effectId) { + return `payout/attempt-effect/fiat/${effectId}`; + } + + normalizeTargetedPayoutPlanOutput(rail, output, expectedIndex) { + if (!output || typeof output !== 'object' || Array.isArray(output) || + !this.isHexBytes(output.economic_op_id, 32) || + output.economic_op_id !== output.economic_op_id.toLowerCase() || + output.output_index !== expectedIndex) { + return new Error('Invalid targeted payout epoch output identity.'); + } + const { + economic_op_id: economicOpId, + output_index: outputIndex, + ...economicOutput + } = output; + let normalized; + if (rail === 'tnk') { + const allowed = economicOutput.role === 'provider' + ? [ + 'role', + 'provider', + 'payout_revision', + 'to', + 'paid_cum_au_before', + 'aggregate_paid_cum_au_before', + 'au', + 'tnk_e18', + ] + : ['role', 'to', 'au', 'tnk_e18']; + const shapeError = this.validateExactObjectKeys( + economicOutput, + allowed, + 'targeted TNK epoch output' + ); + if (shapeError) return shapeError; + if (economicOutput.role === 'provider') { + if (!this.isHexBytes(economicOutput.provider, 32) || + economicOutput.provider !== economicOutput.provider.toLowerCase() || + !this.isHexBytes(economicOutput.payout_revision, 32) || + economicOutput.payout_revision !== economicOutput.payout_revision.toLowerCase()) { + return new Error('Invalid targeted TNK epoch provider identity.'); + } + const paidCumAuBefore = this.normalizeAu( + economicOutput.paid_cum_au_before, + 'targeted TNK epoch liability watermark', + { allowZero: true } + ); + const aggregatePaidCumAuBefore = this.normalizeAu( + economicOutput.aggregate_paid_cum_au_before, + 'targeted TNK epoch aggregate watermark', + { allowZero: true } + ); + if (paidCumAuBefore instanceof Error || + aggregatePaidCumAuBefore instanceof Error) { + return new Error('Invalid targeted TNK epoch output watermark.'); + } + normalized = this.normalizeTargetedTnkSettlementOutput(economicOutput); + if (normalized instanceof Error) return normalized; + normalized = { + ...normalized, + payout_revision: economicOutput.payout_revision, + paid_cum_au_before: paidCumAuBefore, + aggregate_paid_cum_au_before: aggregatePaidCumAuBefore, + }; + } else if (economicOutput.role === 'operator_fee') { + normalized = this.normalizeTargetedTnkSettlementOutput(economicOutput); + if (normalized instanceof Error) return normalized; + } else { + return new Error('Invalid targeted TNK epoch output role.'); + } + } else if (rail === 'fiat') { + normalized = this.normalizeTargetedFiatPreparationOutput(economicOutput); + if (normalized instanceof Error) return normalized; + } else { + return new Error('Targeted payout epoch plans support only fiat and TNK.'); + } + const result = { + economic_op_id: economicOpId, + output_index: outputIndex, + ...normalized, + }; + return stableJson(result) === stableJson(output) + ? result + : new Error('Targeted payout epoch output must be canonical.'); + } + + normalizeTargetedPayoutCarry(value) { + const shapeError = this.validateExactObjectKeys( + value, + [ + 'provider', + 'payout_revision', + 'liability_au', + 'held_au', + 'payable_au', + 'payout_min_au', + 'reason', + ], + 'targeted payout epoch carry' + ); + if (shapeError) return shapeError; + if (!this.isHexBytes(value.provider, 32) || + value.provider !== value.provider.toLowerCase() || + !this.isHexBytes(value.payout_revision, 32) || + value.payout_revision !== value.payout_revision.toLowerCase() || + !['held', 'below_payout_minimum'].includes(value.reason)) { + return new Error('Invalid targeted payout epoch carry identity.'); + } + const liabilityAu = this.normalizeAu( + value.liability_au, + 'targeted payout carry liability', + { allowZero: false } + ); + const heldAu = this.normalizeAu( + value.held_au, + 'targeted payout carry held amount', + { allowZero: true } + ); + const payableAu = this.normalizeAu( + value.payable_au, + 'targeted payout carry payable amount', + { allowZero: true } + ); + const payoutMinAu = this.normalizeAu( + value.payout_min_au, + 'targeted payout carry minimum', + { allowZero: true } + ); + if ([liabilityAu, heldAu, payableAu, payoutMinAu] + .some((entry) => entry instanceof Error)) { + return new Error('Invalid targeted payout epoch carry amount.'); + } + const classified = this.safeAddAu(heldAu, payableAu); + if (classified instanceof Error || + classified !== liabilityAu || + (value.reason === 'held' && + (this.isZeroAu(heldAu) || !this.isZeroAu(payableAu))) || + (value.reason === 'below_payout_minimum' && + (this.isZeroAu(payableAu) || + this.compareAu(payableAu, payoutMinAu) >= 0))) { + return new Error('Targeted payout epoch carry classification is invalid.'); + } + return { + provider: value.provider, + payout_revision: value.payout_revision, + liability_au: liabilityAu, + held_au: heldAu, + payable_au: payableAu, + payout_min_au: payoutMinAu, + reason: value.reason, + }; + } + + async normalizeTargetedPayoutEpochValue(value) { + const shapeError = this.validateExactObjectKeys( + value, + [ + 'op', + 'contract_version', + 'rail', + 'epoch', + 'at', + 'epoch_apply_hash', + 'snapshot_signed_length', + 'outcome', + 'outputs', + 'carry', + 'outputs_root', + 'carry_root', + 'plan_root', + 'admin', + 'admin_sig', + ], + 'targeted payout epoch plan' + ); + if (shapeError) return shapeError; + if (value.op !== 'prepare_targeted_payout_epoch' || + value.contract_version !== CONTRACT_VERSION || + !['fiat', 'tnk'].includes(value.rail) || + !Number.isSafeInteger(value.epoch) || + value.epoch < 1 || + !Number.isSafeInteger(value.at) || + value.at < 0 || + !this.isHexBytes(value.epoch_apply_hash, 32) || + value.epoch_apply_hash !== value.epoch_apply_hash.toLowerCase() || + !Number.isSafeInteger(value.snapshot_signed_length) || + value.snapshot_signed_length < 1 || + !['payouts', 'carry', 'no_work'].includes(value.outcome) || + !Array.isArray(value.outputs) || + !Array.isArray(value.carry) || + !this.isHexBytes(value.outputs_root, 32) || + value.outputs_root !== value.outputs_root.toLowerCase() || + !this.isHexBytes(value.carry_root, 32) || + value.carry_root !== value.carry_root.toLowerCase() || + !this.isHexBytes(value.plan_root, 32) || + value.plan_root !== value.plan_root.toLowerCase() || + !this.isHexBytes(value.admin, 32) || + value.admin !== value.admin.toLowerCase() || + !this.isHexBytes(value.admin_sig, 64)) { + return new Error('Invalid targeted payout epoch plan.'); + } + const outputs = value.outputs.map((output, index) => + this.normalizeTargetedPayoutPlanOutput(value.rail, output, index) + ); + const outputError = outputs.find((output) => output instanceof Error); + if (outputError) return outputError; + const economicIds = new Set(outputs.map((output) => output.economic_op_id)); + if (economicIds.size !== outputs.length) { + return new Error('Targeted payout epoch output identities must be unique.'); + } + if (outputs.filter((output) => output.role === 'operator_fee').length > 1) { + return new Error('Targeted payout epoch may contain only one operator output.'); + } + for (let index = 1; index < outputs.length; index += 1) { + const left = outputs[index - 1]; + const right = outputs[index]; + const order = left.role === right.role + ? left.role === 'provider' + ? compareCodepoint(left.provider, right.provider) || + compareCodepoint(left.payout_revision, right.payout_revision) + : compareCodepoint(left.economic_op_id, right.economic_op_id) + : left.role === 'provider' ? -1 : 1; + if (order >= 0) { + return new Error('Targeted payout epoch outputs are not canonically ordered.'); + } + } + const carry = value.carry.map((entry) => this.normalizeTargetedPayoutCarry(entry)); + const carryError = carry.find((entry) => entry instanceof Error); + if (carryError) return carryError; + carry.sort((left, right) => + compareCodepoint(left.provider, right.provider) || + compareCodepoint(left.payout_revision, right.payout_revision) + ); + if (stableJson(carry) !== stableJson(value.carry) || + new Set(carry.map((entry) => + `${entry.provider}/${entry.payout_revision}` + )).size !== carry.length) { + return new Error('Targeted payout epoch carries must be canonical and unique.'); + } + const plannedLiabilities = new Set( + outputs + .filter((output) => output.role === 'provider') + .map((output) => `${output.provider}/${output.payout_revision}`) + ); + if (carry.some((entry) => + plannedLiabilities.has(`${entry.provider}/${entry.payout_revision}`) + )) { + return new Error('Targeted payout liability cannot be both payable and carried.'); + } + const expectedOutcome = outputs.length > 0 + ? 'payouts' + : carry.length > 0 ? 'carry' : 'no_work'; + if (value.outcome !== expectedOutcome) { + return new Error('Targeted payout epoch outcome does not match its work.'); + } + const outputsRoot = await this.opaqueHash( + 'mayhem-targeted-payout-epoch-outputs-v1', + { rail: value.rail, epoch: value.epoch, outputs } + ); + const carryRoot = await this.opaqueHash( + 'mayhem-targeted-payout-epoch-carry-v1', + { rail: value.rail, epoch: value.epoch, carry } + ); + const planRoot = await this.opaqueHash( + 'mayhem-targeted-payout-epoch-plan-v1', + { + rail: value.rail, + epoch: value.epoch, + at: value.at, + epoch_apply_hash: value.epoch_apply_hash, + snapshot_signed_length: value.snapshot_signed_length, + outcome: value.outcome, + outputs_root: outputsRoot, + carry_root: carryRoot, + } + ); + if (value.outputs_root !== outputsRoot || + value.carry_root !== carryRoot || + value.plan_root !== planRoot) { + return new Error('Targeted payout epoch root mismatch.'); + } + const normalized = { + op: 'prepare_targeted_payout_epoch', + contract_version: CONTRACT_VERSION, + rail: value.rail, + epoch: value.epoch, + at: value.at, + epoch_apply_hash: value.epoch_apply_hash, + snapshot_signed_length: value.snapshot_signed_length, + outcome: value.outcome, + outputs, + carry, + outputs_root: outputsRoot, + carry_root: carryRoot, + plan_root: planRoot, + admin: value.admin, + admin_sig: value.admin_sig.toLowerCase(), + }; + return stableJson(normalized) === stableJson(value) + ? normalized + : new Error('Targeted payout epoch plan must be canonical.'); + } + + async validateTargetedPayoutEpochCompleteness(value, params) { + const index = await this.providerPayoutLiabilityIndex(value.rail); + if (index instanceof Error) return index; + const providerOutputs = new Map( + value.outputs + .filter((output) => output.role === 'provider') + .map((output) => [ + `${output.provider}/${output.payout_revision}`, + output, + ]) + ); + const carries = new Map(value.carry.map((entry) => [ + `${entry.provider}/${entry.payout_revision}`, + entry, + ])); + const classified = new Set(); + const aggregateCursors = new Map(); + + for (const entry of index.entries) { + const identity = `${entry.provider}/${entry.payout_revision}`; + const liability = await this.get( + this.providerPayoutLiabilityKey( + entry.provider, + value.rail, + entry.payout_revision + ) + ); + if (!liability || + liability.type !== 'provider_payout_liability' || + liability.provider !== entry.provider || + liability.rail !== value.rail || + liability.revision !== entry.payout_revision || + liability.updated_epoch > value.epoch) { + return new Error('Targeted payout liability index does not match canonical state.'); + } + const liabilityError = this.guardianValidatePayoutLiabilityRecord( + liability, + entry.provider, + value.rail, + entry.payout_revision + ); + if (liabilityError) return liabilityError; + const provider = await this.get(`prov/${entry.provider}`); + if (!provider || + (provider.status !== 'active' && provider.status !== 'banned')) { + return new Error('Targeted payout indexed provider status is not payable.'); + } + const binding = await this.get( + this.providerPayoutBindingFeatureKey( + value.rail, + entry.provider, + entry.payout_revision + ) + ); + if (!binding || + binding.verified !== true || + binding.provider !== entry.provider || + binding.rail !== value.rail || + binding.revision !== entry.payout_revision || + binding.activation_epoch > value.epoch || + binding.target !== liability.target || + (binding.currency ?? null) !== (liability.currency ?? null) || + (binding.chain_id ?? null) !== (liability.chain_id ?? null)) { + return new Error('Targeted payout indexed liability binding mismatch.'); + } + const probeGate = await this.probeGateForEarning( + entry.provider, + liability, + params + ); + if (probeGate instanceof Error) return probeGate; + const lockedEpochs = this.providerLockedEarningEpochs(provider, params); + if (lockedEpochs instanceof Error) return lockedEpochs; + const disputeGate = await this.providerHasOpenDispute(entry.provider); + if (disputeGate instanceof Error) return disputeGate; + const refreshed = this.refreshEarningHoldback( + liability, + value.epoch, + lockedEpochs, + probeGate, + disputeGate + ); + if (refreshed instanceof Error) return refreshed; + const outstanding = this.safeSubAu( + refreshed.total_au, + refreshed.paid_cum_au + ); + const payable = this.safeSubAu(outstanding, refreshed.held_au); + if (outstanding instanceof Error || payable instanceof Error) { + return new Error('Targeted payout canonical liability is invalid.'); + } + const output = providerOutputs.get(identity) ?? null; + const carry = carries.get(identity) ?? null; + if (this.isZeroAu(outstanding)) { + if (output !== null || carry !== null) { + return new Error('Settled targeted payout liability must be omitted.'); + } + continue; + } + + const isPayable = + !this.isZeroAu(payable) && + this.compareAu(payable, params.payout_min_au) >= 0; + if (!isPayable) { + const expectedCarry = { + provider: entry.provider, + payout_revision: entry.payout_revision, + liability_au: outstanding, + held_au: refreshed.held_au, + payable_au: payable, + payout_min_au: params.payout_min_au, + reason: this.isZeroAu(payable) ? 'held' : 'below_payout_minimum', + }; + if (output !== null || + carry === null || + stableJson(carry) !== stableJson(expectedCarry)) { + return new Error( + 'Targeted payout plan must explicitly carry every held or below-minimum liability.' + ); + } + classified.add(identity); + continue; + } + + if (output === null || carry !== null || + output.to !== binding.target || + output.paid_cum_au_before !== refreshed.paid_cum_au || + (value.rail === 'tnk' && output.au !== payable) || + (value.rail === 'fiat' && + (output.liability_au !== payable || + output.destination_currency !== binding.currency))) { + return new Error( + 'Targeted payout plan must include every payable canonical liability exactly.' + ); + } + let aggregate = aggregateCursors.get(entry.provider); + if (!aggregate) { + const earning = await this.earningRecord(entry.provider, value.rail); + if (earning instanceof Error) return earning; + const earningError = this.guardianValidateEarningRecord( + earning, + entry.provider, + value.rail + ); + if (earningError) return earningError; + const aggregateProbeGate = await this.probeGateForEarning( + entry.provider, + earning, + params + ); + if (aggregateProbeGate instanceof Error) return aggregateProbeGate; + const refreshedEarning = this.refreshEarningHoldback( + earning, + value.epoch, + lockedEpochs, + aggregateProbeGate, + disputeGate + ); + if (refreshedEarning instanceof Error) return refreshedEarning; + aggregate = { paid_cum_au: refreshedEarning.paid_cum_au }; + } + if (output.aggregate_paid_cum_au_before !== aggregate.paid_cum_au) { + return new Error('Targeted payout aggregate paid watermark mismatch.'); + } + const settledAu = value.rail === 'fiat' ? output.paid_au : output.au; + const nextPaid = this.safeAddAu(aggregate.paid_cum_au, settledAu); + if (nextPaid instanceof Error) return nextPaid; + aggregateCursors.set(entry.provider, { paid_cum_au: nextPaid }); + classified.add(identity); + } + + if (classified.size !== carries.size + providerOutputs.size) { + return new Error( + 'Targeted payout plan contains a liability absent from the canonical index.' + ); + } + + const operatorOutputs = value.outputs.filter( + (output) => output.role === 'operator_fee' + ); + const fee = await this.feeCumRecord(value.rail); + if (fee instanceof Error) return fee; + const feeError = this.guardianValidateFeeRecord(fee, value.rail); + if (feeError) return feeError; + const payableFee = this.safeSubAu(fee.cum_au, fee.swept_cum_au); + if (payableFee instanceof Error) return payableFee; + if (this.isZeroAu(payableFee)) { + if (operatorOutputs.length !== 0) { + return new Error('Targeted payout plan includes a nonexistent operator fee.'); + } + } else if ( + operatorOutputs.length !== 1 || + (value.rail === 'tnk' && operatorOutputs[0].au !== payableFee) || + (value.rail === 'fiat' && + operatorOutputs[0].liability_au !== payableFee) + ) { + return new Error( + 'Targeted payout plan must include the complete canonical operator fee.' + ); + } + return null; + } + + async applyTargetedPayoutEpochFeature(key, value) { + const normalized = await this.normalizeTargetedPayoutEpochValue(value); + if (normalized instanceof Error) return normalized; + const expectedKey = await this.targetedPayoutEpochFeatureKey(normalized); + if (expectedKey instanceof Error) return expectedKey; + if (key !== expectedKey) return new Error('Invalid targeted payout epoch feature key.'); + const adminError = await this.requireTargetedPayoutAdminSignature( + normalized, + 'Targeted payout epoch plan' + ); + if (adminError) return adminError; + const applyAnchor = await this.requireEpochApplyAnchor( + normalized.epoch, + normalized.epoch_apply_hash, + 'Targeted payout epoch plan' + ); + if (applyAnchor instanceof Error) return applyAnchor; + const recordKey = this.targetedPayoutEpochPlanRecordKey( + normalized.rail, + normalized.epoch + ); + const existing = await this.get(recordKey); + if (existing !== null) { + if (existing.plan_root === normalized.plan_root && + stableJson(existing.value) === stableJson(normalized)) { + return { + ok: true, + op: 'targetedPayoutEpochPrepared', + rail: normalized.rail, + epoch: normalized.epoch, + plan_root: normalized.plan_root, + idempotent: true, + }; + } + return new Error('Targeted payout epoch plan already exists.'); + } + const applyState = await this.epochApplyStateRecord(); + if (applyState.updated_epoch !== normalized.epoch || + applyState.last_apply_hash !== normalized.epoch_apply_hash || + (applyState.pending_epoch ?? null) !== null) { + return new Error( + 'Targeted payout epoch plan must freeze the latest completed canonical apply.' + ); + } + const params = await this.activeParamsAt( + normalized.at, + [ + normalized.rail === 'tnk' + ? 'max_tnk_settlement_outputs' + : 'max_fiat_settlement_outputs', + 'payout_min_au', + 'holdback_epochs', + 'challenge_epochs', + 'canary_probe_holdback_bps', + 'canary_probe_release_min_passes', + ] + ); + const maxOutputs = normalized.rail === 'tnk' + ? params.max_tnk_settlement_outputs + : params.max_fiat_settlement_outputs; + if (normalized.outputs.length > maxOutputs) { + return new Error('Targeted payout epoch output count exceeds limit.'); + } + const completenessError = await this.validateTargetedPayoutEpochCompleteness( + normalized, + params + ); + if (completenessError) return completenessError; + await this.put(recordKey, { + type: 'targeted_payout_epoch_plan', + rail: normalized.rail, + epoch: normalized.epoch, + plan_root: normalized.plan_root, + value: normalized, + prepared_at: this.tx, + }); + return { + ok: true, + op: 'targetedPayoutEpochPrepared', + rail: normalized.rail, + epoch: normalized.epoch, + plan_root: normalized.plan_root, + idempotent: false, + }; + } + + normalizeTargetedFiatAttemptRequest(request) { + const shapeError = this.validateExactObjectKeys( + request, + [ + 'processor', + 'kind', + 'destination', + 'source_currency', + 'source_amount_minor', + 'destination_currency', + 'destination_amount_min_minor', + 'destination_amount_max_minor', + 'fx_quote_id', + 'fx_quote_hash', + 'transfer_group', + 'metadata_hash', + ], + 'targeted fiat attempt request' + ); + if (shapeError) return shapeError; + const sourceCurrency = this.normalizeFiatCurrency(request.source_currency); + const sourceAmountMinor = this.normalizeFiatMinor(request.source_amount_minor); + if (request.processor !== 'stripe' || + !['stripe_transfer', 'platform_balance'].includes(request.kind) || + !this.isSafeKeyPart(request.destination) || + sourceCurrency instanceof Error || + sourceCurrency !== request.source_currency || + sourceAmountMinor instanceof Error || + sourceAmountMinor !== request.source_amount_minor || + !this.isHexBytes(request.metadata_hash, 32) || + request.metadata_hash !== request.metadata_hash.toLowerCase()) { + return new Error('Invalid targeted fiat attempt request.'); + } + if (request.kind === 'platform_balance') { + if (request.destination_currency !== null || + request.destination_amount_min_minor !== null || + request.destination_amount_max_minor !== null || + request.fx_quote_id !== null || + request.fx_quote_hash !== null || + request.transfer_group !== null) { + return new Error('Platform-balance fiat attempt must not contain Stripe transfer terms.'); + } + return { + processor: 'stripe', + kind: 'platform_balance', + destination: request.destination, + source_currency: sourceCurrency, + source_amount_minor: sourceAmountMinor, + destination_currency: null, + destination_amount_min_minor: null, + destination_amount_max_minor: null, + fx_quote_id: null, + fx_quote_hash: null, + transfer_group: null, + metadata_hash: request.metadata_hash, + }; + } + const destinationCurrency = this.normalizeFiatCurrency(request.destination_currency); + const destinationMin = this.normalizeFiatMinor(request.destination_amount_min_minor); + const destinationMax = this.normalizeFiatMinor(request.destination_amount_max_minor); + const expectedGroup = /^mayhem_fiat_epoch_[1-9][0-9]*_[0-9a-f]{16}$/; + if (destinationCurrency instanceof Error || + destinationCurrency !== request.destination_currency || + destinationMin instanceof Error || + destinationMax instanceof Error || + BigInt(destinationMax) < BigInt(destinationMin) || + !expectedGroup.test(String(request.transfer_group || ''))) { + return new Error('Invalid targeted fiat attempt destination terms.'); + } + const requiresQuote = sourceCurrency !== 'usd' || destinationCurrency !== 'usd'; + if (requiresQuote) { + if (!/^fxq_[A-Za-z0-9._-]+$/.test(String(request.fx_quote_id || '')) || + !this.isHexBytes(request.fx_quote_hash, 32) || + request.fx_quote_hash !== request.fx_quote_hash.toLowerCase()) { + return new Error('Targeted fiat attempt requires a canonical FX quote.'); + } + } else if (request.fx_quote_id !== null || request.fx_quote_hash !== null) { + return new Error('Direct USD fiat attempt must not contain an FX quote.'); + } + return { + processor: 'stripe', + kind: 'stripe_transfer', + destination: request.destination, + source_currency: sourceCurrency, + source_amount_minor: sourceAmountMinor, + destination_currency: destinationCurrency, + destination_amount_min_minor: destinationMin, + destination_amount_max_minor: destinationMax, + fx_quote_id: request.fx_quote_id, + fx_quote_hash: request.fx_quote_hash, + transfer_group: request.transfer_group, + metadata_hash: request.metadata_hash, + }; + } + + async targetedFiatAttemptId(economicOpId, attemptNo, request) { + return await this.opaqueHash( + 'mayhem-targeted-fiat-attempt-id-v1', + { + economic_op_id: economicOpId, + attempt_no: attemptNo, + request, + } + ); + } + + async targetedFiatAttemptIdempotencyKeyHash(attemptId) { + const digest = await blake3(b4a.from(stableJson({ + domain: 'mayhem-targeted-fiat-idempotency-key-v1', + key: `mayhem:fiat:attempt:v1:${attemptId}`, + }))); + return b4a.toString(digest, 'hex'); + } + + async normalizePrepareTargetedFiatAttemptValue(value) { + const shapeError = this.validateExactObjectKeys( + value, + [ + 'op', + 'contract_version', + 'rail', + 'epoch', + 'epoch_apply_hash', + 'plan_root', + 'economic_op_id', + 'output_index', + 'attempt_id', + 'attempt_no', + 'prepared_at', + 'quote_expires_at', + 'idempotency_key_hash', + 'request_hash', + 'request', + 'admin', + 'admin_sig', + ], + 'targeted fiat attempt preparation' + ); + if (shapeError) return shapeError; + if (value.op !== 'prepare_targeted_fiat_attempt' || + value.contract_version !== CONTRACT_VERSION || + value.rail !== 'fiat' || + !Number.isSafeInteger(value.epoch) || + value.epoch < 1 || + !this.isHexBytes(value.epoch_apply_hash, 32) || + value.epoch_apply_hash !== value.epoch_apply_hash.toLowerCase() || + !this.isHexBytes(value.plan_root, 32) || + value.plan_root !== value.plan_root.toLowerCase() || + !this.isHexBytes(value.economic_op_id, 32) || + value.economic_op_id !== value.economic_op_id.toLowerCase() || + !Number.isSafeInteger(value.output_index) || + value.output_index < 0 || + !this.isHexBytes(value.attempt_id, 32) || + value.attempt_id !== value.attempt_id.toLowerCase() || + !Number.isSafeInteger(value.attempt_no) || + value.attempt_no < 1 || + !Number.isSafeInteger(value.prepared_at) || + value.prepared_at < 0 || + !this.isHexBytes(value.idempotency_key_hash, 32) || + value.idempotency_key_hash !== value.idempotency_key_hash.toLowerCase() || + !this.isHexBytes(value.request_hash, 32) || + value.request_hash !== value.request_hash.toLowerCase() || + !this.isHexBytes(value.admin, 32) || + value.admin !== value.admin.toLowerCase() || + !this.isHexBytes(value.admin_sig, 64)) { + return new Error('Invalid targeted fiat attempt preparation.'); + } + const request = this.normalizeTargetedFiatAttemptRequest(value.request); + if (request instanceof Error) return request; + const expectedAttemptId = await this.targetedFiatAttemptId( + value.economic_op_id, + value.attempt_no, + request + ); + if (value.attempt_id !== expectedAttemptId) { + return new Error('Targeted fiat attempt id does not match its canonical request.'); + } + const expectedIdempotencyKeyHash = + await this.targetedFiatAttemptIdempotencyKeyHash(expectedAttemptId); + if (value.idempotency_key_hash !== expectedIdempotencyKeyHash) { + return new Error( + 'Targeted fiat attempt idempotency key hash does not match its canonical attempt.' + ); + } + if ((request.kind === 'stripe_transfer' && + (!Number.isSafeInteger(value.quote_expires_at) || + value.quote_expires_at <= value.prepared_at)) || + (request.kind === 'platform_balance' && value.quote_expires_at !== null)) { + return new Error('Invalid targeted fiat attempt quote expiry.'); + } + const requestHash = await this.opaqueHash( + 'mayhem-targeted-fiat-attempt-request-v1', + { + economic_op_id: value.economic_op_id, + attempt_id: value.attempt_id, + attempt_no: value.attempt_no, + request, + } + ); + if (requestHash !== value.request_hash) { + return new Error('Targeted fiat attempt request hash mismatch.'); + } + const normalized = { + op: 'prepare_targeted_fiat_attempt', + contract_version: CONTRACT_VERSION, + rail: 'fiat', + epoch: value.epoch, + epoch_apply_hash: value.epoch_apply_hash, + plan_root: value.plan_root, + economic_op_id: value.economic_op_id, + output_index: value.output_index, + attempt_id: value.attempt_id, + attempt_no: value.attempt_no, + prepared_at: value.prepared_at, + quote_expires_at: value.quote_expires_at, + idempotency_key_hash: value.idempotency_key_hash.toLowerCase(), + request_hash: requestHash, + request, + admin: value.admin, + admin_sig: value.admin_sig.toLowerCase(), + }; + return stableJson(normalized) === stableJson(value) + ? normalized + : new Error('Targeted fiat attempt preparation must be canonical.'); + } + + async applyTargetedFiatAttemptFeature(key, value) { + const normalized = await this.normalizePrepareTargetedFiatAttemptValue(value); + if (normalized instanceof Error) return normalized; + const expectedKey = await this.targetedFiatAttemptFeatureKey(normalized); + if (expectedKey instanceof Error) return expectedKey; + if (key !== expectedKey) return new Error('Invalid targeted fiat attempt feature key.'); + const adminError = await this.requireTargetedPayoutAdminSignature( + normalized, + 'Targeted fiat attempt preparation' + ); + if (adminError) return adminError; + const plan = await this.get( + this.targetedPayoutEpochPlanRecordKey('fiat', normalized.epoch) + ); + const output = plan?.value?.outputs?.[normalized.output_index]; + if (!plan || + plan.plan_root !== normalized.plan_root || + plan.value.epoch_apply_hash !== normalized.epoch_apply_hash || + output?.economic_op_id !== normalized.economic_op_id) { + return new Error('Targeted fiat attempt does not match the canonical epoch plan.'); + } + const preparation = await this.get( + this.payoutPreparationRecordKey('fiat', normalized.economic_op_id) + ); + if (!preparation || + preparation.consumed !== false || + preparation.payload?.plan_root !== normalized.plan_root || + preparation.payload?.output_index !== normalized.output_index || + stableJson(preparation.payload?.output) !== stableJson(output)) { + return new Error('Targeted fiat attempt requires its unconsumed economic preparation.'); + } + const expectedKind = output.role === 'provider' + ? 'stripe_transfer' + : 'platform_balance'; + if (normalized.request.kind !== expectedKind || + normalized.request.destination !== output.to || + normalized.request.source_currency !== output.source_currency || + normalized.request.source_amount_minor !== output.source_amount_minor || + (output.role === 'provider' && + (normalized.request.destination_currency !== output.destination_currency || + normalized.request.destination_amount_min_minor !== + output.destination_amount_min_minor || + normalized.request.destination_amount_max_minor !== + output.destination_amount_max_minor || + normalized.request.transfer_group !== + `mayhem_fiat_epoch_${normalized.epoch}_${normalized.epoch_apply_hash.slice(0, 16)}`))) { + return new Error('Targeted fiat attempt request does not match its planned output.'); + } + const recordKey = this.targetedFiatAttemptRecordKey( + normalized.economic_op_id, + normalized.attempt_id + ); + const existing = await this.get(recordKey); + if (existing !== null) { + if (stableJson(existing.preparation) === stableJson(normalized)) { + return { + ok: true, + op: 'targetedFiatAttemptPrepared', + economic_op_id: normalized.economic_op_id, + attempt_id: normalized.attempt_id, + status: existing.status, + idempotent: true, + }; + } + return new Error('Targeted fiat attempt identity already exists.'); + } + const tailKey = this.targetedFiatAttemptTailKey(normalized.economic_op_id); + const tail = await this.get(tailKey); + if ((!tail && normalized.attempt_no !== 1) || + (tail && + (tail.status !== 'expired_pre_effect' || + normalized.attempt_no !== tail.attempt_no + 1))) { + return new Error( + 'Targeted fiat attempt may renew only after definitive pre-effect expiry.' + ); + } + if ((await this.get( + `payout/attempt-idempotency/fiat/${normalized.idempotency_key_hash}` + )) !== null) { + return new Error('Targeted fiat attempt idempotency key was already used.'); + } + if (normalized.request.fx_quote_id !== null && + (await this.get( + `payout/attempt-quote/fiat/${normalized.request.fx_quote_id}` + )) !== null) { + return new Error('Targeted fiat attempt FX quote was already used.'); + } + const record = { + type: 'targeted_fiat_attempt', + economic_op_id: normalized.economic_op_id, + attempt_id: normalized.attempt_id, + attempt_no: normalized.attempt_no, + status: 'prepared', + preparation: normalized, + result: null, + consumed: false, + consumed_by: null, + updated_at: this.tx, + }; + await this.put(recordKey, record); + await this.put(tailKey, { + economic_op_id: normalized.economic_op_id, + attempt_id: normalized.attempt_id, + attempt_no: normalized.attempt_no, + status: 'prepared', + updated_at: this.tx, + }); + await this.put( + `payout/attempt-idempotency/fiat/${normalized.idempotency_key_hash}`, + { + economic_op_id: normalized.economic_op_id, + attempt_id: normalized.attempt_id, + updated_at: this.tx, + } + ); + if (normalized.request.fx_quote_id !== null) { + await this.put( + `payout/attempt-quote/fiat/${normalized.request.fx_quote_id}`, + { + economic_op_id: normalized.economic_op_id, + attempt_id: normalized.attempt_id, + quote_hash: normalized.request.fx_quote_hash, + updated_at: this.tx, + } + ); + } + return { + ok: true, + op: 'targetedFiatAttemptPrepared', + economic_op_id: normalized.economic_op_id, + attempt_id: normalized.attempt_id, + status: 'prepared', + idempotent: false, + }; + } + + normalizeFinalizeTargetedFiatAttemptValue(value) { + const shapeError = this.validateExactObjectKeys( + value, + [ + 'op', + 'contract_version', + 'rail', + 'epoch', + 'economic_op_id', + 'attempt_id', + 'status', + 'at', + 'evidence', + 'admin', + 'admin_sig', + ], + 'targeted fiat attempt finalization' + ); + if (shapeError) return shapeError; + if (value.op !== 'finalize_targeted_fiat_attempt' || + value.contract_version !== CONTRACT_VERSION || + value.rail !== 'fiat' || + !Number.isSafeInteger(value.epoch) || + value.epoch < 1 || + !this.isHexBytes(value.economic_op_id, 32) || + value.economic_op_id !== value.economic_op_id.toLowerCase() || + !this.isHexBytes(value.attempt_id, 32) || + value.attempt_id !== value.attempt_id.toLowerCase() || + !['succeeded', 'expired_pre_effect'].includes(value.status) || + !Number.isSafeInteger(value.at) || + value.at < 0 || + !this.isHexBytes(value.admin, 32) || + value.admin !== value.admin.toLowerCase() || + !this.isHexBytes(value.admin_sig, 64)) { + return new Error('Invalid targeted fiat attempt finalization.'); + } + let evidence; + if (value.status === 'succeeded') { + evidence = this.normalizeStripeTransferEvidence( + value.evidence, + 'targeted fiat attempt success evidence', + { expectedAttemptId: value.attempt_id } + ); + if (evidence instanceof Error) return evidence; + } else { + const expiryShape = this.validateExactObjectKeys( + value.evidence, + [ + 'fx_quote_id', + 'fx_quote_hash', + 'quote_expires_at', + 'error_code', + 'external_effect_absent', + ], + 'targeted fiat attempt expiry evidence' + ); + if (expiryShape) return expiryShape; + if (!/^fxq_[A-Za-z0-9._-]+$/.test(String(value.evidence.fx_quote_id || '')) || + !this.isHexBytes(value.evidence.fx_quote_hash, 32) || + !Number.isSafeInteger(value.evidence.quote_expires_at) || + value.evidence.quote_expires_at < 0 || + value.evidence.error_code !== 'fx_quote_expired' || + value.evidence.external_effect_absent !== true) { + return new Error('Invalid targeted fiat attempt expiry evidence.'); + } + evidence = stableValue(value.evidence); + } + const normalized = { + op: 'finalize_targeted_fiat_attempt', + contract_version: CONTRACT_VERSION, + rail: 'fiat', + epoch: value.epoch, + economic_op_id: value.economic_op_id, + attempt_id: value.attempt_id, + status: value.status, + at: value.at, + evidence, + admin: value.admin, + admin_sig: value.admin_sig.toLowerCase(), + }; + return stableJson(normalized) === stableJson(value) + ? normalized + : new Error('Targeted fiat attempt finalization must be canonical.'); + } + + async applyFinalizeTargetedFiatAttemptFeature(key, value) { + const normalized = this.normalizeFinalizeTargetedFiatAttemptValue(value); + if (normalized instanceof Error) return normalized; + const expectedKey = await this.finalizeTargetedFiatAttemptFeatureKey(normalized); + if (expectedKey instanceof Error) return expectedKey; + if (key !== expectedKey) { + return new Error('Invalid targeted fiat attempt finalization key.'); + } + const adminError = await this.requireTargetedPayoutAdminSignature( + normalized, + 'Targeted fiat attempt finalization' + ); + if (adminError) return adminError; + const recordKey = this.targetedFiatAttemptRecordKey( + normalized.economic_op_id, + normalized.attempt_id + ); + const record = await this.get(recordKey); + if (!record || + record.type !== 'targeted_fiat_attempt' || + record.preparation.epoch !== normalized.epoch) { + return new Error('Targeted fiat attempt preparation not found.'); + } + if (record.status !== 'prepared') { + if (record.status === normalized.status && + stableJson(record.result) === stableJson(normalized)) { + return { + ok: true, + op: 'targetedFiatAttemptFinalized', + economic_op_id: normalized.economic_op_id, + attempt_id: normalized.attempt_id, + status: normalized.status, + idempotent: true, + }; + } + return new Error('Targeted fiat attempt is already terminal.'); + } + const request = record.preparation.request; + if (normalized.status === 'expired_pre_effect') { + if (request.kind !== 'stripe_transfer' || + normalized.at < record.preparation.quote_expires_at || + normalized.evidence.fx_quote_id !== request.fx_quote_id || + normalized.evidence.fx_quote_hash !== request.fx_quote_hash || + normalized.evidence.quote_expires_at !== + record.preparation.quote_expires_at) { + return new Error('Targeted fiat attempt expiry does not match its prepared quote.'); + } + } else { + const transfer = normalized.evidence; + const expectedKind = request.kind; + if (transfer.kind !== expectedKind || + transfer.destination !== request.destination || + transfer.source_currency !== request.source_currency || + transfer.source_amount_minor !== request.source_amount_minor || + (expectedKind === 'stripe_transfer' && + (transfer.destination_currency !== request.destination_currency || + BigInt(transfer.destination_amount_minor) < + BigInt(request.destination_amount_min_minor) || + BigInt(transfer.destination_amount_minor) > + BigInt(request.destination_amount_max_minor) || + transfer.fx_quote_id !== request.fx_quote_id || + transfer.fx_quote_hash !== request.fx_quote_hash || + transfer.transfer_group !== request.transfer_group))) { + return new Error('Targeted fiat attempt result does not match its prepared request.'); + } + const effectKey = this.targetedFiatAttemptEffectKey(transfer.ref); + const priorEffect = await this.get(effectKey); + if (priorEffect !== null) { + return new Error('Targeted fiat external effect was already finalized.'); + } + await this.put(effectKey, { + economic_op_id: normalized.economic_op_id, + attempt_id: normalized.attempt_id, + effect_id: transfer.ref, + consumed: false, + updated_at: this.tx, + }); + } + const terminal = { + ...record, + status: normalized.status, + result: normalized, + updated_at: this.tx, + }; + await this.put(recordKey, terminal); + await this.put(this.targetedFiatAttemptTailKey(normalized.economic_op_id), { + economic_op_id: normalized.economic_op_id, + attempt_id: normalized.attempt_id, + attempt_no: record.attempt_no, + status: normalized.status, + updated_at: this.tx, + }); + return { + ok: true, + op: 'targetedFiatAttemptFinalized', + economic_op_id: normalized.economic_op_id, + attempt_id: normalized.attempt_id, + status: normalized.status, + idempotent: false, + }; + } + + normalizeTargetedTnkOutputSettlementValue(value) { + const shapeError = this.validateExactObjectKeys( + value, + [ + 'op', + 'contract_version', + 'rail', + 'epoch', + 'at', + 'epoch_apply_hash', + 'plan_root', + 'economic_op_id', + 'output_index', + 'preparation_id', + 'external_effect_id', + 'msb_transfer', + 'admin', + 'admin_sig', + ], + 'targeted TNK output settlement' + ); + if (shapeError) return shapeError; + const transfer = this.normalizeMsbTransferEvidence( + value.msb_transfer, + 'targeted TNK output transfer' + ); + if (transfer instanceof Error) return transfer; + if (value.op !== 'settle_targeted_tnk_output' || + value.contract_version !== CONTRACT_VERSION || + value.rail !== 'tnk' || + !Number.isSafeInteger(value.epoch) || + value.epoch < 1 || + !Number.isSafeInteger(value.at) || + value.at < 0 || + !this.isHexBytes(value.epoch_apply_hash, 32) || + value.epoch_apply_hash !== value.epoch_apply_hash.toLowerCase() || + !this.isHexBytes(value.plan_root, 32) || + value.plan_root !== value.plan_root.toLowerCase() || + !this.isHexBytes(value.economic_op_id, 32) || + value.economic_op_id !== value.economic_op_id.toLowerCase() || + !Number.isSafeInteger(value.output_index) || + value.output_index < 0 || + value.preparation_id !== value.economic_op_id || + !this.isHexBytes(value.external_effect_id, 32) || + value.external_effect_id !== transfer.tx_hash || + !this.isHexBytes(value.admin, 32) || + value.admin !== value.admin.toLowerCase() || + !this.isHexBytes(value.admin_sig, 64)) { + return new Error('Invalid targeted TNK output settlement.'); + } + const normalized = { + op: 'settle_targeted_tnk_output', + contract_version: CONTRACT_VERSION, + rail: 'tnk', + epoch: value.epoch, + at: value.at, + epoch_apply_hash: value.epoch_apply_hash, + plan_root: value.plan_root, + economic_op_id: value.economic_op_id, + output_index: value.output_index, + preparation_id: value.preparation_id, + external_effect_id: value.external_effect_id, + msb_transfer: transfer, + admin: value.admin, + admin_sig: value.admin_sig.toLowerCase(), + }; + return stableJson(normalized) === stableJson(value) + ? normalized + : new Error('Targeted TNK output settlement must be canonical.'); + } + + normalizeTargetedFiatOutputSettlementValue(value) { + const shapeError = this.validateExactObjectKeys( + value, + [ + 'op', + 'contract_version', + 'rail', + 'epoch', + 'at', + 'epoch_apply_hash', + 'plan_root', + 'economic_op_id', + 'output_index', + 'preparation_id', + 'attempt_id', + 'stripe_transfer', + 'admin', + 'admin_sig', + ], + 'targeted fiat output settlement' + ); + if (shapeError) return shapeError; + const transfer = this.normalizeStripeTransferEvidence( + value.stripe_transfer, + 'targeted fiat output transfer', + { expectedAttemptId: value.attempt_id } + ); + if (transfer instanceof Error) return transfer; + if (value.op !== 'settle_targeted_fiat_output' || + value.contract_version !== CONTRACT_VERSION || + value.rail !== 'fiat' || + !Number.isSafeInteger(value.epoch) || + value.epoch < 1 || + !Number.isSafeInteger(value.at) || + value.at < 0 || + !this.isHexBytes(value.epoch_apply_hash, 32) || + value.epoch_apply_hash !== value.epoch_apply_hash.toLowerCase() || + !this.isHexBytes(value.plan_root, 32) || + value.plan_root !== value.plan_root.toLowerCase() || + !this.isHexBytes(value.economic_op_id, 32) || + value.economic_op_id !== value.economic_op_id.toLowerCase() || + !Number.isSafeInteger(value.output_index) || + value.output_index < 0 || + value.preparation_id !== value.economic_op_id || + !this.isHexBytes(value.attempt_id, 32) || + value.attempt_id !== value.attempt_id.toLowerCase() || + !this.isHexBytes(value.admin, 32) || + value.admin !== value.admin.toLowerCase() || + !this.isHexBytes(value.admin_sig, 64)) { + return new Error('Invalid targeted fiat output settlement.'); + } + const normalized = { + op: 'settle_targeted_fiat_output', + contract_version: CONTRACT_VERSION, + rail: 'fiat', + epoch: value.epoch, + at: value.at, + epoch_apply_hash: value.epoch_apply_hash, + plan_root: value.plan_root, + economic_op_id: value.economic_op_id, + output_index: value.output_index, + preparation_id: value.preparation_id, + attempt_id: value.attempt_id, + stripe_transfer: transfer, + admin: value.admin, + admin_sig: value.admin_sig.toLowerCase(), + }; + return stableJson(normalized) === stableJson(value) + ? normalized + : new Error('Targeted fiat output settlement must be canonical.'); + } + + async targetedPayoutPlannedOutput(value, rail) { + const plan = await this.get( + this.targetedPayoutEpochPlanRecordKey(rail, value.epoch) + ); + const output = plan?.value?.outputs?.[value.output_index]; + if (!plan || + plan.plan_root !== value.plan_root || + plan.value.epoch_apply_hash !== value.epoch_apply_hash || + output?.economic_op_id !== value.economic_op_id) { + return new Error('Targeted output settlement does not match its epoch plan.'); + } + return { plan, output }; + } + + async targetedPayoutPreparationForOutput(value, output, rail) { + const preparation = await this.get( + this.payoutPreparationRecordKey(rail, value.preparation_id) + ); + if (!preparation || + preparation.type !== 'targeted_payout_preparation' || + preparation.consumed !== false || + preparation.economic_op_id !== value.economic_op_id || + preparation.epoch !== value.epoch || + preparation.epoch_apply_hash !== value.epoch_apply_hash || + preparation.output_index !== value.output_index || + preparation.payload?.plan_root !== value.plan_root || + stableJson(preparation.payload?.output) !== stableJson(output)) { + return new Error('Targeted output settlement requires its unconsumed preparation.'); + } + return preparation; + } + + async applyTargetedTnkOutputFeature(key, value) { + const normalized = this.normalizeTargetedTnkOutputSettlementValue(value); + if (normalized instanceof Error) return normalized; + const expectedKey = await this.targetedTnkOutputFeatureKey(normalized); + if (expectedKey instanceof Error) return expectedKey; + if (key !== expectedKey) return new Error('Invalid targeted TNK output feature key.'); + const adminError = await this.requireTargetedPayoutAdminSignature( + normalized, + 'Targeted TNK output settlement' + ); + if (adminError) return adminError; + const recordKey = this.targetedPayoutOutputRecordKey( + 'tnk', + normalized.epoch, + normalized.economic_op_id + ); + const existing = await this.get(recordKey); + if (existing !== null) { + if (stableJson(existing.value) === stableJson(normalized)) { + return { + ok: true, + op: 'targetedTnkOutputSettled', + epoch: normalized.epoch, + economic_op_id: normalized.economic_op_id, + idempotent: true, + }; + } + return new Error('Targeted TNK output is already settled.'); + } + const planned = await this.targetedPayoutPlannedOutput(normalized, 'tnk'); + if (planned instanceof Error) return planned; + const preparation = await this.targetedPayoutPreparationForOutput( + normalized, + planned.output, + 'tnk' + ); + if (preparation instanceof Error) return preparation; + const transfer = normalized.msb_transfer; + const payload = preparation.payload; + if (preparation.external_effect_ids.length !== 1 || + preparation.external_effect_ids[0] !== normalized.external_effect_id || + payload.msb_tx_hash !== normalized.external_effect_id || + payload.msb_payload?.tro?.tx !== normalized.external_effect_id || + transfer.network !== payload.network || + transfer.from !== payload.treasury_from || + transfer.to !== planned.output.to || + transfer.amount_e18 !== planned.output.tnk_e18) { + return new Error('Targeted TNK output evidence does not match its signed preparation.'); + } + const rate = await this.guardianRequireHistoricalTnkRate(payload, normalized.at); + if (rate instanceof Error) return rate; + const expectedTnkE18 = this.auToTnkE18Ceil( + planned.output.au, + payload.rate_tnk_usd_au + ); + if (expectedTnkE18 instanceof Error || + expectedTnkE18.toString() !== planned.output.tnk_e18) { + return new Error('Targeted TNK output does not match its oracle rate.'); + } + if ((await this.get(this.msbTransferSeenKey(transfer))) !== null) { + return new Error('Targeted TNK transfer was already consumed.'); + } + const preparationReady = await this.validatePayoutPreparationConsumption( + [preparation] + ); + if (preparationReady instanceof Error) return preparationReady; + if (planned.output.role === 'provider') { + const updates = await this.targetedPayoutSettlementUpdates( + [planned.output], + 'tnk', + normalized.epoch, + normalized.at, + [transfer.tx_hash] + ); + if (updates instanceof Error) return updates; + for (const update of updates.liabilityUpdates) { + await this.put(update.key, update.value); + } + for (const earning of updates.earningUpdates) { + await this.put(this.earningKey(earning.provider, 'tnk'), earning); + } + } else { + const fee = await this.feeCumRecord('tnk'); + if (fee instanceof Error) return fee; + const payable = this.safeSubAu(fee.cum_au, fee.swept_cum_au); + if (payable instanceof Error || this.compareAu(payable, planned.output.au) < 0) { + return new Error('Targeted TNK fee output does not match canonical fee state.'); + } + const swept = this.safeAddAu(fee.swept_cum_au, planned.output.au); + if (swept instanceof Error) return swept; + const nextFee = { + ...fee, + swept_cum_au: swept, + updated_epoch: Math.max(fee.updated_epoch, normalized.epoch), + last_targeted_settlement_epoch: normalized.epoch, + last_targeted_settlement_transfer: transfer.tx_hash, + }; + const nextFeeError = this.guardianValidateFeeRecord(nextFee, 'tnk'); + if (nextFeeError) return nextFeeError; + await this.put(this.feeCumKey('tnk'), nextFee); + } + await this.put(this.msbTransferSeenKey(transfer), { + rail: 'tnk', + purpose: 'targeted_output_settlement', + epoch: normalized.epoch, + economic_op_id: normalized.economic_op_id, + consumed_at: this.tx, + }); + const preparationError = await this.consumePayoutPreparations( + [preparation], + recordKey + ); + if (preparationError) return preparationError; + await this.put(recordKey, { + type: 'targeted_tnk_output_settlement', + rail: 'tnk', + epoch: normalized.epoch, + economic_op_id: normalized.economic_op_id, + value: normalized, + settled_at: this.tx, + }); + return { + ok: true, + op: 'targetedTnkOutputSettled', + epoch: normalized.epoch, + economic_op_id: normalized.economic_op_id, + external_effect_id: normalized.external_effect_id, + idempotent: false, + }; + } + + async applyTargetedFiatOutputFeature(key, value) { + const normalized = this.normalizeTargetedFiatOutputSettlementValue(value); + if (normalized instanceof Error) return normalized; + const expectedKey = await this.targetedFiatOutputFeatureKey(normalized); + if (expectedKey instanceof Error) return expectedKey; + if (key !== expectedKey) return new Error('Invalid targeted fiat output feature key.'); + const adminError = await this.requireTargetedPayoutAdminSignature( + normalized, + 'Targeted fiat output settlement' + ); + if (adminError) return adminError; + const recordKey = this.targetedPayoutOutputRecordKey( + 'fiat', + normalized.epoch, + normalized.economic_op_id + ); + const existing = await this.get(recordKey); + if (existing !== null) { + if (stableJson(existing.value) === stableJson(normalized)) { + return { + ok: true, + op: 'targetedFiatOutputSettled', + epoch: normalized.epoch, + economic_op_id: normalized.economic_op_id, + idempotent: true, + }; + } + return new Error('Targeted fiat output is already settled.'); + } + const planned = await this.targetedPayoutPlannedOutput(normalized, 'fiat'); + if (planned instanceof Error) return planned; + const preparation = await this.targetedPayoutPreparationForOutput( + normalized, + planned.output, + 'fiat' + ); + if (preparation instanceof Error) return preparation; + const attemptKey = this.targetedFiatAttemptRecordKey( + normalized.economic_op_id, + normalized.attempt_id + ); + const attempt = await this.get(attemptKey); + const transfer = normalized.stripe_transfer; + if (!attempt || + attempt.status !== 'succeeded' || + attempt.consumed !== false || + stableJson(attempt.result?.evidence) !== stableJson(transfer)) { + return new Error('Targeted fiat output requires a succeeded canonical attempt.'); + } + const attemptEffectKey = this.targetedFiatAttemptEffectKey(transfer.ref); + const attemptEffect = await this.get(attemptEffectKey); + if (!attemptEffect || + attemptEffect.economic_op_id !== normalized.economic_op_id || + attemptEffect.attempt_id !== normalized.attempt_id || + attemptEffect.consumed !== false) { + return new Error('Targeted fiat attempt effect lock mismatch.'); + } + for (const seenKey of [ + this.stripeTransferSeenKey(transfer), + this.stripeFxQuoteSeenKey(transfer), + this.stripeDestinationPaymentSeenKey(transfer), + ]) { + if (seenKey && (await this.get(seenKey)) !== null) { + return new Error('Targeted Stripe transfer evidence was already consumed.'); + } + } + const preparationReady = await this.validatePayoutPreparationConsumption( + [preparation] + ); + if (preparationReady instanceof Error) return preparationReady; + if (planned.output.role === 'provider') { + const updates = await this.targetedPayoutSettlementUpdates( + [planned.output], + 'fiat', + normalized.epoch, + normalized.at, + [transfer.ref] + ); + if (updates instanceof Error) return updates; + for (const update of updates.liabilityUpdates) { + await this.put(update.key, update.value); + } + for (const earning of updates.earningUpdates) { + await this.put(this.earningKey(earning.provider, 'fiat'), earning); + } + } else { + const fee = await this.feeCumRecord('fiat'); + if (fee instanceof Error) return fee; + const payable = this.safeSubAu(fee.cum_au, fee.swept_cum_au); + if (payable instanceof Error || + this.compareAu(payable, planned.output.liability_au) < 0 || + planned.output.paid_au !== planned.output.liability_au) { + return new Error('Targeted fiat fee output does not match canonical fee state.'); + } + const swept = this.safeAddAu(fee.swept_cum_au, planned.output.paid_au); + if (swept instanceof Error) return swept; + const nextFee = { + ...fee, + swept_cum_au: swept, + updated_epoch: Math.max(fee.updated_epoch, normalized.epoch), + last_targeted_settlement_epoch: normalized.epoch, + last_targeted_settlement_transfer: transfer.ref, + }; + const nextFeeError = this.guardianValidateFeeRecord(nextFee, 'fiat'); + if (nextFeeError) return nextFeeError; + await this.put(this.feeCumKey('fiat'), nextFee); + } + for (const seenKey of [ + this.stripeTransferSeenKey(transfer), + this.stripeFxQuoteSeenKey(transfer), + this.stripeDestinationPaymentSeenKey(transfer), + ]) { + if (!seenKey) continue; + await this.put(seenKey, { + rail: 'fiat', + purpose: 'targeted_output_settlement', + epoch: normalized.epoch, + economic_op_id: normalized.economic_op_id, + consumed_at: this.tx, + }); + } + const preparationError = await this.consumePayoutPreparations( + [preparation], + recordKey + ); + if (preparationError) return preparationError; + await this.put(attemptKey, { + ...attempt, + consumed: true, + consumed_by: recordKey, + updated_at: this.tx, + }); + await this.put(attemptEffectKey, { + ...attemptEffect, + consumed: true, + consumed_by: recordKey, + updated_at: this.tx, + }); + await this.put(recordKey, { + type: 'targeted_fiat_output_settlement', + rail: 'fiat', + epoch: normalized.epoch, + economic_op_id: normalized.economic_op_id, + value: normalized, + settled_at: this.tx, + }); + return { + ok: true, + op: 'targetedFiatOutputSettled', + epoch: normalized.epoch, + economic_op_id: normalized.economic_op_id, + external_effect_id: transfer.ref, + idempotent: false, + }; + } + + normalizeCloseTargetedPayoutEpochValue(value) { + const shapeError = this.validateExactObjectKeys( + value, + [ + 'op', + 'contract_version', + 'rail', + 'epoch', + 'at', + 'epoch_apply_hash', + 'plan_root', + 'admin', + 'admin_sig', + ], + 'targeted payout epoch close' + ); + if (shapeError) return shapeError; + if (value.op !== 'close_targeted_payout_epoch' || + value.contract_version !== CONTRACT_VERSION || + !['fiat', 'tnk'].includes(value.rail) || + !Number.isSafeInteger(value.epoch) || + value.epoch < 1 || + !Number.isSafeInteger(value.at) || + value.at < 0 || + !this.isHexBytes(value.epoch_apply_hash, 32) || + value.epoch_apply_hash !== value.epoch_apply_hash.toLowerCase() || + !this.isHexBytes(value.plan_root, 32) || + value.plan_root !== value.plan_root.toLowerCase() || + !this.isHexBytes(value.admin, 32) || + value.admin !== value.admin.toLowerCase() || + !this.isHexBytes(value.admin_sig, 64)) { + return new Error('Invalid targeted payout epoch close.'); + } + const normalized = { + op: 'close_targeted_payout_epoch', + contract_version: CONTRACT_VERSION, + rail: value.rail, + epoch: value.epoch, + at: value.at, + epoch_apply_hash: value.epoch_apply_hash, + plan_root: value.plan_root, + admin: value.admin, + admin_sig: value.admin_sig.toLowerCase(), + }; + return stableJson(normalized) === stableJson(value) + ? normalized + : new Error('Targeted payout epoch close must be canonical.'); + } + + async applyCloseTargetedPayoutEpochFeature(key, value) { + const normalized = this.normalizeCloseTargetedPayoutEpochValue(value); + if (normalized instanceof Error) return normalized; + const expectedKey = await this.closeTargetedPayoutEpochFeatureKey(normalized); + if (expectedKey instanceof Error) return expectedKey; + if (key !== expectedKey) return new Error('Invalid targeted payout epoch close key.'); + const adminError = await this.requireTargetedPayoutAdminSignature( + normalized, + 'Targeted payout epoch close' + ); + if (adminError) return adminError; + const recordKey = this.targetedPayoutEpochCloseRecordKey( + normalized.rail, + normalized.epoch + ); + const existing = await this.get(recordKey); + if (existing !== null) { + if (stableJson(existing.value) === stableJson(normalized)) { + return { + ok: true, + op: 'targetedPayoutEpochClosed', + rail: normalized.rail, + epoch: normalized.epoch, + idempotent: true, + }; + } + return new Error('Targeted payout epoch is already closed.'); + } + const plan = await this.get( + this.targetedPayoutEpochPlanRecordKey(normalized.rail, normalized.epoch) + ); + if (!plan || + plan.plan_root !== normalized.plan_root || + plan.value.epoch_apply_hash !== normalized.epoch_apply_hash) { + return new Error('Targeted payout epoch close does not match its plan.'); + } + for (const output of plan.value.outputs) { + const settled = await this.get( + this.targetedPayoutOutputRecordKey( + normalized.rail, + normalized.epoch, + output.economic_op_id + ) + ); + if (!settled || + settled.economic_op_id !== output.economic_op_id || + settled.value.plan_root !== normalized.plan_root) { + return new Error('Targeted payout epoch has unsettled planned outputs.'); + } + } + await this.put(recordKey, { + type: 'targeted_payout_epoch_close', + rail: normalized.rail, + epoch: normalized.epoch, + plan_root: normalized.plan_root, + outcome: plan.value.outcome, + output_count: plan.value.outputs.length, + carry_count: plan.value.carry.length, + outputs_root: plan.value.outputs_root, + carry_root: plan.value.carry_root, + value: normalized, + closed_at: this.tx, + }); + return { + ok: true, + op: 'targetedPayoutEpochClosed', + rail: normalized.rail, + epoch: normalized.epoch, + outcome: plan.value.outcome, + output_count: plan.value.outputs.length, + carry_count: plan.value.carry.length, + idempotent: false, + }; + } + + async applyTargetedTnkSettlementFeature(key, value) { + const expectedKey = await this.targetedTnkSettlementFeatureKey(value); + if (expectedKey instanceof Error) return expectedKey; + if (key !== expectedKey) return new Error('Invalid targeted TNK settlement key.'); + const previousTx = this.tx; + this.tx = key; + try { + return await this.targetedTnkSettlement(value); + } finally { + this.tx = previousTx; + } + } + + async applyTargetedTapSettlementFeature(key, value) { + const expectedKey = await this.targetedTapSettlementFeatureKey(value); + if (expectedKey instanceof Error) return expectedKey; + if (key !== expectedKey) return new Error('Invalid targeted TAP settlement key.'); + const previousTx = this.tx; + this.tx = key; + try { + return await this.targetedTapSettlement(value); + } finally { + this.tx = previousTx; + } + } + + async applyTargetedFiatSettlementFeature(key, value) { + const expectedKey = await this.targetedFiatSettlementFeatureKey(value); + if (expectedKey instanceof Error) return expectedKey; + if (key !== expectedKey) return new Error('Invalid targeted fiat settlement key.'); + const previousTx = this.tx; + this.tx = key; + try { + return await this.targetedFiatSettlement(value); + } finally { + this.tx = previousTx; + } + } + + async applyFiatDustSweepFeature(key, value) { + const expectedKey = await this.fiatDustSweepFeatureKey(value); + if (expectedKey instanceof Error) return expectedKey; + if (key !== expectedKey) return; + + const previousTx = this.tx; + this.tx = key; + try { + return await this.fiatDustSweep(); + } finally { + this.tx = previousTx; + } + } + + async applyReputationAnchorFeature(key, value) { + const expectedKey = await this.reputationAnchorFeatureKey(value); + if (expectedKey instanceof Error) return expectedKey; + if (key !== expectedKey) return; + + const previousTx = this.tx; + const previousValue = this.value; + this.tx = key; + this.value = value; + try { + return await this.anchorReputation(); + } finally { + this.tx = previousTx; + this.value = previousValue; + } + } + + async applyTier3MeasurementFeature(key, value) { + const expectedKey = await this.tier3MeasurementFeatureKey(value); + if (expectedKey instanceof Error) return expectedKey; + if (key !== expectedKey) return; + + const previousTx = this.tx; + const previousValue = this.value; + this.tx = key; + this.value = value; + try { + return await this.tier3BlessMeasurement(); + } finally { + this.tx = previousTx; + this.value = previousValue; + } + } + + async tier3BlessMeasurement() { + const adminError = await this.requireAdmin(); + if (adminError) return adminError; + const validationError = this.validateTier3MeasurementBlessValue(this.value); + if (validationError) return validationError; + + const platform = this.value.platform; + const layer = this.value.layer; + const measurementName = this.value.measurement_name; + const measurement = this.value.measurement.toLowerCase(); + const key = `tier3/measurement/${platform}`; + const current = await this.get(key); + const record = current + ? cloneValue(current) + : { + schema_version: 1, + platform, + entries: [], + measurements: {}, + created_at: this.tx, + created_by: this.address, + }; + if (record.platform !== platform) return new Error('Tier-3 measurement platform mismatch.'); + if (!Array.isArray(record.entries)) record.entries = []; + if (!record.measurements || typeof record.measurements !== 'object' || Array.isArray(record.measurements)) { + record.measurements = {}; + } + if (!record.measurements[layer] || typeof record.measurements[layer] !== 'object' || Array.isArray(record.measurements[layer])) { + record.measurements[layer] = {}; + } + + const already = record.entries.find((entry) => + entry.layer === layer && entry.measurement_name === measurementName && entry.measurement === measurement + ); + if (already) { + return { + ok: true, + op: 'tier3BlessMeasurement', + platform, + layer, + measurement_name: measurementName, + measurement, + status: 'already_blessed', + }; + } + + const entry = { + layer, + measurement_name: measurementName, + measurement, + effective_epoch: this.value.effective_epoch, + region: this.value.region ?? null, + derivation_hash: this.value.derivation_hash ?? null, + source: this.value.source ?? 'admin-derive', + blessed_at: this.tx, + blessed_by: this.address, + }; + record.entries.push(entry); + const values = Array.isArray(record.measurements[layer][measurementName]) + ? record.measurements[layer][measurementName] + : []; + if (!values.includes(measurement)) values.push(measurement); + values.sort(); + record.measurements[layer][measurementName] = values; + record.updated_at = this.tx; + record.updated_by = this.address; + await this.put(key, record); + await this.put(`tier3/measurement/${platform}/${layer}/${measurementName}/${measurement}`, entry); + console.log('mayhem tier3BlessMeasurement', entry); + return { + ok: true, + op: 'tier3BlessMeasurement', + platform, + layer, + measurement_name: measurementName, + measurement, + status: 'blessed', + }; + } + + async setRules() { + const adminError = await this.requireAdmin(); + if (adminError) return adminError; + + const current = await this.currentRules(); + if (current && this.value.ver <= current.ver) { + return new Error('Rules version must increase.'); + } + + const rules = { + ver: this.value.ver, + hash: this.value.hash, + set_by: this.address, + set_by_role: 'admin', + activated_at: this.tx, + }; + await this.put(`rules/${rules.ver}`, rules); + await this.put(CURRENT_RULES_KEY, rules); + if ((await this.get('epoch/apply/state')) === null) { + await this.put('epoch/apply/state', { + updated_epoch: 0, + updated_at: null, + last_apply_hash: null, + }); + } + console.log('mayhem setRules', rules); + return { ok: true, op: 'setRules', rules }; + } + + async setParams() { + const adminError = await this.requireAdmin(); + if (adminError) return adminError; + + const governanceParams = await this.activeParamsAt(this.value.submitted_at, [ + 'param_activation_delay_seconds', + ]); + if ( + this.value.effective_at - this.value.submitted_at < + governanceParams.param_activation_delay_seconds + ) { + return new Error('Parameter changes require at least the active param_activation_delay_seconds.'); + } + + const valuesError = this.validateParamValues(this.value.values); + if (valuesError) return valuesError; + const normalizedValues = this.normalizeParamValues(this.value.values); + if (normalizedValues instanceof Error) return normalizedValues; + + const existingAtEffective = await this.activeParamsAt(this.value.effective_at); + const mergedAtEffective = { ...existingAtEffective, ...normalizedValues }; + const boundsError = this.validateParamBounds(mergedAtEffective); + if (boundsError) return boundsError; + + const meta = await this.get('params/current'); + const ver = meta ? meta.ver + 1 : 1; + const keys = Object.keys(normalizedValues).sort(); + const update = { + ver, + values: cloneValue(normalizedValues), + submitted_at: this.value.submitted_at, + effective_at: this.value.effective_at, + set_by: this.address, + set_by_role: 'admin', + tx: this.tx, + }; + + for (const key of keys) { + const record = await this.paramRecord(key); + if (record.pending && record.pending.effective_at > this.value.submitted_at) { + return new Error(`Pending parameter change already scheduled for ${key}.`); + } + + const current = this.paramActiveEntry(record, this.value.submitted_at); + const updated = { + key, + current, + pending: { + value: normalizedValues[key], + ver, + submitted_at: this.value.submitted_at, + effective_at: this.value.effective_at, + set_by: this.address, + set_by_role: 'admin', + set_at: this.tx, + }, + }; + await this.put(`params/${key}`, updated); + } + + await this.put(`params/update/${ver}`, update); + await this.put('params/current', { + ver, + keys, + set_by: this.address, + set_by_role: 'admin', + updated_at: this.tx, + effective_at: this.value.effective_at, + }); + console.log('mayhem setParams', update); + return { ok: true, op: 'setParams', ver, effective_at: this.value.effective_at, keys }; + } + + async setPayments() { + const adminError = await this.requireAdmin(); + if (adminError) return adminError; + + const normalized = this.normalizePaymentConfig(this.value); + if (normalized instanceof Error) return normalized; + const current = await this.get('payments/current'); + if (current && this.value.ver <= current.ver) { + return new Error('Payment config version must increase.'); + } + const record = { + denom: PRICE_DENOMINATION, + rails: PROVIDER_ACCEPTED_RAIL_ORDER.slice(), + ...normalized, + ver: this.value.ver, + updated_at: this.tx, + set_by: this.address, + set_by_role: 'admin', + }; + await this.put('payments/current', record); + console.log('mayhem setPayments', record); + return { ok: true, op: 'setPayments', ver: record.ver }; + } + + async readParams() { + const keys = this.value.keys ?? Object.keys(PARAM_DEFINITIONS); + const keyError = this.validateParamKeys(keys); + if (keyError) return keyError; + + const params = await this.activeParamsAt(this.value.at, keys); + console.log('mayhem readParams', { at: this.value.at, params }); + return { ok: true, op: 'readParams', at: this.value.at, params }; + } + + async setCtxBrackets() { + const adminError = await this.requireAdmin(); + if (adminError) return adminError; + + const governanceParams = await this.activeParamsAt(this.value.submitted_at, [ + 'param_activation_delay_seconds', + ]); + if ( + this.value.effective_at - this.value.submitted_at < + governanceParams.param_activation_delay_seconds + ) { + return new Error('Context bracket changes require at least the active param_activation_delay_seconds.'); + } + + const brackets = this.normalizeCtxBracketTable(this.value.brackets); + if (brackets instanceof Error) return brackets; + + const schedule = await this.ctxBracketSchedule(); + if (schedule.pending && schedule.pending.effective_at > this.value.submitted_at) { + return new Error('Pending context bracket table already scheduled.'); + } + const latest = this.ctxBracketLatestEntry(schedule); + const record = { + ver: latest.ver + 1, + brackets, + submitted_at: this.value.submitted_at, + effective_at: this.value.effective_at, + effective_from: this.tx, + updated_at: this.tx, + set_by: this.address, + set_by_role: 'admin', + }; + const updated = { + current: schedule.current, + pending: schedule.pending, + }; + if (updated.pending && updated.pending.effective_at <= this.value.effective_at) { + updated.current = updated.pending; + updated.pending = null; + } + if (updated.pending) return new Error('Pending context bracket table already scheduled.'); + updated.pending = record; + + await this.put('ctx_brackets', updated); + await this.put(`ctx_brackets/v/${record.ver}`, record); + console.log('mayhem setCtxBrackets', record); + return { ok: true, op: 'setCtxBrackets', ver: record.ver, effective_at: record.effective_at }; + } + + async readCtxBrackets() { + if (hasOwn(this.value, 'ver') && hasOwn(this.value, 'at')) { + return new Error('Read context brackets by either ver or at, not both.'); + } + const table = hasOwn(this.value, 'ver') + ? await this.ctxBracketTableByVersion(this.value.ver) + : await this.ctxBracketTableAt(this.value.at ?? 0); + if (table instanceof Error) return table; + console.log('mayhem readCtxBrackets', table); + return { ok: true, op: 'readCtxBrackets', table }; + } + + async consent() { + const rules = await this.currentRules(); + if (!rules) return new Error('Rules are not set.'); + if (this.value.ver !== rules.ver || this.value.hash !== rules.hash) { + return new Error('Consent must match the current rules.'); + } + if (!this.verifyConsentSignature(this.address, this.value.ver, this.value.hash, this.value.sig)) { + return new Error('Invalid consent signature.'); + } + + const record = { + ver: this.value.ver, + hash: this.value.hash, + at: this.tx, + }; + await this.put(`consent/${this.address}`, record); + console.log('mayhem consent', { address: this.address, ...record }); + return { ok: true, op: 'consent', address: this.address, ...record }; + } + + async registerProvider() { + const shapeError = this.validateExactCommandValue(['op'], 'register_provider'); + if (shapeError) return shapeError; + if (this.value.op !== 'register_provider') return new Error('Invalid provider registration op.'); + + return this.applyRegisterProvider(this.address, this.tx); + } + + async applyRegisterProvider(providerId, stamp) { + const consentError = await this.requireConsent(providerId); + if (consentError) return consentError; + + const auditor = await this.get(`auditor/${providerId}`); + if (auditor?.status === 'active') { + return new Error('Auditor keys cannot register as providers.'); + } + + const key = `prov/${providerId}`; + if ((await this.get(key)) !== null) return new Error('Provider already registered.'); + + const record = { + provider: providerId, + accepted_rails: ['fiat'], + accepted_rails_schema_version: PROVIDER_RAIL_SCHEMA_VERSION, + accepted_rails_set_by: providerId, + accepted_rails_set_at: stamp, + status: 'active', + enclaves: [], + probation: { + since: stamp, + since_seconds: 0, + successful_sessions: 0, + }, + registered_at: stamp, + updated_at: stamp, + }; + await this.put(key, record); + console.log('mayhem registerProvider', record); + return { ok: true, op: 'registerProvider', provider: providerId }; + } + + normalizeProviderAcceptedRails(rails) { + if (!Array.isArray(rails) || rails.length === 0) { + return new Error('Provider accepted rails cannot be empty.'); + } + const accepted = []; + for (const rawRail of rails) { + if (typeof rawRail !== 'string') return new Error('Invalid provider rail.'); + const rail = rawRail.toLowerCase(); + if (!PROVIDER_ACCEPTED_RAILS.has(rail)) { + return new Error('Unsupported provider payment rail.'); + } + if (accepted.includes(rail)) return new Error('Duplicate provider payment rail.'); + accepted.push(rail); + } + if (accepted.length === 0) return new Error('Provider accepted rails cannot be empty.'); + accepted.sort( + (left, right) => + PROVIDER_ACCEPTED_RAIL_ORDER.indexOf(left) - PROVIDER_ACCEPTED_RAIL_ORDER.indexOf(right) + ); + return accepted; + } + + normalizeLedgerRail(value, label = 'ledger rail') { + if (typeof value !== 'string') return new Error(`Invalid ${label}.`); + const rail = value.toLowerCase(); + if (!PROVIDER_ACCEPTED_RAILS.has(rail)) return new Error(`Unsupported ${label}.`); + return rail; + } + + balanceKey(user, rail) { + return `bal/${user}/${rail}`; + } + + spendHoldKey(user, rail, epoch) { + return `hold/${rail}/${user}/${epoch}`; + } + + targetedSpendHoldKey(user, rail) { + return `hold/targeted-outstanding/${rail}/${user}`; + } + + targetedSpendSummaryKey(user, rail) { + return `hold/targeted-summary/${rail}/${user}`; + } + + targetedSpendLegacyReleaseSummaryKey(user, rail) { + return `hold/targeted-legacy-release/${rail}/${user}`; + } + + targetedSpendSessionKey(user, rail, reservationId) { + return `hold/targeted-session/${rail}/${user}/${reservationId}`; + } + + targetedSpendLegacySessionKey(user, rail, reservationId) { + return `hold/targeted-legacy-session/${rail}/${user}/${reservationId}`; + } + + targetedSpendSessionIndexKey(user, rail, sessionId) { + return `hold/targeted-session-index/${rail}/${user}/${sessionId}`; + } + + targetedSpendBillingAttemptKey(user, rail, billingId, billingAttempt) { + return `hold/targeted-billing/${rail}/${user}/${billingId}/${billingAttempt}`; + } + + receiptBillingKey(billingId) { + return `receipt/billing/${billingId}`; + } + + receiptReservationKey(reservationId) { + return `receipt/reservation/${reservationId}`; + } + + receiptReservationCloseKey(reservationId) { + return `receipt/reservation-close/${reservationId}`; + } + + receiptHeadKey(billingId, billingAttempt) { + return `receipt/head/${billingId}/${billingAttempt}`; + } + + receiptConsumedKey(billingId, billingAttempt) { + return `receipt/consumed/${billingId}/${billingAttempt}`; + } + + receiptEpochIndexKey(epoch) { + return `receipt/epoch/${epoch}/index`; + } + + receiptEpochPageKey(epoch, page) { + return `receipt/epoch/${epoch}/page/${page}`; + } + + disputeOpenCountKey(opener) { + return `disp/open/${opener}`; + } + + providerOpenDisputeCountKey(provider) { + return `disp/provider-open/${provider}`; + } + + async disputeOpenCount(key) { + const record = await this.get(key); + const count = record?.count ?? 0; + if (!Number.isSafeInteger(count) || count < 0) { + return new Error('Invalid open dispute count.'); + } + return count; + } + + async providerHasOpenDispute(provider) { + const count = await this.disputeOpenCount(this.providerOpenDisputeCountKey(provider)); + if (count instanceof Error) return count; + return count > 0; + } + + async closeDisputeCounts(dispute) { + const openerKey = this.disputeOpenCountKey(dispute.opened_by); + const providerKey = this.providerOpenDisputeCountKey(dispute.provider); + const openerCount = await this.disputeOpenCount(openerKey); + if (openerCount instanceof Error) return openerCount; + const providerCount = await this.disputeOpenCount(providerKey); + if (providerCount instanceof Error) return providerCount; + if (openerCount < 1 || providerCount < 1) { + return new Error('Open dispute count underflow.'); + } + await this.put(openerKey, { + opener: dispute.opened_by, + count: openerCount - 1, + updated_at: this.tx, + }); + await this.put(providerKey, { + provider: dispute.provider, + count: providerCount - 1, + updated_at: this.tx, + }); + return null; + } + + earningKey(provider, rail) { + return `earn/${rail}/${provider}`; + } + + feeCumKey(rail) { + return `fee/${rail}/cum`; + } + + burnCumKey(rail) { + return `burn/${rail}/cum`; + } + + async setProviderRails() { + const shapeError = this.validateExactCommandValue(['op', 'rails'], 'set_provider_rails'); + if (shapeError) return shapeError; + return await this.applySetProviderRails(this.address, this.value.rails, this.tx); + } + + async applySetProviderRails(providerId, acceptedRails, stamp) { + const consentError = await this.requireConsent(providerId); + if (consentError) return consentError; + const provider = await this.get(`prov/${providerId}`); + if (!provider || provider.status !== 'active') return new Error('Provider registration required.'); + const rails = this.normalizeProviderAcceptedRails(acceptedRails); + if (rails instanceof Error) return rails; + + const updated = { + ...provider, + accepted_rails: rails, + accepted_rails_schema_version: PROVIDER_RAIL_SCHEMA_VERSION, + accepted_rails_set_by: providerId, + accepted_rails_set_at: stamp, + updated_at: stamp, + }; + await this.put(`prov/${providerId}`, updated); + console.log('mayhem setProviderRails', updated); + return { ok: true, op: 'setProviderRails', provider: providerId, rails }; + } + + async setProviderKyb() { + const adminError = await this.requireAdmin(); + if (adminError) return adminError; + const shapeError = this.validateExactCommandValue( + [ + 'op', + 'provider', + 'legal_name', + 'jurisdiction', + 'proof_hash', + 'kyb_ref', + 'verified_at', + 'admin_sig', + ], + 'set_provider_kyb', + ['schema_version'] + ); + if (shapeError) return shapeError; + + const normalized = this.normalizeProviderKybValue(this.value); + if (normalized instanceof Error) return normalized; + if (!(await this.verifyProviderKybSignature(normalized))) { + return new Error('Invalid provider KYB admin signature.'); + } + + const key = `prov/${normalized.provider}`; + const provider = await this.get(key); + if (!provider) return new Error('Provider not found.'); + if (provider.status === 'banned') return new Error('Provider is banned.'); + const kybBanError = await this.rejectBannedProviderKyb(normalized); + if (kybBanError) return kybBanError; + + const record = { + status: 'verified', + provider: normalized.provider, + legal_name: normalized.legal_name, + jurisdiction: normalized.jurisdiction, + proof_hash: normalized.proof_hash, + kyb_ref: normalized.kyb_ref, + verified_at: normalized.verified_at, + verified_by: this.address, + verified_by_role: 'admin', + admin_sig: normalized.admin_sig, + schema_version: normalized.schema_version, + updated_at: this.tx, + }; + const providerSummary = { + status: 'verified', + legal_name: record.legal_name, + jurisdiction: record.jurisdiction, + proof_hash: record.proof_hash, + kyb_ref: record.kyb_ref, + verified_at: record.verified_at, + verified_by: record.verified_by, + verified_by_role: 'admin', + schema_version: record.schema_version, + set_at: this.tx, + }; + const updatedProvider = { + ...provider, + kyb: providerSummary, + updated_at: this.tx, + }; + + await this.put(`kyb/${normalized.provider}`, record); + await this.put(key, updatedProvider); + console.log('mayhem setProviderKyb', record); + return { + ok: true, + op: 'setProviderKyb', + provider: normalized.provider, + att_tier: 4, + }; + } + + async revokeProviderKyb() { + const adminError = await this.requireAdmin(); + if (adminError) return adminError; + const shapeError = this.validateExactCommandValue( + ['op', 'provider'], + 'revoke_provider_kyb', + ['reason_hash'] + ); + if (shapeError) return shapeError; + if (!this.isHexBytes(this.value.provider, 32)) return new Error('Invalid provider id.'); + if ( + this.value.reason_hash !== undefined && + !this.isHexBytes(this.value.reason_hash, 32) + ) { + return new Error('Invalid provider KYB revoke reason hash.'); + } + + const key = `prov/${this.value.provider}`; + const provider = await this.get(key); + if (!provider) return new Error('Provider not found.'); + const current = await this.get(`kyb/${this.value.provider}`); + if (!current || current.status !== 'verified') return new Error('Active provider KYB not found.'); + + const revoked = { + ...current, + status: 'revoked', + revoked_at: this.tx, + revoked_by: this.address, + revoked_by_role: 'admin', + revoke_reason_hash: this.value.reason_hash ?? null, + updated_at: this.tx, + }; + const updatedProvider = { + ...provider, + kyb: { + ...(provider.kyb ?? {}), + status: 'revoked', + revoked_at: this.tx, + revoked_by: this.address, + revoked_by_role: 'admin', + revoke_reason_hash: this.value.reason_hash ?? null, + }, + updated_at: this.tx, + }; + + await this.put(`kyb/${this.value.provider}`, revoked); + await this.put(key, updatedProvider); + const kybBanIndexError = await this.writeProviderKybBanIndexes(revoked, { + status: 'revoked', + source: 'revoke_provider_kyb', + provider: this.value.provider, + reason_hash: this.value.reason_hash ?? null, + recorded_at: this.tx, + recorded_by: this.address, + recorded_by_role: 'admin', + }); + if (kybBanIndexError instanceof Error) return kybBanIndexError; + console.log('mayhem revokeProviderKyb', revoked); + return { + ok: true, + op: 'revokeProviderKyb', + provider: this.value.provider, + }; + } + + async banProvider() { + const adminError = await this.requireAdmin(); + if (adminError) return adminError; + if (!this.isSafeKeyPart(this.value.provider)) return new Error('Invalid provider id.'); + if (this.value.device_key !== undefined && !this.isHexBytes(this.value.device_key, 32)) { + return new Error('Invalid provider device key.'); + } + if (this.value.hardware_fingerprint !== undefined && !this.isHexBytes(this.value.hardware_fingerprint, 32)) { + return new Error('Invalid provider hardware fingerprint.'); + } + + const key = `prov/${this.value.provider}`; + const record = await this.get(key); + if (!record) return new Error('Provider not found.'); + if (record.status === 'banned') return new Error('Provider already banned.'); + + const tombstones = await this.tombstoneProviderEnclaves( + this.value.provider, + this.providerActiveEnclaves(record), + this.value.reason_hash ?? null + ); + if (tombstones instanceof Error) return tombstones; + + const updated = { + ...record, + status: 'banned', + enclaves: [], + tombstoned_enclaves: tombstones.map((tombstone) => tombstone.enclave_id), + banned_at: this.tx, + banned_by: this.address, + banned_by_role: 'admin', + ban_reason_hash: this.value.reason_hash ?? null, + updated_at: this.tx, + }; + const providerBan = { + target_type: 'provider', + target: this.value.provider, + provider: this.value.provider, + status: 'banned', + reason_hash: this.value.reason_hash ?? null, + banned_at: this.tx, + banned_by: this.address, + banned_by_role: 'admin', + reversible: true, + }; + const deviceKey = this.value.device_key ?? record.device_key ?? null; + const fingerprint = this.value.hardware_fingerprint ?? record.hardware_fingerprint ?? null; + await this.put(key, updated); + await this.put(`ban/provider/${this.value.provider}`, providerBan); + if (deviceKey) { + await this.put(`ban/device/${deviceKey}`, { + target_type: 'device', + target: deviceKey, + provider: this.value.provider, + status: 'banned', + reason_hash: this.value.reason_hash ?? null, + banned_at: this.tx, + banned_by: this.address, + banned_by_role: 'admin', + reversible: true, + }); + } + if (fingerprint) { + const fpKey = `ban/fingerprint/${fingerprint}`; + const current = await this.get(fpKey); + const wallets = { + ...(current?.wallets ?? {}), + [this.value.provider]: { + provider: this.value.provider, + reason_hash: this.value.reason_hash ?? null, + banned_at: this.tx, + }, + }; + await this.put(fpKey, { + target_type: 'fingerprint', + target: fingerprint, + status: 'banned', + wallets, + reason_hash: this.value.reason_hash ?? null, + banned_at: current?.banned_at ?? this.tx, + updated_at: this.tx, + banned_by: this.address, + banned_by_role: 'admin', + reversible: true, + auto_reject: true, + }); + } + if (record.kyb?.status === 'verified') { + const kybBanIndexError = await this.writeProviderKybBanIndexes(record.kyb, { + status: 'banned', + source: 'ban_provider', + provider: this.value.provider, + reason_hash: this.value.reason_hash ?? null, + recorded_at: this.tx, + recorded_by: this.address, + recorded_by_role: 'admin', + }); + if (kybBanIndexError instanceof Error) return kybBanIndexError; + } + console.log('mayhem banProvider', updated); + return { + ok: true, + op: 'banProvider', + provider: this.value.provider, + tombstoned_enclaves: updated.tombstoned_enclaves, + device_key: deviceKey, + hardware_fingerprint: fingerprint, + }; + } + + async unban() { + const adminError = await this.requireAdmin(); + if (adminError) return adminError; + const shapeError = this.validateExactCommandValue( + ['op', 'target_type', 'target', 'reason_hash'], + 'unban' + ); + if (shapeError) return shapeError; + const targetType = String(this.value.target_type).toLowerCase(); + if (!BAN_TARGET_TYPES.has(targetType)) return new Error('Unsupported ban target type.'); + if (!this.isHexBytes(this.value.target, 32)) return new Error('Invalid ban target.'); + if (!this.isHexBytes(this.value.reason_hash, 32)) return new Error('Invalid unban reason hash.'); + + if (targetType === 'kyb') { + const provider = await this.get(`prov/${this.value.target}`); + if (!provider) return new Error('Provider not found.'); + const kyb = await this.get(`kyb/${this.value.target}`); + if (!kyb || kyb.status !== 'revoked') { + return new Error('Revoked provider KYB not found.'); + } + const keys = await this.kybBanIndexKeys(kyb); + if (keys instanceof Error) return keys; + const records = []; + for (const key of keys) { + const current = await this.get(key); + if (!current || current.target_type !== 'kyb' || current.reversible !== true) { + return new Error('Reversible provider KYB ban index not found.'); + } + if (!['banned', 'revoked', 'unbanned'].includes(current.status)) { + return new Error('Invalid provider KYB ban index status.'); + } + if (!current.providers?.[this.value.target]) { + return new Error('Provider KYB ban index does not bind this provider.'); + } + records.push([key, current]); + } + for (const [key, current] of records) { + await this.put(key, { + ...current, + status: 'unbanned', + unbanned_at: this.tx, + unbanned_by: this.address, + unbanned_by_role: 'admin', + unban_reason_hash: this.value.reason_hash, + reversible: true, + }); + } + return { + ok: true, + op: 'unban', + target_type: targetType, + target: this.value.target, + }; + } + + if (targetType === 'provider') { + const provider = await this.get(`prov/${this.value.target}`); + if (!provider) return new Error('Provider not found.'); + if (provider.status === 'banned') { + await this.put(`prov/${this.value.target}`, { + ...provider, + status: 'active', + unbanned_at: this.tx, + unbanned_by: this.address, + unbanned_by_role: 'admin', + unban_reason_hash: this.value.reason_hash, + updated_at: this.tx, + }); + } + } + + const key = this.banRecordKey(targetType, this.value.target); + const current = await this.get(key); + const record = { + ...(current ?? {}), + target_type: targetType, + target: this.value.target, + status: 'unbanned', + unbanned_at: this.tx, + unbanned_by: this.address, + unbanned_by_role: 'admin', + unban_reason_hash: this.value.reason_hash, + reversible: true, + }; + await this.put(key, record); + console.log('mayhem unban', record); + return { + ok: true, + op: 'unban', + target_type: targetType, + target: this.value.target, + }; + } + + async deviceRebind() { + const adminError = await this.requireAdmin(); + if (adminError) return adminError; + const shapeError = this.validateExactCommandValue( + ['op', 'device_key', 'provider', 'reason_hash'], + 'device_rebind' + ); + if (shapeError) return shapeError; + if (!this.isHexBytes(this.value.device_key, 32)) return new Error('Invalid device key.'); + if (!this.isHexBytes(this.value.provider, 32)) return new Error('Invalid provider id.'); + if (!this.isHexBytes(this.value.reason_hash, 32)) return new Error('Invalid rebind reason hash.'); + const provider = await this.get(`prov/${this.value.provider}`); + if (!provider || provider.status !== 'active') return new Error('Active provider not found.'); + const deviceBan = await this.get(`ban/device/${this.value.device_key}`); + if (deviceBan?.status === 'banned') return new Error('Device key is banned.'); + const current = await this.get(`device/${this.value.device_key}`); + const record = { + ...(current ?? {}), + device_key: this.value.device_key, + provider: this.value.provider, + status: 'active', + rebound_at: this.tx, + rebound_by: this.address, + rebound_by_role: 'admin', + rebind_reason_hash: this.value.reason_hash, + previous_provider: current?.provider ?? null, + }; + await this.put(`device/${this.value.device_key}`, record); + await this.put(`prov/${this.value.provider}`, { + ...provider, + device_key: this.value.device_key, + device_key_bound_at: this.tx, + updated_at: this.tx, + }); + console.log('mayhem deviceRebind', record); + return { + ok: true, + op: 'deviceRebind', + device_key: this.value.device_key, + provider: this.value.provider, + }; + } + + async setModelRef() { + const adminError = await this.requireAdmin(); + if (adminError) return adminError; + + const validationError = this.validateModelRef(this.value); + if (validationError) return validationError; + + const key = `modelref/${this.value.model_id}`; + const current = await this.get(key); + const calibration = Object.hasOwn(this.value, 'activity_calibration') + ? this.value.activity_calibration : current?.activity_calibration; + if (calibration) { + const error = this.validateActivityCalibration(calibration, this.modelClassFor(this.value), this.value.rate_map); + if (error) return error; + } + const record = { + model_id: this.value.model_id, + model_class: this.modelClassFor(this.value), + denom: PRICE_DENOMINATION, + rate_map: this.normalizeRateMap(this.value.rate_map), + ver: (current?.ver ?? 0) + 1, + source_hash: this.value.source_hash ?? null, + ...(calibration ? { + activity_calibration: cloneValue(calibration), + } : {}), + updated_at: this.tx, + set_by: this.address, + set_by_role: 'admin', + }; + await this.put(key, record); + console.log('mayhem setModelRef', record); + return { ok: true, op: 'setModelRef', model_id: record.model_id, ver: record.ver }; + } + + async publishCatalog() { + const adminError = await this.requireAdmin(); + if (adminError) return adminError; + + const validationError = this.validateCatalogRelease(this.value); + if (validationError) return validationError; + + const current = await this.get('catalog/current'); + const record = { + catalog_id: this.value.catalog_id, + source_kind: this.value.source_kind, + catalog_url: this.value.catalog_url, + signature_url: this.value.signature_url, + catalog_hash: this.value.catalog_hash, + signature_hash: this.value.signature_hash, + key_id: this.value.key_id, + public_key: this.value.public_key, + model_count: this.value.model_count, + artifact_count: this.value.artifact_count, + canaries: cloneValue(this.value.canaries), + ver: (current?.ver ?? 0) + 1, + supersedes: current?.catalog_hash ?? null, + status: 'active', + published_at: this.tx, + published_by: this.address, + published_by_role: 'admin', + }; + if (hasOwn(this.value, 'parts_anchor')) { + record.parts_anchor = cloneValue(this.value.parts_anchor); + } + if (hasOwn(this.value, 'blessed_runtimes')) { + record.blessed_runtimes = cloneValue(this.value.blessed_runtimes); + } + if (hasOwn(this.value, 'outcome_classes')) { + record.outcome_classes = cloneValue(this.value.outcome_classes); + } + await this.put(`catalog/release/${record.catalog_hash}`, record); + await this.put('catalog/current', record); + console.log('mayhem publishCatalog', record); + return { + ok: true, + op: 'publishCatalog', + catalog_hash: record.catalog_hash, + ver: record.ver, + }; + } + + async registerEnclave() { + const adminError = await this.requireAdmin(); + if (adminError) return adminError; + if (!this.isSafeKeyPart(this.value.enclave_id)) return new Error('Invalid enclave id.'); + if (!this.isSafeModelId(this.value.model_id)) return new Error('Invalid model id.'); + + const key = `enclave/${this.value.enclave_id}`; + if ((await this.get(key)) !== null) return new Error('Enclave already registered.'); + const modelClass = this.modelClassFor(this.value); + const classError = this.validateModelClass(modelClass, 'Enclave model_class'); + if (classError) return classError; + const tierError = this.validateLaunchEnclaveAttestationTier(this.value.att_tier); + if (tierError) return tierError; + const capsError = this.validateEnclaveCaps(this.value.caps, modelClass); + if (capsError) return capsError; + const artifactError = this.validateEnclaveArtifactBinding(this.value); + if (artifactError) return artifactError; + if (hasOwn(this.value, 'approved_binary_hashes') && !Array.isArray(this.value.approved_binary_hashes)) { + return new Error('Enclave approved_binary_hashes must be an array.'); + } + const approvedBinaryHashes = this.normalizeApprovedBinaryHashes( + this.value.binary_hash, + this.value.approved_binary_hashes + ); + const binaryHashesError = this.validateApprovedBinaryHashes( + this.value.binary_hash, + approvedBinaryHashes + ); + if (binaryHashesError) return binaryHashesError; + const launchMeasurements = this.normalizeEnclaveLaunchMeasurements(this.value.launch_measurements); + const measurementsError = this.validateEnclaveLaunchMeasurements(launchMeasurements, this.value.att_tier); + if (measurementsError) return measurementsError; + const quant = this.normalizeEnclaveQuant(this.value.quant ?? 'unknown'); + const quantError = this.validateEnclaveQuant(quant); + if (quantError) return quantError; + + const record = { + enclave_id: this.value.enclave_id, + model_id: this.value.model_id, + model_class: modelClass, + backend: this.value.backend, + artifact_root: this.value.artifact_root, + artifact_root_kind: this.value.artifact_root_kind, + artifact_source: cloneValue(this.value.artifact_source), + artifact_sidecars: cloneValue(this.value.artifact_sidecars ?? {}), + source_sha256: this.value.source_sha256 ?? null, + manifest_hash: this.value.manifest_hash, + att_tier: this.value.att_tier, + min_att_tier: this.value.att_tier, + pending_min_att_tier: null, + quant, + binary_hash: this.value.binary_hash, + approved_binary_hashes: approvedBinaryHashes, + launch_measurements: launchMeasurements, + caps: cloneValue(this.value.caps), + status: 'active', + providers: [], + created_by: this.address, + created_by_role: 'admin', + registered_at: this.tx, + updated_at: this.tx, + retired_at: null, + }; + await this.put(key, record); + console.log('mayhem registerEnclave', record); + return { ok: true, op: 'registerEnclave', enclave_id: record.enclave_id }; + } + + async updateEnclave() { + const adminError = await this.requireAdmin(); + if (adminError) return adminError; + if (!this.isSafeKeyPart(this.value.enclave_id)) return new Error('Invalid enclave id.'); + const allowedFields = new Set(['op', 'enclave_id', ...ENCLAVE_UPDATE_FIELDS]); + const unknownFields = Object.keys(this.value).filter((field) => !allowedFields.has(field)).sort(); + if (unknownFields.length > 0) { + return new Error(`update_enclave does not accept immutable fields: ${unknownFields.join(', ')}.`); + } + + const key = `enclave/${this.value.enclave_id}`; + const record = await this.get(key); + if (!record) return new Error('Enclave not found.'); + if (record.status === 'retired') return new Error('Enclave is retired.'); + + let changed = false; + const updated = cloneValue(record); + for (const field of ENCLAVE_UPDATE_FIELDS) { + if (!hasOwn(this.value, field)) continue; + updated[field] = cloneValue(this.value[field]); + changed = true; + } + if (!changed) return new Error('No enclave fields to update.'); + if (hasOwn(this.value, 'quant')) { + updated.quant = this.normalizeEnclaveQuant(updated.quant); + } + const modelClass = this.modelClassFor(updated); + const classError = this.validateModelClass(modelClass, 'Enclave model_class'); + if (classError) return classError; + const tierError = this.validateLaunchEnclaveAttestationTier(updated.att_tier); + if (tierError) return tierError; + const capsError = this.validateEnclaveCaps(updated.caps, modelClass); + if (capsError) return capsError; + const artifactError = this.validateEnclaveArtifactBinding(updated); + if (artifactError) return artifactError; + if (hasOwn(this.value, 'approved_binary_hashes')) { + if (!Array.isArray(this.value.approved_binary_hashes)) { + return new Error('Enclave approved_binary_hashes must be an array.'); + } + updated.approved_binary_hashes = this.normalizeApprovedBinaryHashes( + updated.binary_hash, + this.value.approved_binary_hashes + ); + } else { + updated.approved_binary_hashes = this.normalizeApprovedBinaryHashes( + updated.binary_hash, + [record.binary_hash, ...(record.approved_binary_hashes ?? [])] + ); + } + const binaryHashesError = this.validateApprovedBinaryHashes( + updated.binary_hash, + updated.approved_binary_hashes + ); + if (binaryHashesError) return binaryHashesError; + updated.launch_measurements = this.normalizeEnclaveLaunchMeasurements(updated.launch_measurements); + const measurementsError = this.validateEnclaveLaunchMeasurements(updated.launch_measurements, updated.att_tier); + if (measurementsError) return measurementsError; + const quantError = this.validateEnclaveQuant(updated.quant ?? 'unknown'); + if (quantError) return quantError; + updated.quant = this.normalizeEnclaveQuant(updated.quant ?? 'unknown'); + + updated.updated_by = this.address; + updated.updated_by_role = 'admin'; + updated.updated_at = this.tx; + await this.put(key, updated); + console.log('mayhem updateEnclave', updated); + return { ok: true, op: 'updateEnclave', enclave_id: updated.enclave_id }; + } + + async setEnclaveMinTier() { + const adminError = await this.requireAdmin(); + if (adminError) return adminError; + const shapeError = this.validateExactCommandValue( + [ + 'op', + 'enclave_id', + 'min_att_tier', + 'submitted_epoch', + 'effective_epoch', + 'submitted_at', + 'reason_hash', + ], + 'set_enclave_min_tier' + ); + if (shapeError) return shapeError; + if (!this.isSafeKeyPart(this.value.enclave_id)) return new Error('Invalid enclave id.'); + if (!this.isHexBytes(this.value.reason_hash, 32)) return new Error('Invalid min-tier reason hash.'); + + const tierError = this.validateLaunchEnclaveAttestationTier(this.value.min_att_tier); + if (tierError) return tierError; + if (this.value.effective_epoch <= this.value.submitted_epoch) { + return new Error('Enclave min-tier effective_epoch must be after submitted_epoch.'); + } + + const enclaveKey = `enclave/${this.value.enclave_id}`; + const enclave = await this.get(enclaveKey); + if (!enclave) return new Error('Enclave not found.'); + if (enclave.status !== 'active') return new Error('Enclave is not active.'); + const enclaveRoleError = this.requireAdminCreatedEnclave(enclave); + if (enclaveRoleError) return enclaveRoleError; + + const params = await this.activeParamsAt(this.value.submitted_at, ['min_tier_notice_epochs']); + if ( + this.value.effective_epoch - this.value.submitted_epoch < + params.min_tier_notice_epochs + ) { + return new Error('Enclave min-tier changes require at least min_tier_notice_epochs notice.'); + } + + const currentEpoch = await this.currentAppliedEpoch(); + const activePolicy = await this.enclaveMinTierPolicy(enclave, currentEpoch); + const pending = { + min_att_tier: this.value.min_att_tier, + previous_min_att_tier: activePolicy.min_att_tier, + submitted_epoch: this.value.submitted_epoch, + effective_epoch: this.value.effective_epoch, + submitted_at: this.value.submitted_at, + reason_hash: this.value.reason_hash, + scheduled_at: this.tx, + scheduled_by: this.address, + scheduled_by_role: 'admin', + }; + const record = { + enclave_id: this.value.enclave_id, + current_min_att_tier: activePolicy.min_att_tier, + current_epoch: currentEpoch, + pending, + updated_at: this.tx, + updated_by: this.address, + updated_by_role: 'admin', + }; + await this.put(`tierpolicy/enclave/${this.value.enclave_id}`, record); + await this.put(enclaveKey, { + ...enclave, + min_att_tier: activePolicy.min_att_tier, + pending_min_att_tier: pending, + updated_at: this.tx, + updated_by: this.address, + updated_by_role: 'admin', + }); + console.log('mayhem setEnclaveMinTier', record); + return { + ok: true, + op: 'setEnclaveMinTier', + enclave_id: this.value.enclave_id, + min_att_tier: this.value.min_att_tier, + effective_epoch: this.value.effective_epoch, + }; + } + + async retireEnclave() { + const adminError = await this.requireAdmin(); + if (adminError) return adminError; + if (!this.isSafeKeyPart(this.value.enclave_id)) return new Error('Invalid enclave id.'); + + const key = `enclave/${this.value.enclave_id}`; + const record = await this.get(key); + if (!record) return new Error('Enclave not found.'); + if (record.status === 'retired') return new Error('Enclave already retired.'); + + const activeProviders = this.enclaveActiveProviders(record); + const tombstones = await this.tombstoneEnclaveProviders( + this.value.enclave_id, + activeProviders, + null + ); + if (tombstones instanceof Error) return tombstones; + + const updated = { + ...record, + status: 'retired', + providers: [], + tombstoned_providers: tombstones + .filter((tombstone) => tombstone.serve_tombstoned) + .map((tombstone) => tombstone.provider), + retired_at: this.tx, + retired_by: this.address, + retired_by_role: 'admin', + updated_by: this.address, + updated_by_role: 'admin', + updated_at: this.tx, + }; + await this.put(key, updated); + console.log('mayhem retireEnclave', updated); + return { + ok: true, + op: 'retireEnclave', + enclave_id: updated.enclave_id, + tombstoned_providers: updated.tombstoned_providers, + }; + } + + async joinEnclave() { + const shapeError = this.validateExactCommandValue( + [ + 'op', + 'enclave_id', + 'att_tier', + 'attestation_head', + 'served_ctx', + 'served_modalities', + 'served_specialities', + 'ctx_bracket', + 'ctx_bracket_table_ver', + ], + 'join_enclave', + ['hardware_fingerprint', 'device_key'] + ); + if (shapeError) return shapeError; + if (!this.isSafeKeyPart(this.value.enclave_id)) return new Error('Invalid enclave id.'); + + return this.applyJoinEnclave( + this.address, + this.value.enclave_id, + this.tx, + this.value.att_tier, + this.value.attestation_head, + this.value.hardware_fingerprint ?? null, + this.value.device_key ?? null, + { + served_ctx: this.value.served_ctx, + served_modalities: this.value.served_modalities, + served_specialities: this.value.served_specialities, + ctx_bracket: this.value.ctx_bracket, + ctx_bracket_table_ver: this.value.ctx_bracket_table_ver, + } + ); + } + + async applyJoinEnclave( + providerId, + enclaveId, + stamp, + attTier, + attestationHead, + hardwareFingerprint = null, + deviceKey = null, + serveTerms = null + ) { + const consentError = await this.requireConsent(providerId); + if (consentError) return consentError; + const providerError = await this.requireProvider(providerId); + if (providerError) return providerError; + if (!Number.isSafeInteger(attTier) || attTier < 1 || attTier > MAX_LAUNCH_ENCLAVE_ATTESTATION_TIER) { + return new Error('Invalid provider attestation tier.'); + } + if (!this.isHexBytes(attestationHead, 32)) { + return new Error('Invalid provider attestation head.'); + } + if (hardwareFingerprint !== null && !this.isHexBytes(hardwareFingerprint, 32)) { + return new Error('Invalid provider hardware fingerprint.'); + } + if (deviceKey !== null && !this.isHexBytes(deviceKey, 32)) { + return new Error('Invalid provider device key.'); + } + + const enclave = await this.get(`enclave/${enclaveId}`); + if (!enclave) return new Error('Enclave not found.'); + if (enclave.status !== 'active') return new Error('Enclave is not active.'); + const enclaveRoleError = this.requireAdminCreatedEnclave(enclave); + if (enclaveRoleError) return enclaveRoleError; + const normalizedServeTerms = await this.normalizeProviderServeTerms( + enclaveId, + serveTerms, + 'provider serve' + ); + if (normalizedServeTerms instanceof Error) return normalizedServeTerms; + const priceError = await this.requireCurrentAdminPrice( + enclaveId, + normalizedServeTerms?.ctx_bracket ?? null + ); + if (priceError) return priceError; + const minTierPolicy = await this.enclaveMinTierPolicy(enclave); + if (attTier !== enclave.att_tier) { + return new Error( + `Provider attestation tier ${attTier} does not match enclave tier ${enclave.att_tier}.` + ); + } + const provider = await this.get(`prov/${providerId}`); + if (!provider || provider.status !== 'active') return new Error('Provider registration required.'); + const effectiveTier = provider.kyb?.status === 'verified' ? 4 : attTier; + if (effectiveTier < minTierPolicy.min_att_tier) { + return new Error( + `Enclave now requires minimum attestation tier ${minTierPolicy.min_att_tier}; provider proved tier ${effectiveTier}.` + ); + } + + const key = `serve/${providerId}/${enclaveId}`; + const existing = await this.get(key); + if (existing && existing.status === 'active') return new Error('Provider already serving enclave.'); + if (deviceKey !== null) { + const deviceBan = await this.get(`ban/device/${deviceKey}`); + if (deviceBan?.status === 'banned') return new Error('Provider device key is banned.'); + const binding = await this.get(`device/${deviceKey}`); + if (binding?.provider && binding.provider !== providerId) { + return new Error('Provider device key is bound to a different wallet; admin rebind required.'); + } + } + + if (hardwareFingerprint !== null) { + const fingerprintBan = await this.get(`ban/fingerprint/${hardwareFingerprint}`); + if (fingerprintBan?.status === 'banned') { + return new Error('Provider hardware fingerprint is banned.'); + } + } + + const record = { + provider: providerId, + enclave_id: enclaveId, + model_id: enclave.model_id, + status: 'active', + att_tier: attTier, + effective_att_tier: effectiveTier, + attestation_head: attestationHead.toLowerCase(), + ...(normalizedServeTerms ?? {}), + ...(hardwareFingerprint !== null ? { hardware_fingerprint: hardwareFingerprint } : {}), + ...(deviceKey !== null ? { device_key: deviceKey } : {}), + joined_at: existing?.joined_at ?? stamp, + updated_at: stamp, + left_at: null, + rooms: Array.isArray(existing?.rooms) ? existing.rooms.slice() : [], + }; + await this.put(key, record); + await this.put(`enclave/${enclaveId}`, { + ...enclave, + providers: this.enclaveProvidersWith(enclave, providerId), + updated_at: stamp, + }); + if (deviceKey !== null) { + const currentDevice = await this.get(`device/${deviceKey}`); + await this.put(`device/${deviceKey}`, { + ...(currentDevice ?? {}), + device_key: deviceKey, + provider: providerId, + status: 'active', + bound_at: stamp, + updated_at: stamp, + }); + } + await this.put(`prov/${providerId}`, { + ...provider, + enclaves: this.providerEnclavesWith(provider, enclaveId), + ...(hardwareFingerprint !== null ? { hardware_fingerprint: hardwareFingerprint } : {}), + ...(deviceKey !== null ? { device_key: deviceKey, device_key_bound_at: stamp } : {}), + updated_at: stamp, + }); + console.log('mayhem joinEnclave', record); + return { ok: true, op: 'joinEnclave', provider: providerId, enclave_id: enclaveId }; + } + + async leaveEnclave() { + const shapeError = this.validateExactCommandValue(['op', 'enclave_id'], 'leave_enclave'); + if (shapeError) return shapeError; + if (!this.isSafeKeyPart(this.value.enclave_id)) return new Error('Invalid enclave id.'); + + return this.applyLeaveEnclave(this.address, this.value.enclave_id, this.tx); + } + + async applyLeaveEnclave(providerId, enclaveId, stamp) { + const key = `serve/${providerId}/${enclaveId}`; + const record = await this.get(key); + if (!record || record.status !== 'active') return new Error('Provider is not serving enclave.'); + if (Array.isArray(record.rooms) && record.rooms.length > 0) { + return new Error('Provider must leave rooms before leaving enclave.'); + } + + const updated = { + ...record, + status: 'inactive', + updated_at: stamp, + left_at: stamp, + }; + await this.put(key, updated); + const provider = await this.get(`prov/${providerId}`); + if (provider) { + await this.put(`prov/${providerId}`, { + ...provider, + enclaves: this.providerEnclavesWithout(provider, enclaveId), + updated_at: stamp, + }); + } + const enclave = await this.get(`enclave/${enclaveId}`); + if (enclave) { + await this.put(`enclave/${enclaveId}`, { + ...enclave, + providers: this.enclaveProvidersWithout(enclave, providerId), + updated_at: stamp, + }); + } + console.log('mayhem leaveEnclave', updated); + return { ok: true, op: 'leaveEnclave', provider: providerId, enclave_id: enclaveId }; + } + + async joinRoom() { + const shapeError = this.validateExactCommandValue( + ['op', 'room_id', 'enclave_id'], + 'join_room' + ); + if (shapeError) return shapeError; + if (!this.isSafeKeyPart(this.value.room_id)) return new Error('Invalid room id.'); + if (!this.isSafeKeyPart(this.value.enclave_id)) return new Error('Invalid enclave id.'); + + return this.applyJoinRoom(this.address, this.value.room_id, this.value.enclave_id, this.tx); + } + + async applyJoinRoom(providerId, roomId, enclaveId, stamp) { + const consentError = await this.requireConsent(providerId); + if (consentError) return consentError; + const providerError = await this.requireProvider(providerId); + if (providerError) return providerError; + + const room = await this.get(`room/${roomId}`); + if (!room) return new Error('Room not found.'); + if (room.status !== 'open') return new Error('Room is not open.'); + + const serving = await this.get(`serve/${providerId}/${enclaveId}`); + if (!serving || serving.status !== 'active') return new Error('Provider is not serving enclave.'); + const enclave = await this.get(`enclave/${enclaveId}`); + if (!enclave || enclave.status !== 'active') return new Error('Enclave is not active.'); + const enclaveRoleError = this.requireAdminCreatedEnclave(enclave); + if (enclaveRoleError) return enclaveRoleError; + const roomRoleError = this.requireAdminCreatedRoom(room); + if (roomRoleError) return roomRoleError; + const priceError = await this.requireCurrentAdminPrice(enclaveId, serving.ctx_bracket ?? null); + if (priceError) return priceError; + if (room.enclave_id !== enclaveId) { + return new Error('Room enclave does not match served enclave.'); + } + if (serving.model_id !== enclave.model_id || enclave.model_id !== room.model_id) { + return new Error('Enclave model does not match room model.'); + } + + const key = `roomserve/${roomId}/${providerId}/${enclaveId}`; + const existing = await this.get(key); + if (existing && existing.status === 'active') return new Error('Provider already joined room with enclave.'); + + const rooms = Array.isArray(serving.rooms) ? serving.rooms.slice() : []; + if (!rooms.includes(roomId)) rooms.push(roomId); + rooms.sort(); + const record = { + room_id: roomId, + sidechannel: room.sidechannel, + provider: providerId, + enclave_id: enclaveId, + model_id: enclave.model_id, + status: 'active', + joined_at: existing?.joined_at ?? stamp, + updated_at: stamp, + left_at: null, + }; + await this.put(key, record); + await this.put(`serve/${providerId}/${enclaveId}`, { + ...serving, + rooms, + updated_at: stamp, + }); + await this.put(`room/${roomId}`, { + ...room, + serves: this.roomServesWith(room, providerId, enclaveId), + serves_updated_at: stamp, + }); + console.log('mayhem joinRoom', record); + return { + ok: true, + op: 'joinRoom', + room_id: roomId, + provider: providerId, + enclave_id: enclaveId, + sidechannel: room.sidechannel, + }; + } + + async leaveRoom() { + const shapeError = this.validateExactCommandValue( + ['op', 'room_id', 'enclave_id'], + 'leave_room' + ); + if (shapeError) return shapeError; + if (!this.isSafeKeyPart(this.value.room_id)) return new Error('Invalid room id.'); + if (!this.isSafeKeyPart(this.value.enclave_id)) return new Error('Invalid enclave id.'); + + return this.applyLeaveRoom(this.address, this.value.room_id, this.value.enclave_id, this.tx); + } + + async applyLeaveRoom(providerId, roomId, enclaveId, stamp) { + const key = `roomserve/${roomId}/${providerId}/${enclaveId}`; + const record = await this.get(key); + if (!record) return new Error('Provider has not joined room with enclave.'); + if (record.status !== 'active') { + return { + ok: true, + op: 'leaveRoom', + room_id: roomId, + provider: providerId, + enclave_id: enclaveId, + sidechannel: record.sidechannel, + status: record.status, + idempotent: true, + }; + } + + const servingKey = `serve/${providerId}/${enclaveId}`; + const serving = await this.get(servingKey); + const rooms = Array.isArray(serving?.rooms) + ? serving.rooms.filter((servingRoomId) => servingRoomId !== roomId) + : []; + const roomKey = `room/${roomId}`; + const room = await this.get(roomKey); + const updated = { + ...record, + status: 'inactive', + updated_at: stamp, + left_at: stamp, + }; + await this.put(key, updated); + if (serving) { + await this.put(servingKey, { + ...serving, + rooms, + updated_at: stamp, + }); + } + if (room) { + await this.put(roomKey, { + ...room, + serves: this.roomServesWithout(room, providerId, enclaveId), + serves_updated_at: stamp, + }); + } + console.log('mayhem leaveRoom', updated); + return { + ok: true, + op: 'leaveRoom', + room_id: roomId, + provider: providerId, + enclave_id: enclaveId, + sidechannel: updated.sidechannel, + }; + } + + async openRoom() { + const adminError = await this.requireAdmin(); + if (adminError) return adminError; + if (!this.isSafeKeyPart(this.value.enclave_id)) return new Error('Invalid enclave id.'); + if (this.value.model_id && !this.isSafeModelId(this.value.model_id)) { + return new Error('Invalid model id.'); + } + + const policyError = this.validateRoomPolicy(this.value.policy); + if (policyError) return policyError; + + let recordModelId = this.value.model_id; + const enclave = await this.get(`enclave/${this.value.enclave_id}`); + if (!enclave) return new Error('Enclave not found.'); + if (enclave.status !== 'active') return new Error('Enclave is not active.'); + const enclaveRoleError = this.requireAdminCreatedEnclave(enclave); + if (enclaveRoleError) return enclaveRoleError; + if (this.value.model_id && this.value.model_id !== enclave.model_id) { + return new Error('Room model does not match enclave model.'); + } + recordModelId = enclave.model_id; + + const roomId = await deriveRoomId(this.value.enclave_id, this.address, this.value.nonce); + const key = `room/${roomId}`; + const existing = await this.get(key); + if (existing && existing.status !== 'closed') return new Error('Room already open.'); + + const record = { + room_id: roomId, + sidechannel: roomSidechannelName(roomId), + enclave_id: this.value.enclave_id, + model_id: recordModelId, + label: this.value.label, + creator: this.address, + creator_role: 'admin', + policy: cloneValue(this.value.policy), + serves: [], + serves_updated_at: null, + created_at: this.tx, + updated_at: this.tx, + closed_at: null, + status: 'open', + }; + await this.put(key, record); + console.log('mayhem openRoom', record); + return { ok: true, op: 'openRoom', room_id: roomId, sidechannel: record.sidechannel }; + } + + async closeRoom() { + const adminError = await this.requireAdmin(); + if (adminError) return adminError; + if (!this.isSafeKeyPart(this.value.room_id)) return new Error('Invalid room id.'); + + const key = `room/${this.value.room_id}`; + const record = await this.get(key); + if (!record) return new Error('Room not found.'); + if (record.status === 'closed') return new Error('Room already closed.'); + + const tombstones = await this.tombstoneRoomServes( + this.value.room_id, + this.roomServingEntries(record), + null + ); + if (tombstones instanceof Error) return tombstones; + const current = (await this.get(key)) ?? record; + const updated = { + ...current, + status: 'closed', + serves: [], + serves_updated_at: this.tx, + tombstoned_serves: tombstones + .filter((tombstone) => tombstone.roomserve_tombstoned) + .map(({ provider, enclave_id }) => ({ provider, enclave_id })), + updated_at: this.tx, + closed_at: this.tx, + closed_by: this.address, + closed_by_role: 'admin', + }; + await this.put(key, updated); + console.log('mayhem closeRoom', updated); + return { + ok: true, + op: 'closeRoom', + room_id: updated.room_id, + sidechannel: updated.sidechannel, + tombstoned_serves: updated.tombstoned_serves, + }; + } + + async setPrice() { + const adminError = await this.requireAdmin(); + if (adminError) return adminError; + if (!this.isSafeKeyPart(this.value.enclave_id)) return new Error('Invalid enclave id.'); + + const enclave = await this.get(`enclave/${this.value.enclave_id}`); + if (!enclave) return new Error('Enclave not found.'); + if (enclave.status === 'retired') return new Error('Enclave is retired.'); + + const modelRef = await this.get(`modelref/${enclave.model_id}`); + if (!modelRef) return new Error('Model reference not found.'); + const enclaveClass = this.modelClassFor(enclave); + const enclaveClassError = this.validateModelClass(enclaveClass, 'Enclave model_class'); + if (enclaveClassError) return enclaveClassError; + const modelRefClass = this.modelClassFor(modelRef); + const modelRefClassError = this.validateModelClass(modelRefClass, 'Model reference model_class'); + if (modelRefClassError) return modelRefClassError; + if (modelRefClass !== enclaveClass) { + return new Error('Model reference model_class must match enclave model_class.'); + } + + const rateError = this.validateRateMap(this.value.rate_map, enclaveClass, 'Enclave price rate_map', { + allowZeroPrice: true, + }); + if (rateError) return rateError; + const priceRateMap = this.normalizeRateMap(this.value.rate_map); + const modalityRateError = this.validateEnclaveModalityRateMap(enclave, priceRateMap); + if (modalityRateError) return modalityRateError; + const perReqAu = this.normalizeAu(this.value.per_req_au, 'Enclave price per_req_au'); + if (perReqAu instanceof Error) return perReqAu; + const minSessionAu = this.normalizeAu(this.value.min_session_au, 'Enclave price min_session_au'); + if (minSessionAu instanceof Error) return minSessionAu; + const params = await this.activeParamsAt(this.value.effective_at, [ + 'price_min_bps', + 'price_max_bps', + 'price_rate_limit_seconds', + ]); + const boundsError = this.validateRateMapBounds(priceRateMap, modelRef.rate_map, params); + if (boundsError) return boundsError; + + const ctxMeta = await this.priceCtxMetaForEnclave(enclave, this.value.ctx_bracket, this.value.effective_at, 'Enclave price'); + if (ctxMeta instanceof Error) return ctxMeta; + const key = this.priceScheduleKey(this.value.enclave_id, ctxMeta?.ctx_bracket ?? null); + const schedule = await this.priceSchedule(key, enclave, ctxMeta); + const latest = this.priceLatestEntry(schedule); + const latestSeed = this.priceLatestSeedEntry(schedule); + if ( + latestSeed && + this.value.effective_at - latestSeed.effective_at < params.price_rate_limit_seconds + ) { + return new Error('Price seed changes are limited by price_rate_limit_seconds.'); + } + + const record = { + enclave_id: this.value.enclave_id, + model_id: enclave.model_id, + denom: PRICE_DENOMINATION, + ver: latest ? latest.ver + 1 : 1, + rate_map: priceRateMap, + per_req_au: perReqAu, + min_session_au: minSessionAu, + effective_at: this.value.effective_at, + effective_from: this.tx, + updated_at: this.tx, + set_by: this.address, + set_by_role: 'admin', + ...(ctxMeta ? { + ctx_bracket: ctxMeta.ctx_bracket, + ctx_bracket_table_ver: ctxMeta.ctx_bracket_table_ver, + } : {}), + }; + + const updated = { + enclave_id: this.value.enclave_id, + model_id: enclave.model_id, + denom: PRICE_DENOMINATION, + ...(ctxMeta ? { + ctx_bracket: ctxMeta.ctx_bracket, + ctx_bracket_table_ver: ctxMeta.ctx_bracket_table_ver, + } : {}), + current: schedule.current, + pending: schedule.pending, + }; + if (!updated.current) { + updated.current = record; + } else { + if (updated.pending && updated.pending.effective_at <= this.value.effective_at) { + updated.current = updated.pending; + updated.pending = null; + } + if (updated.pending) return new Error('Pending price change already scheduled.'); + updated.pending = record; + } + + await this.put(key, updated); + await this.put(this.priceRecordKey(this.value.enclave_id, record.ver, ctxMeta?.ctx_bracket ?? null), record); + console.log('mayhem setPrice', { schedule: updated, record }); + return { + ok: true, + op: 'setPrice', + enclave_id: record.enclave_id, + ver: record.ver, + }; + } + + async readPrice() { + if (!this.isSafeKeyPart(this.value.enclave_id)) return new Error('Invalid enclave id.'); + const enclave = await this.get(`enclave/${this.value.enclave_id}`); + if (!enclave) return new Error('Enclave not found.'); + const ctxMeta = await this.priceCtxMetaForEnclave(enclave, this.value.ctx_bracket, this.value.at, 'Enclave price'); + if (ctxMeta instanceof Error) return ctxMeta; + const key = this.priceScheduleKey(this.value.enclave_id, ctxMeta?.ctx_bracket ?? null); + const schedule = await this.get(key); + if (!schedule) { + return { + ok: true, + op: 'readPrice', + enclave_id: this.value.enclave_id, + at: this.value.at, + ...(ctxMeta ? { + ctx_bracket: ctxMeta.ctx_bracket, + ctx_bracket_table_ver: ctxMeta.ctx_bracket_table_ver, + } : {}), + price: null, + }; + } + + const price = this.priceActiveEntry(schedule, this.value.at); + console.log('mayhem readPrice', { enclave_id: this.value.enclave_id, at: this.value.at, price }); + return { + ok: true, + op: 'readPrice', + enclave_id: this.value.enclave_id, + at: this.value.at, + ...(ctxMeta ? { + ctx_bracket: ctxMeta.ctx_bracket, + ctx_bracket_table_ver: ctxMeta.ctx_bracket_table_ver, + } : {}), + price, + }; + } + + async recordReputationEvent() { + const adminError = await this.requireAdmin(); + if (adminError) return adminError; + + const validationError = this.validateReputationEvent(this.value); + if (validationError) return validationError; + + const provider = await this.get(`prov/${this.value.provider}`); + if (!provider) return new Error('Provider not found.'); + + const record = await this.appendReputationEvent({ + provider: this.value.provider, + event_id: this.value.event_id, + kind: this.value.kind, + epoch: this.value.epoch, + at: this.value.at, + paid_au: this.value.paid_au !== undefined + ? this.normalizeAu(this.value.paid_au, 'reputation paid amount') + : null, + max_spend_au: this.value.max_spend_au !== undefined + ? this.normalizeAu(this.value.max_spend_au, 'reputation max spend') + : null, + evidence_hash: this.value.evidence_hash ?? null, + }); + if (record instanceof Error) return record; + + let slash = null; + if (this.value.kind === 'dispute_lost') { + const params = await this.activeParamsAt(this.value.at, ['dispute_lost_slash_bps']); + slash = await this.applyProviderSlash({ + providerId: this.value.provider, + source: 'dispute', + reason: 'dispute_lost', + evidenceHash: this.value.evidence_hash ?? null, + epoch: this.value.epoch, + at: this.value.at, + slashBps: params.dispute_lost_slash_bps, + beneficiary: this.value.beneficiary ?? null, + enclaveId: this.value.enclave_id ?? null, + banProvider: false, + tombstoneEnclave: false, + }); + if (slash instanceof Error) return slash; + } + + console.log('mayhem recordReputationEvent', record); + return { + ok: true, + op: 'recordReputationEvent', + provider: this.value.provider, + event_id: this.value.event_id, + head: record.head, + slash, + }; + } + + async anchorReputation() { + const adminError = await this.requireAdmin(); + if (adminError) return adminError; + + const validationError = this.validateReputationAnchor(this.value); + if (validationError) return validationError; + + const providerKey = `prov/${this.value.provider}`; + const provider = await this.get(providerKey); + if (!provider) return new Error('Provider not found.'); + + const head = await this.get(`ev/rep/head/${this.value.provider}`); + if (!head || head.head !== this.value.events_head) { + return new Error('Reputation events head mismatch.'); + } + const fold = await this.get(`ev/rep/fold/${this.value.provider}`); + if ( + !fold || + fold.events_head !== head.head || + fold.event_count !== head.count + ) { + return new Error('Reputation fold state mismatch.'); + } + if (this.value.epoch < fold.max_epoch) { + return new Error('Reputation anchor epoch precedes an event.'); + } + const expected = this.reputationFoldAt(fold, this.value.folded_at); + if (expected instanceof Error) return expected; + if ( + this.value.r_bps !== expected.r_bps || + this.value.raw_milli !== expected.raw_milli || + this.value.successful_sessions !== expected.successful_sessions || + (this.value.provenance_violation === true) !== expected.provenance_violation + ) { + return new Error('Reputation anchor does not match the contract fold.'); + } + + const params = await this.activeParamsAt(this.value.folded_at, [ + 'probation_successful_sessions', + 'probation_seconds', + 'probation_max_concurrent_sessions_per_user', + 'probation_price_max_bps', + 'probation_weight_bps', + ]); + const sinceSeconds = provider.probation?.since_seconds ?? 0; + const probationActive = ( + this.value.successful_sessions < params.probation_successful_sessions || + this.value.folded_at - sinceSeconds < params.probation_seconds + ); + const probation = { + active: probationActive, + since: provider.probation?.since ?? provider.registered_at, + since_seconds: sinceSeconds, + successful_sessions: this.value.successful_sessions, + required_successful_sessions: params.probation_successful_sessions, + required_seconds: params.probation_seconds, + caps: { + max_concurrent_sessions_per_user: params.probation_max_concurrent_sessions_per_user, + price_max_bps: params.probation_price_max_bps, + weight_bps: params.probation_weight_bps, + }, + }; + const snapshot = { + provider: this.value.provider, + r: this.value.r_bps / 10_000, + r_bps: this.value.r_bps, + raw: this.value.raw_milli / 1_000, + raw_milli: this.value.raw_milli, + events_head: this.value.events_head, + epoch: this.value.epoch, + folded_at: this.value.folded_at, + updated_at: this.tx, + probation, + provenance_violation: this.value.provenance_violation === true, + }; + const updatedProvider = { + ...provider, + probation: { + ...(provider.probation ?? {}), + successful_sessions: this.value.successful_sessions, + since_seconds: sinceSeconds, + }, + updated_at: this.tx, + }; + + await this.put(`rep/${this.value.provider}`, snapshot); + await this.put(providerKey, updatedProvider); + console.log('mayhem anchorReputation', snapshot); + return { + ok: true, + op: 'anchorReputation', + provider: this.value.provider, + epoch: this.value.epoch, + events_head: this.value.events_head, + }; + } + + async auditorRegister() { + const target = this.value.auditor ?? this.address; + if (!this.isSafeKeyPart(target)) return new Error('Invalid auditor id.'); + + const consentError = await this.requireConsent(target); + if (consentError) return consentError; + + const provider = await this.get(`prov/${target}`); + if (provider) return new Error('Provider keys cannot register as auditors.'); + + const adminRegistersOther = target !== this.address; + if (adminRegistersOther) { + const adminError = await this.requireAdmin(); + if (adminError) return adminError; + } else { + const eligibilityError = await this.requireAuditorEligibility( + target, + this.value.registered_at_seconds ?? 0 + ); + if (eligibilityError) return eligibilityError; + } + + const key = `auditor/${target}`; + const existing = await this.get(key); + if (existing?.status === 'active') return new Error('Auditor already registered.'); + if (existing?.status === 'slashed') return new Error('Auditor is slashed.'); + + const record = { + auditor: target, + status: 'active', + registered_at: this.tx, + registered_at_seconds: this.value.registered_at_seconds ?? 0, + accredited_by: adminRegistersOther ? this.address : null, + successful_probes: existing?.successful_probes ?? 0, + submitted_probes: existing?.submitted_probes ?? 0, + false_reports: existing?.false_reports ?? 0, + updated_at: this.tx, + }; + await this.put(key, record); + console.log('mayhem auditorRegister', record); + return { ok: true, op: 'auditorRegister', auditor: target }; + } + + async auditorSlash() { + const adminError = await this.requireAdmin(); + if (adminError) return adminError; + if (this.value.op !== 'auditor_slash') return new Error('Invalid auditor slash op.'); + if (!this.isHexBytes(this.value.auditor, 32)) return new Error('Invalid auditor slash target.'); + if (!this.isHexBytes(this.value.provider, 32)) return new Error('Invalid auditor slash provider.'); + if (!this.isSafeKeyPart(this.value.probe_id)) return new Error('Invalid auditor slash probe id.'); + if (!AUDITOR_SLASH_REASONS.has(this.value.reason)) return new Error('Unsupported auditor slash reason.'); + if (!this.isHexBytes(this.value.evidence_hash, 32)) return new Error('Invalid auditor slash evidence hash.'); + + const auditorKey = `auditor/${this.value.auditor}`; + const auditor = await this.get(auditorKey); + if (!auditor) return new Error('Auditor not found.'); + if (auditor.status === 'slashed') return new Error('Auditor is already slashed.'); + if (auditor.status !== 'active') return new Error('Auditor is not active.'); + const probeKey = `ev/probe/${this.value.probe_id}`; + const probe = await this.get(probeKey); + if (!probe || probe.probe_kind !== 'canary') return new Error('Canary probe not found.'); + if ( + probe.auditor !== this.value.auditor || + probe.provider !== this.value.provider || + probe.epoch !== this.value.epoch || + probe.pass !== true + ) { + return new Error('Auditor slash evidence does not match the passing probe.'); + } + if (probe.status === 'slashed') return new Error('Canary probe is already slashed.'); + const slashKey = `ev/auditor-slash/${this.value.auditor}/${this.value.probe_id}`; + if ((await this.get(slashKey)) !== null) return new Error('Auditor slash already recorded.'); + + const passKey = `probe/pass/${this.value.provider}/${this.value.epoch}`; + const passRecord = await this.get(passKey); + if (!passRecord || !Array.isArray(passRecord.probes)) { + return new Error('Canary pass record not found.'); + } + const remaining = passRecord.probes.filter((entry) => !( + entry.auditor === this.value.auditor && entry.probe_id === this.value.probe_id + )); + if (remaining.length === passRecord.probes.length) { + return new Error('Canary pass record does not contain the slashed probe.'); + } + const falseReports = this.safeAddCount(auditor.false_reports ?? 0, 1, 'auditor false report count'); + if (falseReports instanceof Error) return falseReports; + const slash = { + auditor: this.value.auditor, + provider: this.value.provider, + probe_id: this.value.probe_id, + epoch: this.value.epoch, + reason: this.value.reason, + evidence_hash: this.value.evidence_hash.toLowerCase(), + reward_forfeited_au: probe.probe_reward_au ?? ZERO_AU, + slashed_at_seconds: this.value.at, + slashed_at: this.tx, + slashed_by: this.address, + slashed_by_role: 'admin', + }; + await this.put(passKey, { + ...passRecord, + pass_count: remaining.length, + auditors: remaining.map((entry) => entry.auditor), + probes: remaining, + last_slash_evidence_hash: slash.evidence_hash, + updated_at: this.tx, + }); + await this.put(probeKey, { + ...probe, + status: 'slashed', + collusion_evidence_hash: slash.evidence_hash, + slashed_at: this.tx, + }); + await this.put(auditorKey, { + ...auditor, + status: 'slashed', + false_reports: falseReports, + slash_reason: this.value.reason, + slash_evidence_hash: slash.evidence_hash, + slashed_at: this.tx, + slashed_by: this.address, + updated_at: this.tx, + }); + await this.put(slashKey, slash); + console.log('mayhem auditorSlash', slash); + return { + ok: true, + op: 'auditorSlash', + auditor: this.value.auditor, + probe_id: this.value.probe_id, + evidence_hash: slash.evidence_hash, + }; + } + + async probeResult() { + const auditor = await this.get(`auditor/${this.address}`); + if (!auditor || auditor.status !== 'active') return new Error('Auditor registration required.'); + if ((await this.get(`prov/${this.address}`)) !== null) { + return new Error('Provider keys cannot submit auditor probes.'); + } + + const validationError = this.validateProbeResult(this.value); + if (validationError) return validationError; + if ((await this.get(`ev/probe/${this.value.probe_id}`)) !== null) { + return new Error('Probe result already recorded.'); + } + + const providerKey = `prov/${this.value.provider}`; + const provider = await this.get(providerKey); + if (!provider) return new Error('Provider not found.'); + + if (this.value.probe_kind === 'canary') { + const canaryBindingError = await this.requireBoundCanaryProbe(this.value, this.address); + if (canaryBindingError) return canaryBindingError; + } + + const params = await this.activeParamsAt(this.value.at, [ + 'canary_match_min_bps', + 'probe_reward_au', + 'uptime_tick_seconds', + 'fraud_slash_bps', + ]); + const pass = this.probePass(this.value, params); + if (this.value.pass !== undefined && this.value.pass !== pass) { + return new Error('Probe pass flag does not match contract threshold.'); + } + if (this.value.probe_kind === 'canary' && pass) { + const passRecord = await this.get(`probe/pass/${this.value.provider}/${this.value.epoch}`); + if (passRecord?.auditors?.includes(this.address)) { + return new Error('Auditor already supplied a canary pass for this provider epoch.'); + } + } + + const reputationEvent = await this.appendReputationEvent({ + provider: this.value.provider, + event_id: `probe-${this.value.probe_id}`, + kind: this.value.probe_kind === 'uptime_tick' + ? 'uptime_tick' + : pass + ? 'probe_ok' + : 'probe_fail', + epoch: this.value.epoch, + at: this.value.at, + paid_au: null, + max_spend_au: null, + evidence_hash: this.value.evidence_hash ?? null, + }); + if (reputationEvent instanceof Error) return reputationEvent; + + let provenanceViolation = false; + let slash = null; + if (this.value.probe_kind === 'canary' && !pass) { + provenanceViolation = true; + const violationEvent = await this.appendReputationEvent({ + provider: this.value.provider, + event_id: `probe-${this.value.probe_id}-violation`, + kind: 'provenance_violation', + epoch: this.value.epoch, + at: this.value.at, + paid_au: null, + max_spend_au: null, + evidence_hash: this.value.evidence_hash ?? null, + }); + if (violationEvent instanceof Error) return violationEvent; + + slash = await this.applyProviderSlash({ + providerId: this.value.provider, + source: 'probe', + reason: 'canary_mismatch', + evidenceHash: this.value.evidence_hash ?? null, + epoch: this.value.epoch, + at: this.value.at, + slashBps: params.fraud_slash_bps, + beneficiary: this.address, + enclaveId: this.value.enclave_id ?? null, + probeId: this.value.probe_id, + banProvider: true, + tombstoneEnclave: true, + }); + if (slash instanceof Error) return slash; + } + + const record = { + probe_id: this.value.probe_id, + probe_kind: this.value.probe_kind, + auditor: this.address, + provider: this.value.provider, + enclave_id: this.value.enclave_id ?? null, + epoch: this.value.epoch, + at: this.value.at, + canary_set: this.value.canary_set ?? null, + canary_prompt_id: this.value.canary_prompt_id ?? null, + challenge_epoch: this.value.challenge_epoch ?? null, + challenge_apply_hash: this.value.challenge_apply_hash ?? null, + challenge_seed: this.value.challenge_seed ?? null, + verification_method: this.value.verification_method ?? null, + binary_hash: this.value.binary_hash ?? null, + match_bps: this.value.match_bps ?? null, + pass, + session_receipt_hash: this.value.session_receipt_hash ?? null, + evidence_hash: this.value.evidence_hash ?? null, + auditor_sig: this.value.auditor_sig ?? null, + reputation_head: (await this.get(`ev/rep/head/${this.value.provider}`))?.head ?? null, + provenance_violation: provenanceViolation, + probe_reward_au: params.probe_reward_au, + slash, + recorded_at: this.tx, + }; + await this.put(`ev/probe/${this.value.probe_id}`, record); + let probePassRecord = null; + if (this.value.probe_kind === 'canary' && pass) { + probePassRecord = await this.recordCanaryProbePass(this.value, this.address); + if (probePassRecord instanceof Error) return probePassRecord; + } + await this.put(`auditor/${this.address}`, { + ...auditor, + submitted_probes: (auditor.submitted_probes ?? 0) + 1, + successful_probes: (auditor.successful_probes ?? 0) + (pass ? 1 : 0), + updated_at: this.tx, + }); + console.log('mayhem probeResult', record); + return { + ok: true, + op: 'probeResult', + probe_id: this.value.probe_id, + provider: this.value.provider, + pass, + ...(probePassRecord ? { probe_pass_record: probePassRecord } : {}), + provenance_violation: provenanceViolation, + }; + } + + async epochApply() { + if (CONTRACT_VERSION >= 17) { + return new Error('Aggregate epoch_apply is disabled; use receipt-bound apply_targeted_epoch.'); + } + const adminError = await this.requireAdmin(); + if (adminError) return adminError; + + const shapeError = this.validateEpochApplyShape(this.value); + if (shapeError) return shapeError; + const roots = this.value.roots === undefined ? null : this.normalizeEpochRoots(this.value.roots); + if (roots instanceof Error) return roots; + const totals = this.value.totals === undefined ? null : this.normalizeEpochTotals(this.value.totals); + if (totals instanceof Error) return totals; + if ((roots && !totals) || (!roots && totals)) { + return new Error('Epoch apply roots and totals must be provided together.'); + } + const page = this.epochApplyPage(this.value); + if (page instanceof Error) return page; + const lastPage = this.epochApplyLastPage(this.value); + if (lastPage instanceof Error) return lastPage; + const pagedApply = hasOwn(this.value, 'page'); + if (pagedApply && (roots || totals)) { + return new Error('Paged epochApply pages must omit aggregate roots and totals.'); + } + + const params = await this.activeParamsAt(this.value.at, [ + 'epoch_seconds', + 'fee_bps', + 'max_apply_batch', + 'max_market_usage_entries', + 'probation_successful_sessions', + 'new_provider_holdback_epochs', + 'holdback_epochs', + 'challenge_epochs', + 'probation_successful_sessions', + 'new_provider_holdback_epochs', + 'canary_probe_holdback_bps', + 'canary_probe_release_min_passes', + ]); + if (this.value.debits.length + this.value.earnings.length > params.max_apply_batch) { + return new Error('Epoch apply batch exceeds max_apply_batch.'); + } + if ((this.value.market_usage?.length ?? 0) > params.max_market_usage_entries) { + return new Error('Epoch market usage batch exceeds max_market_usage_entries.'); + } + + const debitMap = this.aggregateRailLedgerEntries(this.value.debits, 'user', 'au', 'debit'); + if (debitMap instanceof Error) return debitMap; + const grossEarningMap = this.aggregateRailLedgerEntries( + this.value.earnings, + 'provider', + 'gross_au', + 'earning' + ); + if (grossEarningMap instanceof Error) return grossEarningMap; + + const debitTotal = this.sumRailAu(debitMap, 'au'); + if (debitTotal instanceof Error) return debitTotal; + const grossTotal = this.sumRailAu(grossEarningMap, 'gross_au'); + if (grossTotal instanceof Error) return grossTotal; + const debitRailTotals = this.railTotals(debitMap, 'au'); + if (debitRailTotals instanceof Error) return debitRailTotals; + const grossRailTotals = this.railTotals(grossEarningMap, 'gross_au'); + if (grossRailTotals instanceof Error) return grossRailTotals; + const railTotalError = this.assertMatchingRailTotals(debitRailTotals, grossRailTotals); + if (railTotalError) return railTotalError; + const marketUsageProvided = hasOwn(this.value, 'market_usage'); + const marketUsageMap = marketUsageProvided + ? this.aggregateMarketUsageEntries(this.value.market_usage) + : new Map(); + if (marketUsageMap instanceof Error) return marketUsageMap; + const marketUsageTotal = this.sumMarketDemandAu(marketUsageMap); + if (marketUsageTotal instanceof Error) return marketUsageTotal; + const applyState = await this.epochApplyStateRecord(); + const previousApplyHash = page === 0 ? null : applyState.last_apply_hash; + const normalized = { + epoch: this.value.epoch, + page, + last_page: lastPage, + at: this.value.at, + epoch_seconds: params.epoch_seconds, + fee_bps: params.fee_bps, + tap_burn_bps: TAP_BURN_BPS, + debits: this.mapRailEntriesForHash(debitMap, 'user', 'au'), + earnings: this.mapRailEntriesForHash(grossEarningMap, 'provider', 'gross_au'), + roots, + totals, + }; + if (previousApplyHash !== null) { + normalized.previous_apply_hash = previousApplyHash; + } + if (marketUsageProvided) { + normalized.market_usage = this.mapMarketUsageEntriesForHash(marketUsageMap); + } + const applyHash = await this.epochApplyHash(normalized); + if (this.isIdempotentEpochApplyPage(applyState, this.value.epoch, page, lastPage, applyHash)) { + const result = { + ok: true, + op: 'epochApply', + epoch: this.value.epoch, + idempotent: true, + debited_au: ZERO_AU, + earned_au: ZERO_AU, + fee_au: ZERO_AU, + burn_au: ZERO_AU, + }; + if (pagedApply) { + result.page = page; + result.last_page = lastPage; + } + return result; + } + const pageOrderError = this.validateEpochApplyPageOrder(applyState, this.value.epoch, page); + if (pageOrderError) return pageOrderError; + const reservationDebitTotals = this.nextReservationDebitTotals(applyState, this.value.epoch, page, debitMap); + if (reservationDebitTotals instanceof Error) return reservationDebitTotals; + const reservationError = await this.validateEpochDebitReservations(this.value.epoch, reservationDebitTotals); + if (reservationError) return reservationError; + + const balances = new Map(); + for (const debit of debitMap.values()) { + const { rail, user, au: debitAu } = debit; + const balance = await this.balanceRecord(user, rail); + if (balance instanceof Error) return balance; + const balanceError = this.guardianValidateBalanceRecord(balance, user, rail); + if (balanceError) return balanceError; + if (this.compareAu(balance.au, debitAu) < 0) return new Error('Insufficient credit balance.'); + const nextBalanceAu = this.safeSubAu(balance.au, debitAu); + if (nextBalanceAu instanceof Error) return nextBalanceAu; + balances.set(stableJson([rail, user]), { + ...balance, + rail, + au: nextBalanceAu, + updated_epoch: this.value.epoch, + updated_at: this.tx, + }); + } + + const earningDeltas = new Map(); + const feeDeltaByRail = new Map(PROVIDER_ACCEPTED_RAIL_ORDER.map((rail) => [rail, ZERO_AU])); + const burnDeltaByRail = new Map(PROVIDER_ACCEPTED_RAIL_ORDER.map((rail) => [rail, ZERO_AU])); + let feeDeltaTotalAu = ZERO_AU; + let burnDeltaTotalAu = ZERO_AU; + for (const earning of grossEarningMap.values()) { + const { rail, provider, gross_au: grossAu } = earning; + const providerRecord = await this.get(`prov/${provider}`); + if (!providerRecord) return new Error('Provider not found.'); + if (!Array.isArray(providerRecord.accepted_rails)) { + return new Error('Provider accepted rails are not set.'); + } + if (!providerRecord.accepted_rails.includes(rail)) { + return new Error('Provider does not accept payment rail.'); + } + + const feeAu = this.safeMulDivAu(grossAu, params.fee_bps, 10_000); + if (feeAu instanceof Error) return feeAu; + const burnAu = rail === 'tap' + ? this.safeMulDivAu(grossAu, TAP_BURN_BPS, 10_000) + : ZERO_AU; + if (burnAu instanceof Error) return burnAu; + const afterFeeAu = this.safeSubAu(grossAu, feeAu); + if (afterFeeAu instanceof Error) return afterFeeAu; + const providerAu = this.safeSubAu(afterFeeAu, burnAu); + if (providerAu instanceof Error) return providerAu; + feeDeltaTotalAu = this.safeAddAu(feeDeltaTotalAu, feeAu); + if (feeDeltaTotalAu instanceof Error) return feeDeltaTotalAu; + const nextRailFee = this.safeAddAu(feeDeltaByRail.get(rail) ?? ZERO_AU, feeAu); + if (nextRailFee instanceof Error) return nextRailFee; + feeDeltaByRail.set(rail, nextRailFee); + burnDeltaTotalAu = this.safeAddAu(burnDeltaTotalAu, burnAu); + if (burnDeltaTotalAu instanceof Error) return burnDeltaTotalAu; + const nextRailBurn = this.safeAddAu(burnDeltaByRail.get(rail) ?? ZERO_AU, burnAu); + if (nextRailBurn instanceof Error) return nextRailBurn; + burnDeltaByRail.set(rail, nextRailBurn); + const key = stableJson([rail, provider]); + const current = earningDeltas.get(key) ?? { rail, provider, provider_record: providerRecord, au: ZERO_AU }; + const next = this.safeAddAu(current.au, providerAu); + if (next instanceof Error) return next; + earningDeltas.set(key, { ...current, au: next }); + } + + const earnings = new Map(); + let earnCumTotal = ZERO_AU; + for (const delta of earningDeltas.values()) { + const { rail, provider, provider_record: providerRecord, au: deltaAu } = delta; + const current = await this.earningRecord(provider, rail); + if (current instanceof Error) return current; + const currentError = this.guardianValidateEarningRecord(current, provider, rail); + if (currentError) return currentError; + const probeGate = await this.probeGateForEarning(provider, current, params); + if (probeGate instanceof Error) return probeGate; + const lockedEarningEpochs = this.providerLockedEarningEpochs(providerRecord, params); + if (lockedEarningEpochs instanceof Error) return lockedEarningEpochs; + const disputeGate = await this.providerHasOpenDispute(provider); + if (disputeGate instanceof Error) return disputeGate; + const refreshed = this.refreshEarningHoldback( + current, + this.value.epoch, + lockedEarningEpochs, + probeGate, + disputeGate + ); + if (refreshed instanceof Error) return refreshed; + const totalAu = this.safeAddAu(refreshed.total_au, deltaAu); + if (totalAu instanceof Error) return totalAu; + const heldAu = this.safeAddAu(refreshed.held_au, deltaAu); + if (heldAu instanceof Error) return heldAu; + const holdbacks = this.appendHoldbackBucket( + refreshed.holdbacks, + this.value.epoch, + deltaAu, + lockedEarningEpochs + ); + if (holdbacks instanceof Error) return holdbacks; + earnings.set(stableJson([rail, provider]), { + ...refreshed, + rail, + total_au: totalAu, + held_au: heldAu, + holdbacks, + updated_epoch: this.value.epoch, + updated_at: this.tx, + }); + earnCumTotal = this.safeAddAu(earnCumTotal, totalAu); + if (earnCumTotal instanceof Error) return earnCumTotal; + } + + const feeRecords = new Map(); + const nextFeeRecords = new Map(); + const burnRecords = new Map(); + const nextBurnRecords = new Map(); + let nextFeeCumTotal = ZERO_AU; + let nextBurnCumTotal = ZERO_AU; + const touchedRails = new Set([ + ...Array.from(debitRailTotals.entries()).filter(([, au]) => this.compareAu(au, ZERO_AU) > 0).map(([rail]) => rail), + ...Array.from(grossRailTotals.entries()).filter(([, au]) => this.compareAu(au, ZERO_AU) > 0).map(([rail]) => rail), + ]); + for (const rail of PROVIDER_ACCEPTED_RAIL_ORDER) { + const fee = await this.feeCumRecord(rail); + if (fee instanceof Error) return fee; + const feeError = this.guardianValidateFeeRecord(fee, rail); + if (feeError) return feeError; + feeRecords.set(rail, fee); + const feeDeltaAu = feeDeltaByRail.get(rail) ?? ZERO_AU; + const nextFeeCum = this.safeAddAu(fee.cum_au, feeDeltaAu); + if (nextFeeCum instanceof Error) return nextFeeCum; + // settled_cum_au must advance with this epoch's settled rail debits in + // the SAME record that carries the new cum_au, otherwise + // guardianValidateFeeRecord rejects every fee-bearing epoch apply + // (settled < cum). This mirrors next_settled_cum_by_rail in + // guardianValidateEpochTotals, which is also what gets persisted. + const nextFeeSettledCum = this.safeAddAu( + fee.settled_cum_au ?? fee.cum_au, + debitRailTotals.get(rail) ?? ZERO_AU + ); + if (nextFeeSettledCum instanceof Error) return nextFeeSettledCum; + const nextFee = touchedRails.has(rail) + ? { + ...fee, + cum_au: nextFeeCum, + settled_cum_au: nextFeeSettledCum, + updated_epoch: this.value.epoch, + updated_at: this.tx, + last_apply_hash: applyHash, + last_fee_bps: params.fee_bps, + } + : fee; + nextFeeRecords.set(rail, nextFee); + nextFeeCumTotal = this.safeAddAu(nextFeeCumTotal, nextFee.cum_au); + if (nextFeeCumTotal instanceof Error) return nextFeeCumTotal; + + const burn = await this.burnCumRecord(rail); + if (burn instanceof Error) return burn; + const burnError = this.guardianValidateBurnRecord(burn, rail); + if (burnError) return burnError; + burnRecords.set(rail, burn); + const burnDeltaAu = burnDeltaByRail.get(rail) ?? ZERO_AU; + const nextBurnCum = this.safeAddAu(burn.cum_au, burnDeltaAu); + if (nextBurnCum instanceof Error) return nextBurnCum; + const nextBurn = touchedRails.has(rail) + ? { + ...burn, + cum_au: nextBurnCum, + updated_epoch: this.value.epoch, + updated_at: this.tx, + last_apply_hash: applyHash, + burn_bps: rail === 'tap' ? TAP_BURN_BPS : 0, + } + : burn; + nextBurnRecords.set(rail, nextBurn); + nextBurnCumTotal = this.safeAddAu(nextBurnCumTotal, nextBurn.cum_au); + if (nextBurnCumTotal instanceof Error) return nextBurnCumTotal; + } + + const grossAfterFees = this.safeSubAu(grossTotal, feeDeltaTotalAu); + if (grossAfterFees instanceof Error) return grossAfterFees; + const providerDeltaTotal = this.safeSubAu(grossAfterFees, burnDeltaTotalAu); + if (providerDeltaTotal instanceof Error) return providerDeltaTotal; + const guardian = this.guardianCheckEpochApply({ + epoch: this.value.epoch, + page, + applyState, + feeRecords, + burnRecords, + debitTotal, + debitRailTotals, + feeDeltaByRail, + burnDeltaByRail, + providerDeltaTotal, + nextFeeRecords, + nextBurnRecords, + balances, + earnings, + }); + if (guardian instanceof Error) return guardian; + + let marketPriceUpdates = []; + if (marketUsageProvided) { + marketPriceUpdates = await this.computeMarketPriceUpdates(marketUsageMap, { + epoch: this.value.epoch, + at: this.value.at, + epochSeconds: params.epoch_seconds, + }); + if (marketPriceUpdates instanceof Error) return marketPriceUpdates; + } + const priceDerivations = await this.priceDerivationsFromMarketUpdates(marketPriceUpdates, { + epoch: this.value.epoch, + at: this.value.at, + epochSeconds: params.epoch_seconds, + usageRoot: roots?.use ?? null, + }); + if (priceDerivations instanceof Error) return priceDerivations; + + if (totals) { + const totalsError = await this.validateEpochApplyTotals({ + epoch: this.value.epoch, + roots, + totals, + debitTotal, + feeDeltaAu: feeDeltaTotalAu, + nextFeeCum: nextFeeCumTotal, + burnDeltaAu: burnDeltaTotalAu, + nextBurnCum: nextBurnCumTotal, + providerCount: grossEarningMap.size, + earnCumTotal, + epochSeconds: params.epoch_seconds, + priceDerivations, + }); + if (totalsError) return totalsError; + } + + for (const balance of this.sortedRailRecords(balances, 'user')) { + await this.put(this.balanceKey(balance.user, balance.rail), balance); + } + for (const earning of this.sortedRailRecords(earnings, 'provider')) { + await this.put(this.earningKey(earning.provider, earning.rail), earning); + } + for (const rail of PROVIDER_ACCEPTED_RAIL_ORDER.filter((rail) => touchedRails.has(rail))) { + const feeRecord = { + ...nextFeeRecords.get(rail), + settled_cum_au: guardian.next_settled_cum_by_rail.get(rail), + }; + await this.put(this.feeCumKey(rail), feeRecord); + await this.put(this.burnCumKey(rail), nextBurnRecords.get(rail)); + } + const nextApplyState = this.nextEpochApplyState({ + applyState, + epoch: this.value.epoch, + page, + lastPage, + applyHash, + epochSeconds: params.epoch_seconds, + settledAt: this.value.at, + reservationDebitTotals, + }); + const previousChallengeError = await this.rememberCanaryChallengeAnchor(applyState); + if (previousChallengeError) return previousChallengeError; + await this.put('epoch/apply/state', nextApplyState); + if (lastPage) { + const anchorError = await this.rememberEpochApplyAnchor(nextApplyState); + if (anchorError) return anchorError; + const challengeError = await this.rememberCanaryChallengeAnchor(nextApplyState); + if (challengeError) return challengeError; + } + if (totals) { + await this.writeEpochEvidenceRoots({ + epoch: this.value.epoch, + at: this.value.at, + epoch_seconds: params.epoch_seconds, + roots, + totals, + feeDeltaAu: feeDeltaTotalAu, + feeCumAu: nextFeeCumTotal, + burnDeltaAu: burnDeltaTotalAu, + burnCumAu: nextBurnCumTotal, + priceDerivations, + }); + } else if (priceDerivations.length > 0) { + await this.writePriceDerivationEvidence({ + epoch: this.value.epoch, + at: this.value.at, + epoch_seconds: params.epoch_seconds, + root: await this.priceDerivationRoot(priceDerivations), + count: priceDerivations.length, + derivations: priceDerivations, + }); + } + const activityIndexError = await this.writeActivityMarketIndex(marketPriceUpdates); + if (activityIndexError instanceof Error) return activityIndexError; + for (const update of marketPriceUpdates) { + await this.put(update.schedule_key, update.schedule); + await this.put(update.record_key, update.record); + } + + const result = { + ok: true, + op: 'epochApply', + epoch: this.value.epoch, + idempotent: false, + debited_au: debitTotal, + earned_au: providerDeltaTotal, + fee_au: feeDeltaTotalAu, + burn_au: burnDeltaTotalAu, + rails: Array.from(touchedRails).sort( + (left, right) => PROVIDER_ACCEPTED_RAIL_ORDER.indexOf(left) - PROVIDER_ACCEPTED_RAIL_ORDER.indexOf(right) + ), + }; + if (pagedApply) { + result.page = page; + result.last_page = lastPage; + } + if (marketPriceUpdates.length > 0) { + result.market_prices = marketPriceUpdates.map((update) => ({ + enclave_id: update.enclave_id, + ...(update.ctx_bracket ? { + ctx_bracket: update.ctx_bracket, + ctx_bracket_table_ver: update.ctx_bracket_table_ver, + } : {}), + ver: update.ver, + utilization_bps: update.utilization_bps, + multiplier_bps: update.multiplier_bps, + active_supply: update.active_supply, + active_demand_au: update.active_demand_au, + frozen: update.frozen, + derivation_hash: update.derivation_hash, + })); + result.price_root = await this.priceDerivationRoot(priceDerivations); + } + console.log('mayhem epochApply', result); + return result; + } + + async targetedEpochApply(value, revisionBindings, allocations, options = {}) { + const adminError = await this.requireAdmin(); + if (adminError) return adminError; + + const shapeError = this.validateEpochApplyShape(value); + if (shapeError) return shapeError; + const roots = value.roots === undefined ? null : this.normalizeEpochRoots(value.roots); + if (roots instanceof Error) return roots; + const totals = value.totals === undefined ? null : this.normalizeEpochTotals(value.totals); + if (totals instanceof Error) return totals; + if ((roots && !totals) || (!roots && totals)) { + return new Error('Epoch apply roots and totals must be provided together.'); + } + const page = this.epochApplyPage(value); + if (page instanceof Error) return page; + const lastPage = this.epochApplyLastPage(value); + if (lastPage instanceof Error) return lastPage; + const pagedApply = hasOwn(value, 'page'); + if (pagedApply && (roots || totals)) { + return new Error('Paged epochApply pages must omit aggregate roots and totals.'); + } + + const params = await this.activeParamsAt(value.at, [ + 'epoch_seconds', + 'fee_bps', + 'max_apply_batch', + 'max_market_usage_entries', + 'probation_successful_sessions', + 'new_provider_holdback_epochs', + 'holdback_epochs', + 'challenge_epochs', + 'probation_successful_sessions', + 'new_provider_holdback_epochs', + 'canary_probe_holdback_bps', + 'canary_probe_release_min_passes', + ]); + if (value.debits.length + value.earnings.length + allocations.length > params.max_apply_batch) { + return new Error('Epoch apply batch exceeds max_apply_batch.'); + } + if ((value.market_usage?.length ?? 0) > params.max_market_usage_entries) { + return new Error('Epoch market usage batch exceeds max_market_usage_entries.'); + } + + const debitMap = this.aggregateRailLedgerEntries(value.debits, 'user', 'au', 'debit'); + if (debitMap instanceof Error) return debitMap; + const grossEarningMap = this.aggregateRailLedgerEntries( + value.earnings, + 'provider', + 'gross_au', + 'earning' + ); + if (grossEarningMap instanceof Error) return grossEarningMap; + + const debitTotal = this.sumRailAu(debitMap, 'au'); + if (debitTotal instanceof Error) return debitTotal; + const grossTotal = this.sumRailAu(grossEarningMap, 'gross_au'); + if (grossTotal instanceof Error) return grossTotal; + const debitRailTotals = this.railTotals(debitMap, 'au'); + if (debitRailTotals instanceof Error) return debitRailTotals; + const grossRailTotals = this.railTotals(grossEarningMap, 'gross_au'); + if (grossRailTotals instanceof Error) return grossRailTotals; + const railTotalError = this.assertMatchingRailTotals(debitRailTotals, grossRailTotals); + if (railTotalError) return railTotalError; + const marketUsageProvided = hasOwn(value, 'market_usage'); + const marketUsageMap = marketUsageProvided + ? this.aggregateMarketUsageEntries(value.market_usage) + : new Map(); + if (marketUsageMap instanceof Error) return marketUsageMap; + const marketUsageTotal = this.sumMarketDemandAu(marketUsageMap); + if (marketUsageTotal instanceof Error) return marketUsageTotal; + const earningFinals = value.earning_finals ?? []; + + const receiptIndex = this.normalizeReceiptEpochIndexMetadata( + value.receipt_index, + value.epoch + ); + if (receiptIndex instanceof Error) return receiptIndex; + const currentReceiptIndex = this.normalizeReceiptEpochIndexMetadata( + await this.get(this.receiptEpochIndexKey(value.epoch)), + value.epoch + ); + if (currentReceiptIndex instanceof Error) return currentReceiptIndex; + if (stableJson(currentReceiptIndex) !== stableJson(receiptIndex)) { + return new Error('Canonical receipt epoch index changed after the apply snapshot.'); + } + const freezeError = await this.validateFrozenEpoch(value.epoch, value.at, receiptIndex); + if (freezeError) return freezeError; + const epochCommit = options.commitTransition?.record ?? + await this.get(`epoch/commit/${value.epoch}`); + if (!epochCommit || + epochCommit.type !== 'epoch_commit' || + epochCommit.epoch !== value.epoch || + epochCommit.status !== 'provisional' || + epochCommit.commit_hash !== value.epoch_commit_hash) { + return new Error('Matching provisional epoch commit required for targeted apply.'); + } + const boundedReceiptSettlement = + epochCommit.apply_mode === 'targeted_receipt_pages_v1'; + const canonicalPageMarketUsageMap = new Map(); + if (boundedReceiptSettlement) { + if (!Array.isArray(options.canonicalMarketUsage) || + options.canonicalMarketUsage.length === 0) { + return new Error('Bounded receipt canonical market usage is missing.'); + } + let canonicalPageDemandAu = ZERO_AU; + let canonicalPageSessionCount = 0; + for (const usage of options.canonicalMarketUsage) { + const ctxBracket = usage?.ctx_bracket ?? null; + const marketKey = this.priceMarketKey(usage?.enclave_id, ctxBracket); + const demandAu = this.normalizeAu( + usage?.demand_au, + 'bounded receipt canonical market demand', + { allowZero: false } + ); + const providers = Array.isArray(usage?.providers) + ? usage.providers.slice().sort(compareCodepoint) + : []; + const computeMs = this.normalizeAu( + usage?.compute_ms, + 'bounded receipt canonical market compute duration' + ); + const providerCapacities = Array.isArray(usage?.provider_capacities) + ? usage.provider_capacities.slice() + : []; + const capacityProviders = providerCapacities.map((entry) => entry?.provider); + const legacyReceiptCount = usage?.legacy_receipt_count; + const capacitySlotCount = providerCapacities.reduce( + (sum, entry) => Number.isSafeInteger(entry?.capacity_slots) + ? sum + entry.capacity_slots + : Number.MAX_SAFE_INTEGER, + 0 + ); + if (!usage || + !this.isSafeKeyPart(usage.enclave_id) || + (ctxBracket !== null && !this.isSafeKeyPart(ctxBracket)) || + (usage.ctx_bracket_table_ver !== undefined && + (!Number.isSafeInteger(usage.ctx_bracket_table_ver) || + usage.ctx_bracket_table_ver < 1)) || + demandAu instanceof Error || + !Number.isSafeInteger(usage.session_count) || + usage.session_count < 1 || + !Number.isSafeInteger(legacyReceiptCount) || + legacyReceiptCount < 0 || + legacyReceiptCount > usage.session_count || + computeMs instanceof Error || + providers.length < 1 || + providers.some((provider) => !this.isSafeKeyPart(provider)) || + new Set(providers).size !== providers.length || + stableJson(providers) !== stableJson(usage.providers) || + providerCapacities.length > providers.length || + new Set(capacityProviders).size !== capacityProviders.length || + stableJson(capacityProviders.slice().sort(compareCodepoint)) !== + stableJson(capacityProviders) || + capacityProviders.some((provider) => !providers.includes(provider)) || + providerCapacities.some((entry) => + !entry || !Number.isSafeInteger(entry.capacity_slots) || + entry.capacity_slots < 1 || entry.capacity_slots > 1_000_000) || + !Number.isSafeInteger(capacitySlotCount) || capacitySlotCount < 0 || + (legacyReceiptCount === 0 && + (this.isZeroAu(computeMs) || capacitySlotCount < 1 || + providerCapacities.length !== providers.length)) || + canonicalPageMarketUsageMap.has(marketKey)) { + return new Error('Bounded receipt canonical market usage is invalid.'); + } + canonicalPageDemandAu = this.safeAddAu(canonicalPageDemandAu, demandAu); + canonicalPageSessionCount = this.safeAddCount( + canonicalPageSessionCount, + usage.session_count, + 'bounded receipt canonical market sessions' + ); + if (canonicalPageDemandAu instanceof Error || + canonicalPageSessionCount instanceof Error) { + return new Error('Bounded receipt canonical market usage overflow.'); + } + canonicalPageMarketUsageMap.set(marketKey, { + enclave_id: usage.enclave_id, + ...(ctxBracket ? { ctx_bracket: ctxBracket } : {}), + ...(usage.ctx_bracket_table_ver ? { + ctx_bracket_table_ver: usage.ctx_bracket_table_ver, + } : {}), + demand_au: demandAu, + settled_usage: usage.settled_usage, + compute_ms: computeMs, + legacy_receipt_count: legacyReceiptCount, + capacity_slot_count: capacitySlotCount, + session_count: usage.session_count, + providers, + provider_capacities: providerCapacities, + }); + } + if (this.compareAu(canonicalPageDemandAu, grossTotal) !== 0 || + canonicalPageSessionCount !== allocations.length) { + return new Error('Bounded receipt canonical market usage does not match page allocations.'); + } + } + if (boundedReceiptSettlement && epochCommit.at !== value.at) { + return new Error('Bounded receipt settlement timestamp must match its epoch commit.'); + } + if (epochCommit.totals?.use_count !== receiptIndex.count) { + return new Error('Epoch commit receipt count does not match the canonical receipt index.'); + } + if (boundedReceiptSettlement) { + if (lastPage !== hasOwn(value, 'earning_finals') || + lastPage !== marketUsageProvided) { + return new Error( + 'Bounded receipt settlement requires earning_finals and market_usage on its final page only.' + ); + } + if (lastPage && this.compareAu(marketUsageTotal, epochCommit.totals.use_au) !== 0) { + return new Error('Final epoch market usage demand must equal committed usage.'); + } + } else if (marketUsageProvided && this.compareAu(marketUsageTotal, grossTotal) !== 0) { + return new Error('Epoch market usage demand must equal gross provider earnings.'); + } + + const applyState = await this.epochApplyStateRecord(); + const replayPosition = this.isEpochApplyPagePositionReplay( + applyState, + value.epoch, + page, + lastPage + ); + const previousApplyHash = replayPosition + ? (applyState.last_apply_previous_hash ?? null) + : page === 0 + ? null + : applyState.last_apply_hash; + const expectedStatePrefix = replayPosition ? 'last_receipt' : 'pending_receipt'; + if (replayPosition || page > 0) { + if ( + applyState[`${expectedStatePrefix}_index_count`] !== receiptIndex.count || + applyState[`${expectedStatePrefix}_index_revision`] !== receiptIndex.revision || + applyState[`${expectedStatePrefix}_index_page_count`] !== receiptIndex.page_count || + applyState[`${expectedStatePrefix}_index_updated_at`] !== receiptIndex.updated_at || + applyState[`${expectedStatePrefix}_commit_hash`] !== value.epoch_commit_hash + ) { + return new Error('Paged targeted apply receipt snapshot changed between pages.'); + } + } + let cumulativeAllocationCount; + if (replayPosition) { + cumulativeAllocationCount = applyState.last_receipt_allocation_count; + } else { + const priorAllocationCount = page === 0 + ? 0 + : applyState.pending_receipt_allocation_count; + if (!Number.isSafeInteger(priorAllocationCount) || priorAllocationCount < 0) { + return new Error('Paged targeted apply allocation count is missing.'); + } + cumulativeAllocationCount = priorAllocationCount + allocations.length; + } + if (!Number.isSafeInteger(cumulativeAllocationCount) || + cumulativeAllocationCount < 1 || + cumulativeAllocationCount > receiptIndex.count) { + return new Error('Paged targeted apply allocation count exceeds the canonical receipt index.'); + } + if (lastPage && cumulativeAllocationCount !== receiptIndex.count) { + return new Error('Last targeted apply page does not consume the complete canonical receipt index.'); + } + if (!lastPage && cumulativeAllocationCount >= receiptIndex.count) { + return new Error('Non-final targeted apply page already consumes the complete canonical receipt index.'); + } + const normalized = { + epoch: value.epoch, + page, + last_page: lastPage, + at: value.at, + epoch_seconds: params.epoch_seconds, + fee_bps: params.fee_bps, + tap_burn_bps: TAP_BURN_BPS, + epoch_commit_hash: value.epoch_commit_hash, + receipt_index: receiptIndex, + debits: this.mapRailEntriesForHash(debitMap, 'user', 'au'), + earnings: this.mapRailEntriesForHash(grossEarningMap, 'provider', 'gross_au'), + roots, + totals, + }; + if (previousApplyHash !== null) { + normalized.previous_apply_hash = previousApplyHash; + } + if (marketUsageProvided) { + normalized.market_usage = this.mapMarketUsageEntriesForHash(marketUsageMap); + } + if (hasOwn(value, 'earning_finals')) { + normalized.earning_finals = earningFinals; + } + const applyHash = await this.opaqueHash( + 'mayhem-targeted-epoch-apply-v1', + { + value: normalized, + payout_revisions: revisionBindings, + allocations, + } + ); + if (this.isIdempotentEpochApplyPage(applyState, value.epoch, page, lastPage, applyHash)) { + const result = { + ok: true, + op: 'epochApply', + epoch: value.epoch, + idempotent: true, + debited_au: ZERO_AU, + earned_au: ZERO_AU, + fee_au: ZERO_AU, + burn_au: ZERO_AU, + }; + if (pagedApply) { + result.page = page; + result.last_page = lastPage; + } + return result; + } + const cadenceError = await this.validateEpochCadenceTime( + applyState, + value.epoch, + page, + value.at, + params.epoch_seconds + ); + if (cadenceError) return cadenceError; + const pageOrderError = this.validateEpochApplyPageOrder(applyState, value.epoch, page); + if (pageOrderError) return pageOrderError; + const reservationDebitTotals = this.nextReservationDebitTotals(applyState, value.epoch, page, debitMap); + if (reservationDebitTotals instanceof Error) return reservationDebitTotals; + if (lastPage) { + const cumulativeDebitAu = this.sumRailAu(reservationDebitTotals, 'au'); + if (cumulativeDebitAu instanceof Error) return cumulativeDebitAu; + if (this.compareAu(cumulativeDebitAu, epochCommit.totals.use_au) !== 0) { + return new Error('Targeted apply cumulative debit does not match the epoch commit.'); + } + } + + const balances = new Map(); + for (const debit of debitMap.values()) { + const { rail, user, au: debitAu } = debit; + const balance = await this.balanceRecord(user, rail); + if (balance instanceof Error) return balance; + const balanceError = this.guardianValidateBalanceRecord(balance, user, rail); + if (balanceError) return balanceError; + if (this.compareAu(balance.au, debitAu) < 0) return new Error('Insufficient credit balance.'); + const nextBalanceAu = this.safeSubAu(balance.au, debitAu); + if (nextBalanceAu instanceof Error) return nextBalanceAu; + balances.set(stableJson([rail, user]), { + ...balance, + rail, + au: nextBalanceAu, + updated_epoch: value.epoch, + updated_at: this.tx, + }); + } + + const earningDeltas = new Map(); + const feeDeltaByRail = new Map(PROVIDER_ACCEPTED_RAIL_ORDER.map((rail) => [rail, ZERO_AU])); + const burnDeltaByRail = new Map(PROVIDER_ACCEPTED_RAIL_ORDER.map((rail) => [rail, ZERO_AU])); + let feeDeltaTotalAu = ZERO_AU; + let burnDeltaTotalAu = ZERO_AU; + for (const earning of grossEarningMap.values()) { + const { rail, provider, gross_au: grossAu } = earning; + const providerRecord = await this.get(`prov/${provider}`); + if (!providerRecord) return new Error('Provider not found.'); + if (!Array.isArray(providerRecord.accepted_rails)) { + return new Error('Provider accepted rails are not set.'); + } + if (!providerRecord.accepted_rails.includes(rail)) { + return new Error('Provider does not accept payment rail.'); + } + + const settlementDelta = boundedReceiptSettlement + ? options.providerSettlementDeltas?.get(stableJson([rail, provider])) + : null; + if (boundedReceiptSettlement && + (!settlementDelta || settlementDelta.gross_au !== grossAu)) { + return new Error('Bounded receipt provider settlement delta is missing.'); + } + const feeAu = settlementDelta?.fee_au ?? + this.safeMulDivAu(grossAu, params.fee_bps, 10_000); + const burnAu = settlementDelta?.burn_au ?? (rail === 'tap' + ? this.safeMulDivAu(grossAu, TAP_BURN_BPS, 10_000) + : ZERO_AU); + const providerAu = settlementDelta?.provider_au ?? this.safeSubAu( + this.safeSubAu(grossAu, feeAu), + burnAu + ); + if (feeAu instanceof Error || burnAu instanceof Error || providerAu instanceof Error) { + return new Error('Invalid provider settlement delta.'); + } + feeDeltaTotalAu = this.safeAddAu(feeDeltaTotalAu, feeAu); + if (feeDeltaTotalAu instanceof Error) return feeDeltaTotalAu; + const nextRailFee = this.safeAddAu(feeDeltaByRail.get(rail) ?? ZERO_AU, feeAu); + if (nextRailFee instanceof Error) return nextRailFee; + feeDeltaByRail.set(rail, nextRailFee); + burnDeltaTotalAu = this.safeAddAu(burnDeltaTotalAu, burnAu); + if (burnDeltaTotalAu instanceof Error) return burnDeltaTotalAu; + const nextRailBurn = this.safeAddAu(burnDeltaByRail.get(rail) ?? ZERO_AU, burnAu); + if (nextRailBurn instanceof Error) return nextRailBurn; + burnDeltaByRail.set(rail, nextRailBurn); + const key = stableJson([rail, provider]); + const current = earningDeltas.get(key) ?? { rail, provider, provider_record: providerRecord, au: ZERO_AU }; + const next = this.safeAddAu(current.au, providerAu); + if (next instanceof Error) return next; + earningDeltas.set(key, { ...current, au: next }); + } + + const earnings = new Map(); + let earnCumTotal = ZERO_AU; + for (const delta of earningDeltas.values()) { + const { rail, provider, provider_record: providerRecord, au: deltaAu } = delta; + const current = await this.earningRecord(provider, rail); + if (current instanceof Error) return current; + const currentError = this.guardianValidateEarningRecord(current, provider, rail); + if (currentError) return currentError; + const probeGate = await this.probeGateForEarning(provider, current, params); + if (probeGate instanceof Error) return probeGate; + const lockedEarningEpochs = this.providerLockedEarningEpochs(providerRecord, params); + if (lockedEarningEpochs instanceof Error) return lockedEarningEpochs; + const disputeGate = await this.providerHasOpenDispute(provider); + if (disputeGate instanceof Error) return disputeGate; + const refreshed = this.refreshEarningHoldback( + current, + value.epoch, + lockedEarningEpochs, + probeGate, + disputeGate + ); + if (refreshed instanceof Error) return refreshed; + const totalAu = this.safeAddAu(refreshed.total_au, deltaAu); + if (totalAu instanceof Error) return totalAu; + const heldAu = this.safeAddAu(refreshed.held_au, deltaAu); + if (heldAu instanceof Error) return heldAu; + const holdbacks = this.appendHoldbackBucket( + refreshed.holdbacks, + value.epoch, + deltaAu, + lockedEarningEpochs + ); + if (holdbacks instanceof Error) return holdbacks; + earnings.set(stableJson([rail, provider]), { + ...refreshed, + rail, + total_au: totalAu, + held_au: heldAu, + holdbacks, + updated_epoch: value.epoch, + updated_at: this.tx, + }); + earnCumTotal = this.safeAddAu(earnCumTotal, totalAu); + if (earnCumTotal instanceof Error) return earnCumTotal; + } + + const epochEarningUpdates = []; + const epochMarketUsageUpdates = []; + const epochMarketProviderUpdates = []; + let epochProviderCount = 0; + let epochMarketCount = 0; + let epochEarnCumAu = ZERO_AU; + let epochFeeAu = feeDeltaTotalAu; + let epochBurnAu = burnDeltaTotalAu; + if (boundedReceiptSettlement) { + const priorProviderCount = page === 0 + ? 0 + : applyState.pending_receipt_provider_count; + const priorMarketCount = page === 0 + ? 0 + : applyState.pending_receipt_market_count; + const priorEarnCumAu = page === 0 + ? ZERO_AU + : this.normalizeAu( + applyState.pending_receipt_earn_cum_au, + 'pending receipt cumulative earning', + { allowZero: true } + ); + const priorFeeAu = page === 0 + ? ZERO_AU + : this.normalizeAu( + applyState.pending_receipt_fee_au, + 'pending receipt epoch fee', + { allowZero: true } + ); + const priorBurnAu = page === 0 + ? ZERO_AU + : this.normalizeAu( + applyState.pending_receipt_burn_au, + 'pending receipt epoch burn', + { allowZero: true } + ); + if (!Number.isSafeInteger(priorProviderCount) || priorProviderCount < 0 || + !Number.isSafeInteger(priorMarketCount) || priorMarketCount < 0 || + priorEarnCumAu instanceof Error || priorFeeAu instanceof Error || + priorBurnAu instanceof Error) { + return new Error('Bounded receipt settlement cumulative state is invalid.'); + } + let newProviderCount = 0; + for (const earning of this.sortedRailRecords(grossEarningMap, 'provider')) { + const identity = stableJson([earning.rail, earning.provider]); + const delta = earningDeltas.get(identity); + const nextEarning = earnings.get(identity); + if (!delta || !nextEarning) { + return new Error('Bounded receipt provider earning update is missing.'); + } + const key = `epoch/earning-provider/${value.epoch}/${earning.rail}/${earning.provider}`; + const existing = await this.get(key); + const priorTotalAu = this.safeSubAu(nextEarning.total_au, delta.au); + if (priorTotalAu instanceof Error) return priorTotalAu; + let marker = existing; + if (marker === null) { + newProviderCount += 1; + marker = { + type: 'epoch_provider_earning', + epoch: value.epoch, + rail: earning.rail, + provider: earning.provider, + prior_cumulative_au: priorTotalAu, + gross_au: ZERO_AU, + net_au: ZERO_AU, + cumulative_au: priorTotalAu, + first_page: page, + last_page: page - 1, + updated_at: null, + }; + } else if ( + marker.type !== 'epoch_provider_earning' || + marker.epoch !== value.epoch || + marker.rail !== earning.rail || + marker.provider !== earning.provider || + marker.cumulative_au !== priorTotalAu || + !Number.isSafeInteger(marker.first_page) || + !Number.isSafeInteger(marker.last_page) || + marker.first_page < 0 || + marker.last_page >= page + ) { + return new Error('Bounded receipt provider earning marker is inconsistent.'); + } + const grossAu = this.safeAddAu(marker.gross_au, earning.gross_au); + const netAu = this.safeAddAu(marker.net_au, delta.au); + if (grossAu instanceof Error || netAu instanceof Error) { + return new Error('Bounded receipt provider earning marker overflow.'); + } + epochEarningUpdates.push({ + key, + value: { + ...marker, + gross_au: grossAu, + net_au: netAu, + cumulative_au: nextEarning.total_au, + last_page: page, + updated_at: this.tx, + }, + }); + } + let newMarketCount = 0; + const updatedMarketMarkers = new Map(); + for (const usage of canonicalPageMarketUsageMap.values()) { + const ctxKey = usage.ctx_bracket ? `ctx/${usage.ctx_bracket}` : 'base'; + const markerKey = + `epoch/market-usage/${value.epoch}/${usage.enclave_id}/${ctxKey}`; + const existing = await this.get(markerKey); + let marker = existing; + if (marker === null) { + newMarketCount += 1; + marker = { + type: 'epoch_market_usage', + epoch: value.epoch, + enclave_id: usage.enclave_id, + ...(usage.ctx_bracket ? { ctx_bracket: usage.ctx_bracket } : {}), + ...(usage.ctx_bracket_table_ver ? { + ctx_bracket_table_ver: usage.ctx_bracket_table_ver, + } : {}), + demand_au: ZERO_AU, + settled_usage: {}, + compute_ms: ZERO_AU, + legacy_receipt_count: 0, + session_count: 0, + provider_count: 0, + capacity_slot_count: 0, + first_page: page, + last_page: page - 1, + updated_at: null, + }; + } else if ( + marker.type !== 'epoch_market_usage' || + marker.epoch !== value.epoch || + marker.enclave_id !== usage.enclave_id || + (marker.ctx_bracket ?? null) !== (usage.ctx_bracket ?? null) || + (marker.ctx_bracket_table_ver ?? null) !== + (usage.ctx_bracket_table_ver ?? null) || + !Number.isSafeInteger(marker.first_page) || + !Number.isSafeInteger(marker.last_page) || + marker.first_page < 0 || + marker.last_page >= page || + !Number.isSafeInteger(marker.session_count) || + marker.session_count < 1 || + !Number.isSafeInteger(marker.provider_count) || + marker.provider_count < 1 || + !/^(0|[1-9][0-9]*)$/.test(marker.compute_ms ?? '') || + !Number.isSafeInteger(marker.legacy_receipt_count) || + marker.legacy_receipt_count < 0 || + !Number.isSafeInteger(marker.capacity_slot_count) || + marker.capacity_slot_count < 0 + ) { + return new Error('Bounded receipt market usage marker is inconsistent.'); + } + let newProviderCount = 0; + let capacitySlotDelta = 0; + for (const provider of usage.providers) { + const capacitySlots = usage.provider_capacities + .find((entry) => entry.provider === provider)?.capacity_slots ?? 0; + if (!Number.isSafeInteger(capacitySlots) || capacitySlots < 0) { + return new Error('Bounded receipt market provider capacity is invalid.'); + } + const providerKey = + `epoch/market-provider/${value.epoch}/${usage.enclave_id}/${ctxKey}/${provider}`; + const providerMarker = await this.get(providerKey); + if (providerMarker === null) { + newProviderCount += 1; + capacitySlotDelta = this.safeAddCount( + capacitySlotDelta, + capacitySlots, + 'bounded receipt market capacity slots' + ); + if (capacitySlotDelta instanceof Error) return capacitySlotDelta; + epochMarketProviderUpdates.push({ + key: providerKey, + value: { + type: 'epoch_market_provider', + epoch: value.epoch, + enclave_id: usage.enclave_id, + ...(usage.ctx_bracket ? { ctx_bracket: usage.ctx_bracket } : {}), + provider, + capacity_slots: capacitySlots, + first_page: page, + updated_at: this.tx, + }, + }); + } else if ( + providerMarker.type !== 'epoch_market_provider' || + providerMarker.epoch !== value.epoch || + providerMarker.enclave_id !== usage.enclave_id || + (providerMarker.ctx_bracket ?? null) !== (usage.ctx_bracket ?? null) || + providerMarker.provider !== provider || + !Number.isSafeInteger(providerMarker.capacity_slots) || + providerMarker.capacity_slots < 0 || + !Number.isSafeInteger(providerMarker.first_page) || + providerMarker.first_page < 0 || + providerMarker.first_page >= page + ) { + return new Error('Bounded receipt market provider marker is inconsistent.'); + } else if (capacitySlots > providerMarker.capacity_slots) { + capacitySlotDelta = this.safeAddCount( + capacitySlotDelta, + capacitySlots - providerMarker.capacity_slots, + 'bounded receipt market capacity slots' + ); + if (capacitySlotDelta instanceof Error) return capacitySlotDelta; + epochMarketProviderUpdates.push({ + key: providerKey, + value: { + ...providerMarker, + capacity_slots: capacitySlots, + updated_at: this.tx, + }, + }); + } + } + const demandAu = this.safeAddAu(marker.demand_au, usage.demand_au); + const sessionCount = this.safeAddCount( + marker.session_count, + usage.session_count, + 'bounded receipt market sessions' + ); + const providerCount = this.safeAddCount( + marker.provider_count, + newProviderCount, + 'bounded receipt market providers' + ); + const computeMs = this.safeAddAu(marker.compute_ms, usage.compute_ms); + const legacyReceiptCount = this.safeAddCount( + marker.legacy_receipt_count, + usage.legacy_receipt_count, + 'bounded receipt legacy receipt count' + ); + const capacitySlotCount = this.safeAddCount( + marker.capacity_slot_count, + capacitySlotDelta, + 'bounded receipt market capacity slots' + ); + if (demandAu instanceof Error || sessionCount instanceof Error || + providerCount instanceof Error || computeMs instanceof Error || + legacyReceiptCount instanceof Error || + capacitySlotCount instanceof Error) { + return new Error('Bounded receipt market usage marker overflow.'); + } + const settledUsage = this.addSettledUsage(marker.settled_usage, usage.settled_usage); + if (settledUsage instanceof Error) return settledUsage; + const nextMarker = { + ...marker, + settled_usage: settledUsage, + demand_au: demandAu, + compute_ms: computeMs, + legacy_receipt_count: legacyReceiptCount, + session_count: sessionCount, + provider_count: providerCount, + capacity_slot_count: capacitySlotCount, + last_page: page, + updated_at: this.tx, + }; + epochMarketUsageUpdates.push({ key: markerKey, value: nextMarker }); + updatedMarketMarkers.set( + this.priceMarketKey(usage.enclave_id, usage.ctx_bracket ?? null), + nextMarker + ); + } + epochProviderCount = this.safeAddCount( + priorProviderCount, + newProviderCount, + 'bounded receipt provider count' + ); + if (epochProviderCount instanceof Error) return epochProviderCount; + epochMarketCount = this.safeAddCount( + priorMarketCount, + newMarketCount, + 'bounded receipt market count' + ); + if (epochMarketCount instanceof Error) return epochMarketCount; + epochFeeAu = this.safeAddAu(priorFeeAu, feeDeltaTotalAu); + epochBurnAu = this.safeAddAu(priorBurnAu, burnDeltaTotalAu); + if (epochFeeAu instanceof Error || epochBurnAu instanceof Error) { + return new Error('Bounded receipt epoch fee or burn overflow.'); + } + if (!lastPage) { + epochEarnCumAu = priorEarnCumAu; + } else { + if (marketUsageMap.size !== epochMarketCount) { + return new Error('Final market usage count does not match bounded settlement state.'); + } + for (const usage of marketUsageMap.values()) { + const marketKey = this.priceMarketKey( + usage.enclave_id, + usage.ctx_bracket ?? null + ); + const ctxKey = usage.ctx_bracket ? `ctx/${usage.ctx_bracket}` : 'base'; + const marker = updatedMarketMarkers.get(marketKey) ?? await this.get( + `epoch/market-usage/${value.epoch}/${usage.enclave_id}/${ctxKey}` + ); + if (!marker || stableJson({ + enclave_id: marker.enclave_id, + ...(marker.ctx_bracket ? { ctx_bracket: marker.ctx_bracket } : {}), + ...(marker.ctx_bracket_table_ver ? { + ctx_bracket_table_ver: marker.ctx_bracket_table_ver, + } : {}), + demand_au: marker.demand_au, + session_count: marker.session_count, + provider_count: marker.provider_count, + compute_ms: marker.compute_ms, + legacy_receipt_count: marker.legacy_receipt_count, + capacity_slot_count: marker.capacity_slot_count, + }) !== stableJson(usage)) { + return new Error('Final market usage does not match canonical receipt settlement state.'); + } + } + const updatedMarkers = new Map( + epochEarningUpdates.map((update) => [ + stableJson([update.value.rail, update.value.provider]), + update.value, + ]) + ); + if (earningFinals.length !== epochProviderCount) { + return new Error('Final provider earning count does not match bounded settlement state.'); + } + const leaves = []; + for (const final of earningFinals) { + const identity = stableJson([final.rail, final.provider]); + const marker = updatedMarkers.get(identity) ?? + await this.get(`epoch/earning-provider/${value.epoch}/${final.rail}/${final.provider}`); + if (!marker || stableJson({ + rail: marker.rail, + provider: marker.provider, + gross_au: marker.gross_au, + net_au: marker.net_au, + cumulative_au: marker.cumulative_au, + }) !== stableJson(final)) { + return new Error('Final provider earning evidence does not match applied settlement state.'); + } + epochEarnCumAu = this.safeAddAu(epochEarnCumAu, final.cumulative_au); + if (epochEarnCumAu instanceof Error) return epochEarnCumAu; + leaves.push(await this.opaqueHash('mayhem-earn-leaf-v1', final)); + } + if (epochProviderCount !== epochCommit.totals.provider_count || + this.compareAu(epochEarnCumAu, epochCommit.totals.earn_au) !== 0 || + this.compareAu(epochFeeAu, epochCommit.totals.fee_au) !== 0 || + this.compareAu(epochBurnAu, epochCommit.totals.burn_au) !== 0) { + return new Error('Final bounded settlement totals do not match the epoch commit.'); + } + const earnRoot = await this.merkleRoot('earn', leaves); + if (earnRoot !== epochCommit.roots.earn) { + return new Error('Final provider earning root does not match the epoch commit.'); + } + const feeRoot = await this.opaqueHash('mayhem-fee-root-v1', { + epoch: value.epoch, + fee_au: epochFeeAu, + fee_cum_au: epochCommit.totals.fee_cum_au, + burn_au: epochBurnAu, + burn_cum_au: epochCommit.totals.burn_cum_au, + tap_burn_bps: TAP_BURN_BPS, + }); + if (feeRoot !== epochCommit.roots.fee) { + return new Error('Final fee root does not match the epoch commit.'); + } + } + } + + const feeRecords = new Map(); + const nextFeeRecords = new Map(); + const burnRecords = new Map(); + const nextBurnRecords = new Map(); + let nextFeeCumTotal = ZERO_AU; + let nextBurnCumTotal = ZERO_AU; + const touchedRails = new Set([ + ...Array.from(debitRailTotals.entries()).filter(([, au]) => this.compareAu(au, ZERO_AU) > 0).map(([rail]) => rail), + ...Array.from(grossRailTotals.entries()).filter(([, au]) => this.compareAu(au, ZERO_AU) > 0).map(([rail]) => rail), + ]); + for (const rail of PROVIDER_ACCEPTED_RAIL_ORDER) { + const fee = await this.feeCumRecord(rail); + if (fee instanceof Error) return fee; + const feeError = this.guardianValidateFeeRecord(fee, rail); + if (feeError) return feeError; + feeRecords.set(rail, fee); + const feeDeltaAu = feeDeltaByRail.get(rail) ?? ZERO_AU; + const nextFeeCum = this.safeAddAu(fee.cum_au, feeDeltaAu); + if (nextFeeCum instanceof Error) return nextFeeCum; + // settled_cum_au must advance with this epoch's settled rail debits in + // the SAME record that carries the new cum_au, otherwise + // guardianValidateFeeRecord rejects every fee-bearing epoch apply + // (settled < cum). This mirrors next_settled_cum_by_rail in + // guardianValidateEpochTotals, which is also what gets persisted. + const nextFeeSettledCum = this.safeAddAu( + fee.settled_cum_au ?? fee.cum_au, + debitRailTotals.get(rail) ?? ZERO_AU + ); + if (nextFeeSettledCum instanceof Error) return nextFeeSettledCum; + const nextFee = touchedRails.has(rail) + ? { + ...fee, + cum_au: nextFeeCum, + settled_cum_au: nextFeeSettledCum, + updated_epoch: value.epoch, + updated_at: this.tx, + last_apply_hash: applyHash, + last_fee_bps: params.fee_bps, + } + : fee; + nextFeeRecords.set(rail, nextFee); + nextFeeCumTotal = this.safeAddAu(nextFeeCumTotal, nextFee.cum_au); + if (nextFeeCumTotal instanceof Error) return nextFeeCumTotal; + + const burn = await this.burnCumRecord(rail); + if (burn instanceof Error) return burn; + const burnError = this.guardianValidateBurnRecord(burn, rail); + if (burnError) return burnError; + burnRecords.set(rail, burn); + const burnDeltaAu = burnDeltaByRail.get(rail) ?? ZERO_AU; + const nextBurnCum = this.safeAddAu(burn.cum_au, burnDeltaAu); + if (nextBurnCum instanceof Error) return nextBurnCum; + const nextBurn = touchedRails.has(rail) + ? { + ...burn, + cum_au: nextBurnCum, + updated_epoch: value.epoch, + updated_at: this.tx, + last_apply_hash: applyHash, + burn_bps: rail === 'tap' ? TAP_BURN_BPS : 0, + } + : burn; + nextBurnRecords.set(rail, nextBurn); + nextBurnCumTotal = this.safeAddAu(nextBurnCumTotal, nextBurn.cum_au); + if (nextBurnCumTotal instanceof Error) return nextBurnCumTotal; + } + + const grossAfterFees = this.safeSubAu(grossTotal, feeDeltaTotalAu); + if (grossAfterFees instanceof Error) return grossAfterFees; + const providerDeltaTotal = this.safeSubAu(grossAfterFees, burnDeltaTotalAu); + if (providerDeltaTotal instanceof Error) return providerDeltaTotal; + const guardian = this.guardianCheckEpochApply({ + epoch: value.epoch, + page, + applyState, + feeRecords, + burnRecords, + debitTotal, + debitRailTotals, + feeDeltaByRail, + burnDeltaByRail, + providerDeltaTotal, + nextFeeRecords, + nextBurnRecords, + balances, + earnings, + }); + if (guardian instanceof Error) return guardian; + + const canonicalActivity = new Map(); + if (Array.isArray(options.canonicalMarketUsage)) { + for (const usage of options.canonicalMarketUsage) canonicalActivity.set( + this.priceMarketKey(usage.enclave_id, usage.ctx_bracket ?? null), usage); + } + if (boundedReceiptSettlement && lastPage) { + for (const usage of marketUsageMap.values()) { + const ctxKey = usage.ctx_bracket ? `ctx/${usage.ctx_bracket}` : 'base'; + const markerKey = `epoch/market-usage/${value.epoch}/${usage.enclave_id}/${ctxKey}`; + const marker = epochMarketUsageUpdates.find((u) => u.key === markerKey)?.value ?? await this.get(markerKey); + canonicalActivity.set(this.priceMarketKey(usage.enclave_id, usage.ctx_bracket ?? null), marker); + } + } + let marketPriceUpdates = []; + if (marketUsageProvided) { + marketPriceUpdates = await this.computeMarketPriceUpdates(marketUsageMap, { + epoch: value.epoch, + at: value.at, + epochSeconds: params.epoch_seconds, + canonicalActivity, + includeDormant: boundedReceiptSettlement && lastPage, + }); + if (marketPriceUpdates instanceof Error) return marketPriceUpdates; + } + const priceDerivations = await this.priceDerivationsFromMarketUpdates(marketPriceUpdates, { + epoch: value.epoch, + at: value.at, + epochSeconds: params.epoch_seconds, + usageRoot: boundedReceiptSettlement ? epochCommit.roots.use : (roots?.use ?? null), + }); + if (priceDerivations instanceof Error) return priceDerivations; + + if (totals) { + const totalsError = await this.validateEpochApplyTotals({ + epoch: value.epoch, + roots, + totals, + debitTotal, + feeDeltaAu: feeDeltaTotalAu, + nextFeeCum: nextFeeCumTotal, + burnDeltaAu: burnDeltaTotalAu, + nextBurnCum: nextBurnCumTotal, + providerCount: grossEarningMap.size, + earnCumTotal, + epochSeconds: params.epoch_seconds, + priceDerivations, + }); + if (totalsError) return totalsError; + } + + if (lastPage && boundedReceiptSettlement) { + const finalEvidenceError = await this.validatePagedEpochCommitEvidence({ + commit: epochCommit, + feeCumAu: nextFeeCumTotal, + burnCumAu: nextBurnCumTotal, + priceDerivations, + }); + if (finalEvidenceError) return finalEvidenceError; + } + + const nextApplyState = this.nextEpochApplyState({ + applyState, + epoch: value.epoch, + page, + lastPage, + applyHash, + epochSeconds: params.epoch_seconds, + settledAt: value.at, + reservationDebitTotals, + receiptApply: { + index_count: receiptIndex.count, + index_revision: receiptIndex.revision, + index_page_count: receiptIndex.page_count, + index_updated_at: receiptIndex.updated_at, + commit_hash: value.epoch_commit_hash, + allocation_count: cumulativeAllocationCount, + provider_count: epochProviderCount, + market_count: epochMarketCount, + earn_cum_au: epochEarnCumAu, + fee_au: epochFeeAu, + burn_au: epochBurnAu, + }, + }); + const previousChallengeAnchor = await this.prepareCanaryChallengeAnchor(applyState); + if (previousChallengeAnchor instanceof Error) return previousChallengeAnchor; + const epochApplyAnchor = lastPage + ? await this.prepareEpochApplyAnchor(nextApplyState) + : null; + if (epochApplyAnchor instanceof Error) return epochApplyAnchor; + const challengeAnchor = lastPage + ? await this.prepareCanaryChallengeAnchor(nextApplyState) + : null; + if (challengeAnchor instanceof Error) return challengeAnchor; + + if (options.commitTransition?.write) { + if (options.commitTransition.archive) { + await this.put( + options.commitTransition.archive.key, + options.commitTransition.archive.value + ); + } + await this.put(options.commitTransition.key, options.commitTransition.record); + } + + for (const balance of this.sortedRailRecords(balances, 'user')) { + await this.put(this.balanceKey(balance.user, balance.rail), balance); + } + for (const earning of this.sortedRailRecords(earnings, 'provider')) { + await this.put(this.earningKey(earning.provider, earning.rail), earning); + } + for (const update of epochEarningUpdates) { + await this.put(update.key, update.value); + } + for (const update of epochMarketProviderUpdates) { + await this.put(update.key, update.value); + } + for (const update of epochMarketUsageUpdates) { + await this.put(update.key, update.value); + } + for (const rail of PROVIDER_ACCEPTED_RAIL_ORDER.filter((rail) => touchedRails.has(rail))) { + const feeRecord = { + ...nextFeeRecords.get(rail), + settled_cum_au: guardian.next_settled_cum_by_rail.get(rail), + }; + await this.put(this.feeCumKey(rail), feeRecord); + await this.put(this.burnCumKey(rail), nextBurnRecords.get(rail)); + } + await this.writePreparedAnchor(previousChallengeAnchor); + await this.put('epoch/apply/state', nextApplyState); + await this.writePreparedAnchor(epochApplyAnchor); + await this.writePreparedAnchor(challengeAnchor); + if (lastPage && boundedReceiptSettlement) { + await this.writeEpochEvidenceRoots({ + epoch: value.epoch, + at: value.at, + epoch_seconds: params.epoch_seconds, + roots: epochCommit.roots, + totals: epochCommit.totals, + feeDeltaAu: epochCommit.totals.fee_au, + feeCumAu: nextFeeCumTotal, + burnDeltaAu: epochCommit.totals.burn_au, + burnCumAu: nextBurnCumTotal, + priceDerivations: [], + }); + await this.writeBoundedMarketPriceEvidence({ + epoch: value.epoch, + at: value.at, + epochSeconds: params.epoch_seconds, + usageRoot: epochCommit.roots.use, + derivations: priceDerivations, + }); + } else if (totals) { + await this.writeEpochEvidenceRoots({ + epoch: value.epoch, + at: value.at, + epoch_seconds: params.epoch_seconds, + roots, + totals, + feeDeltaAu: feeDeltaTotalAu, + feeCumAu: nextFeeCumTotal, + burnDeltaAu: burnDeltaTotalAu, + burnCumAu: nextBurnCumTotal, + priceDerivations, + }); + } else if (priceDerivations.length > 0) { + await this.writePriceDerivationEvidence({ + epoch: value.epoch, + at: value.at, + epoch_seconds: params.epoch_seconds, + root: await this.priceDerivationRoot(priceDerivations), + count: priceDerivations.length, + derivations: priceDerivations, + }); + } + const activityIndexError = await this.writeActivityMarketIndex(marketPriceUpdates); + if (activityIndexError instanceof Error) return activityIndexError; + for (const update of marketPriceUpdates) { + await this.put(update.schedule_key, update.schedule); + await this.put(update.record_key, update.record); + } + + const result = { + ok: true, + op: 'epochApply', + epoch: value.epoch, + idempotent: false, + debited_au: debitTotal, + earned_au: providerDeltaTotal, + fee_au: feeDeltaTotalAu, + burn_au: burnDeltaTotalAu, + rails: Array.from(touchedRails).sort( + (left, right) => PROVIDER_ACCEPTED_RAIL_ORDER.indexOf(left) - PROVIDER_ACCEPTED_RAIL_ORDER.indexOf(right) + ), + }; + if (pagedApply) { + result.page = page; + result.last_page = lastPage; + } + if (marketPriceUpdates.length > 0) { + result.market_prices = marketPriceUpdates.map((update) => ({ + enclave_id: update.enclave_id, + ...(update.ctx_bracket ? { + ctx_bracket: update.ctx_bracket, + ctx_bracket_table_ver: update.ctx_bracket_table_ver, + } : {}), + ver: update.ver, + utilization_bps: update.utilization_bps, + multiplier_bps: update.multiplier_bps, + active_supply: update.active_supply, + active_demand_au: update.active_demand_au, + frozen: update.frozen, + derivation_hash: update.derivation_hash, + })); + result.price_root = await this.priceDerivationRoot(priceDerivations); + } + console.log('mayhem epochApply', result); + return result; + } + + + async epochSealEmpty() { + const adminError = await this.requireAdmin(); + if (adminError) return adminError; + + const shapeError = this.validateExactCommandValue( + ['op', 'epoch', 'at', 'reason_hash'], + 'epoch_seal_empty' + ); + if (shapeError) return shapeError; + if (!Number.isSafeInteger(this.value.epoch) || this.value.epoch < 1) { + return new Error('Invalid empty epoch seal epoch.'); + } + if (!Number.isSafeInteger(this.value.at) || this.value.at < 0) { + return new Error('Invalid empty epoch seal timestamp.'); + } + if (!this.isHexBytes(this.value.reason_hash, 32)) { + return new Error('Invalid empty epoch seal reason hash.'); + } + + const params = await this.activeParamsAt(this.value.at, ['epoch_seconds']); + const applyState = await this.epochApplyStateRecord(); + const reasonHash = this.value.reason_hash.toLowerCase(); + const key = `epoch/seal/${this.value.epoch}`; + const existing = await this.get(key); + if (existing) { + const existingHash = await this.epochEmptySealHash(this.epochEmptySealHashValue(existing)); + if ( + existing.seal_hash === existingHash && + existing.reason_hash === reasonHash && + existing.at === this.value.at && + existing.sealed_by === this.address && + applyState.updated_epoch === this.value.epoch && + applyState.last_apply_hash === existing.seal_hash && + (applyState.pending_epoch ?? null) === null + ) { + return { + ok: true, + op: 'epochSealEmpty', + epoch: this.value.epoch, + idempotent: true, + seal_hash: existing.seal_hash, + }; + } + return new Error('Empty epoch seal already exists.'); + } + + const receiptIndex = await this.get(this.receiptEpochIndexKey(this.value.epoch)); + if (receiptIndex !== null) { + if (receiptIndex.type !== 'canonical_receipt_epoch_index' || + receiptIndex.epoch !== this.value.epoch || + !Number.isSafeInteger(receiptIndex.count) || + receiptIndex.count < 0 || + !Number.isSafeInteger(receiptIndex.revision) || + receiptIndex.revision < 0) { + return new Error('Canonical receipt epoch index is invalid.'); + } + if (receiptIndex.count !== 0 || receiptIndex.revision !== 0) { + return new Error('Cannot seal an epoch empty while canonical receipts exist.'); + } + } + + const freezeError = await this.validateFrozenEpoch(this.value.epoch, this.value.at, + receiptIndex ?? { + type: 'canonical_receipt_epoch_index', epoch: this.value.epoch, count: 0, + page_size: RECEIPT_EPOCH_INDEX_PAGE_SIZE, page_count: 0, revision: 0, updated_at: null, + }); + if (freezeError) return freezeError; + const notYetActiveAt = this.value.epoch * params.epoch_seconds; + if (this.value.at < notYetActiveAt) { + return new Error('Empty epoch seal is not active until the epoch window ends.'); + } + const cadenceError = await this.validateEpochCadenceTime( + applyState, + this.value.epoch, + 0, + this.value.at, + params.epoch_seconds + ); + if (cadenceError) return cadenceError; + const pageOrderError = this.validateEpochApplyPageOrder(applyState, this.value.epoch, 0); + if (pageOrderError) return pageOrderError; + + const activityUpdates = await this.computeMarketPriceUpdates(new Map(), { + epoch: this.value.epoch, at: this.value.at, epochSeconds: params.epoch_seconds, + canonicalActivity: new Map(), includeDormant: true, + }); + if (activityUpdates instanceof Error) return activityUpdates; + const usageRoot = await this.merkleRoot('use', []); + const activityDerivations = await this.priceDerivationsFromMarketUpdates(activityUpdates, { + epoch: this.value.epoch, at: this.value.at, epochSeconds: params.epoch_seconds, usageRoot, + }); + if (activityDerivations instanceof Error) return activityDerivations; + if ((await this.get(`market/price/${this.value.epoch}`)) !== null) { + return new Error('Empty epoch market price evidence already exists.'); + } + const activityRoot = await this.priceDerivationRoot(activityDerivations); + const sealValue = { + type: 'epoch_empty_seal', + market_price_root: activityRoot, + market_price_count: activityDerivations.length, + epoch: this.value.epoch, + at: this.value.at, + epoch_seconds: params.epoch_seconds, + previous_apply_hash: applyState.last_apply_hash ?? null, + reason_hash: reasonHash, + sealed_by: this.address, + sealed_by_role: 'admin', + totals: { + debited_au: ZERO_AU, + earned_au: ZERO_AU, + fee_au: ZERO_AU, + burn_au: ZERO_AU, + }, + }; + const sealHash = await this.epochEmptySealHash(sealValue); + const record = { + ...sealValue, + seal_hash: sealHash, + sealed_at: this.tx, + }; + const nextApplyState = this.nextEpochApplyState({ + applyState, + epoch: this.value.epoch, + page: 0, + lastPage: true, + applyHash: sealHash, + epochSeconds: params.epoch_seconds, + settledAt: this.value.at, + }); + const previousChallengeAnchor = await this.prepareCanaryChallengeAnchor(applyState); + if (previousChallengeAnchor instanceof Error) return previousChallengeAnchor; + const epochApplyAnchor = await this.prepareEpochApplyAnchor(nextApplyState); + if (epochApplyAnchor instanceof Error) return epochApplyAnchor; + const challengeAnchor = await this.prepareCanaryChallengeAnchor(nextApplyState); + if (challengeAnchor instanceof Error) return challengeAnchor; + + await this.writeBoundedMarketPriceEvidence({ + epoch: this.value.epoch, at: this.value.at, epochSeconds: params.epoch_seconds, + usageRoot, derivations: activityDerivations, + }); + const activityIndexError = await this.writeActivityMarketIndex(activityUpdates); + if (activityIndexError instanceof Error) return activityIndexError; + for (const update of activityUpdates) { + await this.put(update.schedule_key, update.schedule); + await this.put(update.record_key, update.record); + } + await this.put(key, record); + await this.writePreparedAnchor(previousChallengeAnchor); + await this.put('epoch/apply/state', nextApplyState); + await this.writePreparedAnchor(epochApplyAnchor); + await this.writePreparedAnchor(challengeAnchor); + const result = { + ok: true, + op: 'epochSealEmpty', + epoch: this.value.epoch, + idempotent: false, + seal_hash: sealHash, + }; + console.log('mayhem epochSealEmpty', result); + return result; + } + + async epochCommit() { + const banned = await this.get(`committer/ban/${this.address}`); + if (banned?.status === 'banned') return new Error('Epoch committer is banned.'); + + const roots = this.normalizeEpochRoots(this.value.roots); + if (roots instanceof Error) return roots; + const totals = this.normalizeEpochTotals(this.value.totals); + if (totals instanceof Error) return totals; + const params = await this.activeParamsAt(this.value.at, ['challenge_epochs', 'epoch_seconds']); + const normalized = { + epoch: this.value.epoch, + epoch_seconds: params.epoch_seconds, + roots, + totals, + }; + const commitHash = await this.epochCommitHash(normalized); + const key = `epoch/commit/${this.value.epoch}`; + const existing = await this.get(key); + if (existing) { + if (existing.commit_hash === commitHash) { + return { + ok: true, + op: 'epochCommit', + epoch: this.value.epoch, + idempotent: true, + commit_hash: commitHash, + }; + } + return new Error('Epoch commit already exists.'); + } + + const activityEvidence = await this.prepareCommittedActivityEvidence({ + epoch: this.value.epoch, at: this.value.at, epochSeconds: params.epoch_seconds, roots, totals, + }); + if (activityEvidence instanceof Error) return activityEvidence; + const record = { + type: 'epoch_commit', + pricing_schema_version: 2, + ...(activityEvidence ? { expected_activity_evidence: activityEvidence } : {}), + epoch: this.value.epoch, + epoch_seconds: params.epoch_seconds, + roots, + totals, + status: 'provisional', + challenge_epochs: params.challenge_epochs, + provisional_until_epoch: this.value.epoch + params.challenge_epochs, + commit_hash: commitHash, + submitted_by: this.address, + submitted_at: this.tx, + at: this.value.at, + }; + await this.put(key, record); + console.log('mayhem epochCommit', record); + return { + ok: true, + op: 'epochCommit', + epoch: this.value.epoch, + idempotent: false, + commit_hash: commitHash, + }; + } + + async fraudProof() { + if (!FRAUD_PROOF_REASONS.has(this.value.reason)) { + return new Error('Unsupported fraud proof reason.'); + } + if (b4a.from(stableJson(this.value)).byteLength > FRAUD_PROOF_MAX_BYTES) { + return new Error('Fraud proof exceeds 4096 bytes.'); + } + + const commitKey = `epoch/commit/${this.value.epoch}`; + const commit = await this.get(commitKey); + if (!commit) return new Error('Epoch commit not found.'); + + let proofHashPayload; + let proof; + let proofHash = null; + let slashReason = 'receipt_forgery'; + let slashEnclaveId = null; + + if (this.value.reason === 'over_credit') { + if (!hasOwn(this.value, 'receipt')) return new Error('Over-credit fraud proof requires a receipt.'); + if (!hasOwn(this.value, 'claimed_au_owed_cum')) { + return new Error('Over-credit fraud proof requires claimed_au_owed_cum.'); + } + const receipt = await this.normalizeReceiptEnvelope(this.value.receipt); + if (receipt instanceof Error) return receipt; + if (!this.verifyReceiptEnvelope(receipt)) { + return new Error('Invalid receipt signature.'); + } + proofHashPayload = { + epoch: this.value.epoch, + proof_epoch: this.value.proof_epoch, + reason: this.value.reason, + receipt, + claimed_au_owed_cum: this.value.claimed_au_owed_cum, + previous_au_owed_cum: this.value.previous_au_owed_cum ?? ZERO_AU, + }; + proofHash = await this.fraudProofHash(proofHashPayload); + const existingProof = await this.get(`ev/fraud/${this.value.epoch}/${proofHash}`); + if (existingProof) { + return { + ok: true, + op: 'fraudProof', + epoch: this.value.epoch, + idempotent: true, + proof_hash: proofHash, + voided_commit: commit.commit_hash, + banned_submitter: commit.submitted_by, + }; + } + proof = await this.validateOverCreditFraudProof(commit, receipt); + if (proof instanceof Error) return proof; + slashEnclaveId = receipt.body.enclave_id; + } else if (this.value.reason === 'price_derivation') { + if (!hasOwn(this.value, 'price_usage')) { + return new Error('Price derivation fraud proof requires price_usage.'); + } + proof = await this.validatePriceDerivationFraudProof(commit); + if (proof instanceof Error) return proof; + proofHashPayload = { + epoch: this.value.epoch, + proof_epoch: this.value.proof_epoch, + reason: this.value.reason, + price_usage: proof.price_usage, + expected_price_root: proof.expected_price_root, + committed_price_root: proof.committed_price_root, + price_derivation_hash: proof.price_derivation_hash, + }; + slashReason = 'price_forgery'; + slashEnclaveId = proof.enclave_id; + } + + proofHash ??= await this.fraudProofHash(proofHashPayload); + const proofKey = `ev/fraud/${this.value.epoch}/${proofHash}`; + const existingProof = await this.get(proofKey); + if (existingProof) { + return { + ok: true, + op: 'fraudProof', + epoch: this.value.epoch, + idempotent: true, + proof_hash: proofHash, + voided_commit: commit.commit_hash, + banned_submitter: commit.submitted_by, + }; + } + + if (commit.status === 'void') return new Error('Epoch commit is already void.'); + if (this.value.proof_epoch > commit.provisional_until_epoch) { + return new Error('Epoch commit challenge window has closed.'); + } + + const record = { + type: 'fraud_proof', + epoch: this.value.epoch, + proof_epoch: this.value.proof_epoch, + reason: this.value.reason, + proof_hash: proofHash, + submitted_by: this.address, + submitted_at: this.tx, + at: this.value.at, + voided_commit: commit.commit_hash, + banned_submitter: commit.submitted_by, + }; + if (this.value.reason === 'over_credit') { + record.receipt_hash = proof.receipt_hash; + record.actual_au = proof.actual_au; + record.claimed_au = proof.claimed_au; + record.committed_use_root = commit.roots.use; + } else if (this.value.reason === 'price_derivation') { + record.price_usage = proof.price_usage; + record.committed_price_root = proof.committed_price_root; + record.expected_price_root = proof.expected_price_root; + record.price_derivation_hash = proof.price_derivation_hash; + record.price_derivation = proof.price_derivation; + } + const updatedCommit = { + ...commit, + status: 'void', + voided_at: this.tx, + voided_by: this.address, + fraud_reason: this.value.reason, + fraud_proof_hash: proofHash, + }; + const banKey = `committer/ban/${commit.submitted_by}`; + const existingBan = await this.get(banKey); + const ban = existingBan?.status === 'banned' ? existingBan : { + ...(existingBan ?? {}), + submitter: commit.submitted_by, + status: 'banned', + reason: 'fraud_proof', + epoch: this.value.epoch, + proof_hash: proofHash, + banned_at: this.tx, + banned_by: this.address, + }; + + let slash = null; + if ((await this.get(`prov/${commit.submitted_by}`)) !== null) { + const params = await this.activeParamsAt(this.value.at, ['fraud_slash_bps']); + slash = await this.applyProviderSlash({ + providerId: commit.submitted_by, + source: 'fraud_proof', + reason: slashReason, + evidenceHash: proofHash, + epoch: this.value.epoch, + at: this.value.at, + slashBps: params.fraud_slash_bps, + beneficiary: this.address, + enclaveId: slashEnclaveId, + banProvider: true, + tombstoneEnclave: true, + }); + if (slash instanceof Error) return slash; + } + record.slash = slash; + + await this.put(proofKey, record); + await this.put(commitKey, updatedCommit); + await this.put(banKey, ban); + console.log('mayhem fraudProof', record); + return { + ok: true, + op: 'fraudProof', + epoch: this.value.epoch, + idempotent: false, + proof_hash: proofHash, + voided_commit: commit.commit_hash, + banned_submitter: commit.submitted_by, + slash, + }; + } + + async dispute() { + if (!(await this.isAdmin())) { + const consentError = await this.requireConsent(); + if (consentError) return consentError; + } + const validationError = this.validateDisputeOpen(this.value); + if (validationError) return validationError; + + const rail = this.normalizeLedgerRail(this.value.rail, 'dispute rail'); + if (rail instanceof Error) return rail; + const balance = await this.balanceRecord(this.address, rail); + if (balance instanceof Error) return balance; + const balanceError = this.guardianValidateBalanceRecord(balance, this.address, rail); + if (balanceError) return balanceError; + const params = await this.activeParamsAt(this.value.at, [ + 'dispute_deposit_au', + 'dispute_timeout_epochs', + 'max_open_disputes_per_opener', + ]); + const depositAu = params.dispute_deposit_au; + if (this.compareAu(balance.au, depositAu) < 0) return new Error('Insufficient balance for dispute deposit.'); + const applyState = await this.epochApplyStateRecord(); + const disputeEpoch = this.value.epoch ?? applyState.updated_epoch; + if (disputeEpoch > applyState.updated_epoch) { + return new Error('Dispute epoch cannot exceed the latest applied epoch.'); + } + const hold = await this.normalizeSpendHoldRecord( + (await this.get(this.spendHoldKey(this.address, rail, disputeEpoch))) ?? null, + this.address, + rail, + disputeEpoch + ); + if (hold instanceof Error) return hold; + const linkedSession = hold.sessions.find((session) => session.session_id === this.value.session_id.toLowerCase()); + if (!linkedSession) { + return new Error('Dispute must reference an opener-linked spend reservation.'); + } + const sessionDisputeKey = `disp/session/${this.address}/${linkedSession.session_id}`; + if ((await this.get(sessionDisputeKey)) !== null) { + return new Error('Session already has a dispute from this opener.'); + } + if (linkedSession.provider !== this.value.provider.toLowerCase()) { + return new Error('Dispute provider does not match the linked session.'); + } + if (linkedSession.enclave_id !== this.value.enclave_id.toLowerCase()) { + return new Error('Dispute enclave does not match the linked session.'); + } + if ( + this.value.counterparty !== undefined && + this.value.counterparty.toLowerCase() !== linkedSession.provider + ) { + return new Error('Dispute counterparty does not match the linked session provider.'); + } + const openerCountKey = this.disputeOpenCountKey(this.address); + const openerOpenCount = await this.disputeOpenCount(openerCountKey); + if (openerOpenCount instanceof Error) return openerOpenCount; + if (openerOpenCount >= params.max_open_disputes_per_opener) { + return new Error('Open dispute limit reached.'); + } + const providerCountKey = this.providerOpenDisputeCountKey(linkedSession.provider); + const providerOpenCount = await this.disputeOpenCount(providerCountKey); + if (providerOpenCount instanceof Error) return providerOpenCount; + const expiresAfterEpoch = disputeEpoch + params.dispute_timeout_epochs; + if (!Number.isSafeInteger(expiresAfterEpoch)) return new Error('Dispute timeout epoch overflow.'); + + const nextBalanceAu = this.safeSubAu(balance.au, depositAu); + if (nextBalanceAu instanceof Error) return nextBalanceAu; + const nextBalance = { + ...balance, + rail, + au: nextBalanceAu, + updated_epoch: Math.max(balance.updated_epoch, this.value.epoch ?? 0), + updated_at: this.tx, + }; + const nextBalanceError = this.guardianValidateBalanceRecord(nextBalance, this.address, rail); + if (nextBalanceError) return nextBalanceError; + + const next = await this.get('disp/next'); + const disputeId = next?.next ?? 1; + const record = { + type: 'dispute', + dispute_id: disputeId, + status: 'open', + rail, + opened_by: this.address, + session_id: linkedSession.session_id, + reason: this.value.reason, + provider: linkedSession.provider, + counterparty: this.value.counterparty?.toLowerCase() ?? linkedSession.provider, + enclave_id: linkedSession.enclave_id, + reservation_key: this.spendHoldKey(this.address, rail, disputeEpoch), + reservation_voucher_hash: linkedSession.voucher_hash, + epoch: disputeEpoch, + at: this.value.at, + evidence_hash: this.value.evidence_hash ?? null, + evidence: cloneValue(this.value.evidence ?? null), + deposit_au: depositAu, + deposit_holder: this.address, + timeout_epochs: params.dispute_timeout_epochs, + expires_after_epoch: expiresAfterEpoch, + opened_at: this.tx, + updated_at: this.tx, + }; + record.dispute_hash = await this.opaqueHash('mayhem-dispute-v1', record); + + await this.put(this.balanceKey(this.address, rail), nextBalance); + await this.put(`disp/${disputeId}`, record); + await this.put(sessionDisputeKey, { + opener: this.address, + session_id: linkedSession.session_id, + dispute_id: disputeId, + opened_at: this.tx, + }); + await this.put('disp/next', { next: disputeId + 1, updated_at: this.tx }); + await this.put(openerCountKey, { + opener: this.address, + count: openerOpenCount + 1, + updated_at: this.tx, + }); + await this.put(providerCountKey, { + provider: linkedSession.provider, + count: providerOpenCount + 1, + updated_at: this.tx, + }); + console.log('mayhem dispute', record); + return { + ok: true, + op: 'dispute', + dispute_id: disputeId, + deposit_au: depositAu, + expires_after_epoch: expiresAfterEpoch, + dispute_hash: record.dispute_hash, + }; + } + + async disputeResolve() { + const adminError = await this.requireAdmin(); + if (adminError) return adminError; + if (!DISPUTE_OUTCOMES.has(this.value.outcome)) return new Error('Unsupported dispute outcome.'); + if (!DISPUTE_DEPOSIT_ACTIONS.has(this.value.deposit_action)) { + return new Error('Unsupported dispute deposit action.'); + } + + const key = `disp/${this.value.dispute_id}`; + const dispute = await this.get(key); + if (!dispute || dispute.type !== 'dispute') return new Error('Dispute not found.'); + if (dispute.status !== 'open') return new Error('Dispute is not open.'); + if (this.value.beneficiary !== undefined && !this.isSafeKeyPart(this.value.beneficiary)) { + return new Error('Invalid slash beneficiary.'); + } + if (this.value.outcome === 'provider_fault' && !dispute.provider) { + return new Error('Provider fault disputes require a provider.'); + } + if (this.value.outcome === 'provider_fault') { + const provider = await this.get(`prov/${dispute.provider}`); + if (!provider) return new Error('Provider not found.'); + } + if (this.value.slash === true && !dispute.provider) { + return new Error('Dispute slash requires a provider.'); + } + if (this.value.slash === true && this.value.outcome !== 'provider_fault') { + return new Error('Only provider_fault disputes may slash a provider.'); + } + + const applyState = await this.epochApplyStateRecord(); + if (applyState.updated_epoch > dispute.expires_after_epoch) { + return new Error('Dispute resolution window has closed; expire the dispute.'); + } + if ( + this.value.outcome === 'opener_fault' && + this.value.deposit_action !== 'partial_forfeit' + ) { + return new Error('Opener-fault disputes require a partial deposit forfeit.'); + } + if ( + this.value.outcome !== 'opener_fault' && + this.value.deposit_action === 'partial_forfeit' + ) { + return new Error('Partial deposit forfeit is reserved for opener-fault disputes.'); + } + + let depositRefundedAu = ZERO_AU; + let depositForfeitedAu = ZERO_AU; + const rail = this.normalizeLedgerRail(dispute.rail, 'dispute rail'); + if (rail instanceof Error) return rail; + if (this.value.deposit_action === 'refund') { + depositRefundedAu = dispute.deposit_au; + } else if (this.value.deposit_action === 'forfeit') { + depositForfeitedAu = dispute.deposit_au; + } else { + const params = await this.activeParamsAt(this.value.at, [ + 'dispute_opener_fault_forfeit_bps', + ]); + const deposit = this.parseAu(dispute.deposit_au, 'dispute deposit', { allowZero: false }); + if (deposit instanceof Error) return deposit; + if (deposit < 2n) return new Error('Dispute deposit is too small to split.'); + let forfeited = (deposit * BigInt(params.dispute_opener_fault_forfeit_bps)) / 10_000n; + if (forfeited < 1n) forfeited = 1n; + if (forfeited >= deposit) forfeited = deposit - 1n; + depositForfeitedAu = this.canonicalAu(forfeited); + depositRefundedAu = this.safeSubAu(dispute.deposit_au, depositForfeitedAu); + if (depositRefundedAu instanceof Error) return depositRefundedAu; + } + if (this.compareAu(depositRefundedAu, ZERO_AU) > 0) { + const balance = await this.balanceRecord(dispute.opened_by, rail); + if (balance instanceof Error) return balance; + const balanceError = this.guardianValidateBalanceRecord(balance, dispute.opened_by, rail); + if (balanceError) return balanceError; + const nextAu = this.safeAddAu(balance.au, depositRefundedAu); + if (nextAu instanceof Error) return nextAu; + await this.put(this.balanceKey(dispute.opened_by, rail), { + ...balance, + rail, + au: nextAu, + updated_epoch: Math.max(balance.updated_epoch, dispute.epoch ?? 0), + updated_at: this.tx, + }); + } + if (this.compareAu(depositForfeitedAu, ZERO_AU) > 0) { + const fee = await this.feeCumRecord(rail); + if (fee instanceof Error) return fee; + const feeError = this.guardianValidateFeeRecord(fee, rail); + if (feeError) return feeError; + const cumAu = this.safeAddAu(fee.cum_au, depositForfeitedAu); + if (cumAu instanceof Error) return cumAu; + const settledCumAu = this.safeAddAu(fee.settled_cum_au ?? fee.cum_au, depositForfeitedAu); + if (settledCumAu instanceof Error) return settledCumAu; + const updatedFee = { + ...fee, + cum_au: cumAu, + settled_cum_au: settledCumAu, + updated_epoch: Math.max(fee.updated_epoch, dispute.epoch ?? 0), + updated_at: this.tx, + last_dispute_forfeit_at: this.tx, + }; + const updatedFeeError = this.guardianValidateFeeRecord(updatedFee, rail); + if (updatedFeeError) return updatedFeeError; + await this.put(this.feeCumKey(rail), updatedFee); + } + + let reputationEvent = null; + let slash = null; + if (this.value.outcome === 'provider_fault' && dispute.provider) { + reputationEvent = await this.appendReputationEvent({ + provider: dispute.provider, + event_id: `dispute-${dispute.dispute_id}-lost`, + kind: 'dispute_lost', + epoch: dispute.epoch ?? 0, + at: this.value.at, + paid_au: null, + max_spend_au: null, + evidence_hash: this.value.rationale_hash, + }); + if (reputationEvent instanceof Error) return reputationEvent; + + if (this.value.slash === true) { + const params = await this.activeParamsAt(this.value.at, ['dispute_lost_slash_bps']); + slash = await this.applyProviderSlash({ + providerId: dispute.provider, + source: 'dispute', + reason: 'dispute_lost', + evidenceHash: this.value.rationale_hash, + epoch: dispute.epoch ?? 0, + at: this.value.at, + slashBps: params.dispute_lost_slash_bps, + beneficiary: this.value.beneficiary ?? dispute.opened_by, + enclaveId: dispute.enclave_id, + eventId: reputationEvent.event_id, + banProvider: false, + tombstoneEnclave: false, + }); + if (slash instanceof Error) return slash; + } + } + + const resolved = { + ...dispute, + status: 'resolved', + outcome: this.value.outcome, + deposit_action: this.value.deposit_action, + rationale_hash: this.value.rationale_hash, + resolved_by: this.address, + resolved_at: this.tx, + resolved_at_seconds: this.value.at, + deposit_refunded_au: depositRefundedAu, + deposit_forfeited_au: depositForfeitedAu, + reputation_event: reputationEvent, + slash, + updated_at: this.tx, + }; + resolved.resolution_hash = await this.opaqueHash('mayhem-dispute-resolution-v1', resolved); + const countError = await this.closeDisputeCounts(dispute); + if (countError) return countError; + await this.put(key, resolved); + console.log('mayhem disputeResolve', resolved); + return { + ok: true, + op: 'disputeResolve', + dispute_id: dispute.dispute_id, + outcome: resolved.outcome, + deposit_action: resolved.deposit_action, + deposit_refunded_au: depositRefundedAu, + deposit_forfeited_au: depositForfeitedAu, + slash, + resolution_hash: resolved.resolution_hash, + }; + } + + async disputeExpire() { + const key = `disp/${this.value.dispute_id}`; + const dispute = await this.get(key); + if (!dispute || dispute.type !== 'dispute') return new Error('Dispute not found.'); + if (dispute.status !== 'open') return new Error('Dispute is not open.'); + if (!Number.isSafeInteger(dispute.expires_after_epoch) || dispute.expires_after_epoch < 0) { + return new Error('Dispute has no valid timeout epoch.'); + } + + const applyState = await this.epochApplyStateRecord(); + if (applyState.updated_epoch < dispute.expires_after_epoch) { + return new Error('Dispute timeout epoch not reached.'); + } + + const rail = this.normalizeLedgerRail(dispute.rail, 'dispute rail'); + if (rail instanceof Error) return rail; + const balance = await this.balanceRecord(dispute.opened_by, rail); + if (balance instanceof Error) return balance; + const balanceError = this.guardianValidateBalanceRecord(balance, dispute.opened_by, rail); + if (balanceError) return balanceError; + const depositRefundedAu = dispute.deposit_au; + const nextAu = this.safeAddAu(balance.au, depositRefundedAu); + if (nextAu instanceof Error) return nextAu; + const nextBalance = { + ...balance, + rail, + au: nextAu, + updated_epoch: Math.max(balance.updated_epoch, applyState.updated_epoch, dispute.epoch ?? 0), + updated_at: this.tx, + }; + const nextBalanceError = this.guardianValidateBalanceRecord(nextBalance, dispute.opened_by, rail); + if (nextBalanceError) return nextBalanceError; + + const expired = { + ...dispute, + status: 'expired', + outcome: 'timeout_refund', + deposit_action: 'refund', + deposit_holder: null, + expired_by: this.address, + expired_at: this.tx, + expired_at_epoch: applyState.updated_epoch, + expired_at_seconds: this.value.at, + deposit_refunded_au: depositRefundedAu, + deposit_forfeited_au: ZERO_AU, + reputation_event: null, + slash: null, + updated_at: this.tx, + }; + expired.expiry_hash = await this.opaqueHash('mayhem-dispute-expiry-v1', expired); + const countError = await this.closeDisputeCounts(dispute); + if (countError) return countError; + await this.put(this.balanceKey(dispute.opened_by, rail), nextBalance); + await this.put(key, expired); + console.log('mayhem disputeExpire', expired); + return { + ok: true, + op: 'disputeExpire', + dispute_id: dispute.dispute_id, + outcome: expired.outcome, + deposit_action: expired.deposit_action, + deposit_refunded_au: depositRefundedAu, + expired_at_epoch: expired.expired_at_epoch, + expiry_hash: expired.expiry_hash, + }; + } + + async rateOracle() { + this._mayhemApplyStage = 'rate:contract:require-admin'; + const adminError = await this.requireAdmin(); + if (adminError) return adminError; + const shapeError = this.validateRateOracleValue(this.value); + if (shapeError) return shapeError; + if (!RATE_SOURCES.has(this.value.source)) return new Error('Unsupported rate source.'); + + this._mayhemApplyStage = 'rate:contract:read-latest'; + const current = await this.get('rate/latest'); + if (current && this.value.ts < current.ts) { + return new Error('Rate timestamp must not decrease.'); + } + + const record = { + denom: 'tnk_usd_au', + tnk_usd_au: this.normalizeAu(this.value.tnk_usd_au, 'TNK/USD atto-rate', { allowZero: false }), + source: this.value.source, + ts: this.value.ts, + updated_at: this.tx, + posted_by: this.address, + posted_by_role: 'admin', + }; + this._mayhemApplyStage = 'rate:contract:read-history'; + const history = await this.get(this.tx); + if (history && stableJson(history) !== stableJson(record)) { + return new Error('TNK rate oracle history collision.'); + } + if (!history) { + this._mayhemApplyStage = 'rate:contract:write-history'; + await this.put(this.tx, record); + } + this._mayhemApplyStage = 'rate:contract:write-latest'; + await this.put('rate/latest', record); + console.log('mayhem rateOracle', record); + return { ok: true, op: 'rateOracle', ts: record.ts, source: record.source }; + } + + async tapRateOracle() { + this._mayhemApplyStage = 'rate:contract:require-admin'; + const adminError = await this.requireAdmin(); + if (adminError) return adminError; + const shapeError = this.validateTapRateOracleValue(this.value); + if (shapeError) return shapeError; + + this._mayhemApplyStage = 'rate:contract:read-latest'; + const current = await this.get('tap/rate/latest'); + if (current && this.value.ts < current.ts) { + return new Error('TAP rate timestamp must not decrease.'); + } + + const record = { + denom: 'tap_usd_au', + tap_usd_au: this.normalizeAu(this.value.tap_usd_au, 'TAP/USD atto-rate', { allowZero: false }), + source: this.value.source, + ts: this.value.ts, + updated_at: this.tx, + posted_by: this.address, + posted_by_role: 'admin', + }; + this._mayhemApplyStage = 'rate:contract:read-history'; + const history = await this.get(this.tx); + if (history && stableJson(history) !== stableJson(record)) { + return new Error('TAP rate oracle history collision.'); + } + if (!history) { + this._mayhemApplyStage = 'rate:contract:write-history'; + await this.put(this.tx, record); + } + this._mayhemApplyStage = 'rate:contract:write-latest'; + await this.put('tap/rate/latest', record); + console.log('mayhem tapRateOracle', record); + return { ok: true, op: 'tapRateOracle', ts: record.ts, source: record.source }; + } + + validateRateOracleValue(value) { + const shapeError = this.validateExactObjectKeys( + value, + ['op', 'tnk_usd_au', 'source', 'ts'], + 'rate oracle' + ); + if (shapeError) return shapeError; + if (value.op !== 'rate_oracle') return new Error('Invalid rate oracle op.'); + const rate = this.normalizeAu(value.tnk_usd_au, 'TNK/USD atto-rate', { allowZero: false }); + if (rate instanceof Error) { + return new Error('Invalid TNK/USD atto-rate.'); + } + if (typeof value.source !== 'string' || value.source.length < 1 || value.source.length > 64) { + return new Error('Invalid rate source.'); + } + if (!Number.isSafeInteger(value.ts) || value.ts < 0) { + return new Error('Invalid rate timestamp.'); + } + return null; + } + + validateTapRateOracleValue(value) { + const shapeError = this.validateExactObjectKeys( + value, + ['op', 'tap_usd_au', 'source', 'ts'], + 'TAP rate oracle' + ); + if (shapeError) return shapeError; + if (value.op !== 'tap_rate_oracle') return new Error('Invalid TAP rate oracle op.'); + const rate = this.normalizeAu(value.tap_usd_au, 'TAP/USD atto-rate', { allowZero: false }); + if (rate instanceof Error) { + return new Error('Invalid TAP/USD atto-rate.'); + } + if (typeof value.source !== 'string' + || !/^[a-z0-9][a-z0-9._-]{0,63}$/.test(value.source)) { + return new Error('Invalid TAP rate source.'); + } + if (!Number.isSafeInteger(value.ts) || value.ts < 0) { + return new Error('Invalid TAP rate timestamp.'); + } + return null; + } + + async canonicalTnkPaymentConfig() { + const payments = await this.get('payments/current'); + if (!payments || payments.set_by_role !== 'admin' || !payments.tnk) { + return new Error('Canonical TNK payment config required.'); + } + if (!['mainnet', 'testnet1'].includes(payments.tnk.network)) { + return new Error('Canonical TNK payment network is invalid.'); + } + if (!this.isSafeKeyPart(payments.tnk.treasury_address)) { + return new Error('Canonical TNK treasury address is invalid.'); + } + return payments.tnk; + } + + async canonicalTapPaymentConfig({ optional = false } = {}) { + const payments = await this.get('payments/current'); + if (!payments && optional) return null; + if (!payments || payments.set_by_role !== 'admin' || !payments.tap) { + return new Error('Canonical TAP payment config required.'); + } + if (!Number.isSafeInteger(payments.tap.chain_id) || payments.tap.chain_id < 1) { + return new Error('Canonical TAP chain id is invalid.'); + } + if (!this.isEthHexBytes(payments.tap.pool_address, 20)) { + return new Error('Canonical TAP pool address is invalid.'); + } + return { + chain_id: payments.tap.chain_id, + pool_address: payments.tap.pool_address.toLowerCase(), + }; + } + + async requireCanonicalTapPool(chainId, poolAddress) { + const tap = await this.canonicalTapPaymentConfig(); + if (tap instanceof Error) return tap; + if ( + chainId !== tap.chain_id || + typeof poolAddress !== 'string' || + poolAddress.toLowerCase() !== tap.pool_address + ) { + return new Error('TAP operation does not match the canonical payment pool.'); + } + return null; + } + + guardianValidateTapScope(record, amountFields, label) { + const hasChain = record.chain_id !== undefined && record.chain_id !== null; + const hasPool = record.pool_address !== undefined && record.pool_address !== null; + if (!hasChain && !hasPool) { + for (const field of amountFields) { + if (this.compareAu(record[field] ?? ZERO_AU, ZERO_AU) !== 0) { + return new Error(`Guardian TAP ${label} scope invariant failed.`); + } + } + return null; + } + if (!Number.isSafeInteger(record.chain_id) || record.chain_id < 1) { + return new Error(`Guardian TAP ${label} chain invariant failed.`); + } + if (!this.isEthHexBytes(record.pool_address, 20)) { + return new Error(`Guardian TAP ${label} pool invariant failed.`); + } + return null; + } + + msbAddressForPublicKey(publicKey, network) { + if (!this.isHexBytes(publicKey, 32)) return new Error('Invalid MSB owner public key.'); + const prefix = network === 'mainnet' + ? 'trac' + : network === 'testnet1' + ? 'testtrac' + : null; + if (!prefix) return new Error('Invalid MSB network.'); + const address = PeerWallet.encodeBech32mSafe(prefix, b4a.from(publicKey, 'hex')); + return typeof address === 'string' && address.length > 0 + ? address + : new Error('Unable to derive MSB owner address.'); + } + + normalizeMsbTransferEvidence(value, label = 'MSB transfer evidence') { + const shapeError = this.validateExactObjectKeys( + value, + [ + 'schema_version', + 'network', + 'tx_hash', + 'confirmed_length', + 'observed_signed_length', + 'from', + 'to', + 'amount_e18', + ], + label + ); + if (shapeError) return shapeError; + if (value.schema_version !== MSB_TRANSFER_EVIDENCE_VERSION) { + return new Error(`${label} schema version is invalid.`); + } + if (!['mainnet', 'testnet1'].includes(value.network)) { + return new Error(`${label} network is invalid.`); + } + if (!this.isHexBytes(value.tx_hash, 32) || value.tx_hash !== value.tx_hash.toLowerCase()) { + return new Error(`${label} transaction hash is invalid.`); + } + if (!Number.isSafeInteger(value.confirmed_length) || value.confirmed_length < 1) { + return new Error(`${label} confirmed length is invalid.`); + } + if ( + !Number.isSafeInteger(value.observed_signed_length) || + value.observed_signed_length < value.confirmed_length + ) { + return new Error(`${label} observed signed length is invalid.`); + } + if (!this.isSafeKeyPart(value.from) || !this.isSafeKeyPart(value.to)) { + return new Error(`${label} address is invalid.`); + } + const amount = this.parseTnkE18(value.amount_e18); + if (amount instanceof Error) return new Error(`${label} amount is invalid.`); + return { + schema_version: MSB_TRANSFER_EVIDENCE_VERSION, + network: value.network, + tx_hash: value.tx_hash, + confirmed_length: value.confirmed_length, + observed_signed_length: value.observed_signed_length, + from: value.from, + to: value.to, + amount_e18: amount.toString(), + }; + } + + msbTransferSeenKey(evidence) { + return `rail/seen/msb/${evidence.network}/${evidence.tx_hash}`; + } + + normalizeStripeTransferEvidence( + value, + label = 'Stripe settlement evidence', + { expectedAttemptId = null } = {} + ) { + const providerTransfer = value?.kind === 'stripe_transfer'; + const hasAttemptId = hasOwn(value, 'attempt_id'); + const hasFxQuoteId = providerTransfer && hasOwn(value, 'fx_quote_id'); + const hasFxQuoteHash = providerTransfer && hasOwn(value, 'fx_quote_hash'); + if (hasFxQuoteId !== hasFxQuoteHash) { + return new Error(`${label} FX quote identity must be present or absent as a pair.`); + } + if ((hasAttemptId && + (!this.isHexBytes(value.attempt_id, 32) || + value.attempt_id !== value.attempt_id.toLowerCase())) || + (expectedAttemptId !== null && + (!hasAttemptId || value.attempt_id !== expectedAttemptId))) { + return new Error(`${label} attempt id does not match its canonical attempt.`); + } + const shapeError = this.validateExactObjectKeys(value, providerTransfer + ? [ + 'schema_version', + 'kind', + ...(hasAttemptId ? ['attempt_id'] : []), + 'ref', + 'destination', + 'source_currency', + 'source_amount_minor', + 'destination_currency', + 'destination_amount_minor', + ...(hasFxQuoteId ? ['fx_quote_id', 'fx_quote_hash'] : []), + 'destination_payment', + 'transfer_group', + ] + : [ + 'schema_version', + 'kind', + ...(hasAttemptId ? ['attempt_id'] : []), + 'ref', + 'destination', + 'source_currency', + 'source_amount_minor', + 'transfer_group', + ], label); + if (shapeError) return shapeError; + if (value.schema_version !== STRIPE_TRANSFER_EVIDENCE_VERSION) { + return new Error(`${label} schema version is invalid.`); + } + if (!['stripe_transfer', 'platform_balance'].includes(value.kind)) { + return new Error(`${label} kind is invalid.`); + } + if (!this.isSafeKeyPart(value.ref) || !this.isSafeKeyPart(value.destination)) { + return new Error(`${label} reference or destination is invalid.`); + } + if (value.kind === 'stripe_transfer' && !value.ref.startsWith('tr_')) { + return new Error(`${label} requires a Stripe transfer id.`); + } + if (value.kind === 'platform_balance' && !value.ref.startsWith('platform_balance:')) { + return new Error(`${label} platform balance reference is invalid.`); + } + const sourceCurrency = this.normalizeFiatCurrency(value.source_currency); + if (sourceCurrency instanceof Error || sourceCurrency !== value.source_currency) { + return new Error(`${label} source currency is invalid.`); + } + const sourceAmountMinor = this.normalizeFiatMinor(value.source_amount_minor); + if (sourceAmountMinor instanceof Error || sourceAmountMinor === '0') { + return new Error(`${label} source amount must be positive.`); + } + if (providerTransfer && + (typeof value.transfer_group !== 'string' || !this.isSafeKeyPart(value.transfer_group))) { + return new Error(`${label} transfer group is invalid.`); + } + if (!providerTransfer && value.transfer_group !== null) { + return new Error(`${label} platform balance transfer group must be null.`); + } + if (!providerTransfer) return { + schema_version: STRIPE_TRANSFER_EVIDENCE_VERSION, + kind: 'platform_balance', + ...(hasAttemptId ? { attempt_id: value.attempt_id } : {}), + ref: value.ref, + destination: value.destination, + source_currency: sourceCurrency, + source_amount_minor: sourceAmountMinor, + transfer_group: null, + }; + + const destinationCurrency = this.normalizeFiatCurrency(value.destination_currency); + if (destinationCurrency instanceof Error || + destinationCurrency !== value.destination_currency) { + return new Error(`${label} destination currency is invalid.`); + } + const destinationAmountMinor = this.normalizeFiatMinor(value.destination_amount_minor); + if (destinationAmountMinor instanceof Error || destinationAmountMinor === '0') { + return new Error(`${label} destination amount must be positive.`); + } + const requiresUsdValuationQuote = + sourceCurrency !== 'usd' || destinationCurrency !== 'usd'; + if (requiresUsdValuationQuote && + (!hasFxQuoteId || + !/^fxq_[A-Za-z0-9._-]+$/.test(String(value.fx_quote_id || '')) || + !this.isHexBytes(value.fx_quote_hash, 32) || + value.fx_quote_hash !== value.fx_quote_hash.toLowerCase())) { + return new Error(`${label} FX quote identity is required and invalid.`); + } + if (!requiresUsdValuationQuote && hasFxQuoteId && + (value.fx_quote_id !== null || value.fx_quote_hash !== null)) { + return new Error(`${label} direct USD transfer must not include an FX quote identity.`); + } + if (!/^py_[A-Za-z0-9._-]+$/.test(String(value.destination_payment || ''))) { + return new Error(`${label} destination payment readback is invalid.`); + } + const normalized = { + schema_version: STRIPE_TRANSFER_EVIDENCE_VERSION, + kind: 'stripe_transfer', + ...(hasAttemptId ? { attempt_id: value.attempt_id } : {}), + ref: value.ref, + destination: value.destination, + source_currency: sourceCurrency, + source_amount_minor: sourceAmountMinor, + destination_currency: destinationCurrency, + destination_amount_minor: destinationAmountMinor, + destination_payment: value.destination_payment, + transfer_group: value.transfer_group, + }; + if (hasFxQuoteId) { + normalized.fx_quote_id = value.fx_quote_id; + normalized.fx_quote_hash = value.fx_quote_hash; + } + return normalized; + } + + stripeTransferSeenKey(evidence) { + return evidence.kind === 'stripe_transfer' + ? `rail/seen/stripe/${evidence.ref}` + : `rail/seen/stripe-platform/${evidence.ref}`; + } + + stripeFxQuoteSeenKey(evidence) { + return evidence.kind === 'stripe_transfer' && evidence.fx_quote_id != null + ? `rail/seen/stripe-fx-quote/${evidence.fx_quote_id}` + : null; + } + + stripeDestinationPaymentSeenKey(evidence) { + return evidence.kind === 'stripe_transfer' + ? `rail/seen/stripe-destination-payment/${evidence.destination_payment}` + : null; + } + + normalizeTargetedTapRateLock(value) { + const shapeError = this.validateExactObjectKeys( + value, + [ + 'type', + 'epoch', + 'bundle_sha256', + 'denom', + 'tap_usd_au', + 'source', + 'rate_ts', + 'rate_record_key', + 'posted_by', + 'posted_by_role', + 'chain_id', + 'token_address', + 'pool_address', + 'payment_config_ver', + ], + 'targeted TAP settlement rate lock' + ); + if (shapeError) return shapeError; + const tapUsdAu = this.normalizeAu( + value.tap_usd_au, + 'targeted TAP settlement rate', + { allowZero: false } + ); + if (tapUsdAu instanceof Error || + value.type !== 'tap_settlement_rate_lock' || + value.denom !== 'tap_usd_au' || + !Number.isSafeInteger(value.epoch) || + value.epoch < 1 || + !this.isHexBytes(value.bundle_sha256, 32) || + typeof value.source !== 'string' || + !/^[a-z0-9][a-z0-9._-]{0,63}$/.test(value.source) || + !Number.isSafeInteger(value.rate_ts) || + value.rate_ts < 0 || + typeof value.rate_record_key !== 'string' || + !value.rate_record_key.startsWith(`rate/tap/${value.rate_ts}/`) || + !this.isHexBytes(value.rate_record_key.slice(`rate/tap/${value.rate_ts}/`.length), 32) || + !this.isHexBytes(value.posted_by, 32) || + value.posted_by_role !== 'admin' || + !Number.isSafeInteger(value.chain_id) || + value.chain_id < 1 || + !this.isEthHexBytes(value.token_address, 20) || + value.token_address !== value.token_address.toLowerCase() || + !this.isEthHexBytes(value.pool_address, 20) || + value.pool_address !== value.pool_address.toLowerCase() || + !Number.isSafeInteger(value.payment_config_ver) || + value.payment_config_ver < 1) { + return new Error('Invalid targeted TAP settlement rate lock.'); + } + return { + type: value.type, + epoch: value.epoch, + bundle_sha256: value.bundle_sha256.toLowerCase(), + denom: value.denom, + tap_usd_au: tapUsdAu, + source: value.source, + rate_ts: value.rate_ts, + rate_record_key: value.rate_record_key, + posted_by: value.posted_by.toLowerCase(), + posted_by_role: value.posted_by_role, + chain_id: value.chain_id, + token_address: value.token_address.toLowerCase(), + pool_address: value.pool_address.toLowerCase(), + payment_config_ver: value.payment_config_ver, + }; + } + + normalizeTargetedTapSettlementEntry(value) { + const shapeError = this.validateExactObjectKeys( + value, + ['account', 'cumulative_wei'], + 'targeted TAP settlement distribution entry' + ); + if (shapeError) return shapeError; + if (!this.isEthHexBytes(value.account, 20) || + value.account !== value.account.toLowerCase()) { + return new Error('Invalid targeted TAP settlement distribution account.'); + } + const cumulativeWei = this.parseTapWei(value.cumulative_wei); + if (cumulativeWei instanceof Error) { + return new Error('Invalid targeted TAP settlement cumulative claim.'); + } + return { + account: value.account, + cumulative_wei: cumulativeWei.toString(), + }; + } + + normalizeTargetedTapSettlementOutput(value, tapUsdAu) { + const shapeError = this.validateExactObjectKeys( + value, + [ + 'provider', + 'payout_revision', + 'to', + 'paid_cum_au_before', + 'aggregate_paid_cum_au_before', + 'paid_au', + 'tap_wei', + 'prior_cumulative_claim_wei', + 'cumulative_claim_wei', + ], + 'targeted TAP provider output' + ); + if (shapeError) return shapeError; + const paidAu = this.normalizeAu( + value.paid_au, + 'targeted TAP provider paid amount', + { allowZero: false } + ); + const paidCumAuBefore = this.normalizeAu( + value.paid_cum_au_before, + 'targeted TAP provider paid cumulative watermark', + { allowZero: true } + ); + const aggregatePaidCumAuBefore = this.normalizeAu( + value.aggregate_paid_cum_au_before, + 'targeted TAP provider aggregate paid cumulative watermark', + { allowZero: true } + ); + const tapWei = this.parseTapWei(value.tap_wei); + const priorCumulativeClaimWei = this.parseTapWei( + value.prior_cumulative_claim_wei, + { allowZero: true } + ); + const cumulativeClaimWei = this.parseTapWei(value.cumulative_claim_wei); + if (!this.isHexBytes(value.provider, 32) || + value.provider !== value.provider.toLowerCase() || + !this.isHexBytes(value.payout_revision, 32) || + value.payout_revision !== value.payout_revision.toLowerCase() || + !this.isEthHexBytes(value.to, 20) || + value.to !== value.to.toLowerCase() || + paidAu instanceof Error || + paidCumAuBefore instanceof Error || + aggregatePaidCumAuBefore instanceof Error || + tapWei instanceof Error || + priorCumulativeClaimWei instanceof Error || + cumulativeClaimWei instanceof Error) { + return new Error('Invalid targeted TAP provider output.'); + } + const rate = this.parseAu( + tapUsdAu, + 'targeted TAP settlement rate', + { allowZero: false } + ); + if (rate instanceof Error) return rate; + const expectedTapWei = (this.parseAu(paidAu, 'targeted TAP provider paid amount') * TAP_WEI) / rate; + if (expectedTapWei <= 0n || tapWei !== expectedTapWei) { + return new Error('Targeted TAP provider output does not match the locked rate.'); + } + if (cumulativeClaimWei !== priorCumulativeClaimWei + tapWei) { + return new Error('Targeted TAP provider output cumulative claim chain is invalid.'); + } + return { + provider: value.provider, + payout_revision: value.payout_revision, + to: value.to, + paid_cum_au_before: paidCumAuBefore, + aggregate_paid_cum_au_before: aggregatePaidCumAuBefore, + paid_au: paidAu, + tap_wei: tapWei.toString(), + prior_cumulative_claim_wei: priorCumulativeClaimWei.toString(), + cumulative_claim_wei: cumulativeClaimWei.toString(), + }; + } + + targetedTapSettlementLeaf(entry) { + const encoded = b4a.alloc(64); + encoded.set(b4a.from(entry.account.slice(2), 'hex'), 12); + const amount = BigInt(entry.cumulative_wei); + if (amount < 0n || amount >= (1n << 256n)) { + return new Error('Targeted TAP settlement cumulative claim exceeds uint256.'); + } + const amountHex = amount.toString(16).padStart(64, '0'); + encoded.set(b4a.from(amountHex, 'hex'), 32); + return keccak256(keccak256(encoded)); + } + + targetedTapSettlementRoot(entries) { + if (!Array.isArray(entries) || entries.length === 0) { + return new Error('Targeted TAP settlement requires distribution entries.'); + } + const leaves = []; + for (const entry of entries) { + const leaf = this.targetedTapSettlementLeaf(entry); + if (leaf instanceof Error) return leaf; + leaves.push(b4a.toString(leaf, 'hex')); + } + leaves.sort(compareCodepoint); + const tree = new Array(2 * leaves.length - 1); + for (const [index, leaf] of leaves.entries()) { + tree[tree.length - 1 - index] = leaf; + } + for (let index = tree.length - 1 - leaves.length; index >= 0; index -= 1) { + const left = tree[2 * index + 1]; + const right = tree[2 * index + 2]; + const [first, second] = compareCodepoint(left, right) <= 0 + ? [left, right] + : [right, left]; + tree[index] = b4a.toString( + keccak256(b4a.concat([b4a.from(first, 'hex'), b4a.from(second, 'hex')])), + 'hex' + ); + } + return `0x${tree[0]}`; + } + + normalizeTargetedTapSettlementValue(value) { + const shapeError = this.validateExactObjectKeys( + value, + [ + 'op', + 'epoch', + 'at', + 'rail', + 'chain_id', + 'token_address', + 'pool_address', + 'payment_config_ver', + 'epoch_apply_hash', + 'preparation_ids', + 'root_preparation_id', + 'external_effect_ids', + 'tap_rate_lock', + 'root', + 'root_confirmed', + 'proposal_tx', + 'proposal_block_number', + 'proposal_block_hash', + 'execution_tx', + 'execution_status', + 'execution_block_number', + 'execution_block_hash', + 'finalized_block_number', + 'confirmation_depth', + 'confirmation_policy', + 'cumulative_spent_wei', + 'provider_cumulative_claimed_wei', + 'buyer_refund_wei', + 'fee_bps', + 'tap_burn_bps', + 'provider_share_bps', + 'provider_count', + 'provider_paid_au', + 'provider_tap_wei', + 'provider_entries', + 'refunds', + 'entries', + 'outputs', + ], + 'targeted TAP settlement' + ); + if (shapeError) return shapeError; + const rateLock = this.normalizeTargetedTapRateLock(value.tap_rate_lock); + if (rateLock instanceof Error) return rateLock; + if (value.op !== 'settle_targeted_tap' || + value.rail !== 'tap' || + !Number.isSafeInteger(value.epoch) || + value.epoch < 1 || + !Number.isSafeInteger(value.at) || + value.at < 0 || + !Number.isSafeInteger(value.chain_id) || + value.chain_id < 1 || + !this.isEthHexBytes(value.token_address, 20) || + value.token_address !== value.token_address.toLowerCase() || + !this.isEthHexBytes(value.pool_address, 20) || + value.pool_address !== value.pool_address.toLowerCase() || + !Number.isSafeInteger(value.payment_config_ver) || + value.payment_config_ver < 1 || + !this.isHexBytes(value.epoch_apply_hash, 32) || + !Array.isArray(value.preparation_ids) || + value.preparation_ids.length < 1 || + !this.isHexBytes(value.root_preparation_id, 32) || + !Array.isArray(value.external_effect_ids) || + value.external_effect_ids.length !== 2 || + !this.isEthHexBytes(value.root, 32) || + value.root !== value.root.toLowerCase() || + value.root_confirmed !== true || + !this.isEthHexBytes(value.proposal_tx, 32) || + value.proposal_tx !== value.proposal_tx.toLowerCase() || + !Number.isSafeInteger(value.proposal_block_number) || + value.proposal_block_number < 0 || + !this.isEthHexBytes(value.proposal_block_hash, 32) || + value.proposal_block_hash !== value.proposal_block_hash.toLowerCase() || + !this.isEthHexBytes(value.execution_tx, 32) || + value.execution_tx !== value.execution_tx.toLowerCase() || + value.execution_status !== 1 || + !Number.isSafeInteger(value.execution_block_number) || + value.execution_block_number < value.proposal_block_number || + !this.isEthHexBytes(value.execution_block_hash, 32) || + value.execution_block_hash !== value.execution_block_hash.toLowerCase() || + !Number.isSafeInteger(value.finalized_block_number) || + value.finalized_block_number < value.execution_block_number || + !Number.isSafeInteger(value.confirmation_depth) || + value.confirmation_depth !== value.finalized_block_number - value.execution_block_number || + value.confirmation_depth < MIN_TAP_CONFIRMATION_DEPTH || + !this.isSafeKeyPart(value.confirmation_policy) || + (value.chain_id === 1 && value.confirmation_policy !== 'finalized-tag') || + (value.confirmation_policy !== 'finalized-tag' && + value.confirmation_policy !== `depth-${value.confirmation_depth}`) || + value.proposal_tx === value.execution_tx || + !Number.isSafeInteger(value.provider_count) || + value.provider_count < 1 || + !Number.isSafeInteger(value.fee_bps) || + value.fee_bps < 0 || + !Number.isSafeInteger(value.tap_burn_bps) || + value.tap_burn_bps !== TAP_BURN_BPS || + !Number.isSafeInteger(value.provider_share_bps) || + value.provider_share_bps <= 0 || + value.fee_bps + value.tap_burn_bps + value.provider_share_bps !== 10_000 || + !Array.isArray(value.entries) || + value.entries.length < 1 || + !Array.isArray(value.provider_entries) || + value.provider_entries.length < 1 || + !Array.isArray(value.refunds) || + !Array.isArray(value.outputs) || + value.outputs.length < 1) { + return new Error('Invalid targeted TAP settlement.'); + } + if (value.preparation_ids.some((entry) => !this.isHexBytes(entry, 32)) || + new Set(value.preparation_ids).size !== value.preparation_ids.length || + value.external_effect_ids.some((entry) => !this.isHexBytes(entry, 32)) || + new Set(value.external_effect_ids).size !== 2) { + return new Error('Invalid targeted TAP settlement preparation identities.'); + } + if (rateLock.epoch !== value.epoch || + rateLock.chain_id !== value.chain_id || + rateLock.token_address !== value.token_address || + rateLock.pool_address !== value.pool_address || + rateLock.payment_config_ver !== value.payment_config_ver) { + return new Error('Targeted TAP settlement rate lock scope mismatch.'); + } + const cumulativeSpentWei = this.parseTapWei(value.cumulative_spent_wei); + const providerCumulativeClaimedWei = this.parseTapWei( + value.provider_cumulative_claimed_wei + ); + const buyerRefundWei = this.parseTapWei(value.buyer_refund_wei, { + allowZero: true, + }); + const providerPaidAu = this.normalizeAu( + value.provider_paid_au, + 'targeted TAP provider paid total', + { allowZero: false } + ); + const providerTapWei = this.parseTapWei(value.provider_tap_wei); + if (cumulativeSpentWei instanceof Error || + providerCumulativeClaimedWei instanceof Error || + buyerRefundWei instanceof Error || + providerPaidAu instanceof Error || + providerTapWei instanceof Error) { + return new Error('Invalid targeted TAP settlement totals.'); + } + const entries = []; + const seenAccounts = new Set(); + for (const rawEntry of value.entries) { + const entry = this.normalizeTargetedTapSettlementEntry(rawEntry); + if (entry instanceof Error) return entry; + if (seenAccounts.has(entry.account)) { + return new Error('Duplicate targeted TAP settlement distribution account.'); + } + seenAccounts.add(entry.account); + entries.push(entry); + } + entries.sort((left, right) => compareCodepoint(left.account, right.account)); + if (stableJson(entries) !== stableJson(value.entries)) { + return new Error('Targeted TAP settlement distribution entries must be canonical.'); + } + const normalizeClaimEntries = (rawEntries, label) => { + const normalizedEntries = []; + const seen = new Set(); + for (const rawEntry of rawEntries) { + const entry = this.normalizeTargetedTapSettlementEntry(rawEntry); + if (entry instanceof Error) return entry; + if (seen.has(entry.account)) { + return new Error(`Duplicate targeted TAP ${label} account.`); + } + seen.add(entry.account); + normalizedEntries.push(entry); + } + normalizedEntries.sort((left, right) => compareCodepoint(left.account, right.account)); + if (stableJson(normalizedEntries) !== stableJson(rawEntries)) { + return new Error(`Targeted TAP ${label} entries must be canonical.`); + } + return normalizedEntries; + }; + const providerEntries = normalizeClaimEntries( + value.provider_entries, + 'provider claim' + ); + if (providerEntries instanceof Error) return providerEntries; + const refunds = normalizeClaimEntries(value.refunds, 'buyer refund'); + if (refunds instanceof Error) return refunds; + const providerEntryTotal = providerEntries.reduce( + (sum, entry) => sum + BigInt(entry.cumulative_wei), + 0n + ); + const refundTotal = refunds.reduce( + (sum, entry) => sum + BigInt(entry.cumulative_wei), + 0n + ); + if (providerEntryTotal !== providerCumulativeClaimedWei || + refundTotal !== buyerRefundWei) { + return new Error('Targeted TAP provider/refund totals do not match claim entries.'); + } + const combinedClaims = new Map(); + for (const entry of [...providerEntries, ...refunds]) { + combinedClaims.set( + entry.account, + (combinedClaims.get(entry.account) ?? 0n) + BigInt(entry.cumulative_wei) + ); + } + const combinedEntries = [...combinedClaims.entries()] + .map(([account, cumulativeWei]) => ({ + account, + cumulative_wei: cumulativeWei.toString(), + })) + .sort((left, right) => compareCodepoint(left.account, right.account)); + if (stableJson(combinedEntries) !== stableJson(entries)) { + return new Error('Targeted TAP executed entries do not merge provider and refund claims.'); + } + const outputs = []; + const seenLiabilities = new Set(); + for (const rawOutput of value.outputs) { + const output = this.normalizeTargetedTapSettlementOutput( + rawOutput, + rateLock.tap_usd_au + ); + if (output instanceof Error) return output; + const identity = `${output.provider}/${output.payout_revision}`; + if (seenLiabilities.has(identity)) { + return new Error('Duplicate targeted TAP settlement payout liability.'); + } + seenLiabilities.add(identity); + outputs.push(output); + } + outputs.sort((left, right) => ( + compareCodepoint(left.provider, right.provider) || + compareCodepoint(left.payout_revision, right.payout_revision) + )); + if (stableJson(outputs) !== stableJson(value.outputs)) { + return new Error('Targeted TAP settlement outputs must be canonical.'); + } + const paidTotal = outputs.reduce( + (sum, output) => sum + this.parseAu(output.paid_au, 'targeted TAP paid output'), + 0n + ); + const tapTotal = outputs.reduce( + (sum, output) => sum + BigInt(output.tap_wei), + 0n + ); + if (value.provider_count !== outputs.length || + paidTotal.toString() !== providerPaidAu || + tapTotal !== providerTapWei) { + return new Error('Targeted TAP settlement totals do not match outputs.'); + } + const entryMap = new Map(providerEntries.map((entry) => [entry.account, entry])); + const targetCursors = new Map(); + for (const output of outputs) { + const prior = targetCursors.get(output.to); + if (prior !== undefined && + prior !== output.prior_cumulative_claim_wei) { + return new Error('Targeted TAP outputs do not form a canonical per-target claim chain.'); + } + targetCursors.set(output.to, output.cumulative_claim_wei); + } + for (const [target, cumulativeClaimWei] of targetCursors) { + if (entryMap.get(target)?.cumulative_wei !== cumulativeClaimWei) { + return new Error('Targeted TAP output claim chain does not match the executed root entry.'); + } + } + const root = this.targetedTapSettlementRoot(entries); + if (root instanceof Error || root !== value.root) { + return new Error('Targeted TAP settlement root mismatch.'); + } + return { + rate_lock: rateLock, + entries, + provider_entries: providerEntries, + refunds, + outputs, + cumulative_spent_wei: cumulativeSpentWei.toString(), + provider_cumulative_claimed_wei: providerCumulativeClaimedWei.toString(), + buyer_refund_wei: buyerRefundWei.toString(), + fee_bps: value.fee_bps, + tap_burn_bps: value.tap_burn_bps, + provider_share_bps: value.provider_share_bps, + provider_paid_au: providerPaidAu, + provider_tap_wei: providerTapWei.toString(), + }; + } + + normalizeTargetedTnkSettlementValue(value) { + const shapeError = this.validateExactObjectKeys( + value, + [ + 'op', + 'epoch', + 'at', + 'rail', + 'network', + 'treasury_from', + 'operator_to', + 'epoch_apply_hash', + 'preparation_ids', + 'external_effect_ids', + 'rate_tnk_usd_au', + 'rate_source', + 'rate_ts', + 'msb_transfers', + 'transfer_root', + 'provider_count', + 'provider_au', + 'operator_fee_au', + 'gross_au', + 'tnk_e18', + 'outputs', + ], + 'targeted TNK settlement' + ); + if (shapeError) return shapeError; + if (value.op !== 'settle_targeted_tnk' || value.rail !== 'tnk' || + !Number.isSafeInteger(value.epoch) || value.epoch < 1 || + !Number.isSafeInteger(value.at) || value.at < 0 || + !this.isSafeKeyPart(value.network) || + !this.isSafeKeyPart(value.treasury_from) || + !this.isSafeKeyPart(value.operator_to) || + !this.isHexBytes(value.epoch_apply_hash, 32) || + !Array.isArray(value.preparation_ids) || + !Array.isArray(value.external_effect_ids) || + value.preparation_ids.length !== value.outputs?.length || + value.external_effect_ids.length !== value.outputs?.length || + !this.isHexBytes(value.transfer_root, 32) || + !Number.isSafeInteger(value.provider_count) || value.provider_count < 0 || + !Number.isSafeInteger(value.rate_ts) || value.rate_ts < 0 || + !RATE_SOURCES.has(value.rate_source) || + !Array.isArray(value.outputs) || value.outputs.length === 0 || + !Array.isArray(value.msb_transfers) || + value.msb_transfers.length !== value.outputs.length) { + return new Error('Invalid targeted TNK settlement.'); + } + if (value.preparation_ids.some((entry) => !this.isHexBytes(entry, 32)) || + new Set(value.preparation_ids).size !== value.preparation_ids.length || + value.external_effect_ids.some((entry) => !this.isHexBytes(entry, 32)) || + new Set(value.external_effect_ids).size !== value.external_effect_ids.length) { + return new Error('Invalid targeted TNK settlement preparation identities.'); + } + if (this.normalizeAu( + value.rate_tnk_usd_au, + 'targeted TNK settlement rate', + { allowZero: false } + ) instanceof Error || this.parseTnkE18(value.tnk_e18) instanceof Error) { + return new Error('Invalid targeted TNK settlement amount or rate.'); + } + for (const field of ['provider_au', 'operator_fee_au', 'gross_au']) { + if (this.normalizeAu( + value[field], + `targeted TNK settlement ${field}`, + { allowZero: field !== 'gross_au' } + ) instanceof Error) { + return new Error('Invalid targeted TNK settlement total.'); + } + } + const gross = this.safeAddAu(value.provider_au, value.operator_fee_au); + if (gross instanceof Error || this.compareAu(gross, value.gross_au) !== 0) { + return new Error('Targeted TNK settlement gross amount does not balance.'); + } + const seenTransfers = new Set(); + for (const entry of value.msb_transfers) { + const transfer = this.normalizeMsbTransferEvidence( + entry, + 'targeted TNK settlement transfer' + ); + if (transfer instanceof Error) return transfer; + if (seenTransfers.has(transfer.tx_hash)) { + return new Error('Duplicate targeted TNK settlement transfer.'); + } + seenTransfers.add(transfer.tx_hash); + } + const outputs = []; + const seen = new Set(); + let operatorSeen = false; + for (const output of value.outputs) { + if (output?.role === 'provider') { + const shapeError = this.validateExactObjectKeys( + output, + [ + 'role', + 'provider', + 'payout_revision', + 'to', + 'paid_cum_au_before', + 'aggregate_paid_cum_au_before', + 'au', + 'tnk_e18', + ], + 'targeted TNK provider output' + ); + if (shapeError) return shapeError; + if (!this.isHexBytes(output.payout_revision, 32) || + output.payout_revision !== output.payout_revision.toLowerCase()) { + return new Error('Invalid targeted TNK payout revision.'); + } + const identity = stableJson([output.provider, output.payout_revision]); + if (seen.has(identity)) return new Error('Duplicate targeted TNK payout liability.'); + seen.add(identity); + const normalized = this.normalizeTargetedTnkSettlementOutput({ + role: output.role, + provider: output.provider, + to: output.to, + au: output.au, + tnk_e18: output.tnk_e18, + }); + if (normalized instanceof Error) return normalized; + const paidCumAuBefore = this.normalizeAu( + output.paid_cum_au_before, + 'targeted TNK provider paid cumulative watermark', + { allowZero: true } + ); + if (paidCumAuBefore instanceof Error) return paidCumAuBefore; + const aggregatePaidCumAuBefore = this.normalizeAu( + output.aggregate_paid_cum_au_before, + 'targeted TNK provider aggregate paid cumulative watermark', + { allowZero: true } + ); + if (aggregatePaidCumAuBefore instanceof Error) { + return aggregatePaidCumAuBefore; + } + outputs.push({ + ...normalized, + payout_revision: output.payout_revision, + paid_cum_au_before: paidCumAuBefore, + aggregate_paid_cum_au_before: aggregatePaidCumAuBefore, + }); + } else if (output?.role === 'operator_fee') { + const shapeError = this.validateExactObjectKeys( + output, + ['role', 'to', 'au', 'tnk_e18'], + 'targeted TNK operator output' + ); + if (shapeError) return shapeError; + if (operatorSeen) return new Error('Duplicate targeted TNK operator output.'); + operatorSeen = true; + const normalized = this.normalizeTargetedTnkSettlementOutput(output); + if (normalized instanceof Error) return normalized; + outputs.push(normalized); + } else { + return new Error('Invalid targeted TNK settlement output role.'); + } + } + outputs.sort((left, right) => { + if (left.role !== right.role) return left.role === 'provider' ? -1 : 1; + if (left.role === 'operator_fee') return 0; + return compareCodepoint(left.provider, right.provider) || + compareCodepoint(left.payout_revision, right.payout_revision); + }); + if (stableJson(outputs) !== stableJson(value.outputs)) { + return new Error('Targeted TNK settlement outputs must be canonical.'); + } + return { outputs }; + } + + normalizeTargetedFiatSettlementValue(value) { + const shapeError = this.validateExactObjectKeys( + value, + [ + 'op', + 'epoch', + 'at', + 'rail', + 'processor', + 'source_currency', + 'operator_to', + 'epoch_apply_hash', + 'preparation_ids', + 'external_effect_ids', + 'stripe_transfers', + 'transfer_root', + 'provider_count', + 'provider_liability_au', + 'provider_paid_au', + 'operator_fee_liability_au', + 'operator_fee_retained_au', + 'gross_liability_au', + 'gross_paid_au', + 'rounding_au', + 'dust_au', + 'source_amount_minor', + 'destination_totals', + 'outputs', + ], + 'targeted fiat settlement' + ); + if (shapeError) return shapeError; + if (value.op !== 'settle_targeted_fiat' || + value.rail !== 'fiat' || + value.processor !== 'stripe' || + !Number.isSafeInteger(value.epoch) || value.epoch < 1 || + !Number.isSafeInteger(value.at) || value.at < 0 || + !this.isSafeKeyPart(value.operator_to) || + !this.isHexBytes(value.epoch_apply_hash, 32) || + !Array.isArray(value.preparation_ids) || + !Array.isArray(value.external_effect_ids) || + value.preparation_ids.length !== value.outputs?.length || + value.external_effect_ids.length !== value.outputs?.length || + !this.isHexBytes(value.transfer_root, 32) || + !Number.isSafeInteger(value.provider_count) || value.provider_count < 0 || + !Array.isArray(value.outputs) || value.outputs.length === 0 || + !Array.isArray(value.destination_totals) || + !Array.isArray(value.stripe_transfers) || + value.stripe_transfers.length !== value.outputs.length) { + return new Error('Invalid targeted fiat settlement.'); + } + if (value.preparation_ids.some((entry) => !this.isHexBytes(entry, 32)) || + new Set(value.preparation_ids).size !== value.preparation_ids.length || + value.external_effect_ids.some((entry) => !this.isHexBytes(entry, 32)) || + new Set(value.external_effect_ids).size !== value.external_effect_ids.length) { + return new Error('Invalid targeted fiat settlement preparation identities.'); + } + const sourceCurrency = this.normalizeFiatCurrency(value.source_currency); + if (sourceCurrency instanceof Error || sourceCurrency !== value.source_currency) { + return new Error('Invalid targeted fiat settlement source currency.'); + } + const sourceAmountMinor = this.normalizeFiatMinor(value.source_amount_minor); + if (sourceAmountMinor instanceof Error || sourceAmountMinor === '0' || + sourceAmountMinor !== value.source_amount_minor) { + return new Error('Invalid targeted fiat settlement source amount.'); + } + const auFields = [ + 'provider_liability_au', + 'provider_paid_au', + 'operator_fee_liability_au', + 'operator_fee_retained_au', + 'gross_liability_au', + 'gross_paid_au', + 'rounding_au', + 'dust_au', + ]; + for (const field of auFields) { + if (this.normalizeAu( + value[field], + `targeted fiat settlement ${field}`, + { allowZero: !['gross_liability_au', 'gross_paid_au'].includes(field) } + ) instanceof Error) { + return new Error('Invalid targeted fiat settlement total.'); + } + } + const grossLiability = this.safeAddAu( + value.provider_liability_au, + value.operator_fee_liability_au + ); + const grossPaid = this.safeAddAu(value.provider_paid_au, value.operator_fee_retained_au); + const paidPlusDust = this.safeAddAu(value.gross_paid_au, value.dust_au); + if (grossLiability instanceof Error || grossPaid instanceof Error || paidPlusDust instanceof Error || + this.compareAu(grossLiability, value.gross_liability_au) !== 0 || + this.compareAu(grossPaid, value.gross_paid_au) !== 0 || + this.compareAu(paidPlusDust, value.gross_liability_au) !== 0 || + this.compareAu(value.rounding_au, value.dust_au) !== 0) { + return new Error('Targeted fiat settlement canonical AU totals do not balance.'); + } + const seenTransfers = new Set(); + const seenQuotes = new Set(); + const seenDestinationPayments = new Set(); + for (const entry of value.stripe_transfers) { + const transfer = this.normalizeStripeTransferEvidence( + entry, + 'targeted fiat settlement transfer' + ); + if (transfer instanceof Error) return transfer; + if (seenTransfers.has(transfer.ref)) { + return new Error('Duplicate targeted fiat settlement transfer.'); + } + seenTransfers.add(transfer.ref); + if (transfer.kind === 'stripe_transfer') { + if (transfer.fx_quote_id != null) { + if (seenQuotes.has(transfer.fx_quote_id)) { + return new Error('Duplicate targeted fiat settlement FX quote.'); + } + seenQuotes.add(transfer.fx_quote_id); + } + if (seenDestinationPayments.has(transfer.destination_payment)) { + return new Error('Duplicate targeted fiat settlement destination payment.'); + } + seenDestinationPayments.add(transfer.destination_payment); + } + } + const outputs = []; + const seen = new Set(); + let operatorSeen = false; + for (const output of value.outputs) { + if (output?.role === 'provider') { + const hasFxQuoteId = hasOwn(output, 'fx_quote_id'); + const hasFxQuoteHash = hasOwn(output, 'fx_quote_hash'); + if (hasFxQuoteId !== hasFxQuoteHash) { + return new Error( + 'Targeted fiat provider output FX quote identity must be present or absent as a pair.' + ); + } + const shapeError = this.validateExactObjectKeys( + output, + [ + 'role', + 'provider', + 'payout_revision', + 'to', + 'paid_cum_au_before', + 'aggregate_paid_cum_au_before', + 'liability_au', + 'paid_au', + 'rounding_au', + 'dust_au', + 'source_currency', + 'source_amount_minor', + 'destination_currency', + 'destination_amount_min_minor', + 'destination_amount_max_minor', + 'destination_amount_minor', + ...(hasFxQuoteId ? ['fx_quote_id', 'fx_quote_hash'] : []), + ], + 'targeted fiat provider output' + ); + if (shapeError) return shapeError; + if (!this.isHexBytes(output.payout_revision, 32) || + output.payout_revision !== output.payout_revision.toLowerCase()) { + return new Error('Invalid targeted fiat payout revision.'); + } + const identity = stableJson([output.provider, output.payout_revision]); + if (seen.has(identity)) return new Error('Duplicate targeted fiat payout liability.'); + seen.add(identity); + const { payout_revision: payoutRevision, ...settlementOutput } = output; + const paidCumAuBefore = this.normalizeAu( + settlementOutput.paid_cum_au_before, + 'targeted fiat provider paid cumulative watermark', + { allowZero: true } + ); + if (paidCumAuBefore instanceof Error) return paidCumAuBefore; + const aggregatePaidCumAuBefore = this.normalizeAu( + settlementOutput.aggregate_paid_cum_au_before, + 'targeted fiat provider aggregate paid cumulative watermark', + { allowZero: true } + ); + if (aggregatePaidCumAuBefore instanceof Error) { + return aggregatePaidCumAuBefore; + } + delete settlementOutput.paid_cum_au_before; + delete settlementOutput.aggregate_paid_cum_au_before; + const normalized = this.normalizeTargetedFiatSettlementOutput(settlementOutput); + if (normalized instanceof Error) return normalized; + outputs.push({ + ...normalized, + payout_revision: payoutRevision, + paid_cum_au_before: paidCumAuBefore, + aggregate_paid_cum_au_before: aggregatePaidCumAuBefore, + }); + } else if (output?.role === 'operator_fee') { + const shapeError = this.validateExactObjectKeys( + output, + [ + 'role', + 'to', + 'liability_au', + 'paid_au', + 'rounding_au', + 'dust_au', + 'source_currency', + 'source_amount_minor', + ], + 'targeted fiat operator output' + ); + if (shapeError) return shapeError; + if (operatorSeen) return new Error('Duplicate targeted fiat operator output.'); + operatorSeen = true; + const normalized = this.normalizeTargetedFiatSettlementOutput(output); + if (normalized instanceof Error) return normalized; + outputs.push(normalized); + } else { + return new Error('Invalid targeted fiat settlement output role.'); + } + } + outputs.sort((left, right) => { + if (left.role !== right.role) return left.role === 'provider' ? -1 : 1; + if (left.role === 'operator_fee') return 0; + return compareCodepoint(left.provider, right.provider) || + compareCodepoint(left.payout_revision, right.payout_revision); + }); + if (stableJson(outputs) !== stableJson(value.outputs)) { + return new Error('Targeted fiat settlement outputs must be canonical.'); + } + const destinationTotals = this.normalizeFiatDestinationTotals(value.destination_totals); + if (destinationTotals instanceof Error) return destinationTotals; + return { outputs, destination_totals: destinationTotals }; + } + + async targetedTnkSettlementTransferRoot(outputs) { + return await this.opaqueHash( + 'mayhem-targeted-tnk-settlement-transfer-root-v1', + outputs + ); + } + + async targetedFiatSettlementTransferRoot(outputs) { + return await this.opaqueHash( + 'mayhem-targeted-fiat-settlement-transfer-root-v2', + outputs + ); + } + + payoutPreparationLiabilityForOutput(value, output, rail) { + if (output.role === 'operator_fee') return null; + return { + provider: output.provider, + payout_revision: output.payout_revision, + target: output.to, + currency: rail === 'fiat' ? output.destination_currency : null, + chain_id: rail === 'tap' ? value.chain_id : null, + paid_cum_au_before: output.paid_cum_au_before, + aggregate_paid_cum_au_before: output.aggregate_paid_cum_au_before, + liability_au: rail === 'fiat' ? output.liability_au : (output.au ?? output.paid_au), + paid_au: rail === 'fiat' ? output.paid_au : (output.au ?? output.paid_au), + }; + } + + targetedFiatPreparationOutputProjection(output) { + if (output.role === 'operator_fee') { + return stableValue({ + role: output.role, + to: output.to, + liability_au: output.liability_au, + paid_au: output.paid_au, + rounding_au: output.rounding_au, + dust_au: output.dust_au, + source_currency: output.source_currency, + source_amount_minor: output.source_amount_minor, + }); + } + const projection = { + role: output.role, + provider: output.provider, + payout_revision: output.payout_revision, + to: output.to, + paid_cum_au_before: output.paid_cum_au_before, + aggregate_paid_cum_au_before: output.aggregate_paid_cum_au_before, + liability_au: output.liability_au, + paid_au: output.paid_au, + rounding_au: output.rounding_au, + dust_au: output.dust_au, + source_currency: output.source_currency, + source_amount_minor: output.source_amount_minor, + destination_currency: output.destination_currency, + destination_amount_min_minor: output.destination_amount_min_minor, + destination_amount_max_minor: output.destination_amount_max_minor, + }; + return stableValue(projection); + } + + normalizeTargetedFiatPreparationOutput(output) { + if (output?.role === 'operator_fee') { + const shapeError = this.validateExactObjectKeys( + output, + [ + 'role', + 'to', + 'liability_au', + 'paid_au', + 'rounding_au', + 'dust_au', + 'source_currency', + 'source_amount_minor', + ], + 'targeted fiat preparation operator output' + ); + if (shapeError) return shapeError; + const normalized = this.normalizeTargetedFiatSettlementOutput(output); + if (normalized instanceof Error) return normalized; + if (this.compareAu(normalized.paid_au, normalized.liability_au) !== 0 || + !this.isZeroAu(normalized.rounding_au) || + !this.isZeroAu(normalized.dust_au)) { + return new Error( + 'Targeted fiat operator fee must retain its exact AU liability with zero dust.' + ); + } + return stableJson(normalized) === stableJson(output) + ? normalized + : new Error('Targeted fiat preparation operator output must be canonical.'); + } + if (output?.role !== 'provider') { + return new Error('Invalid targeted fiat preparation output role.'); + } + const shapeError = this.validateExactObjectKeys( + output, + [ + 'role', + 'provider', + 'payout_revision', + 'to', + 'paid_cum_au_before', + 'aggregate_paid_cum_au_before', + 'liability_au', + 'paid_au', + 'rounding_au', + 'dust_au', + 'source_currency', + 'source_amount_minor', + 'destination_currency', + 'destination_amount_min_minor', + 'destination_amount_max_minor', + ], + 'targeted fiat preparation provider output' + ); + if (shapeError) return shapeError; + if (!this.isHexBytes(output.payout_revision, 32) || + output.payout_revision !== output.payout_revision.toLowerCase()) { + return new Error('Invalid targeted fiat preparation payout revision.'); + } + const paidCumAuBefore = this.normalizeAu( + output.paid_cum_au_before, + 'targeted fiat preparation paid cumulative watermark', + { allowZero: true } + ); + const aggregatePaidCumAuBefore = this.normalizeAu( + output.aggregate_paid_cum_au_before, + 'targeted fiat preparation aggregate paid cumulative watermark', + { allowZero: true } + ); + if (paidCumAuBefore instanceof Error || + aggregatePaidCumAuBefore instanceof Error) { + return new Error('Invalid targeted fiat preparation cumulative watermark.'); + } + if (!this.isHexBytes(output.provider, 32) || + output.provider !== output.provider.toLowerCase() || + !this.isSafeKeyPart(output.to)) { + return new Error('Invalid targeted fiat preparation provider identity.'); + } + const sourceCurrency = this.normalizeFiatCurrency(output.source_currency); + const destinationCurrency = this.normalizeFiatCurrency(output.destination_currency); + const sourceAmountMinor = this.normalizeFiatMinor(output.source_amount_minor); + const destinationMin = this.normalizeFiatMinor(output.destination_amount_min_minor); + const destinationMax = this.normalizeFiatMinor(output.destination_amount_max_minor); + const canonicalAu = {}; + for (const [field, allowZero] of [ + ['liability_au', false], + ['paid_au', false], + ['rounding_au', true], + ['dust_au', true], + ]) { + canonicalAu[field] = this.normalizeAu( + output[field], + `targeted fiat preparation ${field}`, + { allowZero } + ); + if (canonicalAu[field] instanceof Error) { + return new Error(`Invalid targeted fiat preparation ${field}.`); + } + } + const paidPlusDust = this.safeAddAu(canonicalAu.paid_au, canonicalAu.dust_au); + if (sourceCurrency instanceof Error || + sourceCurrency !== output.source_currency || + destinationCurrency instanceof Error || + destinationCurrency !== output.destination_currency || + sourceAmountMinor instanceof Error || + destinationMin instanceof Error || + destinationMax instanceof Error || + BigInt(destinationMax) < BigInt(destinationMin) || + paidPlusDust instanceof Error || + paidPlusDust !== canonicalAu.liability_au || + canonicalAu.rounding_au !== canonicalAu.dust_au) { + return new Error('Invalid targeted fiat preparation economic terms.'); + } + const projection = { + role: 'provider', + provider: output.provider, + to: output.to, + ...canonicalAu, + source_currency: sourceCurrency, + source_amount_minor: sourceAmountMinor, + destination_currency: destinationCurrency, + destination_amount_min_minor: destinationMin, + destination_amount_max_minor: destinationMax, + payout_revision: output.payout_revision, + paid_cum_au_before: paidCumAuBefore, + aggregate_paid_cum_au_before: aggregatePaidCumAuBefore, + }; + return stableJson(projection) === stableJson(output) + ? projection + : new Error('Targeted fiat preparation provider output must be canonical.'); + } + + normalizeTargetedFiatPreparationPayload(value, payload, liability) { + const shapeError = this.validateExactObjectKeys( + payload, + [ + 'settlement_op', + 'rail', + 'epoch', + 'epoch_apply_hash', + 'plan_root', + 'economic_op_id', + 'output_index', + 'output', + 'processor', + 'source_currency', + ], + 'targeted fiat preparation payload' + ); + if (shapeError) return shapeError; + const sourceCurrency = this.normalizeFiatCurrency(payload.source_currency); + if (payload.settlement_op !== 'settle_targeted_fiat_output' || + payload.rail !== 'fiat' || + payload.epoch !== value.epoch || + payload.epoch_apply_hash !== value.epoch_apply_hash || + !this.isHexBytes(payload.plan_root, 32) || + payload.economic_op_id !== value.economic_op_id || + payload.output_index !== value.output_index || + payload.processor !== 'stripe' || + sourceCurrency instanceof Error || + sourceCurrency !== payload.source_currency) { + return new Error('Invalid targeted fiat preparation payload.'); + } + const output = this.normalizeTargetedPayoutPlanOutput( + 'fiat', + payload.output, + value.output_index + ); + if (output instanceof Error) return output; + if (output.source_currency !== sourceCurrency) { + return new Error('Targeted fiat preparation source currency mismatch.'); + } + if (value.kind === 'liability') { + const expectedLiability = this.payoutPreparationLiabilityForOutput( + value, + output, + 'fiat' + ); + if (output.role !== 'provider' || + stableJson(expectedLiability) !== stableJson(liability)) { + return new Error('Targeted fiat preparation output does not match liability.'); + } + } else if (value.kind !== 'fee' || + output.role !== 'operator_fee' || + liability !== null) { + return new Error('Targeted fiat preparation output does not match kind.'); + } + return stableValue({ + settlement_op: 'settle_targeted_fiat_output', + plan_root: payload.plan_root, + economic_op_id: value.economic_op_id, + rail: 'fiat', + epoch: value.epoch, + epoch_apply_hash: value.epoch_apply_hash, + output_index: value.output_index, + output, + processor: 'stripe', + source_currency: sourceCurrency, + }); + } + + normalizeMsbSignedPayoutPayload(payload, output, treasuryFrom) { + const payloadShape = this.validateExactObjectKeys( + payload, + ['type', 'address', 'tro'], + 'targeted TNK signed MSB payload' + ); + if (payloadShape) return payloadShape; + const transferShape = this.validateExactObjectKeys( + payload?.tro, + ['tx', 'txv', 'to', 'am', 'in', 'is'], + 'targeted TNK signed MSB transfer' + ); + if (transferShape) return transferShape; + const expectedAmountHex = BigInt(output.tnk_e18).toString(16).padStart(32, '0'); + if (!Number.isSafeInteger(payload.type) || + payload.type < 0 || + payload.address !== treasuryFrom || + !this.isHexBytes(payload.tro.tx, 32) || + payload.tro.tx !== payload.tro.tx.toLowerCase() || + !this.isHexBytes(payload.tro.txv, 32) || + payload.tro.txv !== payload.tro.txv.toLowerCase() || + payload.tro.to !== output.to || + !/^[0-9a-f]{32}$/.test(payload.tro.am) || + payload.tro.am !== expectedAmountHex || + !this.isHexBytes(payload.tro.in, 32) || + payload.tro.in !== payload.tro.in.toLowerCase() || + !this.isHexBytes(payload.tro.is, 64) || + payload.tro.is !== payload.tro.is.toLowerCase()) { + return new Error('Invalid targeted TNK signed MSB payload.'); + } + return stableValue(payload); + } + + async normalizeTargetedTnkPreparationPayload( + value, + payload, + liability, + externalEffectIds + ) { + const shapeError = this.validateExactObjectKeys( + payload, + [ + 'settlement_op', + 'rail', + 'epoch', + 'epoch_apply_hash', + 'plan_root', + 'economic_op_id', + 'output_index', + 'output', + 'network', + 'treasury_from', + 'rate_tnk_usd_au', + 'rate_source', + 'rate_ts', + 'msb_tx_hash', + 'msb_payload', + ], + 'targeted TNK preparation payload' + ); + if (shapeError) return shapeError; + if (payload.settlement_op !== 'settle_targeted_tnk_output' || + payload.rail !== 'tnk' || + payload.epoch !== value.epoch || + payload.epoch_apply_hash !== value.epoch_apply_hash || + !this.isHexBytes(payload.plan_root, 32) || + payload.economic_op_id !== value.economic_op_id || + payload.output_index !== value.output_index || + !this.isSafeKeyPart(payload.network) || + !this.isSafeKeyPart(payload.treasury_from) || + !Number.isSafeInteger(payload.rate_ts) || + payload.rate_ts < 0 || + !RATE_SOURCES.has(payload.rate_source) || + !this.isHexBytes(payload.msb_tx_hash, 32) || + payload.msb_tx_hash !== payload.msb_tx_hash.toLowerCase() || + this.normalizeAu( + payload.rate_tnk_usd_au, + 'targeted TNK preparation rate', + { allowZero: false } + ) instanceof Error) { + return new Error('Invalid targeted TNK preparation payload.'); + } + const output = this.normalizeTargetedPayoutPlanOutput( + 'tnk', + payload.output, + value.output_index + ); + if (output instanceof Error) return output; + const msbPayload = this.normalizeMsbSignedPayoutPayload( + payload.msb_payload, + output, + payload.treasury_from + ); + if (msbPayload instanceof Error) return msbPayload; + if (payload.msb_tx_hash !== msbPayload.tro.tx || + externalEffectIds.length !== 1 || + externalEffectIds[0] !== payload.msb_tx_hash) { + return new Error('Targeted TNK preparation effect must equal its signed MSB tx hash.'); + } + const expectedLiability = this.payoutPreparationLiabilityForOutput( + value, + output, + 'tnk' + ); + if ((value.kind === 'liability' && + (output.role !== 'provider' || + stableJson(expectedLiability) !== stableJson(liability))) || + (value.kind === 'fee' && + (output.role !== 'operator_fee' || liability !== null))) { + return new Error('Targeted TNK preparation output does not match its kind.'); + } + return stableValue({ + settlement_op: 'settle_targeted_tnk_output', + rail: 'tnk', + epoch: value.epoch, + epoch_apply_hash: value.epoch_apply_hash, + plan_root: payload.plan_root, + economic_op_id: value.economic_op_id, + output_index: value.output_index, + output, + network: payload.network, + treasury_from: payload.treasury_from, + rate_tnk_usd_au: payload.rate_tnk_usd_au, + rate_source: payload.rate_source, + rate_ts: payload.rate_ts, + msb_tx_hash: payload.msb_tx_hash, + msb_payload: msbPayload, + }); + } + + payoutPreparationOutputPayload(value, output, outputIndex, rail) { + const common = { + settlement_op: value.op, + rail, + epoch: value.epoch, + epoch_apply_hash: value.epoch_apply_hash, + output_index: outputIndex, + output: rail === 'fiat' + ? this.targetedFiatPreparationOutputProjection(output) + : stableValue(output), + }; + if (rail === 'tnk') { + return { + ...common, + network: value.network, + treasury_from: value.treasury_from, + rate_tnk_usd_au: value.rate_tnk_usd_au, + rate_source: value.rate_source, + rate_ts: value.rate_ts, + }; + } + if (rail === 'fiat') { + return { + ...common, + processor: value.processor, + source_currency: value.source_currency, + }; + } + return { + ...common, + chain_id: value.chain_id, + token_address: value.token_address, + pool_address: value.pool_address, + payment_config_ver: value.payment_config_ver, + fee_bps: value.fee_bps, + tap_burn_bps: value.tap_burn_bps, + provider_share_bps: value.provider_share_bps, + tap_rate_lock: stableValue(value.tap_rate_lock), + }; + } + + targetedTapPayoutSplit(feeBps) { + if (!Number.isSafeInteger(feeBps) || + feeBps !== TAP_OPERATOR_BPS) { + return new Error( + 'Targeted TAP fee schedule does not match the fixed on-chain operator split.' + ); + } + return { + fee_bps: feeBps, + tap_burn_bps: TAP_BURN_BPS, + provider_share_bps: 10_000 - feeBps - TAP_BURN_BPS, + }; + } + + async payoutPreparationTapRootPayload(value, normalized) { + return { + settlement_op: value.op, + rail: 'tap', + epoch: value.epoch, + epoch_apply_hash: value.epoch_apply_hash, + chain_id: value.chain_id, + token_address: value.token_address, + pool_address: value.pool_address, + payment_config_ver: value.payment_config_ver, + tap_rate_lock: stableValue(normalized.rate_lock), + root: value.root, + cumulative_spent_wei: normalized.cumulative_spent_wei, + provider_cumulative_claimed_wei: normalized.provider_cumulative_claimed_wei, + buyer_refund_wei: normalized.buyer_refund_wei, + provider_count: value.provider_count, + provider_paid_au: normalized.provider_paid_au, + provider_tap_wei: normalized.provider_tap_wei, + fee_bps: normalized.fee_bps, + tap_burn_bps: normalized.tap_burn_bps, + provider_share_bps: normalized.provider_share_bps, + entries_hash: await this.opaqueHash( + 'mayhem-targeted-tap-preparation-entries-v1', + normalized.entries + ), + provider_entries_hash: await this.opaqueHash( + 'mayhem-targeted-tap-preparation-provider-entries-v1', + normalized.provider_entries + ), + refunds_hash: await this.opaqueHash( + 'mayhem-targeted-tap-preparation-refunds-v1', + normalized.refunds + ), + outputs_hash: await this.opaqueHash( + 'mayhem-targeted-tap-preparation-outputs-v1', + normalized.outputs + ), + }; + } + + async validatePayoutPreparationRecord({ + economicOpId, + rail, + epoch, + epochApplyHash, + kind, + outputIndex, + payload, + liability, + externalEffectIds, + }) { + const record = await this.get(this.payoutPreparationRecordKey(rail, economicOpId)); + if (!record || + record.type !== 'targeted_payout_preparation' || + record.economic_op_id !== economicOpId || + record.rail !== rail || + record.epoch !== epoch || + record.epoch_apply_hash !== epochApplyHash || + record.kind !== kind || + record.output_index !== outputIndex || + record.consumed !== false || + stableJson(record.payload) !== stableJson(payload) || + stableJson(record.liability) !== stableJson(liability) || + stableJson(record.external_effect_ids) !== stableJson(externalEffectIds)) { + return new Error('Targeted settlement does not match an unconsumed canonical preparation.'); + } + const payloadHash = await this.opaqueHash( + 'mayhem-targeted-payout-preparation-payload-v1', + { + economic_op_id: economicOpId, + rail, + epoch, + epoch_apply_hash: epochApplyHash, + kind, + output_index: outputIndex, + payload, + } + ); + if (record.payload_hash !== payloadHash) { + return new Error('Targeted settlement preparation payload hash mismatch.'); + } + if (liability !== null) { + const liabilityLock = await this.get( + this.payoutPreparationLiabilityLockKey(rail, liability) + ); + if (liabilityLock?.economic_op_id !== economicOpId) { + return new Error('Targeted settlement preparation liability lock mismatch.'); + } + } + for (const effectId of externalEffectIds) { + const effectLock = await this.get( + this.payoutPreparationEffectLockKey(rail, effectId) + ); + if (effectLock?.economic_op_id !== economicOpId || + effectLock.consumed !== false) { + return new Error('Targeted settlement preparation effect lock mismatch.'); + } + } + return record; + } + + async payoutPreparationsForSettlement(value, normalized, rail) { + const plans = []; + for (const [outputIndex, output] of normalized.outputs.entries()) { + const economicOpId = value.preparation_ids[outputIndex]; + const liability = this.payoutPreparationLiabilityForOutput(value, output, rail); + const externalEffectIds = rail === 'tap' + ? [] + : [value.external_effect_ids[outputIndex]]; + const record = await this.validatePayoutPreparationRecord({ + economicOpId, + rail, + epoch: value.epoch, + epochApplyHash: value.epoch_apply_hash, + kind: liability === null ? 'fee' : 'liability', + outputIndex, + payload: this.payoutPreparationOutputPayload(value, output, outputIndex, rail), + liability, + externalEffectIds, + }); + if (record instanceof Error) return record; + plans.push(record); + } + if (rail === 'tap') { + const rootPayload = await this.payoutPreparationTapRootPayload(value, normalized); + if (rootPayload instanceof Error) return rootPayload; + const rootRecord = await this.validatePayoutPreparationRecord({ + economicOpId: value.root_preparation_id, + rail, + epoch: value.epoch, + epochApplyHash: value.epoch_apply_hash, + kind: 'tap_root', + outputIndex: 0, + payload: rootPayload, + liability: null, + externalEffectIds: value.external_effect_ids, + }); + if (rootRecord instanceof Error) return rootRecord; + plans.push(rootRecord); + } + return plans; + } + + async validatePayoutPreparationConsumption(plans) { + const lastByProvider = new Map(); + for (const record of plans) { + if (record.liability !== null) { + lastByProvider.set( + `${record.rail}/${record.liability.provider}`, + record + ); + } + } + const validatedTails = []; + for (const record of lastByProvider.values()) { + const tailKey = this.payoutPreparationAggregateTailKey( + record.rail, + record.liability.provider + ); + const tail = await this.get(tailKey); + if (!tail || + tail.economic_op_id !== record.economic_op_id || + tail.consumed !== false) { + return new Error('Targeted payout preparation aggregate tail mismatch.'); + } + validatedTails.push({ key: tailKey, value: tail }); + } + return validatedTails; + } + + async consumePayoutPreparations(plans, settlementKey) { + const validatedTails = await this.validatePayoutPreparationConsumption(plans); + if (validatedTails instanceof Error) return validatedTails; + for (const record of plans) { + await this.put( + this.payoutPreparationRecordKey(record.rail, record.economic_op_id), + { + ...record, + consumed: true, + consumed_by: settlementKey, + } + ); + for (const effectId of record.external_effect_ids) { + await this.put(this.payoutPreparationEffectLockKey(record.rail, effectId), { + economic_op_id: record.economic_op_id, + effect_id: effectId, + consumed: true, + consumed_by: settlementKey, + updated_at: this.tx, + }); + } + } + for (const tail of validatedTails) { + await this.put(tail.key, { + ...tail.value, + consumed: true, + consumed_by: settlementKey, + updated_at: this.tx, + }); + } + return null; + } + + async targetedPayoutSettlementUpdates(outputs, rail, epoch, at, transferIds) { + const params = await this.activeParamsAt(at, [ + 'holdback_epochs', + 'challenge_epochs', + 'canary_probe_holdback_bps', + 'canary_probe_release_min_passes', + ]); + const liabilityUpdates = []; + const paidByProvider = new Map(); + for (const [outputIndex, output] of outputs.entries()) { + if (output.role !== 'provider') continue; + const provider = await this.get(`prov/${output.provider}`); + if (!provider) return new Error('Provider not found.'); + if (provider.status !== 'active' && provider.status !== 'banned') { + return new Error('Targeted settlement provider status is not payable.'); + } + const binding = await this.get( + this.providerPayoutBindingFeatureKey( + rail, + output.provider, + output.payout_revision + ) + ); + if (!binding || + binding.verified !== true || + binding.provider !== output.provider || + binding.rail !== rail || + binding.revision !== output.payout_revision || + binding.activation_epoch > epoch) { + return new Error('Targeted settlement requires its immutable payout binding.'); + } + if (binding.target !== output.to) { + return new Error('Targeted settlement payout target mismatch.'); + } + if (rail === 'fiat' && binding.currency !== output.destination_currency) { + return new Error('Targeted settlement payout currency mismatch.'); + } + const liabilityKey = this.providerPayoutLiabilityKey( + output.provider, + rail, + output.payout_revision + ); + const liability = await this.get(liabilityKey); + if (!liability || + liability.provider !== output.provider || + liability.rail !== rail || + liability.revision !== output.payout_revision || + liability.target !== binding.target || + (liability.currency ?? null) !== binding.currency || + (liability.chain_id ?? null) !== binding.chain_id) { + return new Error('Targeted settlement payout liability mismatch.'); + } + const liabilityError = this.guardianValidatePayoutLiabilityRecord( + liability, + output.provider, + rail, + output.payout_revision + ); + if (liabilityError) return liabilityError; + const probeGate = await this.probeGateForEarning(output.provider, liability, params); + if (probeGate instanceof Error) return probeGate; + const lockedEarningEpochs = this.providerLockedEarningEpochs(provider, params); + if (lockedEarningEpochs instanceof Error) return lockedEarningEpochs; + const disputeGate = await this.providerHasOpenDispute(output.provider); + if (disputeGate instanceof Error) return disputeGate; + const refreshed = this.refreshEarningHoldback( + liability, + epoch, + lockedEarningEpochs, + probeGate, + disputeGate + ); + if (refreshed instanceof Error) return refreshed; + const payable = this.safeSubAu( + this.safeSubAu(refreshed.total_au, refreshed.held_au), + refreshed.paid_cum_au + ); + if (payable instanceof Error) return payable; + if (refreshed.paid_cum_au !== output.paid_cum_au_before) { + return new Error('Targeted settlement paid cumulative watermark mismatch.'); + } + let settledAu = output.au; + if (rail === 'fiat') { + const dust = this.safeSubAu(output.liability_au, output.paid_au); + if (dust instanceof Error || + this.isZeroAu(output.liability_au) || + this.compareAu(output.liability_au, payable) > 0 || + this.compareAu(output.dust_au, dust) !== 0 || + this.compareAu(output.rounding_au, dust) !== 0) { + return new Error('Targeted fiat settlement exceeds its frozen revision liability or mismatches dust.'); + } + settledAu = output.paid_au; + } + if (this.isZeroAu(settledAu)) { + return new Error('Targeted settlement liability has no payable earnings.'); + } + if (rail !== 'fiat' && this.compareAu(output.au, payable) > 0) { + return new Error('Targeted settlement amount exceeds revision liability.'); + } + const paidCumAu = this.safeAddAu(refreshed.paid_cum_au, settledAu); + if (paidCumAu instanceof Error) return paidCumAu; + const nextLiability = { + ...refreshed, + paid_cum_au: paidCumAu, + updated_epoch: Math.max(refreshed.updated_epoch, epoch), + last_settlement_epoch: epoch, + last_settlement_transfer: transferIds[outputIndex], + updated_at: this.tx, + }; + const nextLiabilityError = this.guardianValidatePayoutLiabilityRecord( + nextLiability, + output.provider, + rail, + output.payout_revision + ); + if (nextLiabilityError) return nextLiabilityError; + liabilityUpdates.push({ + key: liabilityKey, + value: nextLiability, + }); + const providerPaid = paidByProvider.get(output.provider) ?? { + aggregate_paid_cum_au_before: output.aggregate_paid_cum_au_before, + paid_au: ZERO_AU, + }; + const expectedWatermark = this.safeAddAu( + providerPaid.aggregate_paid_cum_au_before, + providerPaid.paid_au + ); + if (expectedWatermark instanceof Error) return expectedWatermark; + if (expectedWatermark !== output.aggregate_paid_cum_au_before) { + return new Error('Targeted settlement provider outputs have a discontinuous aggregate paid watermark.'); + } + const nextProviderPaid = this.safeAddAu(providerPaid.paid_au, settledAu); + if (nextProviderPaid instanceof Error) return nextProviderPaid; + paidByProvider.set(output.provider, { + aggregate_paid_cum_au_before: providerPaid.aggregate_paid_cum_au_before, + paid_au: nextProviderPaid, + }); + } + + const earningUpdates = []; + for (const [providerId, providerPaid] of paidByProvider) { + const paidAu = providerPaid.paid_au; + const provider = await this.get(`prov/${providerId}`); + const earning = await this.earningRecord(providerId, rail); + if (earning instanceof Error) return earning; + const earningError = this.guardianValidateEarningRecord( + earning, + providerId, + rail + ); + if (earningError) return earningError; + const probeGate = await this.probeGateForEarning(providerId, earning, params); + if (probeGate instanceof Error) return probeGate; + const lockedEarningEpochs = this.providerLockedEarningEpochs(provider, params); + if (lockedEarningEpochs instanceof Error) return lockedEarningEpochs; + const disputeGate = await this.providerHasOpenDispute(providerId); + if (disputeGate instanceof Error) return disputeGate; + const refreshed = this.refreshEarningHoldback( + earning, + epoch, + lockedEarningEpochs, + probeGate, + disputeGate + ); + if (refreshed instanceof Error) return refreshed; + const payable = this.safeSubAu( + this.safeSubAu(refreshed.total_au, refreshed.held_au), + refreshed.paid_cum_au + ); + if (payable instanceof Error) return payable; + if (refreshed.paid_cum_au !== providerPaid.aggregate_paid_cum_au_before) { + return new Error('Targeted settlement aggregate paid cumulative watermark mismatch.'); + } + if (this.compareAu(paidAu, payable) > 0) { + return new Error('Targeted settlement exceeds aggregate provider earnings.'); + } + const paidCumAu = this.safeAddAu(refreshed.paid_cum_au, paidAu); + if (paidCumAu instanceof Error) return paidCumAu; + const nextEarning = { + ...refreshed, + paid_cum_au: paidCumAu, + updated_epoch: Math.max(refreshed.updated_epoch, epoch), + last_targeted_settlement_epoch: epoch, + updated_at: this.tx, + }; + const nextError = this.guardianValidateEarningRecord( + nextEarning, + providerId, + rail + ); + if (nextError) return nextError; + earningUpdates.push(nextEarning); + } + return { liabilityUpdates, earningUpdates }; + } + + async targetedTapSettlement(value) { + const adminError = await this.requireAdmin(); + if (adminError) return adminError; + const normalized = this.normalizeTargetedTapSettlementValue(value); + if (normalized instanceof Error) return normalized; + const settlementParams = await this.activeParamsAt( + value.at, + ['max_tap_settlement_outputs', 'fee_bps'] + ); + const payoutSplit = this.targetedTapPayoutSplit(settlementParams.fee_bps); + if (payoutSplit instanceof Error) return payoutSplit; + if (normalized.fee_bps !== payoutSplit.fee_bps || + normalized.tap_burn_bps !== payoutSplit.tap_burn_bps || + normalized.provider_share_bps !== payoutSplit.provider_share_bps) { + return new Error( + 'Targeted TAP settlement fee/burn split does not match the historical schedule.' + ); + } + if (normalized.outputs.length > settlementParams.max_tap_settlement_outputs) { + return new Error('Targeted TAP settlement output count exceeds limit.'); + } + + const admin = await this.get('admin'); + const payments = await this.get('payments/current'); + if (!payments || payments.set_by !== admin || payments.set_by_role !== 'admin' || + payments.ver !== value.payment_config_ver || + payments.tap?.chain_id !== value.chain_id || + payments.tap?.token_address?.toLowerCase() !== value.token_address || + payments.tap?.pool_address?.toLowerCase() !== value.pool_address) { + return new Error('Targeted TAP settlement does not match canonical payment configuration.'); + } + const rate = await this.guardianRequireHistoricalTapRateLock( + normalized.rate_lock, + value.at + ); + if (rate instanceof Error) return rate; + + const record = { + type: 'targeted_tap_settlement', + ...value, + tap_rate_lock: normalized.rate_lock, + entries: normalized.entries, + outputs: normalized.outputs, + fee_bps: payoutSplit.fee_bps, + tap_burn_bps: payoutSplit.tap_burn_bps, + provider_share_bps: payoutSplit.provider_share_bps, + settled_by: this.address, + settled_by_role: 'admin', + }; + const recordKey = `settle/targeted/tap/${value.epoch}`; + const existing = await this.get(recordKey); + if (existing) { + if (stableJson(existing) === stableJson(record)) { + return { + ok: true, + op: 'targetedTapSettlement', + epoch: value.epoch, + rail: 'tap', + root: value.root, + execution_tx: value.execution_tx, + idempotent: true, + }; + } + return new Error('Targeted TAP settlement already exists for epoch.'); + } + + const applyAnchor = await this.requireEpochApplyAnchor( + value.epoch, + value.epoch_apply_hash, + 'Targeted TAP settlement' + ); + if (applyAnchor instanceof Error) return applyAnchor; + const preparations = await this.payoutPreparationsForSettlement( + value, + normalized, + 'tap' + ); + if (preparations instanceof Error) return preparations; + + const scope = `${value.chain_id}/${value.pool_address}`; + const rootSeenKey = `rail/seen/tap-settlement-root/${scope}/${value.root}`; + const proposalSeenKey = `rail/seen/tap-settlement-proposal/${scope}/${value.proposal_tx}`; + const executionSeenKey = `rail/seen/tap-settlement-execution/${scope}/${value.execution_tx}`; + if ((await this.get(rootSeenKey)) !== null) { + return new Error('Targeted TAP settlement root was already consumed.'); + } + if ((await this.get(proposalSeenKey)) !== null) { + return new Error('Targeted TAP settlement proposal transaction was already consumed.'); + } + if ((await this.get(executionSeenKey)) !== null) { + return new Error('Targeted TAP settlement execution transaction was already consumed.'); + } + + const stateKey = `settle/targeted/tap/state/${scope}`; + const state = (await this.get(stateKey)) ?? { + type: 'targeted_tap_settlement_state', + chain_id: value.chain_id, + token_address: value.token_address, + pool_address: value.pool_address, + payment_config_ver: value.payment_config_ver, + last_epoch: 0, + cumulative_spent_wei: '0', + cumulative_provider_claimed_wei: '0', + cumulative_buyer_refund_wei: '0', + last_root: null, + last_execution_tx: null, + updated_at: null, + }; + if (state.type !== 'targeted_tap_settlement_state' || + state.chain_id !== value.chain_id || + state.token_address !== value.token_address || + state.pool_address !== value.pool_address || + state.payment_config_ver !== value.payment_config_ver || + !Number.isSafeInteger(state.last_epoch) || + state.last_epoch < 0 || + typeof state.cumulative_spent_wei !== 'string' || + !/^(0|[1-9][0-9]*)$/.test(state.cumulative_spent_wei) || + typeof state.cumulative_provider_claimed_wei !== 'string' || + !/^(0|[1-9][0-9]*)$/.test(state.cumulative_provider_claimed_wei) || + typeof state.cumulative_buyer_refund_wei !== 'string' || + !/^(0|[1-9][0-9]*)$/.test(state.cumulative_buyer_refund_wei)) { + return new Error('Invalid targeted TAP settlement state.'); + } + if (value.epoch <= state.last_epoch) { + return new Error('Targeted TAP settlement epoch must advance.'); + } + const priorSpentWei = BigInt(state.cumulative_spent_wei); + const nextSpentWei = BigInt(normalized.cumulative_spent_wei); + const priorProviderClaimedWei = BigInt(state.cumulative_provider_claimed_wei); + const nextProviderClaimedWei = BigInt(normalized.provider_cumulative_claimed_wei); + const priorBuyerRefundWei = BigInt(state.cumulative_buyer_refund_wei); + const nextBuyerRefundWei = BigInt(normalized.buyer_refund_wei); + if (nextSpentWei <= priorSpentWei) { + return new Error('Targeted TAP cumulative gross spend must advance.'); + } + const grossSpendDeltaWei = nextSpentWei - priorSpentWei; + const expectedProviderDeltaWei = + (grossSpendDeltaWei * BigInt(payoutSplit.provider_share_bps)) / 10_000n; + if (BigInt(normalized.provider_tap_wei) !== expectedProviderDeltaWei || + nextProviderClaimedWei !== + priorProviderClaimedWei + BigInt(normalized.provider_tap_wei)) { + return new Error( + 'Targeted TAP provider entitlement does not match the historical fee/burn split.' + ); + } + if (nextBuyerRefundWei < priorBuyerRefundWei) { + return new Error('Targeted TAP cumulative buyer refunds cannot decrease.'); + } + + const outputsByTarget = new Map(); + for (const output of normalized.outputs) { + const targetOutputs = outputsByTarget.get(output.to) ?? []; + targetOutputs.push(output); + outputsByTarget.set(output.to, targetOutputs); + } + const claimUpdates = []; + for (const entry of normalized.provider_entries) { + const claimKey = `settle/targeted/tap/claim/${scope}/${entry.account}`; + const claim = (await this.get(claimKey)) ?? { + type: 'targeted_tap_cumulative_claim', + chain_id: value.chain_id, + token_address: value.token_address, + pool_address: value.pool_address, + account: entry.account, + cumulative_claim_wei: '0', + updated_epoch: 0, + updated_at: null, + }; + if (claim.type !== 'targeted_tap_cumulative_claim' || + claim.chain_id !== value.chain_id || + claim.token_address !== value.token_address || + claim.pool_address !== value.pool_address || + claim.account !== entry.account || + typeof claim.cumulative_claim_wei !== 'string' || + !/^(0|[1-9][0-9]*)$/.test(claim.cumulative_claim_wei)) { + return new Error('Invalid targeted TAP cumulative claim state.'); + } + const targetOutputs = outputsByTarget.get(entry.account) ?? []; + let cursor = BigInt(claim.cumulative_claim_wei); + for (const output of targetOutputs) { + const prior = BigInt(output.prior_cumulative_claim_wei); + const next = BigInt(output.cumulative_claim_wei); + if (prior !== cursor || next !== prior + BigInt(output.tap_wei)) { + return new Error('Targeted TAP cumulative claim output chain does not advance from canonical state.'); + } + cursor = next; + } + if (BigInt(entry.cumulative_wei) !== cursor) { + return new Error('Targeted TAP cumulative claim chain does not match executed root.'); + } + outputsByTarget.delete(entry.account); + claimUpdates.push({ + key: claimKey, + value: { + ...claim, + cumulative_claim_wei: entry.cumulative_wei, + updated_epoch: value.epoch, + updated_at: this.tx, + }, + }); + } + if (outputsByTarget.size !== 0) { + return new Error('Targeted TAP provider output target is missing from executed root.'); + } + const priorSettlement = state.last_epoch === 0 + ? null + : await this.get(`settle/targeted/tap/${state.last_epoch}`); + const priorRefunds = priorSettlement?.refunds ?? []; + if (state.last_epoch > 0 && !Array.isArray(priorRefunds)) { + return new Error('Prior targeted TAP refund distribution is invalid.'); + } + const refundUpdates = []; + const refundMap = new Map(normalized.refunds.map((entry) => [entry.account, entry])); + for (const priorRefund of priorRefunds) { + const nextRefund = refundMap.get(priorRefund.account); + if (!nextRefund || + BigInt(nextRefund.cumulative_wei) < BigInt(priorRefund.cumulative_wei)) { + return new Error('Targeted TAP refund claim cannot be removed or decreased.'); + } + } + for (const entry of normalized.refunds) { + const refundKey = `settle/targeted/tap/refund/${scope}/${entry.account}`; + const refund = (await this.get(refundKey)) ?? { + type: 'targeted_tap_cumulative_refund', + chain_id: value.chain_id, + token_address: value.token_address, + pool_address: value.pool_address, + account: entry.account, + cumulative_refund_wei: '0', + updated_epoch: 0, + updated_at: null, + }; + if (refund.type !== 'targeted_tap_cumulative_refund' || + refund.chain_id !== value.chain_id || + refund.token_address !== value.token_address || + refund.pool_address !== value.pool_address || + refund.account !== entry.account || + typeof refund.cumulative_refund_wei !== 'string' || + !/^(0|[1-9][0-9]*)$/.test(refund.cumulative_refund_wei) || + BigInt(entry.cumulative_wei) < BigInt(refund.cumulative_refund_wei)) { + return new Error('Invalid or decreasing targeted TAP cumulative refund state.'); + } + refundUpdates.push({ + key: refundKey, + value: { + ...refund, + cumulative_refund_wei: entry.cumulative_wei, + updated_epoch: value.epoch, + updated_at: this.tx, + }, + }); + } + + const settlementOutputs = normalized.outputs.map((output) => ({ + role: 'provider', + provider: output.provider, + payout_revision: output.payout_revision, + to: output.to, + paid_cum_au_before: output.paid_cum_au_before, + aggregate_paid_cum_au_before: output.aggregate_paid_cum_au_before, + au: output.paid_au, + })); + const updates = await this.targetedPayoutSettlementUpdates( + settlementOutputs, + 'tap', + value.epoch, + value.at, + normalized.outputs.map(() => value.execution_tx) + ); + if (updates instanceof Error) return updates; + + for (const update of updates.liabilityUpdates) await this.put(update.key, update.value); + for (const earning of updates.earningUpdates) { + await this.put(this.earningKey(earning.provider, 'tap'), earning); + } + for (const update of claimUpdates) await this.put(update.key, update.value); + for (const update of refundUpdates) await this.put(update.key, update.value); + const replayEvidence = { + rail: 'tap', + purpose: 'targeted_settlement', + epoch: value.epoch, + root: value.root, + proposal_tx: value.proposal_tx, + execution_tx: value.execution_tx, + epoch_apply_hash: value.epoch_apply_hash, + consumed_at: this.tx, + }; + await this.put(rootSeenKey, replayEvidence); + await this.put(proposalSeenKey, replayEvidence); + await this.put(executionSeenKey, replayEvidence); + await this.put(stateKey, { + ...state, + last_epoch: value.epoch, + cumulative_spent_wei: normalized.cumulative_spent_wei, + cumulative_provider_claimed_wei: normalized.provider_cumulative_claimed_wei, + cumulative_buyer_refund_wei: normalized.buyer_refund_wei, + last_root: value.root, + last_execution_tx: value.execution_tx, + updated_at: this.tx, + }); + const preparationError = await this.consumePayoutPreparations( + preparations, + recordKey + ); + if (preparationError) return preparationError; + await this.put(recordKey, record); + return { + ok: true, + op: 'targetedTapSettlement', + epoch: value.epoch, + rail: 'tap', + provider_paid_au: normalized.provider_paid_au, + provider_tap_wei: normalized.provider_tap_wei, + cumulative_spent_wei: normalized.cumulative_spent_wei, + root: value.root, + execution_tx: value.execution_tx, + idempotent: false, + }; + } + + async targetedTnkSettlement(value) { + const adminError = await this.requireAdmin(); + if (adminError) return adminError; + const normalized = this.normalizeTargetedTnkSettlementValue(value); + if (normalized instanceof Error) return normalized; + const settlementParams = await this.activeParamsAt( + value.at, + ['max_tnk_settlement_outputs'] + ); + if (value.outputs.length > settlementParams.max_tnk_settlement_outputs) { + return new Error('Targeted TNK settlement output count exceeds limit.'); + } + const outputs = normalized.outputs; + const transfers = value.msb_transfers.map((entry) => ( + this.normalizeMsbTransferEvidence( + entry, + 'Targeted TNK settlement MSB transfer evidence' + ) + )); + const transferError = transfers.find((entry) => entry instanceof Error); + if (transferError) return transferError; + if (stableJson(transfers) !== stableJson(value.msb_transfers)) { + return new Error('Targeted TNK settlement transfers must be canonical.'); + } + const payment = await this.canonicalTnkPaymentConfig(); + if (payment instanceof Error) return payment; + if (value.network !== payment.network || + value.treasury_from !== payment.treasury_address) { + return new Error('Targeted TNK settlement source does not match payment config.'); + } + for (const [index, output] of outputs.entries()) { + const transfer = transfers[index]; + if (transfer.network !== value.network || + transfer.from !== value.treasury_from || + transfer.to !== output.to || + transfer.amount_e18 !== output.tnk_e18) { + return new Error('Targeted TNK transfer does not match output.'); + } + } + const totals = this.targetedTnkSettlementTotals(outputs, value.rate_tnk_usd_au); + if (totals instanceof Error) return totals; + if (totals.provider_count !== value.provider_count || + this.compareAu(totals.provider_au, value.provider_au) !== 0 || + this.compareAu(totals.operator_fee_au, value.operator_fee_au) !== 0 || + this.compareAu(totals.gross_au, value.gross_au) !== 0 || + totals.tnk_e18 !== value.tnk_e18) { + return new Error('Targeted TNK settlement totals do not match outputs.'); + } + const transferRoot = await this.targetedTnkSettlementTransferRoot(outputs); + if (transferRoot !== value.transfer_root) { + return new Error('Targeted TNK settlement transfer root mismatch.'); + } + const record = { + type: 'targeted_tnk_settlement', + ...value, + outputs, + msb_transfers: transfers, + settled_by: this.address, + settled_by_role: 'admin', + }; + const recordKey = `settle/targeted/tnk/${value.epoch}`; + const existing = await this.get(recordKey); + if (existing) { + if (stableJson(existing) === stableJson(record)) { + return { + ok: true, + op: 'targetedTnkSettlement', + epoch: value.epoch, + rail: 'tnk', + idempotent: true, + msb_transfers: transfers, + }; + } + return new Error('Targeted TNK settlement already exists for epoch.'); + } + const applyAnchor = await this.requireEpochApplyAnchor( + value.epoch, + value.epoch_apply_hash, + 'Targeted TNK settlement' + ); + if (applyAnchor instanceof Error) return applyAnchor; + const preparations = await this.payoutPreparationsForSettlement( + value, + normalized, + 'tnk' + ); + if (preparations instanceof Error) return preparations; + for (const transfer of transfers) { + if ((await this.get(this.msbTransferSeenKey(transfer))) !== null) { + return new Error('Targeted TNK transfer was already consumed.'); + } + } + const rate = await this.guardianRequireHistoricalTnkRate(value, value.at); + if (rate instanceof Error) return rate; + const updates = await this.targetedPayoutSettlementUpdates( + outputs, + 'tnk', + value.epoch, + value.at, + transfers.map((entry) => entry.tx_hash) + ); + if (updates instanceof Error) return updates; + + const fee = await this.feeCumRecord('tnk'); + if (fee instanceof Error) return fee; + const feeError = this.guardianValidateFeeRecord(fee, 'tnk'); + if (feeError) return feeError; + const payableFee = this.safeSubAu(fee.cum_au, fee.swept_cum_au); + if (payableFee instanceof Error) return payableFee; + if (this.compareAu(payableFee, value.operator_fee_au) < 0) { + return new Error('Targeted TNK operator fee does not match fee state.'); + } + const operatorOutputs = outputs.filter((entry) => entry.role === 'operator_fee'); + if ((this.isZeroAu(value.operator_fee_au) && operatorOutputs.length !== 0) || + (this.compareAu(value.operator_fee_au, ZERO_AU) > 0 && + (operatorOutputs.length !== 1 || operatorOutputs[0].to !== value.operator_to))) { + return new Error('Targeted TNK operator output mismatch.'); + } + const sweptCumAu = this.safeAddAu(fee.swept_cum_au, value.operator_fee_au); + if (sweptCumAu instanceof Error) return sweptCumAu; + const operatorIndex = outputs.findIndex((entry) => entry.role === 'operator_fee'); + const nextFee = { + ...fee, + swept_cum_au: sweptCumAu, + updated_epoch: Math.max(fee.updated_epoch, value.epoch), + last_targeted_settlement_epoch: value.epoch, + last_targeted_settlement_transfer: operatorIndex >= 0 + ? transfers[operatorIndex].tx_hash + : null, + }; + const nextFeeError = this.guardianValidateFeeRecord(nextFee, 'tnk'); + if (nextFeeError) return nextFeeError; + for (const update of updates.liabilityUpdates) await this.put(update.key, update.value); + for (const earning of updates.earningUpdates) { + await this.put(this.earningKey(earning.provider, 'tnk'), earning); + } + for (const [index, transfer] of transfers.entries()) { + await this.put(this.msbTransferSeenKey(transfer), { + rail: 'tnk', + purpose: 'targeted_settlement', + epoch: value.epoch, + output_index: index, + transfer_root: value.transfer_root, + consumed_at: this.tx, + }); + } + await this.put(this.feeCumKey('tnk'), nextFee); + const preparationError = await this.consumePayoutPreparations( + preparations, + recordKey + ); + if (preparationError) return preparationError; + await this.put(recordKey, record); + return { + ok: true, + op: 'targetedTnkSettlement', + epoch: value.epoch, + rail: 'tnk', + idempotent: false, + provider_au: value.provider_au, + operator_fee_au: value.operator_fee_au, + gross_au: value.gross_au, + msb_transfers: transfers, + transfer_root: value.transfer_root, + }; + } + + async targetedFiatSettlement(value) { + const adminError = await this.requireAdmin(); + if (adminError) return adminError; + const normalized = this.normalizeTargetedFiatSettlementValue(value); + if (normalized instanceof Error) return normalized; + const settlementParams = await this.activeParamsAt( + value.at, + ['max_fiat_settlement_outputs'] + ); + if (value.outputs.length > settlementParams.max_fiat_settlement_outputs) { + return new Error('Targeted fiat settlement output count exceeds limit.'); + } + const outputs = normalized.outputs; + const payments = await this.get('payments/current'); + const admin = await this.get('admin'); + if (!payments || payments.set_by !== admin || payments.set_by_role !== 'admin' || + payments.denom !== PRICE_DENOMINATION || + payments.fiat?.processor !== 'stripe' || + payments.fiat?.integration_currency !== 'usd' || + payments.fiat?.adaptive_pricing !== true || + !Array.isArray(payments.fiat?.payout_currencies)) { + return new Error('Targeted fiat settlement does not match canonical payment configuration.'); + } + const transfers = value.stripe_transfers.map((entry) => ( + this.normalizeStripeTransferEvidence( + entry, + 'Targeted fiat settlement Stripe evidence' + ) + )); + const transferError = transfers.find((entry) => entry instanceof Error); + if (transferError) return transferError; + if (stableJson(transfers) !== stableJson(value.stripe_transfers)) { + return new Error('Targeted fiat settlement transfers must be canonical.'); + } + const expectedGroup = + `mayhem_fiat_epoch_${value.epoch}_${value.epoch_apply_hash.slice(0, 16)}`; + for (const [index, output] of outputs.entries()) { + const transfer = transfers[index]; + const expectedKind = output.role === 'provider' + ? 'stripe_transfer' + : 'platform_balance'; + if (transfer.kind !== expectedKind || + transfer.destination !== output.to || + transfer.source_currency !== output.source_currency || + transfer.source_amount_minor !== output.source_amount_minor || + (expectedKind === 'stripe_transfer' && + (transfer.destination_currency !== output.destination_currency || + transfer.destination_amount_minor !== output.destination_amount_minor || + transfer.fx_quote_id !== output.fx_quote_id || + transfer.fx_quote_hash !== output.fx_quote_hash || + transfer.transfer_group !== expectedGroup))) { + return new Error('Targeted fiat transfer does not match output.'); + } + if (output.source_currency !== value.source_currency) { + return new Error('Targeted fiat output source currency mismatch.'); + } + if (output.role === 'provider' && + !payments.fiat.payout_currencies.includes(output.destination_currency)) { + return new Error('Targeted fiat provider destination currency is not canonical.'); + } + } + const totals = this.targetedFiatSettlementTotals(outputs); + if (totals instanceof Error) return totals; + if (totals.provider_count !== value.provider_count || + this.compareAu(totals.provider_liability_au, value.provider_liability_au) !== 0 || + this.compareAu(totals.provider_paid_au, value.provider_paid_au) !== 0 || + this.compareAu(totals.operator_fee_liability_au, value.operator_fee_liability_au) !== 0 || + this.compareAu(totals.operator_fee_retained_au, value.operator_fee_retained_au) !== 0 || + this.compareAu(totals.gross_liability_au, value.gross_liability_au) !== 0 || + this.compareAu(totals.gross_paid_au, value.gross_paid_au) !== 0 || + this.compareAu(totals.rounding_au, value.rounding_au) !== 0 || + this.compareAu(totals.dust_au, value.dust_au) !== 0 || + totals.source_currency !== value.source_currency || + totals.source_amount_minor !== value.source_amount_minor || + stableJson(totals.destination_totals) !== stableJson(normalized.destination_totals)) { + return new Error('Targeted fiat settlement totals do not match outputs.'); + } + const transferRoot = await this.targetedFiatSettlementTransferRoot(outputs); + if (transferRoot !== value.transfer_root) { + return new Error('Targeted fiat settlement transfer root mismatch.'); + } + const record = { + type: 'targeted_fiat_settlement', + ...value, + outputs, + stripe_transfers: transfers, + settled_by: this.address, + settled_by_role: 'admin', + }; + const recordKey = `settle/targeted/fiat/${value.epoch}`; + const existing = await this.get(recordKey); + if (existing) { + if (stableJson(existing) === stableJson(record)) { + return { + ok: true, + op: 'targetedFiatSettlement', + epoch: value.epoch, + rail: 'fiat', + processor: 'stripe', + idempotent: true, + stripe_transfers: transfers, + }; + } + return new Error('Targeted fiat settlement already exists for epoch.'); + } + const applyAnchor = await this.requireEpochApplyAnchor( + value.epoch, + value.epoch_apply_hash, + 'Targeted fiat settlement' + ); + if (applyAnchor instanceof Error) return applyAnchor; + const preparations = await this.payoutPreparationsForSettlement( + value, + normalized, + 'fiat' + ); + if (preparations instanceof Error) return preparations; + for (const transfer of transfers) { + for (const seenKey of [ + this.stripeTransferSeenKey(transfer), + this.stripeFxQuoteSeenKey(transfer), + this.stripeDestinationPaymentSeenKey(transfer), + ]) { + if (seenKey && (await this.get(seenKey)) !== null) { + return new Error('Targeted Stripe transfer evidence was already consumed.'); + } + } + } + const updates = await this.targetedPayoutSettlementUpdates( + outputs, + 'fiat', + value.epoch, + value.at, + transfers.map((entry) => entry.ref) + ); + if (updates instanceof Error) return updates; + + const fee = await this.feeCumRecord('fiat'); + if (fee instanceof Error) return fee; + const feeError = this.guardianValidateFeeRecord(fee, 'fiat'); + if (feeError) return feeError; + const payableFee = this.safeSubAu(fee.cum_au, fee.swept_cum_au); + if (payableFee instanceof Error) return payableFee; + const operatorOutputs = outputs.filter((entry) => entry.role === 'operator_fee'); + const retainedFee = operatorOutputs[0]?.paid_au ?? ZERO_AU; + if ((operatorOutputs.length === 0 && + (!this.isZeroAu(value.operator_fee_liability_au) || + !this.isZeroAu(value.operator_fee_retained_au))) || + (operatorOutputs.length === 1 && + (operatorOutputs[0].to !== value.operator_to || + this.compareAu(operatorOutputs[0].liability_au, payableFee) > 0 || + this.compareAu(operatorOutputs[0].paid_au, value.operator_fee_retained_au) !== 0))) { + return new Error('Targeted fiat operator output mismatch.'); + } + const sweptCumAu = this.safeAddAu(fee.swept_cum_au, retainedFee); + if (sweptCumAu instanceof Error) return sweptCumAu; + const operatorIndex = outputs.findIndex((entry) => entry.role === 'operator_fee'); + const nextFee = { + ...fee, + swept_cum_au: sweptCumAu, + updated_epoch: Math.max(fee.updated_epoch, value.epoch), + last_targeted_settlement_epoch: value.epoch, + last_targeted_settlement_transfer: operatorIndex >= 0 + ? transfers[operatorIndex].ref + : null, + }; + const nextFeeError = this.guardianValidateFeeRecord(nextFee, 'fiat'); + if (nextFeeError) return nextFeeError; + for (const update of updates.liabilityUpdates) await this.put(update.key, update.value); + for (const earning of updates.earningUpdates) { + await this.put(this.earningKey(earning.provider, 'fiat'), earning); + } + for (const [index, transfer] of transfers.entries()) { + for (const seenKey of [ + this.stripeTransferSeenKey(transfer), + this.stripeFxQuoteSeenKey(transfer), + this.stripeDestinationPaymentSeenKey(transfer), + ]) { + if (!seenKey) continue; + await this.put(seenKey, { + rail: 'fiat', + purpose: 'targeted_settlement', + epoch: value.epoch, + output_index: index, + transfer_root: value.transfer_root, + consumed_at: this.tx, + }); + } + } + await this.put(this.feeCumKey('fiat'), nextFee); + const preparationError = await this.consumePayoutPreparations( + preparations, + recordKey + ); + if (preparationError) return preparationError; + await this.put(recordKey, record); + return { + ok: true, + op: 'targetedFiatSettlement', + epoch: value.epoch, + rail: 'fiat', + processor: 'stripe', + idempotent: false, + provider_liability_au: value.provider_liability_au, + provider_paid_au: value.provider_paid_au, + operator_fee_liability_au: value.operator_fee_liability_au, + operator_fee_retained_au: value.operator_fee_retained_au, + gross_liability_au: value.gross_liability_au, + gross_paid_au: value.gross_paid_au, + rounding_au: value.rounding_au, + dust_au: value.dust_au, + source_currency: value.source_currency, + source_amount_minor: value.source_amount_minor, + destination_totals: normalized.destination_totals, + stripe_transfers: transfers, + transfer_root: value.transfer_root, + }; + } + + normalizeTargetedTnkSettlementOutput(output) { + if (!this.isSafeKeyPart(output.to)) return new Error('Invalid TNK settlement output address.'); + const au = this.normalizeAu(output.au, 'TNK settlement output amount', { allowZero: false }); + if (au instanceof Error) { + return new Error('Invalid TNK settlement output amount.'); + } + const tnkE18 = this.parseTnkE18(output.tnk_e18); + if (tnkE18 instanceof Error) return tnkE18; + return output.role === 'provider' + ? { + role: 'provider', + provider: output.provider, + to: output.to, + au, + tnk_e18: output.tnk_e18, + } + : { + role: 'operator_fee', + to: output.to, + au, + tnk_e18: output.tnk_e18, + }; + } + + targetedTnkSettlementTotals(outputs, rateTnkUsdAu) { + let providerAu = ZERO_AU; + let operatorFeeAu = ZERO_AU; + let tnkE18 = 0n; + let providerCount = 0; + for (const output of outputs) { + const expectedTnkE18 = this.auToTnkE18Ceil(output.au, rateTnkUsdAu); + if (expectedTnkE18 instanceof Error) return expectedTnkE18; + if (output.tnk_e18 !== expectedTnkE18.toString()) { + return new Error('TNK settlement output amount does not match oracle rate.'); + } + const parsed = this.parseTnkE18(output.tnk_e18); + if (parsed instanceof Error) return parsed; + tnkE18 += parsed; + if (output.role === 'provider') { + providerCount += 1; + providerAu = this.safeAddAu(providerAu, output.au); + if (providerAu instanceof Error) return providerAu; + } else { + operatorFeeAu = this.safeAddAu(operatorFeeAu, output.au); + if (operatorFeeAu instanceof Error) return operatorFeeAu; + } + } + const grossAu = this.safeAddAu(providerAu, operatorFeeAu); + if (grossAu instanceof Error) return grossAu; + return { + provider_count: providerCount, + provider_au: providerAu, + operator_fee_au: operatorFeeAu, + gross_au: grossAu, + tnk_e18: tnkE18.toString(), + }; + } + + auToTnkE18Ceil(au, rateTnkUsdAu) { + const amount = this.parseAu(au, 'TNK settlement amount', { allowZero: false }); + if (amount instanceof Error) return new Error('Invalid TNK settlement amount.'); + const rate = this.parseAu(rateTnkUsdAu, 'TNK/USD atto-rate', { allowZero: false }); + if (rate instanceof Error) return new Error('Invalid TNK/USD atto-rate.'); + const numerator = amount * TNK_E18; + const denominator = rate; + // Payout conversion rounds up so the provider is never underpaid in TNK atomic units. + return (numerator + denominator - 1n) / denominator; + } + + async fiatDustSweep() { + const adminError = await this.requireAdmin(); + if (adminError) return adminError; + const shapeError = this.validateFiatDustSweepValue(this.value); + if (shapeError) return shapeError; + + const recordKey = `settle/fiat-dust/${this.value.provider}/${this.value.epoch}`; + const existing = await this.get(recordKey); + if (existing) { + return { + ok: true, + op: 'fiatDustSweep', + provider: existing.provider, + epoch: existing.epoch, + dust_au: existing.dust_au, + idempotent: true, + }; + } + + const applyState = await this.epochApplyStateRecord(); + if (applyState.updated_epoch !== this.value.epoch) { + return new Error('Fiat dust sweep epoch must equal the latest applied epoch.'); + } + const provider = await this.get(`prov/${this.value.provider}`); + if (!provider) return new Error('Provider not found.'); + if (!Array.isArray(provider.enclaves)) return new Error('Invalid provider enclave state.'); + if (provider.enclaves.length > 0) { + return new Error('Fiat dust sweep requires a provider with no active enclaves.'); + } + + const params = await this.activeParamsAt(this.value.at, [ + 'holdback_epochs', + 'challenge_epochs', + 'canary_probe_holdback_bps', + 'canary_probe_release_min_passes', + ]); + const earning = await this.earningRecord(this.value.provider, 'fiat'); + if (earning instanceof Error) return earning; + const earningError = this.guardianValidateEarningRecord(earning, this.value.provider, 'fiat'); + if (earningError) return earningError; + const probeGate = await this.probeGateForEarning(this.value.provider, earning, params); + if (probeGate instanceof Error) return probeGate; + const lockedEarningEpochs = this.providerLockedEarningEpochs(provider, params); + if (lockedEarningEpochs instanceof Error) return lockedEarningEpochs; + const disputeGate = await this.providerHasOpenDispute(this.value.provider); + if (disputeGate instanceof Error) return disputeGate; + const refreshed = this.refreshEarningHoldback( + earning, + this.value.epoch, + lockedEarningEpochs, + probeGate, + disputeGate + ); + if (refreshed instanceof Error) return refreshed; + if (!this.isZeroAu(refreshed.held_au)) { + return new Error('Fiat dust cannot be swept while provider earnings are held.'); + } + const unpaid = this.safeSubAu(refreshed.total_au, refreshed.paid_cum_au); + if (unpaid instanceof Error) return unpaid; + const unpaidValue = this.parseAu(unpaid, 'Fiat unpaid provider earnings'); + if (unpaidValue instanceof Error) return unpaidValue; + const dustValue = unpaidValue % USD_CENT_AU; + if (dustValue === 0n) return new Error('Provider has no fiat dust to sweep.'); + const dustAu = this.canonicalAu(dustValue); + const totalAu = this.safeSubAu(refreshed.total_au, dustAu); + if (totalAu instanceof Error) return totalAu; + const dustSweptCumAu = this.safeAddAu(refreshed.fiat_dust_swept_cum_au ?? ZERO_AU, dustAu); + if (dustSweptCumAu instanceof Error) return dustSweptCumAu; + const nextEarning = { + ...refreshed, + total_au: totalAu, + fiat_dust_swept_cum_au: dustSweptCumAu, + updated_epoch: Math.max(refreshed.updated_epoch, this.value.epoch), + updated_at: this.tx, + last_fiat_dust_sweep_epoch: this.value.epoch, + last_fiat_dust_sweep_at: this.tx, + }; + const nextEarningError = this.guardianValidateEarningRecord( + nextEarning, + this.value.provider, + 'fiat' + ); + if (nextEarningError) return nextEarningError; + + const fee = await this.feeCumRecord('fiat'); + if (fee instanceof Error) return fee; + const feeError = this.guardianValidateFeeRecord(fee, 'fiat'); + if (feeError) return feeError; + const cumAu = this.safeAddAu(fee.cum_au, dustAu); + if (cumAu instanceof Error) return cumAu; + const settledCumAu = this.safeAddAu(fee.settled_cum_au ?? fee.cum_au, dustAu); + if (settledCumAu instanceof Error) return settledCumAu; + const nextFee = { + ...fee, + cum_au: cumAu, + settled_cum_au: settledCumAu, + updated_epoch: Math.max(fee.updated_epoch, this.value.epoch), + updated_at: this.tx, + last_fiat_dust_sweep_at: this.tx, + }; + const nextFeeError = this.guardianValidateFeeRecord(nextFee, 'fiat'); + if (nextFeeError) return nextFeeError; + + const record = { + type: 'fiat_dust_sweep', + provider: this.value.provider, + epoch: this.value.epoch, + at: this.value.at, + dust_au: dustAu, + provider_total_before_au: refreshed.total_au, + provider_total_after_au: totalAu, + provider_paid_cum_au: refreshed.paid_cum_au, + destination: 'operator_fee', + swept_by: this.address, + swept_by_role: 'admin', + swept_at: this.tx, + }; + await this.put(this.earningKey(this.value.provider, 'fiat'), nextEarning); + await this.put(this.feeCumKey('fiat'), nextFee); + await this.put(recordKey, record); + return { + ok: true, + op: 'fiatDustSweep', + provider: this.value.provider, + epoch: this.value.epoch, + dust_au: dustAu, + idempotent: false, + }; + } + + validateFiatDustSweepValue(value) { + const shapeError = this.validateExactObjectKeys( + value, + ['op', 'provider', 'epoch', 'at'], + 'Fiat dust sweep' + ); + if (shapeError) return shapeError; + if (value.op !== 'fiat_dust_sweep') return new Error('Invalid fiat dust sweep op.'); + if (!this.isHexBytes(value.provider, 32) || value.provider !== value.provider.toLowerCase()) { + return new Error('Invalid fiat dust sweep provider.'); + } + if (!Number.isSafeInteger(value.epoch) || value.epoch < 1) { + return new Error('Invalid fiat dust sweep epoch.'); + } + if (!Number.isSafeInteger(value.at) || value.at < 0) { + return new Error('Invalid fiat dust sweep timestamp.'); + } + return null; + } + + normalizeTargetedFiatSettlementOutput(output) { + if (!this.isSafeKeyPart(output.to)) return new Error('Invalid fiat settlement output target.'); + const sourceCurrency = this.normalizeFiatCurrency(output.source_currency); + if (sourceCurrency instanceof Error || sourceCurrency !== output.source_currency) { + return new Error('Invalid fiat settlement source currency.'); + } + const sourceAmountMinor = this.normalizeFiatMinor(output.source_amount_minor); + if (sourceAmountMinor instanceof Error || sourceAmountMinor === '0') { + return new Error('Fiat settlement source amount must be positive.'); + } + const canonicalAu = {}; + for (const [field, allowZero] of [ + ['liability_au', false], + ['paid_au', false], + ['rounding_au', true], + ['dust_au', true], + ]) { + canonicalAu[field] = this.normalizeAu( + output[field], + `Fiat settlement output ${field}`, + { allowZero } + ); + if (canonicalAu[field] instanceof Error) { + return new Error(`Invalid fiat settlement output ${field}.`); + } + } + const paidPlusDust = this.safeAddAu(canonicalAu.paid_au, canonicalAu.dust_au); + if (paidPlusDust instanceof Error || + this.compareAu(paidPlusDust, canonicalAu.liability_au) !== 0 || + this.compareAu(canonicalAu.rounding_au, canonicalAu.dust_au) !== 0) { + return new Error('Fiat settlement output liability, paid amount, rounding, and dust do not balance.'); + } + if (output.role === 'operator_fee') return { + role: 'operator_fee', + to: output.to, + ...canonicalAu, + source_currency: sourceCurrency, + source_amount_minor: sourceAmountMinor, + }; + if (!this.isHexBytes(output.provider, 32) || + output.provider !== output.provider.toLowerCase()) { + return new Error('Invalid fiat settlement provider.'); + } + const destinationCurrency = this.normalizeFiatCurrency(output.destination_currency); + if (destinationCurrency instanceof Error || + destinationCurrency !== output.destination_currency) { + return new Error('Invalid fiat settlement destination currency.'); + } + const destinationAmountMinor = this.normalizeFiatMinor(output.destination_amount_minor); + if (destinationAmountMinor instanceof Error || destinationAmountMinor === '0') { + return new Error('Fiat settlement destination amount must be positive.'); + } + const destinationAmountMinMinor = this.normalizeFiatMinor( + output.destination_amount_min_minor + ); + const destinationAmountMaxMinor = this.normalizeFiatMinor( + output.destination_amount_max_minor + ); + if (destinationAmountMinMinor instanceof Error || + destinationAmountMaxMinor instanceof Error || + destinationAmountMinMinor === '0' || + BigInt(destinationAmountMaxMinor) < BigInt(destinationAmountMinMinor) || + BigInt(destinationAmountMinor) < BigInt(destinationAmountMinMinor) || + BigInt(destinationAmountMinor) > BigInt(destinationAmountMaxMinor)) { + return new Error( + 'Fiat settlement destination amount must be within its authorized range.' + ); + } + const hasFxQuoteId = hasOwn(output, 'fx_quote_id'); + const hasFxQuoteHash = hasOwn(output, 'fx_quote_hash'); + if (hasFxQuoteId !== hasFxQuoteHash) { + return new Error('Fiat settlement FX quote identity must be present or absent as a pair.'); + } + const requiresUsdValuationQuote = + sourceCurrency !== 'usd' || destinationCurrency !== 'usd'; + if (requiresUsdValuationQuote && + (!hasFxQuoteId || + !/^fxq_[A-Za-z0-9._-]+$/.test(String(output.fx_quote_id || '')) || + !this.isHexBytes(output.fx_quote_hash, 32) || + output.fx_quote_hash !== output.fx_quote_hash.toLowerCase())) { + return new Error('Fiat settlement FX quote identity is required and invalid.'); + } + if (!requiresUsdValuationQuote && hasFxQuoteId && + (output.fx_quote_id !== null || output.fx_quote_hash !== null)) { + return new Error('Direct USD fiat settlement must not include an FX quote identity.'); + } + const normalized = { + role: 'provider', + provider: output.provider, + to: output.to, + ...canonicalAu, + source_currency: sourceCurrency, + source_amount_minor: sourceAmountMinor, + destination_currency: destinationCurrency, + destination_amount_min_minor: destinationAmountMinMinor, + destination_amount_max_minor: destinationAmountMaxMinor, + destination_amount_minor: destinationAmountMinor, + }; + if (hasFxQuoteId) { + normalized.fx_quote_id = output.fx_quote_id; + normalized.fx_quote_hash = output.fx_quote_hash; + } + return normalized; + } + + targetedFiatSettlementTotals(outputs) { + let providerLiabilityAu = ZERO_AU; + let providerPaidAu = ZERO_AU; + let operatorFeeLiabilityAu = ZERO_AU; + let operatorFeeRetainedAu = ZERO_AU; + let roundingAu = ZERO_AU; + let dustAu = ZERO_AU; + let sourceAmountMinor = 0n; + let sourceCurrency = null; + let providerCount = 0; + const destinationTotals = new Map(); + for (const output of outputs) { + if (sourceCurrency === null) sourceCurrency = output.source_currency; + if (sourceCurrency !== output.source_currency) { + return new Error('Targeted fiat outputs must use one platform source currency.'); + } + sourceAmountMinor += BigInt(output.source_amount_minor); + roundingAu = this.safeAddAu(roundingAu, output.rounding_au); + if (roundingAu instanceof Error) return roundingAu; + dustAu = this.safeAddAu(dustAu, output.dust_au); + if (dustAu instanceof Error) return dustAu; + if (output.role === 'provider') { + providerCount += 1; + providerLiabilityAu = this.safeAddAu(providerLiabilityAu, output.liability_au); + if (providerLiabilityAu instanceof Error) return providerLiabilityAu; + providerPaidAu = this.safeAddAu(providerPaidAu, output.paid_au); + if (providerPaidAu instanceof Error) return providerPaidAu; + destinationTotals.set( + output.destination_currency, + (destinationTotals.get(output.destination_currency) ?? 0n) + + BigInt(output.destination_amount_minor) + ); + } else { + operatorFeeLiabilityAu = this.safeAddAu( + operatorFeeLiabilityAu, + output.liability_au + ); + if (operatorFeeLiabilityAu instanceof Error) return operatorFeeLiabilityAu; + operatorFeeRetainedAu = this.safeAddAu(operatorFeeRetainedAu, output.paid_au); + if (operatorFeeRetainedAu instanceof Error) return operatorFeeRetainedAu; + } + } + const grossLiabilityAu = this.safeAddAu(providerLiabilityAu, operatorFeeLiabilityAu); + if (grossLiabilityAu instanceof Error) return grossLiabilityAu; + const grossPaidAu = this.safeAddAu(providerPaidAu, operatorFeeRetainedAu); + if (grossPaidAu instanceof Error) return grossPaidAu; + return { + provider_count: providerCount, + provider_liability_au: providerLiabilityAu, + provider_paid_au: providerPaidAu, + operator_fee_liability_au: operatorFeeLiabilityAu, + operator_fee_retained_au: operatorFeeRetainedAu, + gross_liability_au: grossLiabilityAu, + gross_paid_au: grossPaidAu, + rounding_au: roundingAu, + dust_au: dustAu, + source_currency: sourceCurrency, + source_amount_minor: sourceAmountMinor.toString(), + destination_totals: [...destinationTotals] + .sort(([left], [right]) => compareCodepoint(left, right)) + .map(([currency, amount]) => ({ currency, amount_minor: amount.toString() })), + }; + } + + normalizeFiatDestinationTotals(value) { + const totals = []; + const seen = new Set(); + for (const entry of value) { + const shapeError = this.validateExactObjectKeys( + entry, + ['currency', 'amount_minor'], + 'targeted fiat destination total' + ); + if (shapeError) return shapeError; + const currency = this.normalizeFiatCurrency(entry.currency); + const amountMinor = this.normalizeFiatMinor(entry.amount_minor); + if (currency instanceof Error || currency !== entry.currency || + amountMinor instanceof Error || amountMinor === '0' || + amountMinor !== entry.amount_minor || + seen.has(currency)) { + return new Error('Invalid targeted fiat destination total.'); + } + seen.add(currency); + totals.push({ currency, amount_minor: amountMinor }); + } + totals.sort((left, right) => compareCodepoint(left.currency, right.currency)); + if (stableJson(totals) !== stableJson(value)) { + return new Error('Targeted fiat destination totals must be canonical.'); + } + return totals; + } + + normalizeFiatMinor(value) { + if (typeof value !== 'string' || !/^(0|[1-9][0-9]*)$/.test(value)) { + return new Error('Fiat minor amount must be a canonical decimal string.'); + } + const parsed = BigInt(value); + if (parsed <= 0n) return new Error('Fiat minor amount must be positive.'); + return parsed.toString(); + } + + async depositTnk() { + const consentError = await this.requireConsent(); + if (consentError) return consentError; + if (!this.isSafeKeyPart(this.value.memo_hash)) return new Error('Invalid deposit memo hash.'); + if (!this.isSafeKeyPart(this.value.treasury_address)) return new Error('Invalid TNK treasury address.'); + const tnkE18 = this.parseTnkE18(this.value.tnk_e18); + if (tnkE18 instanceof Error) return tnkE18; + const quotedAu = this.normalizeAu(this.value.quoted_au, 'TNK quoted credit', { allowZero: false }); + if (quotedAu instanceof Error) return quotedAu; + const quotedRate = this.normalizeAu(this.value.rate_tnk_usd_au, 'TNK quoted rate', { allowZero: false }); + if (quotedRate instanceof Error) return quotedRate; + + const payment = await this.canonicalTnkPaymentConfig(); + if (payment instanceof Error) return payment; + if (this.value.treasury_address !== payment.treasury_address) { + return new Error('TNK deposit intent treasury does not match canonical payment config.'); + } + const rateLock = await this.guardianAcceptTnkDepositIntentRate(this.value); + if (rateLock instanceof Error) return rateLock; + const msbFrom = this.msbAddressForPublicKey(this.address, payment.network); + if (msbFrom instanceof Error) return msbFrom; + + const key = `dep/pending/${this.value.memo_hash}`; + if ((await this.get(key)) !== null) return new Error('TNK deposit memo already pending.'); + if ((await this.get(`dep/tnk-credited/${this.value.memo_hash}`)) !== null) { + return new Error('TNK deposit memo already credited.'); + } + + const record = { + memo_hash: this.value.memo_hash, + user: this.address, + status: 'pending', + requested_at: this.tx, + msb_network: payment.network, + msb_from: msbFrom, + treasury_address: this.value.treasury_address, + tnk_e18: this.value.tnk_e18, + quoted_au: quotedAu, + rate_tnk_usd_au: quotedRate, + rate_source: rateLock.source, + rate_ts: rateLock.ts, + rate_record_key: rateLock.updated_at, + }; + await this.put(key, record); + console.log('mayhem depositTnk', record); + return { + ok: true, + op: 'depositTnk', + memo_hash: this.value.memo_hash, + user: this.address, + }; + } + + async tnkDeposit() { + const adminError = await this.requireAdmin(); + if (adminError) return adminError; + const shapeError = this.validateExactObjectKeys( + this.value, + ['op', 'memo_hash', 'msb_transfer', 'epoch', 'at'], + 'TNK deposit credit' + ); + if (shapeError) return shapeError; + if (this.value.op !== 'tnk_deposit') return new Error('Invalid TNK deposit credit op.'); + if (!this.isSafeKeyPart(this.value.memo_hash)) return new Error('Invalid deposit memo hash.'); + if (!Number.isSafeInteger(this.value.epoch) || this.value.epoch < 1) { + return new Error('Invalid TNK deposit epoch.'); + } + if (!Number.isSafeInteger(this.value.at) || this.value.at < 0) { + return new Error('Invalid TNK deposit timestamp.'); + } + + const transfer = this.normalizeMsbTransferEvidence( + this.value.msb_transfer, + 'TNK deposit MSB transfer evidence' + ); + if (transfer instanceof Error) return transfer; + const creditedKey = `dep/tnk-credited/${this.value.memo_hash}`; + const existingCredit = await this.get(creditedKey); + if (existingCredit) { + if (stableJson(existingCredit.msb_transfer) !== stableJson(transfer)) { + return new Error('TNK deposit memo already credited by a different MSB transfer.'); + } + return { + ok: true, + op: 'tnkDeposit', + who: existingCredit.user, + au: existingCredit.au, + epoch: existingCredit.epoch, + deposit_root: existingCredit.deposit_root, + rate_ts: existingCredit.rate_ts, + msb_tx_hash: transfer.tx_hash, + idempotent: true, + }; + } + + const pendingKey = `dep/pending/${this.value.memo_hash}`; + const pending = await this.get(pendingKey); + if (!pending || pending.status !== 'pending') return new Error('Pending TNK deposit intent not found.'); + + const payment = await this.canonicalTnkPaymentConfig(); + if (payment instanceof Error) return payment; + if ( + pending.msb_network !== payment.network || + pending.treasury_address !== payment.treasury_address + ) { + return new Error('Pending TNK deposit no longer matches canonical payment config.'); + } + if ( + transfer.network !== payment.network || + transfer.from !== pending.msb_from || + transfer.to !== payment.treasury_address + ) { + return new Error('TNK deposit MSB transfer identity does not match pending intent.'); + } + const transferSeenKey = this.msbTransferSeenKey(transfer); + if ((await this.get(transferSeenKey)) !== null) { + return new Error('MSB transfer already consumed by Mayhem.'); + } + + const tnkE18 = this.parseTnkE18(transfer.amount_e18); + if (tnkE18 instanceof Error) return tnkE18; + const pendingTnkE18 = this.parseTnkE18(pending.tnk_e18); + if (pendingTnkE18 instanceof Error) return pendingTnkE18; + if (pendingTnkE18 !== tnkE18) return new Error('TNK deposit amount does not match pending intent.'); + const rate = await this.guardianRequireTnkDepositRateLock(pending, this.value.at); + if (rate instanceof Error) return rate; + const au = this.tnkE18ToAu(tnkE18, pending.rate_tnk_usd_au); + if (au instanceof Error) return au; + if (this.isZeroAu(au)) return new Error('TNK deposit converts to zero au.'); + if (this.compareAu(pending.quoted_au, au) !== 0) { + return new Error('TNK deposit credit does not match pending intent.'); + } + + const ledgerRail = 'tnk'; + const balance = await this.balanceRecord(pending.user, ledgerRail); + if (balance instanceof Error) return balance; + const nextAu = this.safeAddAu(balance.au, au); + if (nextAu instanceof Error) return nextAu; + const leaf = await this.depositLeafHash({ + rail: 'tnk', + memo_hash: this.value.memo_hash, + user_hash: await this.opaqueHash('deposit-user', pending.user), + au, + msb_transfer: transfer, + rate_ts: rate.ts, + treasury_address_hash: await this.opaqueHash('deposit-treasury', pending.treasury_address), + quoted_au: pending.quoted_au, + rate_tnk_usd_au: pending.rate_tnk_usd_au, + }); + const depositRoot = await this.nextDepositRoot({ + epoch: this.value.epoch, + leaf, + au, + at: this.value.at, + }); + if (depositRoot instanceof Error) return depositRoot; + + const record = { + ...balance, + rail: ledgerRail, + au: nextAu, + updated_at: this.tx, + last_deposit_rail: ledgerRail, + last_deposit_rate_ts: rate.ts, + }; + await this.put(this.balanceKey(pending.user, ledgerRail), record); + await this.put(`ev/dep/${this.value.epoch}`, depositRoot); + await this.put(creditedKey, { + rail: 'tnk', + memo_hash: this.value.memo_hash, + user: pending.user, + au, + epoch: this.value.epoch, + rate_ts: rate.ts, + rate_source: rate.source, + rate_tnk_usd_au: pending.rate_tnk_usd_au, + deposit_root: depositRoot.merkle_root, + msb_transfer: transfer, + credited_at: this.tx, + }); + await this.put(transferSeenKey, { + rail: 'tnk', + purpose: 'deposit', + memo_hash: this.value.memo_hash, + user: pending.user, + amount_e18: transfer.amount_e18, + consumed_at: this.tx, + }); + await this.del(pendingKey); + console.log('mayhem tnkDeposit', { + who: pending.user, + au, + tnk_e18: transfer.amount_e18, + msb_tx_hash: transfer.tx_hash, + rate_ts: rate.ts, + rate_tnk_usd_au: pending.rate_tnk_usd_au, + epoch: this.value.epoch, + }); + return { + ok: true, + op: 'tnkDeposit', + who: pending.user, + au, + epoch: this.value.epoch, + deposit_root: depositRoot.merkle_root, + rate_ts: rate.ts, + msb_tx_hash: transfer.tx_hash, + idempotent: false, + }; + } + + normalizeTapAccountBinding(value) { + const shapeError = this.validateExactObjectKeys( + value, + [ + 'op', + 'user', + 'ethereum_address', + 'chain_id', + 'pool_address', + 'user_sig', + 'ethereum_sig', + ], + 'TAP account binding' + ); + if (shapeError) return shapeError; + if (value.op !== 'tap_account_bind') return new Error('Invalid TAP account binding op.'); + if (!this.isHexBytes(value.user, 32)) return new Error('Invalid TAP account user.'); + if (!this.isEthHexBytes(value.ethereum_address, 20)) { + return new Error('Invalid TAP Ethereum account.'); + } + if (!Number.isSafeInteger(value.chain_id) || value.chain_id < 1) { + return new Error('Invalid TAP account chain id.'); + } + if (!this.isEthHexBytes(value.pool_address, 20)) { + return new Error('Invalid TAP account pool address.'); + } + if (!this.isHexBytes(value.user_sig, 64)) { + return new Error('Invalid TAP account user signature.'); + } + if (!this.isEthHexBytes(value.ethereum_sig, 65)) { + return new Error('Invalid TAP account Ethereum signature.'); + } + return { + op: 'tap_account_bind', + user: value.user.toLowerCase(), + ethereum_address: value.ethereum_address.toLowerCase(), + chain_id: value.chain_id, + pool_address: value.pool_address.toLowerCase(), + user_sig: value.user_sig.toLowerCase(), + ethereum_sig: value.ethereum_sig.toLowerCase(), + }; + } + + async tapDeposit() { + const adminError = await this.requireAdmin(); + if (adminError) return adminError; + const shapeError = this.validateTapDepositValue(this.value); + if (shapeError) return shapeError; + const poolError = await this.requireCanonicalTapPool( + this.value.chain_id, + this.value.pool_address + ); + if (poolError) return poolError; + + const ethereumAddress = this.value.who.toLowerCase(); + const ethTxHash = this.value.eth_tx_hash.toLowerCase(); + const blockHash = this.value.block_hash.toLowerCase(); + const poolAddress = this.value.pool_address.toLowerCase(); + const eventSignature = this.value.event_signature.toLowerCase(); + const seenKey = `dep/tap/${this.tapDepositIdentity(this.value)}`; + const existing = await this.get(seenKey); + if (existing !== null) { + return { + ok: true, + op: 'tapDeposit', + duplicate: true, + who: existing.who, + ethereum_address: existing.ethereum_address ?? this.value.who.toLowerCase(), + au: ZERO_AU, + credited_au: existing.au ?? null, + epoch: existing.epoch ?? this.value.epoch, + deposit_root: (await this.get(`ev/dep/${existing.epoch ?? this.value.epoch}`))?.merkle_root ?? null, + }; + } + + const tapWei = this.parseTapWei(this.value.tap_wei); + if (tapWei instanceof Error) return tapWei; + const rate = await this.guardianRequireFreshTapRate(this.value.at); + if (rate instanceof Error) return rate; + const au = this.tapWeiToAu(tapWei, rate.tap_usd_au); + if (au instanceof Error) return au; + if (this.isZeroAu(au)) return new Error('TAP deposit converts to zero au.'); + + const binding = await this.get( + this.tapAccountAddressKey(ethereumAddress, this.value.chain_id, poolAddress) + ); + if (!binding || binding.status !== 'active') { + return new Error('TAP account binding required before deposit credit.'); + } + if (!this.isHexBytes(binding.user, 32)) { + return new Error('Invalid TAP account binding user.'); + } + if ( + binding.ethereum_address !== ethereumAddress || + binding.chain_id !== this.value.chain_id || + binding.pool_address !== poolAddress + ) { + return new Error('TAP account binding does not match deposit evidence.'); + } + const who = binding.user; + + const ledgerRail = 'tap'; + const balance = await this.balanceRecord(who, ledgerRail); + if (balance instanceof Error) return balance; + const balanceError = this.guardianValidateBalanceRecord(balance, who, ledgerRail); + if (balanceError) return balanceError; + const nextAu = this.safeAddAu(balance.au, au); + if (nextAu instanceof Error) return nextAu; + const leaf = await this.depositLeafHash({ + rail: 'tap', + user_hash: await this.opaqueHash('deposit-user', who), + ethereum_address_hash: await this.opaqueHash('deposit-ethereum-account', ethereumAddress), + au, + tap_wei: this.value.tap_wei, + tap_usd_au: rate.tap_usd_au, + rate_ts: rate.ts, + rate_source: rate.source, + eth_tx_hash: ethTxHash, + log_index: this.value.log_index, + block_number: this.value.block_number, + block_hash: blockHash, + chain_id: this.value.chain_id, + pool_address_hash: await this.opaqueHash('deposit-pool', poolAddress), + finalized_block_number: this.value.finalized_block_number, + confirmation_depth: this.value.confirmation_depth, + confirmation_policy: this.value.confirmation_policy, + event_signature: eventSignature, + watcher_id: this.value.watcher_id, + }); + const depositRoot = await this.nextDepositRoot({ + epoch: this.value.epoch, + leaf, + au, + at: this.value.at, + }); + if (depositRoot instanceof Error) return depositRoot; + + const seen = { + rail: 'tap', + who, + ethereum_address: ethereumAddress, + tap_wei: this.value.tap_wei, + tap_usd_au: rate.tap_usd_au, + rate_ts: rate.ts, + rate_source: rate.source, + au, + eth_tx_hash: ethTxHash, + log_index: this.value.log_index, + block_number: this.value.block_number, + block_hash: blockHash, + pool_address: poolAddress, + chain_id: this.value.chain_id, + finalized_block_number: this.value.finalized_block_number, + confirmation_depth: this.value.confirmation_depth, + confirmation_policy: this.value.confirmation_policy, + event_signature: eventSignature, + watcher_id: this.value.watcher_id, + epoch: this.value.epoch, + at: this.value.at, + credited_at: this.tx, + credited_by: this.address, + credited_by_role: 'admin', + }; + const record = { + ...balance, + user: who, + rail: ledgerRail, + au: nextAu, + updated_epoch: Math.max(balance.updated_epoch, this.value.epoch), + updated_at: this.tx, + last_deposit_rail: 'tap', + last_deposit_rate_ts: rate.ts, + last_deposit_rate_source: rate.source, + last_deposit_tap_usd_au: rate.tap_usd_au, + }; + await this.put(seenKey, seen); + await this.put(this.balanceKey(who, ledgerRail), record); + await this.put(`ev/dep/${this.value.epoch}`, depositRoot); + console.log('mayhem tapDeposit', { + who, + ethereum_address: ethereumAddress, + au, + tap_wei: this.value.tap_wei, + tap_usd_au: rate.tap_usd_au, + rate_ts: rate.ts, + eth_tx_hash: ethTxHash, + log_index: this.value.log_index, + epoch: this.value.epoch, + }); + return { + ok: true, + op: 'tapDeposit', + duplicate: false, + who, + ethereum_address: ethereumAddress, + au, + epoch: this.value.epoch, + deposit_root: depositRoot.merkle_root, + eth_tx_hash: ethTxHash, + log_index: this.value.log_index, + rate_ts: rate.ts, + }; + } + + validateTapDepositValue(value) { + const shapeError = this.validateExactObjectKeys( + value, + [ + 'op', + 'who', + 'tap_wei', + 'eth_tx_hash', + 'log_index', + 'block_number', + 'block_hash', + 'pool_address', + 'chain_id', + 'finalized_block_number', + 'confirmation_depth', + 'confirmation_policy', + 'event_signature', + 'watcher_id', + 'epoch', + 'at', + ], + 'TAP deposit' + ); + if (shapeError) return shapeError; + if (value.op !== 'tap_deposit') return new Error('Invalid TAP deposit op.'); + if (!this.isSafeKeyPart(value.who)) return new Error('Invalid TAP deposit recipient.'); + if (!this.isEthHexBytes(value.eth_tx_hash, 32)) return new Error('Invalid Ethereum tx hash.'); + if (!Number.isSafeInteger(value.log_index) || value.log_index < 0) { + return new Error('Invalid TAP deposit log index.'); + } + if (!Number.isSafeInteger(value.block_number) || value.block_number < 0) { + return new Error('Invalid TAP deposit block number.'); + } + if (!this.isEthHexBytes(value.block_hash, 32)) return new Error('Invalid TAP deposit block hash.'); + if (!this.isEthHexBytes(value.pool_address, 20)) return new Error('Invalid TAP pool address.'); + if (!Number.isSafeInteger(value.chain_id) || value.chain_id < 1) return new Error('Invalid TAP chain id.'); + if (!Number.isSafeInteger(value.finalized_block_number) || value.finalized_block_number < value.block_number) { + return new Error('Invalid TAP finalized block number.'); + } + if (!Number.isSafeInteger(value.confirmation_depth) || value.confirmation_depth < 0) { + return new Error('Invalid TAP confirmation depth.'); + } + if (value.confirmation_depth !== value.finalized_block_number - value.block_number) { + return new Error('TAP confirmation depth does not match finalized block.'); + } + if (!this.isSafeKeyPart(value.confirmation_policy)) return new Error('Invalid TAP confirmation policy.'); + if (value.confirmation_depth < MIN_TAP_CONFIRMATION_DEPTH) { + return new Error(`TAP confirmation depth below minimum ${MIN_TAP_CONFIRMATION_DEPTH}.`); + } + if (value.chain_id === 1 && value.confirmation_policy !== 'finalized-tag') { + return new Error('Ethereum mainnet TAP deposits require finalized-tag policy.'); + } + if (value.confirmation_policy !== 'finalized-tag') { + if (value.confirmation_policy !== `depth-${value.confirmation_depth}`) { + return new Error('TAP confirmation policy must match the confirmed depth or use finalized-tag.'); + } + } + if (!this.isEthHexBytes(value.event_signature, 32)) return new Error('Invalid TAP event signature.'); + if (value.event_signature.toLowerCase() !== TAP_DEPOSIT_EVENT_SIGNATURE) { + return new Error('TAP deposit event signature mismatch.'); + } + if (value.watcher_id !== TAP_DEPOSIT_WATCHER_ID) return new Error('Invalid TAP deposit watcher id.'); + if (!Number.isSafeInteger(value.epoch) || value.epoch < 1) return new Error('Invalid TAP deposit epoch.'); + if (!Number.isSafeInteger(value.at) || value.at < 0) return new Error('Invalid TAP deposit timestamp.'); + const tapWei = this.parseTapWei(value.tap_wei); + if (tapWei instanceof Error) return tapWei; + return null; + } + + async tapDepositReversal() { + const adminError = await this.requireAdmin(); + if (adminError) return adminError; + const shapeError = this.validateTapDepositReversalValue(this.value); + if (shapeError) return shapeError; + const poolError = await this.requireCanonicalTapPool( + this.value.chain_id, + this.value.pool_address + ); + if (poolError) return poolError; + + const depositSeenKey = `dep/tap/${this.tapDepositIdentity(this.value)}`; + const depositSeen = await this.get(depositSeenKey); + if (depositSeen === null) return new Error('TAP deposit event not found.'); + if (depositSeen.block_number !== this.value.block_number) { + return new Error('TAP reversal block number does not match credited event.'); + } + const reversalSeenKey = `${depositSeenKey}/reversal`; + const existing = await this.get(reversalSeenKey); + if (existing !== null) { + return { + ok: true, + op: 'tapDepositReversal', + duplicate: true, + who: existing.who, + au: ZERO_AU, + reversed_au: existing.au, + clawback_au: ZERO_AU, + credited_clawback_au: existing.clawback_au, + network_absorbed_au: ZERO_AU, + credited_network_absorbed_au: existing.network_absorbed_au, + frozen: existing.frozen, + epoch: existing.epoch, + }; + } + if (depositSeen.reversed === true) return new Error('TAP deposit is already reversed.'); + + const who = depositSeen.who; + const au = this.normalizeAu(depositSeen.au, 'credited TAP deposit amount', { allowZero: false }); + if (au instanceof Error) return au; + const balance = await this.balanceRecord(who, 'tap'); + if (balance instanceof Error) return balance; + const balanceError = this.guardianValidateBalanceRecord(balance, who, 'tap'); + if (balanceError) return balanceError; + const clawbackAu = this.compareAu(balance.au, au) < 0 ? balance.au : au; + const networkAbsorbedAu = this.safeSubAu(au, clawbackAu); + if (networkAbsorbedAu instanceof Error) return networkAbsorbedAu; + const nextAu = this.safeSubAu(balance.au, clawbackAu); + if (nextAu instanceof Error) return nextAu; + const frozen = !this.isZeroAu(networkAbsorbedAu); + + const leaf = await this.depositLeafHash({ + rail: 'tap', + user_hash: await this.opaqueHash('deposit-user', who), + ethereum_address_hash: await this.opaqueHash( + 'deposit-ethereum-account', + depositSeen.ethereum_address + ), + au, + clawback_au: clawbackAu, + network_absorbed_au: networkAbsorbedAu, + chain_id: this.value.chain_id, + pool_address_hash: await this.opaqueHash('deposit-pool', this.value.pool_address), + eth_tx_hash: this.value.eth_tx_hash, + log_index: this.value.log_index, + block_number: this.value.block_number, + block_hash: this.value.block_hash, + reconciliation_from_block: this.value.reconciliation_from_block, + reconciliation_to_block: this.value.reconciliation_to_block, + finalized_block_number: this.value.finalized_block_number, + confirmation_policy: this.value.confirmation_policy, + watcher_id: this.value.watcher_id, + reason: this.value.reason, + reversed: true, + }); + const depositRoot = await this.nextDepositReversalRoot({ + epoch: this.value.epoch, + leaf, + disputedAu: au, + clawbackAu, + absorbedAu: networkAbsorbedAu, + at: this.value.at, + }); + if (depositRoot instanceof Error) return depositRoot; + + const reversalSeen = { + rail: 'tap', + who, + ethereum_address: depositSeen.ethereum_address, + au, + clawback_au: clawbackAu, + network_absorbed_au: networkAbsorbedAu, + chain_id: this.value.chain_id, + pool_address: this.value.pool_address.toLowerCase(), + eth_tx_hash: this.value.eth_tx_hash.toLowerCase(), + log_index: this.value.log_index, + block_number: this.value.block_number, + block_hash: this.value.block_hash.toLowerCase(), + reconciliation_from_block: this.value.reconciliation_from_block, + reconciliation_to_block: this.value.reconciliation_to_block, + finalized_block_number: this.value.finalized_block_number, + confirmation_policy: this.value.confirmation_policy, + watcher_id: this.value.watcher_id, + reason: this.value.reason, + frozen, + epoch: this.value.epoch, + at: this.value.at, + reversed_at: this.tx, + reversed_by: this.address, + reversed_by_role: 'admin', + }; + let freezeRecord = null; + if (frozen) { + const existingFrozen = await this.get(`frozen/${who}`); + const disputedAuCum = this.safeAddAu(existingFrozen?.disputed_au_cum ?? ZERO_AU, au); + if (disputedAuCum instanceof Error) return disputedAuCum; + const clawbackAuCum = this.safeAddAu(existingFrozen?.clawback_au_cum ?? ZERO_AU, clawbackAu); + if (clawbackAuCum instanceof Error) return clawbackAuCum; + const absorbedAuCum = this.safeAddAu( + existingFrozen?.network_absorbed_au_cum ?? ZERO_AU, + networkAbsorbedAu + ); + if (absorbedAuCum instanceof Error) return absorbedAuCum; + freezeRecord = { + user: who, + status: 'frozen', + reason: 'tap_deposit_reorg_shortfall', + rail: 'tap', + first_frozen_at: existingFrozen?.first_frozen_at ?? this.tx, + first_frozen_at_seconds: existingFrozen?.first_frozen_at_seconds ?? this.value.at, + updated_at: this.tx, + updated_at_seconds: this.value.at, + updated_epoch: Math.max(existingFrozen?.updated_epoch ?? 0, this.value.epoch), + dispute_count: (existingFrozen?.dispute_count ?? 0) + 1, + disputed_au_cum: disputedAuCum, + clawback_au_cum: clawbackAuCum, + network_absorbed_au_cum: absorbedAuCum, + last_tap_deposit_identity: this.tapDepositIdentity(this.value), + }; + } + + await this.put(this.balanceKey(who, 'tap'), { + ...balance, + au: nextAu, + updated_epoch: Math.max(balance.updated_epoch, this.value.epoch), + updated_at: this.tx, + last_deposit_reversal_rail: 'tap', + last_deposit_reversal_at: this.tx, + }); + await this.put(depositSeenKey, { + ...depositSeen, + reversed: true, + reversed_au: au, + clawback_au: clawbackAu, + network_absorbed_au: networkAbsorbedAu, + reversed_at: this.tx, + reversed_at_seconds: this.value.at, + reversed_epoch: this.value.epoch, + }); + await this.put(reversalSeenKey, reversalSeen); + if (freezeRecord) await this.put(`frozen/${who}`, freezeRecord); + await this.put(`ev/dep/${this.value.epoch}`, depositRoot); + return { + ok: true, + op: 'tapDepositReversal', + duplicate: false, + who, + au, + clawback_au: clawbackAu, + network_absorbed_au: networkAbsorbedAu, + frozen, + epoch: this.value.epoch, + deposit_root: depositRoot.merkle_root, + }; + } + + validateTapDepositReversalValue(value) { + const shapeError = this.validateExactObjectKeys( + value, + [ + 'op', + 'chain_id', + 'pool_address', + 'eth_tx_hash', + 'log_index', + 'block_number', + 'block_hash', + 'reconciliation_from_block', + 'reconciliation_to_block', + 'finalized_block_number', + 'confirmation_policy', + 'watcher_id', + 'reason', + 'epoch', + 'at', + ], + 'TAP deposit reversal' + ); + if (shapeError) return shapeError; + if (value.op !== 'tap_deposit_reversal') return new Error('Invalid TAP deposit reversal op.'); + const identityError = this.validateTapDepositIdentity(value); + if (identityError) return identityError; + if (!Number.isSafeInteger(value.block_number) || value.block_number < 0) { + return new Error('Invalid TAP reversal block number.'); + } + if (!Number.isSafeInteger(value.reconciliation_from_block) + || value.reconciliation_from_block > value.block_number) { + return new Error('Invalid TAP reversal reconciliation start.'); + } + if (!Number.isSafeInteger(value.reconciliation_to_block) + || value.reconciliation_to_block < value.block_number) { + return new Error('Invalid TAP reversal reconciliation end.'); + } + if (!Number.isSafeInteger(value.finalized_block_number) + || value.finalized_block_number - value.reconciliation_to_block < MIN_TAP_CONFIRMATION_DEPTH) { + return new Error('TAP reversal is not sufficiently behind the finalized reference.'); + } + if (value.confirmation_policy !== 'finalized-tag') { + return new Error('TAP reversal requires finalized-tag policy.'); + } + if (value.watcher_id !== TAP_DEPOSIT_WATCHER_ID) return new Error('TAP watcher id mismatch.'); + if (value.reason !== 'canonical_event_missing') return new Error('Invalid TAP reversal reason.'); + if (!Number.isSafeInteger(value.epoch) || value.epoch < 1) return new Error('Invalid TAP reversal epoch.'); + if (!Number.isSafeInteger(value.at) || value.at < 0) return new Error('Invalid TAP reversal timestamp.'); + return null; + } + + async fiatDeposit() { + const adminError = await this.requireAdmin(); + if (adminError) return adminError; + if (!FIAT_DEPOSIT_RAILS.has(this.value.rail)) return new Error('Unsupported fiat deposit rail.'); + if (!this.isSafeKeyPart(this.value.who)) return new Error('Invalid deposit recipient.'); + if (!this.isSafeKeyPart(this.value.ext_ref_hash)) return new Error('Invalid external reference hash.'); + const fiat = this.fiatEvidenceFields(); + if (fiat instanceof Error) return fiat; + const au = this.normalizeAu(this.value.au, 'fiat deposit amount', { allowZero: false }); + if (au instanceof Error) return au; + + const seenKey = `dep/fiat/${this.value.ext_ref_hash}`; + const existing = await this.get(seenKey); + if (existing !== null) { + return { + ok: true, + op: 'fiatDeposit', + duplicate: true, + rail: existing.rail ?? 'fiat', + processor_rail: existing.processor_rail ?? this.value.rail, + who: existing.who, + au: ZERO_AU, + credited_au: existing.au ?? null, + epoch: existing.epoch ?? this.value.epoch, + deposit_root: (await this.get(`ev/dep/${existing.epoch ?? this.value.epoch}`))?.merkle_root ?? null, + }; + } + + const ledgerRail = 'fiat'; + const balance = await this.balanceRecord(this.value.who, ledgerRail); + if (balance instanceof Error) return balance; + const balanceError = this.guardianValidateBalanceRecord(balance, this.value.who, ledgerRail); + if (balanceError) return balanceError; + const nextAu = this.safeAddAu(balance.au, au); + if (nextAu instanceof Error) return nextAu; + const leaf = await this.depositLeafHash({ + rail: ledgerRail, + processor_rail: this.value.rail, + user_hash: await this.opaqueHash('deposit-user', this.value.who), + au, + ext_ref_hash: this.value.ext_ref_hash, + ...fiat, + }); + const depositRoot = await this.nextDepositRoot({ + epoch: this.value.epoch, + leaf, + au, + at: this.value.at, + }); + if (depositRoot instanceof Error) return depositRoot; + + const record = { + ...balance, + rail: ledgerRail, + au: nextAu, + updated_epoch: Math.max(balance.updated_epoch, this.value.epoch), + updated_at: this.tx, + last_deposit_rail: ledgerRail, + last_deposit_processor_rail: this.value.rail, + ...(fiat.fiat_currency ? { last_deposit_fiat_currency: fiat.fiat_currency } : {}), + }; + const seen = { + rail: ledgerRail, + processor_rail: this.value.rail, + who: this.value.who, + au, + ext_ref_hash: this.value.ext_ref_hash, + ...fiat, + epoch: this.value.epoch, + at: this.value.at, + credited_at: this.tx, + credited_by: this.address, + credited_by_role: 'admin', + chargeback_au_cum: ZERO_AU, + network_absorbed_au_cum: ZERO_AU, + }; + await this.put(seenKey, seen); + await this.put(this.balanceKey(this.value.who, ledgerRail), record); + await this.put(`ev/dep/${this.value.epoch}`, depositRoot); + console.log('mayhem fiatDeposit', { + rail: ledgerRail, + processor_rail: this.value.rail, + who: this.value.who, + au, + ...fiat, + epoch: this.value.epoch, + }); + return { + ok: true, + op: 'fiatDeposit', + rail: ledgerRail, + processor_rail: this.value.rail, + who: this.value.who, + au, + duplicate: false, + epoch: this.value.epoch, + deposit_root: depositRoot.merkle_root, + ...fiat, + }; + } + + async fiatChargeback() { + const adminError = await this.requireAdmin(); + if (adminError) return adminError; + if (!FIAT_DEPOSIT_RAILS.has(this.value.rail)) return new Error('Unsupported fiat chargeback rail.'); + if (!this.isSafeKeyPart(this.value.who)) return new Error('Invalid chargeback account.'); + if (!this.isSafeKeyPart(this.value.ext_ref_hash)) return new Error('Invalid external reference hash.'); + if (!this.isSafeKeyPart(this.value.dispute_ref_hash)) return new Error('Invalid dispute reference hash.'); + const fiat = this.fiatEvidenceFields(); + if (fiat instanceof Error) return fiat; + const au = this.normalizeAu(this.value.au, 'fiat chargeback amount', { allowZero: false }); + if (au instanceof Error) return au; + + const depositSeenKey = `dep/fiat/${this.value.ext_ref_hash}`; + const depositSeen = await this.get(depositSeenKey); + if (depositSeen === null) return new Error('Fiat deposit reference not found.'); + if (depositSeen.who !== this.value.who) return new Error('Fiat chargeback recipient mismatch.'); + if (depositSeen.processor_rail !== this.value.rail) return new Error('Fiat chargeback processor rail mismatch.'); + const chargebackSeenKey = `${depositSeenKey}/chargeback/${this.value.dispute_ref_hash}`; + const existingChargeback = await this.get(chargebackSeenKey); + if (existingChargeback !== null) { + return { + ok: true, + op: 'fiatChargeback', + duplicate: true, + rail: existingChargeback.rail ?? 'fiat', + processor_rail: existingChargeback.processor_rail ?? this.value.rail, + who: existingChargeback.who, + au: ZERO_AU, + disputed_au: existingChargeback.au ?? null, + clawback_au: ZERO_AU, + credited_clawback_au: existingChargeback.clawback_au ?? null, + network_absorbed_au: ZERO_AU, + frozen: true, + epoch: existingChargeback.epoch ?? this.value.epoch, + deposit_root: (await this.get(`ev/dep/${existingChargeback.epoch ?? this.value.epoch}`))?.merkle_root ?? null, + ...fiat, + }; + } + const depositDisputedAuCum = this.safeAddAu(depositSeen.disputed_au_cum ?? ZERO_AU, au); + if (depositDisputedAuCum instanceof Error) return depositDisputedAuCum; + if (this.compareAu(depositDisputedAuCum, depositSeen.au) > 0) { + return new Error('Fiat chargeback exceeds original deposit.'); + } + + const ledgerRail = 'fiat'; + const balance = await this.balanceRecord(this.value.who, ledgerRail); + if (balance instanceof Error) return balance; + const balanceError = this.guardianValidateBalanceRecord(balance, this.value.who, ledgerRail); + if (balanceError) return balanceError; + const clawbackAu = this.compareAu(balance.au, au) < 0 ? balance.au : au; + const networkAbsorbedAu = this.safeSubAu(au, clawbackAu); + if (networkAbsorbedAu instanceof Error) return networkAbsorbedAu; + const nextAu = this.safeSubAu(balance.au, clawbackAu); + if (nextAu instanceof Error) return nextAu; + + const leaf = await this.depositLeafHash({ + rail: ledgerRail, + processor_rail: this.value.rail, + user_hash: await this.opaqueHash('deposit-user', this.value.who), + au, + clawback_au: clawbackAu, + network_absorbed_au: networkAbsorbedAu, + ext_ref_hash: this.value.ext_ref_hash, + dispute_ref_hash: this.value.dispute_ref_hash, + reversed: true, + ...fiat, + }); + const depositRoot = await this.nextDepositReversalRoot({ + epoch: this.value.epoch, + leaf, + disputedAu: au, + clawbackAu, + absorbedAu: networkAbsorbedAu, + at: this.value.at, + }); + if (depositRoot instanceof Error) return depositRoot; + + const frozen = await this.get(`frozen/${this.value.who}`); + const frozenDisputedAuCum = this.safeAddAu(frozen?.disputed_au_cum ?? ZERO_AU, au); + if (frozenDisputedAuCum instanceof Error) return frozenDisputedAuCum; + const clawbackAuCum = this.safeAddAu(frozen?.clawback_au_cum ?? ZERO_AU, clawbackAu); + if (clawbackAuCum instanceof Error) return clawbackAuCum; + const absorbedAuCum = this.safeAddAu(frozen?.network_absorbed_au_cum ?? ZERO_AU, networkAbsorbedAu); + if (absorbedAuCum instanceof Error) return absorbedAuCum; + const freezeRecord = { + user: this.value.who, + status: 'frozen', + reason: 'fiat_chargeback', + rail: ledgerRail, + processor_rail: this.value.rail, + first_frozen_at: frozen?.first_frozen_at ?? this.tx, + first_frozen_at_seconds: frozen?.first_frozen_at_seconds ?? this.value.at, + updated_at: this.tx, + updated_at_seconds: this.value.at, + updated_epoch: Math.max(frozen?.updated_epoch ?? 0, this.value.epoch), + dispute_count: (frozen?.dispute_count ?? 0) + 1, + disputed_au_cum: frozenDisputedAuCum, + clawback_au_cum: clawbackAuCum, + network_absorbed_au_cum: absorbedAuCum, + last_ext_ref_hash: this.value.ext_ref_hash, + last_dispute_ref_hash: this.value.dispute_ref_hash, + last_fiat_currency: fiat.fiat_currency, + }; + const depositChargebackAuCum = this.safeAddAu(depositSeen.chargeback_au_cum ?? ZERO_AU, clawbackAu); + if (depositChargebackAuCum instanceof Error) return depositChargebackAuCum; + const depositAbsorbedAuCum = this.safeAddAu(depositSeen.network_absorbed_au_cum ?? ZERO_AU, networkAbsorbedAu); + if (depositAbsorbedAuCum instanceof Error) return depositAbsorbedAuCum; + const chargebackSeen = { + rail: ledgerRail, + processor_rail: this.value.rail, + who: this.value.who, + au, + clawback_au: clawbackAu, + network_absorbed_au: networkAbsorbedAu, + ext_ref_hash: this.value.ext_ref_hash, + dispute_ref_hash: this.value.dispute_ref_hash, + ...fiat, + epoch: this.value.epoch, + at: this.value.at, + credited_at: this.tx, + credited_by: this.address, + credited_by_role: 'admin', + }; + + await this.put(this.balanceKey(this.value.who, ledgerRail), { + ...balance, + rail: ledgerRail, + au: nextAu, + updated_epoch: Math.max(balance.updated_epoch, this.value.epoch), + updated_at: this.tx, + last_chargeback_rail: ledgerRail, + last_chargeback_processor_rail: this.value.rail, + last_chargeback_fiat_currency: fiat.fiat_currency, + }); + await this.put(depositSeenKey, { + ...depositSeen, + disputed_au_cum: depositDisputedAuCum, + chargeback_au_cum: depositChargebackAuCum, + network_absorbed_au_cum: depositAbsorbedAuCum, + last_dispute_ref_hash: this.value.dispute_ref_hash, + last_chargeback_at: this.tx, + last_chargeback_at_seconds: this.value.at, + last_chargeback_epoch: this.value.epoch, + }); + await this.put(chargebackSeenKey, chargebackSeen); + await this.put(`frozen/${this.value.who}`, freezeRecord); + await this.put(`ev/dep/${this.value.epoch}`, depositRoot); + console.log('mayhem fiatChargeback', { + rail: ledgerRail, + processor_rail: this.value.rail, + who: this.value.who, + au, + duplicate: false, + clawback_au: clawbackAu, + network_absorbed_au: networkAbsorbedAu, + ...fiat, + epoch: this.value.epoch, + }); + return { + ok: true, + op: 'fiatChargeback', + rail: ledgerRail, + processor_rail: this.value.rail, + who: this.value.who, + au: this.value.au, + clawback_au: clawbackAu, + network_absorbed_au: networkAbsorbedAu, + frozen: true, + epoch: this.value.epoch, + deposit_root: depositRoot.merkle_root, + ...fiat, + }; + } + + async currentRules() { + return await this.get(CURRENT_RULES_KEY); + } + + async isAdmin(sender = this.address) { + const admin = await this.get('admin'); + return typeof admin === 'string' && admin === sender; + } + + async requireAdmin(sender = this.address) { + if (await this.isAdmin(sender)) return null; + return new Error('Admin required.'); + } + + async requireConsent(sender = this.address) { + if (!sender) return new Error('Consent required.'); + + const rules = await this.currentRules(); + if (!rules) return new Error('Rules are not set.'); + + const consent = await this.get(`consent/${sender}`); + if (!consent || consent.ver !== rules.ver || consent.hash !== rules.hash) { + return new Error(`Consent required for rules version ${rules.ver}.`); + } + const frozen = await this.get(`frozen/${sender}`); + if (frozen?.status === 'frozen') return new Error('Account frozen.'); + return null; + } + + async requireProvider(sender = this.address) { + const provider = await this.get(`prov/${sender}`); + if (!provider || provider.status !== 'active') return new Error('Provider registration required.'); + return null; + } + + async requireCurrentAdminPrice(enclaveId, ctxBracket = null) { + const enclave = await this.get(`enclave/${enclaveId}`); + const resolvedCtxBracket = + ctxBracket ?? + (enclave && this.enclaveUsesCtxPrice(enclave) + ? await this.defaultPriceCtxBracketForEnclave(enclave, 0) + : null); + if (resolvedCtxBracket instanceof Error) return resolvedCtxBracket; + const schedule = await this.get(this.priceScheduleKey(enclaveId, resolvedCtxBracket)); + const current = schedule?.current; + if (!current) { + return new Error('Current admin price required before provider serving.'); + } + if (schedule.denom !== PRICE_DENOMINATION || current.denom !== PRICE_DENOMINATION) { + return new Error('Provider serving requires a current au_usd admin price.'); + } + if (current.enclave_id !== enclaveId) { + return new Error('Provider serving requires a current admin-set enclave price.'); + } + if ((current.ctx_bracket ?? null) !== (resolvedCtxBracket ?? null)) { + return new Error('Provider serving requires a current admin-set enclave price for the context bracket.'); + } + if (!current.set_by || typeof current.set_by !== 'string') { + return new Error('Provider serving requires a current admin-set enclave price.'); + } + const admin = await this.get('admin'); + if (admin === null) { + return new Error('Provider serving requires a current admin key.'); + } + if (current.set_by !== admin) { + return new Error('Provider serving requires a current price set by the current admin.'); + } + if (current.set_by_role !== 'admin') { + return new Error('Provider serving requires a current admin-set enclave price.'); + } + return null; + } + + enclaveUsesCtxPrice(enclave) { + return this.modelClassFor(enclave) === DEFAULT_MODEL_CLASS; + } + + enclaveCtxCapacity(enclave) { + const caps = enclave?.caps && typeof enclave.caps === 'object' && !Array.isArray(enclave.caps) + ? enclave.caps + : {}; + const value = caps.ctx_max ?? caps.ctx ?? 0; + if (!Number.isSafeInteger(value) || value < 0) { + return new Error('Invalid enclave context capacity.'); + } + return value; + } + + async defaultPriceCtxBracketForEnclave(enclave, at) { + const table = await this.ctxBracketTableAt(at); + if (table instanceof Error) return table; + const ctx = this.enclaveCtxCapacity(enclave); + if (ctx instanceof Error) return ctx; + const bracket = ctxBracketForTokens(ctx, table.brackets); + if (!bracket) return new Error('No context bracket covers enclave context capacity.'); + return bracket; + } + + async priceCtxMetaForEnclave(enclave, ctxBracket, at, label = 'Price') { + if (!this.enclaveUsesCtxPrice(enclave)) { + if (ctxBracket !== undefined && ctxBracket !== null) { + return new Error(`${label} context bracket is only valid for text-generation enclaves.`); + } + return null; + } + const table = await this.ctxBracketTableAt(at); + if (table instanceof Error) return table; + const bracket = ctxBracket ?? await this.defaultPriceCtxBracketForEnclave(enclave, at); + if (bracket instanceof Error) return bracket; + if (!this.isSafeKeyPart(bracket)) return new Error(`Invalid ${label} context bracket.`); + if (!Array.isArray(table.brackets) || !table.brackets.some((entry) => entry.id === bracket)) { + return new Error(`${label} context bracket is not in the active admin table.`); + } + return { ctx_bracket: bracket, ctx_bracket_table_ver: table.ver }; + } + + priceScheduleKey(enclaveId, ctxBracket = null) { + return ctxBracket ? `price/${enclaveId}/${ctxBracket}` : `price/${enclaveId}`; + } + + priceRecordKey(enclaveId, ver, ctxBracket = null) { + return ctxBracket ? `price/${enclaveId}/${ctxBracket}/v/${ver}` : `price/${enclaveId}/v/${ver}`; + } + + priceMarketKey(enclaveId, ctxBracket = null) { + return stableJson([enclaveId, ctxBracket ?? null]); + } + + requireAdminCreatedEnclave(enclave) { + if (enclave?.created_by_role !== 'admin') { + return new Error('Canonical serving requires an admin-created enclave.'); + } + return null; + } + + requireAdminCreatedRoom(room) { + if (room?.creator_role !== 'admin') { + return new Error('Provider room serving requires an admin-created room.'); + } + return null; + } + + validateExactCommandValue(allowedKeys, opName, optionalKeys = []) { + if (!this.value || typeof this.value !== 'object' || Array.isArray(this.value)) { + return new Error(`${opName} value must be an object.`); + } + const allowed = new Set([...allowedKeys, ...optionalKeys]); + const unknown = Object.keys(this.value).filter((key) => !allowed.has(key)).sort(); + if (unknown.length > 0) { + return new Error(`${opName} does not accept provider-authored fields: ${unknown.join(', ')}.`); + } + for (const key of allowedKeys) { + if (!hasOwn(this.value, key)) return new Error(`${opName} is missing ${key}.`); + } + if (hasOwn(this.value, 'op') && this.value.op !== opName) { + return new Error(`Invalid ${opName} op.`); + } + return null; + } + + validateExactObjectKeys(value, allowedKeys, label) { + if (!value || typeof value !== 'object' || Array.isArray(value)) { + return new Error(`${label} value must be an object.`); + } + const allowed = new Set(allowedKeys); + const unknown = Object.keys(value).filter((key) => !allowed.has(key)).sort(); + if (unknown.length > 0) { + return new Error(`${label} does not accept fields: ${unknown.join(', ')}.`); + } + for (const key of allowedKeys) { + if (!hasOwn(value, key)) return new Error(`${label} is missing ${key}.`); + } + return null; + } + + normalizeCtxBracketTable(brackets) { + if (!Array.isArray(brackets) || brackets.length === 0 || brackets.length > 32) { + return new Error('Context bracket table must be a non-empty array.'); + } + const ids = new Set(); + let previousMax = 0; + const normalized = []; + for (let idx = 0; idx < brackets.length; idx += 1) { + const entry = brackets[idx]; + const shapeError = this.validateExactObjectKeys(entry, ['id', 'max_ctx'], 'context bracket'); + if (shapeError) return shapeError; + if (!this.isSafeKeyPart(entry.id)) return new Error('Invalid context bracket id.'); + if (ids.has(entry.id)) return new Error('Duplicate context bracket id.'); + ids.add(entry.id); + + const isLast = idx === brackets.length - 1; + if (isLast) { + if (entry.max_ctx !== null) return new Error('Last context bracket max_ctx must be null.'); + normalized.push({ id: entry.id, max_ctx: null }); + continue; + } + if (!Number.isSafeInteger(entry.max_ctx) || entry.max_ctx <= previousMax) { + return new Error('Context bracket max_ctx values must increase.'); + } + previousMax = entry.max_ctx; + normalized.push({ id: entry.id, max_ctx: entry.max_ctx }); + } + return normalized; + } + + defaultCtxBracketTableRecord() { + return { + ver: CTX_BRACKET_TABLE_VERSION, + brackets: cloneValue(CTX_BRACKETS), + submitted_at: 0, + effective_at: 0, + effective_from: null, + updated_at: null, + set_by: null, + set_by_role: 'genesis', + }; + } + + async ctxBracketSchedule() { + if (!this.storage) return { current: this.defaultCtxBracketTableRecord(), pending: null }; + const stored = await this.get('ctx_brackets'); + const fallback = this.defaultCtxBracketTableRecord(); + if (!stored) return { current: fallback, pending: null }; + return { + current: stored.current ?? fallback, + pending: stored.pending ?? null, + }; + } + + ctxBracketLatestEntry(schedule) { + if (schedule.pending && schedule.pending.ver > schedule.current.ver) return schedule.pending; + return schedule.current; + } + + ctxBracketActiveEntry(schedule, at) { + if (schedule.pending && schedule.pending.effective_at <= at) return schedule.pending; + return schedule.current; + } + + async ctxBracketTableAt(at) { + if (!Number.isSafeInteger(at) || at < 0) return new Error('Invalid context bracket timestamp.'); + return cloneValue(this.ctxBracketActiveEntry(await this.ctxBracketSchedule(), at)); + } + + async ctxBracketTableByVersion(ver) { + if (!Number.isSafeInteger(ver) || ver < 1) return new Error('Invalid context bracket table version.'); + if (ver === CTX_BRACKET_TABLE_VERSION) return this.defaultCtxBracketTableRecord(); + const record = await this.get(`ctx_brackets/v/${ver}`); + if (!record) return new Error('Unknown context bracket table version.'); + return cloneValue(record); + } + + validateCtxBracketEvidence(tokens, bracket, tableVer, table, label) { + if (typeof bracket !== 'string') return new Error(`Invalid ${label} context bracket.`); + if (!Number.isSafeInteger(tableVer) || tableVer < 1) { + return new Error(`Invalid ${label} context bracket table version.`); + } + if (!table || table.ver !== tableVer) { + return new Error(`${label} context bracket table version mismatch.`); + } + if (!Array.isArray(table.brackets) || !table.brackets.some((entry) => entry.id === bracket)) { + return new Error(`Invalid ${label} context bracket.`); + } + if (ctxBracketForTokens(tokens, table.brackets) !== bracket) { + return new Error(`${label} context bracket does not match served context.`); + } + return null; + } + + async normalizeCtxBracketEvidenceForEnclave(enclaveId, tokens, bracket, tableVer, table, label) { + const enclave = await this.get(`enclave/${enclaveId}`); + if (!enclave) return new Error(`${label} enclave not found.`); + if (!this.enclaveUsesCtxPrice(enclave)) { + if (bracket !== undefined && bracket !== null) { + return new Error(`${label} context bracket is only valid for text-generation enclaves.`); + } + if (tableVer !== undefined && tableVer !== null) { + return new Error(`${label} context bracket table version is only valid for text-generation enclaves.`); + } + return { + enclave, + ctx_bracket: null, + ctx_bracket_table_ver: null, + }; + } + const ctxError = this.validateCtxBracketEvidence(tokens, bracket, tableVer, table, label); + if (ctxError) return ctxError; + return { + enclave, + ctx_bracket: bracket, + ctx_bracket_table_ver: tableVer, + }; + } + + validateProviderLifecycleIntent(intent) { + if (!PROVIDER_LIFECYCLE_OPS.has(intent.op)) return new Error('Unsupported provider lifecycle op.'); + const allowed = intent.op === 'register_provider' + ? ['op', 'provider', 'nonce'] + : intent.op === 'set_provider_rails' + ? ['op', 'provider', 'rails', 'nonce'] + : intent.op === 'join_room' || intent.op === 'leave_room' + ? ['op', 'provider', 'enclave_id', 'room_id', 'nonce'] + : intent.op === 'join_enclave' + ? [ + 'op', + 'provider', + 'enclave_id', + 'nonce', + 'att_tier', + 'attestation_head', + 'served_ctx', + 'served_modalities', + 'served_specialities', + 'ctx_bracket', + 'ctx_bracket_table_ver', + 'hardware_fingerprint', + 'device_key', + ] + : ['op', 'provider', 'enclave_id', 'nonce']; + const required = intent.op === 'join_enclave' + ? [ + 'op', + 'provider', + 'enclave_id', + 'nonce', + 'att_tier', + 'attestation_head', + 'served_ctx', + 'served_modalities', + 'served_specialities', + 'ctx_bracket', + 'ctx_bracket_table_ver', + ] + : allowed; + const allowedSet = new Set(allowed); + const unknown = Object.keys(intent).filter((key) => !allowedSet.has(key)).sort(); + if (unknown.length > 0) { + return new Error(`provider lifecycle intent does not accept fields: ${unknown.join(', ')}.`); + } + for (const key of required) { + if (!hasOwn(intent, key)) return new Error(`provider lifecycle intent is missing ${key}.`); + } + if (!this.isHexBytes(intent.provider, 32)) return new Error('Invalid provider id.'); + if (!this.isHexBytes(intent.nonce, 32)) return new Error('Invalid lifecycle nonce.'); + if (hasOwn(intent, 'enclave_id') && !this.isSafeKeyPart(intent.enclave_id)) { + return new Error('Invalid enclave id.'); + } + if (hasOwn(intent, 'room_id') && !this.isSafeKeyPart(intent.room_id)) { + return new Error('Invalid room id.'); + } + if ( + hasOwn(intent, 'served_ctx') && + (!Number.isSafeInteger(intent.served_ctx) || intent.served_ctx < 0) + ) { + return new Error('Invalid provider served context.'); + } + if (hasOwn(intent, 'served_modalities')) { + const modalitiesError = this.validateModalitySet(intent.served_modalities, 'provider served_modalities'); + if (modalitiesError) return modalitiesError; + } + if (hasOwn(intent, 'served_specialities')) { + const specialitiesError = this.validateSpecialityLevelMap( + intent.served_specialities, + 'provider served_specialities', + { allowEmpty: true } + ); + if (specialitiesError) return specialitiesError; + } + if ( + hasOwn(intent, 'ctx_bracket') && + intent.ctx_bracket !== null && + !this.isSafeKeyPart(intent.ctx_bracket) + ) { + return new Error('Invalid provider context bracket.'); + } + if ( + hasOwn(intent, 'ctx_bracket_table_ver') && + intent.ctx_bracket_table_ver !== null && + (!Number.isSafeInteger(intent.ctx_bracket_table_ver) || intent.ctx_bracket_table_ver < 1) + ) { + return new Error('Invalid provider context bracket table version.'); + } + if (hasOwn(intent, 'hardware_fingerprint') && !this.isHexBytes(intent.hardware_fingerprint, 32)) { + return new Error('Invalid provider hardware fingerprint.'); + } + if (hasOwn(intent, 'device_key') && !this.isHexBytes(intent.device_key, 32)) { + return new Error('Invalid provider device key.'); + } + if ( + hasOwn(intent, 'att_tier') && + (!Number.isSafeInteger(intent.att_tier) || intent.att_tier < 1 || intent.att_tier > MAX_LAUNCH_ENCLAVE_ATTESTATION_TIER) + ) { + return new Error('Invalid provider attestation tier.'); + } + if (hasOwn(intent, 'attestation_head') && !this.isHexBytes(intent.attestation_head, 32)) { + return new Error('Invalid provider attestation head.'); + } + if (hasOwn(intent, 'rails')) { + const rails = this.normalizeProviderAcceptedRails(intent.rails); + if (rails instanceof Error) return rails; + } + return null; + } + + async normalizeProviderServeTerms(enclaveId, terms, label) { + if (terms === null || terms === undefined) { + return new Error(`${label} terms are required.`); + } + if (!terms || typeof terms !== 'object' || Array.isArray(terms)) { + return new Error(`${label} terms must be an object.`); + } + for (const field of ['served_ctx', 'served_modalities', 'served_specialities', 'ctx_bracket', 'ctx_bracket_table_ver']) { + if (!hasOwn(terms, field)) return new Error(`${label} terms are missing ${field}.`); + } + if (!Number.isSafeInteger(terms.served_ctx) || terms.served_ctx < 0) { + return new Error(`Invalid ${label} served context.`); + } + const servedModalitiesError = this.validateModalitySet( + terms.served_modalities, + `${label} served_modalities` + ); + if (servedModalitiesError) return servedModalitiesError; + const enclave = await this.get(`enclave/${enclaveId}`); + if (!enclave || enclave.status !== 'active') return new Error('Enclave is not active.'); + const enclaveModalitiesError = this.validateModalitySet( + enclave.caps?.modality_set, + 'admin enclave modality_set' + ); + if (enclaveModalitiesError) return enclaveModalitiesError; + const enclaveModalities = new Set(enclave.caps.modality_set); + if (terms.served_modalities.some((modality) => !enclaveModalities.has(modality))) { + return new Error(`${label} served_modalities must be a subset of the admin enclave modality_set.`); + } + const coreModalities = this.coreModalitiesForModelClass(this.modelClassFor(enclave)); + if ([...coreModalities].some((modality) => !terms.served_modalities.includes(modality))) { + return new Error(`${label} cannot disable a core model modality.`); + } + const enclaveSpecialitiesError = this.validateSpecialityLevelMap( + enclave.caps?.speciality_levels, + 'admin enclave speciality_levels', + { allowEmpty: true } + ); + if (enclaveSpecialitiesError) return enclaveSpecialitiesError; + const servedSpecialitiesError = this.validateSpecialityLevelMap( + terms.served_specialities, + `${label} served_specialities`, + { allowEmpty: true } + ); + if (servedSpecialitiesError) return servedSpecialitiesError; + const adminSpecialityNames = Object.keys(enclave.caps.speciality_levels).sort(compareCodepoint); + const servedSpecialityNames = Object.keys(terms.served_specialities).sort(compareCodepoint); + if (stableJson(servedSpecialityNames) !== stableJson(adminSpecialityNames)) { + return new Error(`${label} served_specialities must cover exactly the admin enclave speciality names.`); + } + for (const name of adminSpecialityNames) { + const available = new Set(enclave.caps.speciality_levels[name]); + if (terms.served_specialities[name].some((level) => !available.has(level))) { + return new Error(`${label} served_specialities ${name} must be a subset of the admin enclave levels.`); + } + } + const table = terms.ctx_bracket_table_ver === null || terms.ctx_bracket_table_ver === undefined + ? null + : await this.ctxBracketTableByVersion(terms.ctx_bracket_table_ver); + if (table instanceof Error) return table; + const ctxMeta = await this.normalizeCtxBracketEvidenceForEnclave( + enclaveId, + terms.served_ctx, + terms.ctx_bracket, + terms.ctx_bracket_table_ver, + table, + label + ); + if (ctxMeta instanceof Error) return ctxMeta; + return { + served_ctx: terms.served_ctx, + served_modalities: terms.served_modalities.slice(), + served_specialities: Object.fromEntries( + Object.entries(terms.served_specialities) + .sort(([left], [right]) => compareCodepoint(left, right)) + .map(([name, levels]) => [name, levels.slice()]) + ), + ctx_bracket: ctxMeta.ctx_bracket, + ctx_bracket_table_ver: ctxMeta.ctx_bracket_table_ver, + }; + } + + validateCommittedServeTerms(serve, normalized) { + if (!Number.isSafeInteger(serve.served_ctx) || serve.served_ctx < 0) { + return new Error('Provider serve record is missing committed context terms.'); + } + if (serve.served_ctx !== normalized.served_ctx) { + return new Error('Spend reservation served context does not match provider committed context.'); + } + const servedModalitiesError = this.validateModalitySet( + serve.served_modalities, + 'provider committed served_modalities' + ); + if (servedModalitiesError) return servedModalitiesError; + const requiredModalitiesError = this.validateModalitySet( + normalized.required_modalities, + 'spend reservation required_modalities' + ); + if (requiredModalitiesError) return requiredModalitiesError; + const servedModalities = new Set(serve.served_modalities); + if (normalized.required_modalities.some((modality) => !servedModalities.has(modality))) { + return new Error('Provider committed modalities do not cover the spend reservation.'); + } + const servedSpecialitiesError = this.validateSpecialityLevelMap( + serve.served_specialities, + 'provider committed served_specialities', + { allowEmpty: true } + ); + if (servedSpecialitiesError) return servedSpecialitiesError; + const requiredSpecialitiesError = this.validateSpecialitySelection( + normalized.required_specialities, + 'spend reservation required_specialities' + ); + if (requiredSpecialitiesError) return requiredSpecialitiesError; + const servedSpecialityNames = Object.keys(serve.served_specialities).sort(compareCodepoint); + const requiredSpecialityNames = Object.keys(normalized.required_specialities).sort(compareCodepoint); + if (stableJson(servedSpecialityNames) !== stableJson(requiredSpecialityNames)) { + return new Error('Spend reservation must bind every provider committed speciality.'); + } + for (const [name, level] of Object.entries(normalized.required_specialities)) { + if (!serve.served_specialities[name].includes(level)) { + return new Error('Provider committed specialities do not cover the spend reservation.'); + } + } + if ((serve.ctx_bracket ?? null) !== (normalized.ctx_bracket ?? null)) { + return new Error('Spend reservation context bracket does not match provider committed context.'); + } + if ((serve.ctx_bracket_table_ver ?? null) !== (normalized.ctx_bracket_table_ver ?? null)) { + return new Error('Spend reservation context bracket table does not match provider committed context.'); + } + return null; + } + + banRecordKey(targetType, target) { + switch (targetType) { + case 'provider': + return `ban/provider/${target}`; + case 'device': + return `ban/device/${target}`; + case 'fingerprint': + return `ban/fingerprint/${target}`; + case 'committer': + return `committer/ban/${target}`; + default: + return `ban/unknown/${target}`; + } + } + + normalizedKybIdentityValues(kyb) { + if (!kyb || typeof kyb !== 'object') return new Error('Invalid KYB identity.'); + const legalName = typeof kyb.legal_name === 'string' + ? kyb.legal_name.trim().replace(/\s+/g, ' ').toLowerCase() + : ''; + const kybRef = typeof kyb.kyb_ref === 'string' ? kyb.kyb_ref.trim() : ''; + const proofHash = typeof kyb.proof_hash === 'string' ? kyb.proof_hash.toLowerCase() : ''; + if (!legalName) return new Error('Invalid KYB legal name.'); + if (!this.isSafeExternalRef(kybRef)) return new Error('Invalid provider KYB reference.'); + if (!this.isHexBytes(proofHash, 32)) return new Error('Invalid provider KYB proof hash.'); + return { + legal_name: legalName, + kyb_ref: kybRef, + proof_hash: proofHash, + }; + } + + async kybBanIndexKey(kind, value) { + return `ban/kyb/${kind}/${await this.opaqueHash('mayhem-kyb-ban-index-v1', { kind, value })}`; + } + + async kybBanIndexKeys(kyb) { + const values = this.normalizedKybIdentityValues(kyb); + if (values instanceof Error) return values; + return [ + await this.kybBanIndexKey('legal_name', values.legal_name), + await this.kybBanIndexKey('kyb_ref', values.kyb_ref), + await this.kybBanIndexKey('proof_hash', values.proof_hash), + ]; + } + + async rejectBannedProviderKyb(kyb) { + const keys = await this.kybBanIndexKeys(kyb); + if (keys instanceof Error) return keys; + for (const key of keys) { + const ban = await this.get(key); + if (ban?.status === 'banned' || ban?.status === 'revoked') { + return new Error('Provider KYB identity is banned or revoked.'); + } + } + return null; + } + + async writeProviderKybBanIndexes(kyb, meta) { + const keys = await this.kybBanIndexKeys(kyb); + if (keys instanceof Error) return keys; + const values = this.normalizedKybIdentityValues(kyb); + if (values instanceof Error) return values; + for (const key of keys) { + const current = await this.get(key); + await this.put(key, { + ...(current ?? {}), + target_type: 'kyb', + target: key.split('/').at(-1), + status: meta.status, + provider: meta.provider, + source: meta.source, + reason_hash: meta.reason_hash, + recorded_at: meta.recorded_at, + recorded_by: meta.recorded_by, + recorded_by_role: meta.recorded_by_role, + legal_name_hash: await this.kybBanIndexKey('legal_name', values.legal_name).then((k) => k.split('/').at(-1)), + kyb_ref_hash: await this.kybBanIndexKey('kyb_ref', values.kyb_ref).then((k) => k.split('/').at(-1)), + proof_hash: values.proof_hash, + providers: { + ...(current?.providers ?? {}), + [meta.provider]: { + provider: meta.provider, + source: meta.source, + reason_hash: meta.reason_hash, + recorded_at: meta.recorded_at, + }, + }, + reversible: true, + }); + } + return null; + } + + async providerLifecycleFeatureKey(intent) { + const digest = await blake3(b4a.from(providerLifecycleIntentMessage(intent))); + return `intent/provider/${intent.provider}/${intent.op}/${b4a.toString(digest, 'hex')}`; + } + + async providerLifecycleFeatureKeys(intent) { + return [await this.providerLifecycleFeatureKey(intent)]; + } + + async providerPayoutBindingRevision(intent) { + const digest = await blake3(b4a.from(providerPayoutBindingMessage(intent))); + return b4a.toString(digest, 'hex'); + } + + async targetedSpendReservationFeatureKey(value) { + const normalized = await this.normalizeTargetedSpendReserveValue(value); + if (normalized instanceof Error) return normalized; + const digest = await blake3(b4a.from(stableJson({ + domain: 'mayhem-targeted-spend-reservation-feature-v1', + value: targetedSpendReservationEvidence({ + ...normalized, + payout_revision: value.payout_revision, + }), + }))); + return `hold/targeted/${normalized.rail}/${normalized.user}/${normalized.epoch}/${normalized.session_id}/${b4a.toString(digest, 'hex')}`; + } + + async providerPayoutPaymentConfigHash(payments) { + return await this.opaqueHash('mayhem-payout-payment-config-v1', payments); + } + + async providerPayoutContextFeatureKey(value) { + const revision = await this.providerPayoutContextRevision(value); + return this.providerPayoutContextRecordKey(value.payment_config_version, revision); + } + + async providerPayoutContextRevision(value) { + const digest = await blake3(b4a.from(stableJson({ + domain: 'mayhem-payout-context-feature-v1', + value, + }))); + return b4a.toString(digest, 'hex'); + } + + providerPayoutContextRecordKey(paymentConfigVersion, revision) { + return `payout/context/${paymentConfigVersion}/${revision}`; + } + + payoutParameterKey(key) { + return `payout/params/${key}`; + } + + async payoutParameterFeatureKey(value) { + const digest = await blake3(b4a.from(stableJson({ + domain: 'mayhem-payout-parameter-feature-v1', + value, + }))); + return `payout/params/${value.key}/${b4a.toString(digest, 'hex')}`; + } + + async payoutParameterRecord(key) { + const definition = PAYOUT_PARAM_DEFINITIONS[key]; + if (!definition) return new Error('Unknown payout parameter.'); + return (await this.get(this.payoutParameterKey(key))) ?? { + key, + current: { + key, + value: definition.default, + effective_epoch: 0, + scheduled_at: null, + scheduled_by: null, + scheduled_by_role: 'default', + }, + pending: null, + }; + } + + async activePayoutParamsAtEpoch(epoch) { + if (!Number.isSafeInteger(epoch) || epoch < 0) { + return new Error('Invalid payout parameter epoch.'); + } + const values = {}; + for (const key of Object.keys(PAYOUT_PARAM_DEFINITIONS)) { + const schedule = await this.payoutParameterRecord(key); + if (schedule instanceof Error) return schedule; + const active = schedule.pending && schedule.pending.effective_epoch <= epoch + ? schedule.pending + : schedule.current; + values[key] = active.value; + } + return values; + } + + providerPayoutBindingFeatureKey(rail, provider, revision) { + return `payout/binding/${rail}/${provider}/${revision}`; + } + + providerPayoutBindingPointerKey(provider, rail) { + return `payout/current/${rail}/${provider}`; + } + + providerPayoutBindingNonceKey(provider, nonce) { + return `payout/nonce/${provider}/${nonce}`; + } + + providerPayoutLiabilityKey(provider, rail, revision) { + return `payout/liability/${rail}/${provider}/${revision}`; + } + + providerPayoutLiabilityIndexKey(rail) { + return `payout/liability-index/${rail}`; + } + + // MAYHEM PATCH: canonical per-rail liability index avoids ledger-wide scans + // when targeted payout epochs collect provider earnings. + normalizeProviderPayoutLiabilityIndex(value, rail) { + const shapeError = this.validateExactObjectKeys( + value, + ['type', 'rail', 'entries', 'updated_epoch', 'updated_at'], + 'provider payout liability index' + ); + if (shapeError) return shapeError; + if (value.type !== 'provider_payout_liability_index' || + value.rail !== rail || + !PROVIDER_PAYOUT_BINDING_RAILS.has(rail) || + !Array.isArray(value.entries) || + !Number.isSafeInteger(value.updated_epoch) || + value.updated_epoch < 0 || + (value.updated_at !== null && + (typeof value.updated_at !== 'string' || value.updated_at.length === 0))) { + return new Error('Invalid provider payout liability index.'); + } + const entries = []; + for (const entry of value.entries) { + const entryError = this.validateExactObjectKeys( + entry, + ['provider', 'payout_revision'], + 'provider payout liability index entry' + ); + if (entryError) return entryError; + if (!this.isHexBytes(entry.provider, 32) || + entry.provider !== entry.provider.toLowerCase() || + !this.isHexBytes(entry.payout_revision, 32) || + entry.payout_revision !== entry.payout_revision.toLowerCase()) { + return new Error('Invalid provider payout liability index entry.'); + } + entries.push({ + provider: entry.provider, + payout_revision: entry.payout_revision, + }); + } + entries.sort((left, right) => + compareCodepoint(left.provider, right.provider) || + compareCodepoint(left.payout_revision, right.payout_revision) + ); + const identities = entries.map((entry) => + `${entry.provider}/${entry.payout_revision}` + ); + if (new Set(identities).size !== entries.length || + stableJson(entries) !== stableJson(value.entries)) { + return new Error('Provider payout liability index must be canonical and unique.'); + } + return { + type: 'provider_payout_liability_index', + rail, + entries, + updated_epoch: value.updated_epoch, + updated_at: value.updated_at, + }; + } + + async providerPayoutLiabilityIndex(rail) { + if (!PROVIDER_PAYOUT_BINDING_RAILS.has(rail)) { + return new Error('Invalid provider payout liability index rail.'); + } + const current = await this.get(this.providerPayoutLiabilityIndexKey(rail)); + if (current === null) { + return { + type: 'provider_payout_liability_index', + rail, + entries: [], + updated_epoch: 0, + updated_at: null, + }; + } + return this.normalizeProviderPayoutLiabilityIndex(current, rail); + } + + async nextProviderPayoutLiabilityIndexes(liabilityUpdates, epoch, featureKey) { + const byRail = new Map(); + for (const update of liabilityUpdates) { + const { provider, rail, revision: payoutRevision } = update.value; + let index = byRail.get(rail); + if (!index) { + index = await this.providerPayoutLiabilityIndex(rail); + if (index instanceof Error) return index; + } + const identity = `${provider}/${payoutRevision}`; + if (!index.entries.some((entry) => + `${entry.provider}/${entry.payout_revision}` === identity + )) { + index = { + ...index, + entries: [ + ...index.entries, + { provider, payout_revision: payoutRevision }, + ].sort((left, right) => + compareCodepoint(left.provider, right.provider) || + compareCodepoint(left.payout_revision, right.payout_revision) + ), + }; + } + byRail.set(rail, { + ...index, + updated_epoch: Math.max(index.updated_epoch, epoch), + updated_at: featureKey, + }); + } + return [...byRail.values()].map((value) => ({ + key: this.providerPayoutLiabilityIndexKey(value.rail), + value, + })); + } + + providerStripePayoutVerificationTargetKey(provider, target) { + return `payout/stripe-verified/target/${provider}/${target}`; + } + + async providerStripePayoutVerificationForTarget(provider, target) { + const pointer = await this.get( + this.providerStripePayoutVerificationTargetKey(provider, target) + ); + const verification = pointer?.record_key + ? await this.get(pointer.record_key) + : null; + if (!verification || + pointer.provider !== provider || + pointer.target !== target || + pointer.revision !== verification.revision || + pointer.processor_revision !== verification.processor_revision || + verification.type !== 'stripe_payout_verification' || + verification.provider !== provider || + verification.target !== target) { + return null; + } + return verification; + } + + providerPayoutEpochSnapshotKey(epoch, page, provider, rail) { + return `payout/epoch/${epoch}/${page}/${rail}/${provider}`; + } + + async targetedEpochFeatureKey(value) { + const normalized = await this.normalizeTargetedEpochFeatureValue(value); + if (normalized instanceof Error) return normalized; + const digest = await blake3(b4a.from(stableJson({ + domain: 'mayhem-targeted-epoch-feature-v1', + value, + }))); + return `epoch/targeted/${value.epoch}/${b4a.toString(digest, 'hex')}`; + } + + async commitTargetedEpochPageZeroFeatureKey(value) { + const normalized = await this.normalizeCommitTargetedEpochPageZeroFeatureValue(value); + if (normalized instanceof Error) return normalized; + const digest = await blake3(b4a.from(stableJson({ + domain: 'mayhem-commit-targeted-epoch-page-zero-feature-v1', + value, + }))); + return `epoch/targeted/${value.epoch}/${b4a.toString(digest, 'hex')}`; + } + + async providerPayoutBindingContext(intent) { + const context = { + network: intent.network, + admin: intent.admin, + bootstrap: intent.bootstrap, + context_revision: intent.context_revision, + payment_config_version: intent.payment_config_version, + }; + if (!this.isSafeKeyPart(context.network) || + !this.isHexBytes(context.admin, 32) || + !this.isHexBytes(context.bootstrap, 32) || + !this.isHexBytes(context.context_revision, 32) || + !Number.isSafeInteger(context.payment_config_version) || + context.payment_config_version < 1) { + return new Error('Invalid provider payout binding canonical context.'); + } + return context; + } + + async providerPayoutBindingForEpoch( + provider, + rail, + revision, + epoch, + { requireCurrentReadiness = false } = {} + ) { + if (!PROVIDER_PAYOUT_BINDING_RAILS.has(rail)) { + return new Error('Targeted payout rail is not supported.'); + } + if (!this.isHexBytes(provider, 32) || + !this.isHexBytes(revision, 32) || + !Number.isSafeInteger(epoch) || + epoch < 1) { + return new Error('Invalid targeted payout binding reference.'); + } + const binding = await this.get( + this.providerPayoutBindingFeatureKey(rail, provider, revision) + ); + if (!binding || binding.verified !== true || + binding.provider !== provider || + binding.rail !== rail || + binding.revision !== revision) { + return new Error('Active verified provider payout binding required.'); + } + if (binding.activation_epoch > epoch) { + return new Error('Provider payout binding is not active for targeted epoch.'); + } + const pointer = await this.get(this.providerPayoutBindingPointerKey(provider, rail)); + if (!pointer) return new Error('Provider payout binding pointer required.'); + const activeRevision = pointer.pending_revision !== null && + pointer.pending_activation_epoch <= epoch + ? pointer.pending_revision + : pointer.current_revision; + if (activeRevision !== revision) { + return new Error('Provider payout binding revision is not active for targeted epoch.'); + } + if (requireCurrentReadiness && rail === 'fiat') { + const verification = await this.providerStripePayoutVerificationForTarget( + provider, + binding.target + ); + if (!verification || + verification.ready !== true || + verification.target !== binding.target || + verification.processor_revision !== binding.stripe_processor_revision) { + return new Error('Provider Stripe payout binding is not currently ready.'); + } + } + return binding; + } + + receiptConsumptionRecord(epoch, allocation, featureKey) { + return { + type: 'canonical_receipt_consumption', + epoch, + billing_epoch: allocation.billing_epoch, + billing_id: allocation.billing_id, + billing_attempt: allocation.billing_attempt, + receipt_seq: allocation.receipt_seq, + receipt_hash: allocation.receipt_hash, + session_id: allocation.session_id, + user: allocation.user, + rail: allocation.rail, + provider: allocation.provider, + payout_revision: allocation.payout_revision, + au: allocation.au, + feature_key: featureKey, + consumed_at: featureKey, + }; + } + + async validateTargetedEpochReservationBindings(value, earnings, featureKey) { + const debitTotals = new Map(); + for (const debit of value.debits) { + const rail = this.normalizeLedgerRail(debit.rail, 'targeted epoch debit rail'); + if (rail instanceof Error) return rail; + const key = stableJson([rail, debit.user]); + const next = this.safeAddAu(debitTotals.get(key) ?? ZERO_AU, debit.au); + if (next instanceof Error) return next; + debitTotals.set(key, next); + } + const earningTotals = new Map( + earnings.map((earning) => [ + stableJson([earning.rail, earning.provider, earning.payout_revision]), + earning.gross_au, + ]) + ); + const allocatedDebits = new Map(); + const allocatedEarnings = new Map(); + const holds = new Map(); + const summaries = new Map(); + const legacyReleases = new Map(); + const sessionDeletes = []; + const sessions = new Set(); + const billingAttempts = new Set(); + const marketUsage = new Map(); + for (const allocation of value.allocations) { + if (sessions.has(allocation.session_id)) { + return new Error('Targeted epoch session allocation is duplicated.'); + } + sessions.add(allocation.session_id); + const billingAttempt = `${allocation.billing_id}:${allocation.billing_attempt}`; + if (billingAttempts.has(billingAttempt)) { + return new Error('Targeted epoch billing attempt allocation is duplicated.'); + } + billingAttempts.add(billingAttempt); + const head = await this.get( + this.receiptHeadKey(allocation.billing_id, allocation.billing_attempt) + ); + if (!head || + head.type !== 'canonical_receipt_head' || + head.epoch !== value.epoch || + head.settlement_epoch !== value.epoch || + head.billing_epoch !== allocation.billing_epoch || + head.settlement_ready !== true || + head.billing_id !== allocation.billing_id || + head.billing_attempt !== allocation.billing_attempt || + head.receipt_seq !== allocation.receipt_seq || + head.receipt_hash !== allocation.receipt_hash || + head.session_id !== allocation.session_id || + head.user !== allocation.user || + head.rail !== allocation.rail || + head.provider !== allocation.provider || + head.payout_revision !== allocation.payout_revision || + this.compareAu(head.incremental_au, allocation.au) !== 0) { + return new Error('Targeted epoch allocation does not match its canonical receipt head.'); + } + const receiptBody = head.receipt?.body; + if (!receiptBody || + receiptBody.session_id !== allocation.session_id || + receiptBody.provider !== allocation.provider || + !this.isSafeKeyPart(receiptBody.enclave_id) || + (receiptBody.ctx_bracket !== undefined && + receiptBody.ctx_bracket !== null && + !this.isSafeKeyPart(receiptBody.ctx_bracket)) || + (receiptBody.ctx_bracket_table_ver !== undefined && + receiptBody.ctx_bracket_table_ver !== null && + (!Number.isSafeInteger(receiptBody.ctx_bracket_table_ver) || + receiptBody.ctx_bracket_table_ver < 1))) { + return new Error('Canonical receipt market identity is invalid.'); + } + const marketKey = this.priceMarketKey( + receiptBody.enclave_id, + receiptBody.ctx_bracket ?? null + ); + const currentMarket = marketUsage.get(marketKey) ?? { + enclave_id: receiptBody.enclave_id, + ...(receiptBody.ctx_bracket ? { ctx_bracket: receiptBody.ctx_bracket } : {}), + ...(receiptBody.ctx_bracket_table_ver ? { + ctx_bracket_table_ver: receiptBody.ctx_bracket_table_ver, + } : {}), + demand_au: ZERO_AU, + settled_usage: {}, + compute_ms: ZERO_AU, + legacy_receipt_count: 0, + session_count: 0, + providers: new Set(), + provider_capacities: new Map(), + }; + if ((currentMarket.ctx_bracket_table_ver ?? null) !== + (receiptBody.ctx_bracket_table_ver ?? currentMarket.ctx_bracket_table_ver ?? null)) { + return new Error('Canonical receipt market context version changed within an epoch.'); + } + const marketDemandAu = this.safeAddAu(currentMarket.demand_au, allocation.au); + const marketSessionCount = this.safeAddCount( + currentMarket.session_count, + 1, + 'canonical receipt market session count' + ); + if (marketDemandAu instanceof Error || marketSessionCount instanceof Error) { + return new Error('Canonical receipt market usage overflow.'); + } + const increment = this.incrementalSettledUsage(receiptBody); + if (increment instanceof Error) return increment; + const settledUsage = this.addSettledUsage(currentMarket.settled_usage, increment); + if (settledUsage instanceof Error) return settledUsage; + currentMarket.settled_usage = settledUsage; + if (receiptBody.schema_version === SESSION_RECEIPT_SCHEMA_VERSION) { + const computeMs = this.safeAddAu( + currentMarket.compute_ms, + String(receiptBody.compute_ms) + ); + if (computeMs instanceof Error) { + return new Error('Canonical receipt market compute duration overflow.'); + } + currentMarket.compute_ms = computeMs; + currentMarket.provider_capacities.set( + allocation.provider, + Math.max( + currentMarket.provider_capacities.get(allocation.provider) ?? 0, + receiptBody.capacity_slots + ) + ); + } else { + const legacyReceiptCount = this.safeAddCount( + currentMarket.legacy_receipt_count, + 1, + 'canonical legacy receipt count' + ); + if (legacyReceiptCount instanceof Error) return legacyReceiptCount; + currentMarket.legacy_receipt_count = legacyReceiptCount; + } + currentMarket.demand_au = marketDemandAu; + currentMarket.session_count = marketSessionCount; + currentMarket.providers.add(allocation.provider); + marketUsage.set(marketKey, currentMarket); + const consumeKey = this.receiptConsumedKey( + allocation.billing_id, + allocation.billing_attempt + ); + const expectedConsumption = this.receiptConsumptionRecord( + value.epoch, + allocation, + featureKey + ); + const existingConsumption = await this.get(consumeKey); + if (existingConsumption !== null && + stableJson(existingConsumption) !== stableJson(expectedConsumption)) { + return new Error('Canonical receipt billing attempt is already consumed.'); + } + if (existingConsumption === null) { + const reservationState = await this.targetedSpendReservationState( + allocation.user, + allocation.rail, + head.reservation_id, + allocation.session_id + ); + if (reservationState instanceof Error) return reservationState; + if (reservationState.kind === 'missing') { + return new Error('Targeted epoch allocation does not match its reserved session.'); + } + const holdIdentity = stableJson([allocation.rail, allocation.user]); + const isLegacy = reservationState.kind === 'legacy' || + reservationState.kind === 'legacy_overlay'; + const session = isLegacy ? reservationState.session : reservationState.session; + if (!session || + session.billing_id !== allocation.billing_id || + session.billing_attempt !== allocation.billing_attempt || + session.billing_epoch !== allocation.billing_epoch || + session.provider !== allocation.provider || + session.payout_revision !== allocation.payout_revision || + session.settlement_ready !== true) { + return new Error('Targeted epoch allocation does not match its reserved session.'); + } + if (session.reservation_id !== head.reservation_id || + session.user !== allocation.user || + session.rail !== allocation.rail || + this.compareAu(allocation.au, session.max_spend_au) !== 0 || + this.compareAu(allocation.au, head.incremental_au) !== 0) { + return new Error('Targeted epoch allocation does not exactly consume its reserved receipt.'); + } + if (isLegacy) { + let legacyRelease = legacyReleases.get(holdIdentity) ?? + reservationState.legacyRelease; + const nextReleasedAu = this.safeAddAu(legacyRelease.released_au, allocation.au); + if (nextReleasedAu instanceof Error || + this.compareAu(nextReleasedAu, reservationState.hold.reserved_au) > 0) { + return new Error('Targeted epoch allocation exceeds outstanding legacy holds.'); + } + legacyRelease = { + ...legacyRelease, + released_au: nextReleasedAu, + updated_at: featureKey, + }; + legacyReleases.set(holdIdentity, legacyRelease); + if (reservationState.kind === 'legacy_overlay') { + sessionDeletes.push(reservationState.legacySessionKey); + } + } else { + let summary = summaries.get(holdIdentity) ?? reservationState.summary; + const nextReservedAu = this.safeSubAu(summary.reserved_au, allocation.au); + if (nextReservedAu instanceof Error) { + return new Error('Targeted epoch allocation exceeds outstanding sharded holds.'); + } + summary = { + ...summary, + reserved_au: nextReservedAu, + updated_at: featureKey, + }; + summaries.set(holdIdentity, summary); + sessionDeletes.push( + reservationState.sessionKey, + reservationState.sessionIndexKey, + reservationState.billingAttemptKey + ); + } + } + const debitKey = stableJson([allocation.rail, allocation.user]); + const nextDebit = this.safeAddAu( + allocatedDebits.get(debitKey) ?? ZERO_AU, + allocation.au + ); + if (nextDebit instanceof Error) return nextDebit; + allocatedDebits.set(debitKey, nextDebit); + const earningKey = stableJson([ + allocation.rail, + allocation.provider, + allocation.payout_revision, + ]); + const nextEarning = this.safeAddAu( + allocatedEarnings.get(earningKey) ?? ZERO_AU, + allocation.au + ); + if (nextEarning instanceof Error) return nextEarning; + allocatedEarnings.set(earningKey, nextEarning); + } + for (const [key, total] of debitTotals) { + if (allocatedDebits.get(key) !== total) { + return new Error('Targeted epoch debit does not equal session allocations.'); + } + } + if (allocatedDebits.size !== debitTotals.size) { + return new Error('Targeted epoch allocation has no matching debit.'); + } + for (const [key, total] of earningTotals) { + if (allocatedEarnings.get(key) !== total) { + return new Error('Targeted epoch earning does not equal session allocations.'); + } + } + if (allocatedEarnings.size !== earningTotals.size) { + return new Error('Targeted epoch allocation has no matching earning.'); + } + return { + hold_updates: Array.from(holds.values()).map((hold) => ({ + key: this.targetedSpendHoldKey(hold.user, hold.rail), + value: hold, + })), + summary_updates: Array.from(summaries.values()).map((summary) => ({ + key: this.targetedSpendSummaryKey(summary.user, summary.rail), + value: summary, + })), + legacy_release_updates: Array.from(legacyReleases.values()).map((summary) => ({ + key: this.targetedSpendLegacyReleaseSummaryKey(summary.user, summary.rail), + value: summary, + })), + session_deletes: [...new Set(sessionDeletes)], + market_usage: Array.from(marketUsage.values()) + .sort((left, right) => ( + compareCodepoint(left.enclave_id, right.enclave_id) || + compareCodepoint(left.ctx_bracket ?? '', right.ctx_bracket ?? '') + )) + .map((entry) => ({ + enclave_id: entry.enclave_id, + ...(entry.ctx_bracket ? { ctx_bracket: entry.ctx_bracket } : {}), + ...(entry.ctx_bracket_table_ver ? { + ctx_bracket_table_ver: entry.ctx_bracket_table_ver, + } : {}), + demand_au: entry.demand_au, + settled_usage: entry.settled_usage, + compute_ms: entry.compute_ms, + legacy_receipt_count: entry.legacy_receipt_count, + session_count: entry.session_count, + providers: Array.from(entry.providers).sort(compareCodepoint), + provider_capacities: Array.from(entry.provider_capacities.entries()) + .sort(([left], [right]) => compareCodepoint(left, right)) + .map(([provider, capacity_slots]) => ({ provider, capacity_slots })), + })), + }; + } + + async spendReservationFeatureKey(value) { + const normalized = value.voucher_body ? value : await this.normalizeSpendReserveValue(value); + if (normalized instanceof Error) return normalized; + const digest = b4a.toString( + await blake3(b4a.from(stableJson({ + domain: 'mayhem-spend-reservation-feature-v1', + value: spendReservationEvidence(normalized), + }))), + 'hex' + ); + return `hold/reserve/${normalized.rail}/${normalized.user}/${normalized.epoch}/${normalized.session_id}/${digest}`; + } + + async activeParamsAt(at, keys = Object.keys(PARAM_DEFINITIONS)) { + const params = {}; + for (const key of keys) { + params[key] = this.paramActiveEntry(await this.paramRecord(key), at).value; + } + return params; + } + + async paramRecord(key) { + const existing = await this.get(`params/${key}`); + if (existing) return this.sanitizeMarketBoundRecord(key, existing); + return { + key, + current: { + value: PARAM_DEFINITIONS[key].default, + ver: 0, + submitted_at: 0, + effective_at: 0, + set_at: null, + }, + pending: null, + }; + } + + paramActiveEntry(record, at) { + if (record.pending && record.pending.effective_at <= at) return cloneValue(record.pending); + return cloneValue(record.current); + } + + validateParamValues(values) { + if (!values || typeof values !== 'object' || Array.isArray(values)) { + return new Error('Parameter values must be an object.'); + } + const keys = Object.keys(values); + if (keys.length === 0) return new Error('At least one parameter is required.'); + const keyError = this.validateParamKeys(keys); + if (keyError) return keyError; + + for (const key of keys) { + const value = values[key]; + const def = PARAM_DEFINITIONS[key]; + if (def.deprecated) return new Error(`Parameter ${key} is deprecated and read-only.`); + if (def.money) { + const au = this.normalizeAu(value, `Parameter ${key}`, { allowZero: def.min === ZERO_AU }); + if (au instanceof Error) return au; + if (this.compareAu(au, def.min) < 0) return new Error(`Parameter ${key} is out of range.`); + continue; + } + if (!Number.isInteger(value)) return new Error(`Parameter ${key} must be an integer.`); + if (value < def.min || value > def.max) return new Error(`Parameter ${key} is out of range.`); + } + return null; + } + + normalizeParamValues(values) { + const normalized = {}; + for (const [key, value] of Object.entries(values)) { + const def = PARAM_DEFINITIONS[key]; + normalized[key] = def.money + ? this.normalizeAu(value, `Parameter ${key}`, { allowZero: def.min === ZERO_AU }) + : value; + if (normalized[key] instanceof Error) return normalized[key]; + } + return normalized; + } + + validateParamKeys(keys) { + if (!Array.isArray(keys) || keys.length === 0 || keys.length > 64) { + return new Error('Invalid parameter keys.'); + } + for (const key of keys) { + if (!hasOwn(PARAM_DEFINITIONS, key)) return new Error(`Unknown parameter ${key}.`); + } + return null; + } + + validateParamBounds(params) { + if (params.price_min_bps > params.price_max_bps) { + return new Error('price_min_bps must not exceed price_max_bps.'); + } + return null; + } + + validateModelRef(value) { + if (!this.isSafeModelId(value.model_id)) return new Error('Invalid model id.'); + const classError = this.validateModelClass(this.modelClassFor(value), 'Model reference model_class'); + if (classError) return classError; + const rateError = this.validateRateMap(value.rate_map, this.modelClassFor(value), 'Model reference rate_map'); + if (rateError) return rateError; + if (value.source_hash !== undefined && !this.isSafeKeyPart(value.source_hash)) { + return new Error('Invalid model reference source hash.'); + } + if (value.activity_calibration !== undefined && value.activity_calibration !== null) { + return this.validateActivityCalibration(value.activity_calibration, this.modelClassFor(value), value.rate_map); + } + return null; + } + + normalizePaymentConfig(value) { + const shapeError = this.validateExactObjectKeys( + value, + ['op', 'ver', 'fiat', 'tap', 'tnk'], + 'payment config' + ); + if (shapeError) return shapeError; + if (value.op !== 'set_payments') return new Error('Invalid set_payments op.'); + if (!Number.isSafeInteger(value.ver) || value.ver <= 0) { + return new Error('Payment config version must be a positive safe integer.'); + } + + const fiatShape = this.validateExactObjectKeys( + value.fiat, + [ + 'processor', + 'integration_currency', + 'adaptive_pricing', + 'payout_currencies', + 'locale', + ], + 'fiat payment config' + ); + if (fiatShape) return fiatShape; + if (value.fiat.processor !== 'stripe') return new Error('Fiat processor must be stripe.'); + if (value.fiat.integration_currency !== 'usd') { + return new Error('Stripe integration currency must be usd for au_usd accounting.'); + } + if (value.fiat.adaptive_pricing !== true) { + return new Error('Stripe Adaptive Pricing must be enabled.'); + } + if (!Array.isArray(value.fiat.payout_currencies) || + value.fiat.payout_currencies.length < REQUIRED_FIAT_PAYOUT_CURRENCIES.length) { + return new Error('Fiat payout currencies must include EUR, GBP, and USD.'); + } + const payoutCurrencies = []; + for (const currency of value.fiat.payout_currencies) { + const normalized = this.normalizeFiatCurrency(currency); + if (normalized instanceof Error) return normalized; + if (normalized !== currency) { + return new Error('Fiat payout currencies must be canonical lowercase codes.'); + } + if (payoutCurrencies.includes(normalized)) { + return new Error('Duplicate fiat payout currency.'); + } + payoutCurrencies.push(normalized); + } + const sortedPayoutCurrencies = payoutCurrencies.slice().sort(compareCodepoint); + if (stableJson(payoutCurrencies) !== stableJson(sortedPayoutCurrencies)) { + return new Error('Fiat payout currencies must be sorted.'); + } + for (const required of REQUIRED_FIAT_PAYOUT_CURRENCIES) { + if (!payoutCurrencies.includes(required)) { + return new Error('Fiat payout currencies must include EUR, GBP, and USD.'); + } + } + if (value.fiat.locale !== 'en') return new Error('Stripe checkout locale must be en.'); + + const tapShape = this.validateExactObjectKeys( + value.tap, + ['chain_id', 'token_address', 'pool_address'], + 'TAP payment config' + ); + if (tapShape) return tapShape; + if (!Number.isSafeInteger(value.tap.chain_id) || value.tap.chain_id <= 0) { + return new Error('Invalid TAP chain id.'); + } + if (!this.isEthHexBytes(value.tap.token_address, 20)) { + return new Error('Invalid TAP token address.'); + } + if (!this.isEthHexBytes(value.tap.pool_address, 20)) { + return new Error('Invalid TAP pool address.'); + } + + const tnkShape = this.validateExactObjectKeys( + value.tnk, + ['network', 'treasury_address'], + 'TNK payment config' + ); + if (tnkShape) return tnkShape; + if (!['mainnet', 'testnet1'].includes(value.tnk.network)) { + return new Error('TNK network must be mainnet or testnet1.'); + } + if (typeof value.tnk.treasury_address !== 'string' || + !/^(trac1|testtrac1)[a-z0-9]{20,120}$/.test(value.tnk.treasury_address)) { + return new Error('Invalid TNK treasury address.'); + } + if (value.tnk.network === 'mainnet' && !value.tnk.treasury_address.startsWith('trac1')) { + return new Error('TNK mainnet treasury must use a trac1 address.'); + } + if (value.tnk.network === 'testnet1' && !value.tnk.treasury_address.startsWith('testtrac1')) { + return new Error('TNK testnet1 treasury must use a testtrac1 address.'); + } + + return { + fiat: { + processor: 'stripe', + integration_currency: 'usd', + adaptive_pricing: true, + payout_currencies: payoutCurrencies, + locale: 'en', + }, + tap: { + chain_id: value.tap.chain_id, + token_address: value.tap.token_address.toLowerCase(), + pool_address: value.tap.pool_address.toLowerCase(), + }, + tnk: { + network: value.tnk.network, + treasury_address: value.tnk.treasury_address, + }, + }; + } + + validateCatalogRelease(value) { + const releaseKeys = [ + 'op', + 'catalog_id', + 'source_kind', + 'catalog_url', + 'signature_url', + 'catalog_hash', + 'signature_hash', + 'key_id', + 'public_key', + 'model_count', + 'artifact_count', + 'canaries', + ]; + for (const optionalKey of ['parts_anchor', 'blessed_runtimes', 'outcome_classes']) { + if (hasOwn(value, optionalKey)) releaseKeys.push(optionalKey); + } + const shapeError = this.validateExactObjectKeys( + value, + releaseKeys, + 'catalog release' + ); + if (shapeError) return shapeError; + if (value.op !== 'publish_catalog') return new Error('Invalid publish_catalog op.'); + if (!this.isSafeKeyPart(value.catalog_id)) return new Error('Invalid catalog id.'); + if (!CATALOG_SOURCE_KINDS.has(value.source_kind)) { + return new Error('Unsupported catalog source kind.'); + } + if (!this.isHttpsUrl(value.catalog_url) || !this.isHttpsUrl(value.signature_url)) { + return new Error('Catalog release URLs must be HTTPS.'); + } + if (value.source_kind === 'huggingface') { + if (!this.isPinnedHuggingFaceResolveUrl(value.catalog_url)) { + return new Error('Hugging Face catalog URL must use huggingface.co/resolve/<40-hex-revision>/.'); + } + if (!this.isPinnedHuggingFaceResolveUrl(value.signature_url)) { + return new Error('Hugging Face catalog signature URL must use huggingface.co/resolve/<40-hex-revision>/.'); + } + } + if (!this.isHexBytes(value.catalog_hash, 32)) { + return new Error('Catalog hash must be a 32-byte hex BLAKE3 hash.'); + } + if (!this.isHexBytes(value.signature_hash, 32)) { + return new Error('Catalog signature hash must be a 32-byte hex BLAKE3 hash.'); + } + if (!this.isSafeKeyPart(value.key_id)) return new Error('Invalid catalog key id.'); + if (!this.isHexBytes(value.public_key, 32)) { + return new Error('Catalog public key must be 32-byte hex.'); + } + const seen = new Set(); + for (const entry of value.canaries) { + const entryError = this.validateCatalogCanaryRef(entry); + if (entryError) return entryError; + if (value.source_kind === 'huggingface' && !this.isPinnedHuggingFaceResolveUrl(entry.url)) { + return new Error('Hugging Face catalog canary URL must use huggingface.co/resolve/<40-hex-revision>/.'); + } + if (seen.has(entry.set_id)) return new Error('Duplicate catalog canary set.'); + seen.add(entry.set_id); + } + if (hasOwn(value, 'parts_anchor')) { + const partsAnchorError = this.validateCatalogPartsAnchor(value.parts_anchor); + if (partsAnchorError) return partsAnchorError; + } + if (hasOwn(value, 'blessed_runtimes')) { + if (!Array.isArray(value.blessed_runtimes)) { + return new Error('Catalog blessed_runtimes must be an array.'); + } + const runtimeIds = new Set(); + for (const entry of value.blessed_runtimes) { + const runtimeError = this.validateCatalogBlessedRuntime(entry); + if (runtimeError) return runtimeError; + if (runtimeIds.has(entry.runtime_id)) return new Error('Duplicate catalog blessed runtime id.'); + runtimeIds.add(entry.runtime_id); + } + } + if (hasOwn(value, 'outcome_classes')) { + if (!Array.isArray(value.outcome_classes)) { + return new Error('Catalog outcome_classes must be an array.'); + } + const classIds = new Set(); + for (const entry of value.outcome_classes) { + const classError = this.validateCatalogOutcomeClassRef(entry); + if (classError) return classError; + if (classIds.has(entry.class_id)) return new Error('Duplicate catalog outcome class id.'); + classIds.add(entry.class_id); + } + } + return null; + } + + validateCatalogPartsAnchor(anchor) { + const shapeError = this.validateExactObjectKeys( + anchor, + [ + 'index_ver', + 'source_kind', + 'index_url', + 'anchor_url', + 'anchor_hash', + 'index_root', + 'record_count', + 'repo_revision', + ], + 'catalog parts anchor' + ); + if (shapeError) return shapeError; + if (!Number.isSafeInteger(anchor.index_ver) || anchor.index_ver <= 0) { + return new Error('Catalog parts anchor index_ver must be a positive integer.'); + } + if (!CATALOG_SOURCE_KINDS.has(anchor.source_kind)) { + return new Error('Unsupported catalog parts anchor source kind.'); + } + if (!this.isHttpsUrl(anchor.index_url) || !this.isHttpsUrl(anchor.anchor_url)) { + return new Error('Catalog parts anchor URLs must be HTTPS.'); + } + if (anchor.source_kind === 'huggingface') { + const indexRevision = this.pinnedHuggingFaceResolveRevision(anchor.index_url); + const anchorRevision = this.pinnedHuggingFaceResolveRevision(anchor.anchor_url); + if (indexRevision === null) { + return new Error('Hugging Face parts index URL must use huggingface.co/resolve/<40-hex-revision>/.'); + } + if (anchorRevision === null) { + return new Error('Hugging Face parts anchor URL must use huggingface.co/resolve/<40-hex-revision>/.'); + } + if (indexRevision !== anchor.repo_revision || anchorRevision !== anchor.repo_revision) { + return new Error('Hugging Face parts anchor URLs must match repo_revision.'); + } + } else if (!this.isSafeExternalRef(anchor.repo_revision)) { + return new Error('Invalid catalog parts repo revision.'); + } + if (!this.isHexBytes(anchor.anchor_hash, 32)) { + return new Error('Catalog parts anchor hash must be a 32-byte hex hash.'); + } + if (!this.isHexBytes(anchor.index_root, 32)) { + return new Error('Catalog parts index root must be a 32-byte hex hash.'); + } + if (!Number.isSafeInteger(anchor.record_count) || anchor.record_count < 1) { + return new Error('Catalog parts anchor record_count must be a positive integer.'); + } + return null; + } + + validateCatalogBlessedRuntime(entry) { + const shapeError = this.validateExactObjectKeys( + entry, + [ + 'runtime_id', + 'comfy_release_hash', + 'env_lock_hash', + 'whitelist_ver', + 'status', + 'min_grace_epochs', + ], + 'catalog blessed runtime' + ); + if (shapeError) return shapeError; + if (!this.isSafeExternalRef(entry.runtime_id)) return new Error('Invalid catalog runtime id.'); + if (!this.isHexBytes(entry.comfy_release_hash, 32)) { + return new Error('Catalog runtime comfy_release_hash must be a 32-byte hex hash.'); + } + if (!this.isHexBytes(entry.env_lock_hash, 32)) { + return new Error('Catalog runtime env_lock_hash must be a 32-byte hex hash.'); + } + if (!Number.isSafeInteger(entry.whitelist_ver) || entry.whitelist_ver <= 0) { + return new Error('Catalog runtime whitelist_ver must be a positive integer.'); + } + if (!CATALOG_RUNTIME_STATUSES.has(entry.status)) return new Error('Invalid catalog runtime status.'); + if (!Number.isSafeInteger(entry.min_grace_epochs) || entry.min_grace_epochs < 0) { + return new Error('Catalog runtime min_grace_epochs must be a non-negative integer.'); + } + return null; + } + + validateCatalogOutcomeClassRef(entry) { + const shapeError = this.validateExactObjectKeys( + entry, + ['class_id', 'enclave_id', 'definition_hash', 'status'], + 'catalog outcome class' + ); + if (shapeError) return shapeError; + if (!this.isSafeExternalRef(entry.class_id)) return new Error('Invalid catalog outcome class id.'); + if (!this.isHexBytes(entry.enclave_id, 32)) return new Error('Invalid catalog outcome class enclave id.'); + if (!this.isHexBytes(entry.definition_hash, 32)) { + return new Error('Catalog outcome class definition_hash must be a 32-byte hex hash.'); + } + if (!CATALOG_OUTCOME_CLASS_STATUSES.has(entry.status)) { + return new Error('Invalid catalog outcome class status.'); + } + return null; + } + + validateCatalogCanaryRef(entry) { + const shapeError = this.validateExactObjectKeys( + entry, + ['set_id', 'url', 'hash', 'prompt_ids'], + 'catalog canary ref' + ); + if (shapeError) return shapeError; + if (!this.isSafeKeyPart(entry.set_id)) return new Error('Invalid catalog canary set id.'); + if (!this.isHttpsUrl(entry.url)) return new Error('Catalog canary URL must be HTTPS.'); + if (!this.isHexBytes(entry.hash, 32)) { + return new Error('Catalog canary hash must be a 32-byte hex BLAKE3 hash.'); + } + if (!Array.isArray(entry.prompt_ids) || entry.prompt_ids.length < 1 || entry.prompt_ids.length > 1_024) { + return new Error('Catalog canary prompt_ids must be a non-empty bounded array.'); + } + const promptIds = new Set(); + for (const promptId of entry.prompt_ids) { + if (!this.isSafeKeyPart(promptId)) return new Error('Invalid catalog canary prompt id.'); + if (promptIds.has(promptId)) return new Error('Duplicate catalog canary prompt id.'); + promptIds.add(promptId); + } + return null; + } + + validateEnclaveCaps(caps, modelClass = DEFAULT_MODEL_CLASS) { + if (!caps || typeof caps !== 'object' || Array.isArray(caps)) { + return new Error('Enclave caps must be an object.'); + } + const classError = this.validateModelClass(modelClass, 'Enclave caps model_class'); + if (classError) return classError; + const unknown = Object.keys(caps).filter((key) => !ENCLAVE_CAP_FIELDS.has(key)).sort(); + if (unknown.length > 0) { + return new Error(`Unsupported enclave caps field: ${unknown.join(', ')}.`); + } + for (const key of ENCLAVE_CAP_BOOLEAN_FIELDS) { + if (hasOwn(caps, key) && typeof caps[key] !== 'boolean') { + return new Error(`Enclave caps ${key} must be a boolean.`); + } + } + const hasCtx = hasOwn(caps, 'ctx'); + const hasCtxMax = hasOwn(caps, 'ctx_max'); + for (const key of ENCLAVE_CAP_INTEGER_FIELDS) { + if (hasOwn(caps, key) && (!Number.isSafeInteger(caps[key]) || caps[key] <= 0)) { + return new Error(`Enclave caps ${key} must be a positive integer.`); + } + } + if (hasOwn(caps, 'vllm_gpu_memory_utilization_pct') && caps.vllm_gpu_memory_utilization_pct > 100) { + return new Error('Enclave caps vllm_gpu_memory_utilization_pct must be between 1 and 100.'); + } + for (const key of ENCLAVE_CAP_STRING_FIELDS) { + if (hasOwn(caps, key) && (typeof caps[key] !== 'string' || caps[key].length === 0 || caps[key].length > 64)) { + return new Error(`Enclave caps ${key} must be a non-empty string with at most 64 characters.`); + } + } + if (hasCtx && hasCtxMax && caps.ctx !== caps.ctx_max) { + return new Error('Enclave caps ctx and ctx_max must match when both are set.'); + } + const modalitySetError = this.validateModalitySet(caps.modality_set, 'Enclave caps modality_set'); + if (modalitySetError) return modalitySetError; + const specialityLevelsError = this.validateSpecialityLevelMap( + caps.speciality_levels, + 'Enclave caps speciality_levels', + { allowEmpty: true } + ); + if (specialityLevelsError) return specialityLevelsError; + const coreModalities = this.coreModalitiesForModelClass(modelClass); + if ([...coreModalities].some((modality) => !caps.modality_set.includes(modality))) { + return new Error(`Enclave caps modality_set is missing a core modality for model_class ${modelClass}.`); + } + if (caps.vision === true && !caps.modality_set.includes('image')) { + return new Error('Enclave caps vision requires image in modality_set.'); + } + if (caps.audio === true && !caps.modality_set.includes('audio')) { + return new Error('Enclave caps audio requires audio in modality_set.'); + } + if (caps.video === true && !caps.modality_set.includes('video')) { + return new Error('Enclave caps video requires video in modality_set.'); + } + const allowedOutputModalities = MODEL_CLASS_OUTPUT_MODALITIES[modelClass] ?? new Set(); + const validateOutputModality = (modality, label) => { + if (typeof modality !== 'string' || modality.length === 0 || modality.length > 32) { + return new Error(`Enclave caps ${label} must be a non-empty string.`); + } + if (!CAP_OUTPUT_MODALITIES.has(modality)) { + return new Error(`Unsupported enclave caps ${label}: ${modality}.`); + } + if (!allowedOutputModalities.has(modality)) { + return new Error(`Enclave caps ${label} ${modality} is not allowed for model_class ${modelClass}.`); + } + return null; + }; + const outputModalities = new Set(); + if (hasOwn(caps, 'output_modality')) { + const error = validateOutputModality(caps.output_modality, 'output_modality'); + if (error) return error; + outputModalities.add(caps.output_modality); + } + if (hasOwn(caps, 'output_modalities')) { + if (!Array.isArray(caps.output_modalities) || caps.output_modalities.length === 0 || caps.output_modalities.length > 8) { + return new Error('Enclave caps output_modalities must be a non-empty array with at most 8 entries.'); + } + const seenOutputModalities = new Set(); + for (const modality of caps.output_modalities) { + const error = validateOutputModality(modality, 'output_modalities entry'); + if (error) return error; + if (seenOutputModalities.has(modality)) { + return new Error(`Enclave caps output_modalities has duplicate modality ${modality}.`); + } + seenOutputModalities.add(modality); + outputModalities.add(modality); + } + if (hasOwn(caps, 'output_modality') && !caps.output_modalities.includes(caps.output_modality)) { + return new Error('Enclave caps output_modalities must include output_modality.'); + } + } + for (const [flag, modality] of [['image', 'image'], ['video', 'video']]) { + if (caps[flag] === true && !allowedOutputModalities.has(modality)) { + return new Error(`Enclave caps ${flag} output is not allowed for model_class ${modelClass}.`); + } + } + return null; + } + + validateModalitySet(value, label) { + if (!Array.isArray(value) || value.length === 0 || value.length > 8) { + return new Error(`${label} must be a non-empty array with at most 8 entries.`); + } + const seen = new Set(); + for (const modality of value) { + if (typeof modality !== 'string' || !ENCLAVE_MODALITIES.has(modality)) { + return new Error(`${label} contains unsupported modality ${String(modality)}.`); + } + if (seen.has(modality)) return new Error(`${label} contains duplicate modality ${modality}.`); + seen.add(modality); + } + return null; + } + + validateSpecialityLevelMap(value, label, { allowEmpty = false } = {}) { + if (!value || typeof value !== 'object' || Array.isArray(value)) { + return new Error(`${label} must be an object.`); + } + const names = Object.keys(value); + if ((!allowEmpty && names.length === 0) || names.length > 16) { + return new Error(`${label} must contain between ${allowEmpty ? 0 : 1} and 16 specialities.`); + } + for (const name of names) { + if (!this.isSafeKeyPart(name) || name.length > 128) { + return new Error(`${label} contains invalid speciality ${String(name)}.`); + } + const levels = value[name]; + if (!Array.isArray(levels) || levels.length === 0 || levels.length > 16) { + return new Error(`${label} ${name} must contain between 1 and 16 levels.`); + } + const seen = new Set(); + for (const level of levels) { + if (typeof level !== 'string' || !this.isSafeKeyPart(level) || level.length > 128) { + return new Error(`${label} ${name} contains an invalid level.`); + } + if (seen.has(level)) return new Error(`${label} ${name} contains duplicate level ${level}.`); + seen.add(level); + } + } + return null; + } + + validateSpecialitySelection(value, label, { allowEmpty = true } = {}) { + if (!value || typeof value !== 'object' || Array.isArray(value)) { + return new Error(`${label} must be an object.`); + } + const names = Object.keys(value); + if ((!allowEmpty && names.length === 0) || names.length > 16) { + return new Error(`${label} must contain between ${allowEmpty ? 0 : 1} and 16 specialities.`); + } + for (const name of names) { + const level = value[name]; + if (!this.isSafeKeyPart(name) || name.length > 128) { + return new Error(`${label} contains an invalid speciality name.`); + } + if (typeof level !== 'string' || !this.isSafeKeyPart(level) || level.length > 128) { + return new Error(`${label} ${name} contains an invalid level.`); + } + } + return null; + } + + coreModalitiesForModelClass(modelClass) { + switch (modelClass) { + case DEFAULT_MODEL_CLASS: + return new Set(['text']); + case 'embedding': + return new Set(['embedding']); + case 'image-generation': + return new Set(['image']); + case 'video-generation': + return new Set(['video']); + case 'stt': + return new Set(['audio', 'text']); + case 'tts': + case 'audio-generation': + case 'music-generation': + return new Set(['audio']); + default: + return new Set(); + } + } + + validateEnclaveArtifactBinding(value) { + const classError = this.validateModelClass(this.modelClassFor(value), 'Enclave model_class'); + if (classError) return classError; + const backendError = this.validateEnclaveBackend(value.backend); + if (backendError) return backendError; + if (!this.isHexBytes(value.artifact_root, 32)) { + return new Error('Enclave artifact_root must be a 32-byte hex Merkle root.'); + } + if (value.artifact_root_kind !== ENCLAVE_ARTIFACT_ROOT_KIND) { + return new Error(`Enclave artifact_root_kind must be ${ENCLAVE_ARTIFACT_ROOT_KIND}.`); + } + if (!this.isHexBytes(value.manifest_hash, 32)) { + return new Error('Enclave manifest_hash must be 32-byte hex.'); + } + if (!this.isHexBytes(value.binary_hash, 32)) { + return new Error('Enclave binary_hash must be 32-byte hex.'); + } + if ( + value.source_sha256 !== undefined && + value.source_sha256 !== null && + !this.isHexBytes(value.source_sha256, 32) + ) { + return new Error('Enclave source_sha256 must be 32-byte hex.'); + } + const sourceError = this.validateHuggingFaceArtifactSource(value.artifact_source, 'enclave artifact_source'); + if (sourceError) return sourceError; + return this.validateEnclaveArtifactSidecars(value.artifact_sidecars ?? {}); + } + + validateEnclaveBackend(value) { + if ( + typeof value !== 'string' || + value.length === 0 || + value.length > ENCLAVE_BACKEND_MAX_LENGTH || + !ENCLAVE_BACKEND_PATTERN.test(value) + ) { + return new Error('Enclave backend must be a lowercase canonical identifier of at most 64 ASCII characters.'); + } + return null; + } + + normalizeApprovedBinaryHashes(primary, values) { + const hashes = [primary, ...(Array.isArray(values) ? values : [])] + .filter((value) => typeof value === 'string') + .map((value) => value.toLowerCase()); + return [...new Set(hashes)].sort(); + } + + validateApprovedBinaryHashes(primary, values) { + if (!Array.isArray(values) || values.length === 0) { + return new Error('Enclave approved_binary_hashes must be a non-empty array.'); + } + if (values.length > ENCLAVE_APPROVED_BINARY_HASHES_MAX) { + return new Error(`Enclave approved_binary_hashes may contain at most ${ENCLAVE_APPROVED_BINARY_HASHES_MAX} entries.`); + } + const normalizedPrimary = typeof primary === 'string' ? primary.toLowerCase() : primary; + const seen = new Set(); + for (const hash of values) { + if (!this.isHexBytes(hash, 32)) { + return new Error('Enclave approved_binary_hashes entries must be 32-byte hex.'); + } + const normalized = hash.toLowerCase(); + if (seen.has(normalized)) { + return new Error('Enclave approved_binary_hashes must not contain duplicates.'); + } + seen.add(normalized); + } + if (!seen.has(normalizedPrimary)) { + return new Error('Enclave approved_binary_hashes must include binary_hash.'); + } + return null; + } + + normalizeEnclaveLaunchMeasurements(value) { + if (value === undefined || value === null) return null; + if (!value || typeof value !== 'object' || Array.isArray(value)) return cloneValue(value); + if (hasOwn(value, 'layers')) { + return { + schema_version: value.schema_version ?? 1, + effective_epoch: value.effective_epoch ?? 0, + ...(hasOwn(value, 'platform') ? { platform: value.platform } : {}), + layers: cloneValue(value.layers), + }; + } + if (hasOwn(value, 'measurements')) { + return { + schema_version: value.schema_version ?? 1, + effective_epoch: value.effective_epoch ?? 0, + ...(hasOwn(value, 'platform') ? { platform: value.platform } : {}), + layers: { workload: cloneValue(value.measurements) }, + }; + } + const measurements = cloneValue(value); + delete measurements.schema_version; + delete measurements.effective_epoch; + delete measurements.platform; + delete measurements.layers; + return { + schema_version: 1, + effective_epoch: value.effective_epoch ?? 0, + ...(hasOwn(value, 'platform') ? { platform: value.platform } : {}), + layers: { workload: measurements }, + }; + } + + validateEnclaveLaunchMeasurements(value, attTier) { + if (attTier < 3 && (value === undefined || value === null)) return null; + if (attTier >= 3 && (value === undefined || value === null)) { + return new Error('Tier-3 enclaves require admin-published launch_measurements.'); + } + if (!value || typeof value !== 'object' || Array.isArray(value)) { + return new Error('Enclave launch_measurements must be an object.'); + } + const allowed = new Set(['schema_version', 'effective_epoch', 'platform', 'layers']); + const unknown = Object.keys(value).filter((key) => !allowed.has(key)).sort(); + if (unknown.length > 0) { + return new Error(`enclave launch measurements does not accept fields: ${unknown.join(', ')}.`); + } + if (value.schema_version !== 1) { + return new Error('Enclave launch_measurements schema_version must be 1.'); + } + if (!Number.isSafeInteger(value.effective_epoch) || value.effective_epoch < 0) { + return new Error('Enclave launch_measurements effective_epoch must be a non-negative integer.'); + } + if (attTier >= 3 && !hasOwn(value, 'platform')) { + return new Error('Tier-3 launch_measurements must include a platform label.'); + } + if (hasOwn(value, 'platform') && !this.isSafeKeyPart(value.platform)) { + return new Error('Enclave launch_measurements platform must be a safe label.'); + } + const layers = value.layers; + if (!layers || typeof layers !== 'object' || Array.isArray(layers)) { + return new Error('Enclave launch_measurements layers must be an object.'); + } + let count = 0; + let workloadCount = 0; + let names = 0; + for (const [layer, measurements] of Object.entries(layers)) { + if (!this.isTier3MeasurementLayer(layer)) { + return new Error('Enclave launch_measurements layers must be vendor or workload.'); + } + if (!measurements || typeof measurements !== 'object' || Array.isArray(measurements)) { + return new Error('Enclave launch_measurements layer values must be objects.'); + } + const layerNames = Object.keys(measurements); + const layerCount = this.countLaunchMeasurementValues(measurements); + names += layerNames.length; + count += layerCount; + if (layer === 'workload') workloadCount += layerCount; + for (const [name, measurement] of Object.entries(measurements)) { + if (!this.isSafeLaunchMeasurementName(name)) { + return new Error('Enclave launch_measurements names must be non-empty safe labels.'); + } + const measurementError = this.validateLaunchMeasurementValueList(name, measurement, `Enclave launch_measurements ${layer}`); + if (measurementError) return measurementError; + } + } + if (attTier >= 3 && count === 0) { + return new Error('Tier-3 enclaves require at least one launch measurement.'); + } + if (attTier >= 3 && workloadCount === 0) { + return new Error('Tier-3 enclaves require workload PCR/stack measurements.'); + } + if (names > TIER3_MEASUREMENT_MAX_NAMES) { + return new Error('Enclave launch_measurements may contain at most 32 measurements.'); + } + if (count > TIER3_MEASUREMENT_MAX_VALUES) { + return new Error('Enclave launch_measurements may contain at most 128 measurement values.'); + } + return null; + } + + countLaunchMeasurementValues(measurements) { + let count = 0; + for (const value of Object.values(measurements ?? {})) { + if (typeof value === 'string') count += 1; + else if (Array.isArray(value)) count += value.length; + else if (value && typeof value === 'object' && Array.isArray(value.values)) count += value.values.length; + else if (value && typeof value === 'object' && typeof value.measurement === 'string') count += 1; + } + return count; + } + + isSafeLaunchMeasurementName(value) { + return typeof value === 'string' && value.length > 0 && value.length <= 64 && /^[A-Za-z0-9_.:-]+$/.test(value); + } + + isTier3MeasurementLayer(value) { + return value === 'vendor' || value === 'workload'; + } + + validateLaunchMeasurementHex(label, measurement) { + if ( + typeof measurement !== 'string' || + !/^[0-9a-fA-F]+$/.test(measurement) || + measurement.length < 64 || + measurement.length > 256 || + measurement.length % 2 !== 0 + ) { + return new Error(`${label} must be a 32-128 byte hex value.`); + } + return null; + } + + validateLaunchMeasurementValueList(name, value, label) { + if (typeof value === 'string') { + return this.validateLaunchMeasurementHex(`${label} ${name}`, value); + } + if (Array.isArray(value)) { + if (value.length === 0) return new Error(`${label} ${name} must not be empty.`); + for (const item of value) { + const measurement = typeof item === 'string' ? item : item?.measurement; + const error = this.validateLaunchMeasurementHex(`${label} ${name}`, measurement); + if (error) return error; + } + return null; + } + if (value && typeof value === 'object' && !Array.isArray(value)) { + if (Array.isArray(value.values)) return this.validateLaunchMeasurementValueList(name, value.values, label); + return this.validateLaunchMeasurementHex(`${label} ${name}`, value.measurement); + } + return new Error(`${label} ${name} must be a 32-128 byte hex value or array of values.`); + } + + validateTier3MeasurementBlessValue(value) { + if (!value || typeof value !== 'object' || Array.isArray(value)) { + return new Error('Tier-3 measurement blessing value must be an object.'); + } + const required = ['op', 'platform', 'layer', 'measurement_name', 'measurement', 'effective_epoch', 'at']; + const allowed = new Set([...required, 'region', 'derivation_hash', 'source']); + const unknown = Object.keys(value).filter((key) => !allowed.has(key)).sort(); + if (unknown.length > 0) { + return new Error(`tier3 measurement blessing does not accept fields: ${unknown.join(', ')}.`); + } + for (const key of required) { + if (!hasOwn(value, key)) return new Error(`Tier-3 measurement blessing is missing ${key}.`); + } + if (value.op !== 'tier3_bless_measurement') return new Error('Invalid Tier-3 measurement blessing op.'); + if (!this.isSafeKeyPart(value.platform)) return new Error('Invalid Tier-3 platform.'); + if (!this.isTier3MeasurementLayer(value.layer)) return new Error('Invalid Tier-3 measurement layer.'); + if (!this.isSafeLaunchMeasurementName(value.measurement_name)) return new Error('Invalid Tier-3 measurement name.'); + const measurementError = this.validateLaunchMeasurementHex('Tier-3 measurement', value.measurement); + if (measurementError) return measurementError; + if (!Number.isSafeInteger(value.effective_epoch) || value.effective_epoch < 0) { + return new Error('Invalid Tier-3 measurement effective epoch.'); + } + if (!Number.isSafeInteger(value.at) || value.at < 0) { + return new Error('Invalid Tier-3 measurement timestamp.'); + } + if (hasOwn(value, 'region') && value.region !== null && !this.isSafeKeyPart(value.region)) { + return new Error('Invalid Tier-3 measurement region.'); + } + if (hasOwn(value, 'derivation_hash') && value.derivation_hash !== null && !this.isHexBytes(value.derivation_hash, 32)) { + return new Error('Invalid Tier-3 derivation hash.'); + } + if (hasOwn(value, 'source') && (typeof value.source !== 'string' || value.source.length < 1 || value.source.length > 64)) { + return new Error('Invalid Tier-3 measurement source.'); + } + return null; + } + + normalizeEnclaveQuant(value) { + const normalized = String(value ?? 'unknown').trim().toLowerCase().replace(/_/g, '-'); + if (ENCLAVE_QUANT_BUCKETS.has(normalized)) return normalized; + return this.quantBucketFromDescriptor(normalized); + } + + quantBucketFromDescriptor(descriptor) { + const normalized = String(descriptor).toLowerCase().replace(/_/g, '-'); + const tokens = normalized.split(/[^a-z0-9]+/); + if (normalized.includes('nvfp4')) return 'nvfp4'; + if (normalized.includes('mxfp8')) return 'mxfp8'; + if (normalized.includes('mxfp6')) return 'mxfp6'; + if (normalized.includes('mxfp4')) return 'mxfp4'; + if (tokens.includes('nf4')) return 'nf4'; + if (normalized.includes('fp8')) return 'fp8'; + if (normalized.includes('fp6')) return 'fp6'; + if (normalized.includes('fp4')) return 'fp4'; + if (normalized.includes('bf16')) return 'bf16'; + if (normalized.includes('fp16') || normalized.includes('f16')) return 'fp16'; + if (normalized.includes('tf32')) return 'tf32'; + if (normalized.includes('fp32') || normalized.includes('f32')) return 'fp32'; + if (normalized.includes('fp64') || normalized.includes('f64')) return 'fp64'; + for (let bits = 8; bits >= 1; bits -= 1) { + const aliases = new Set([`int${bits}`, `${bits}bit`, `q${bits}`, `iq${bits}`, `tq${bits}`]); + if (tokens.some((token) => aliases.has(token))) return `int${bits}`; + } + return normalized; + } + + validateEnclaveQuant(value) { + if (typeof value !== 'string') return new Error('Enclave quant must be a string.'); + const quant = this.normalizeEnclaveQuant(value); + if (quant.length > ENCLAVE_QUANT_BUCKET_MAX_LENGTH || !ENCLAVE_QUANT_BUCKET_PATTERN.test(quant)) { + return new Error('Enclave quant must be a lowercase canonical identifier of at most 32 ASCII characters.'); + } + return null; + } + + validateEnclaveArtifactSidecars(sidecars) { + if (!sidecars || typeof sidecars !== 'object' || Array.isArray(sidecars)) { + return new Error('Enclave artifact_sidecars must be an object.'); + } + const names = Object.keys(sidecars).sort(); + if (names.length > ENCLAVE_ARTIFACT_SIDECARS_MAX) { + return new Error('Enclave artifact_sidecars has too many entries.'); + } + for (const name of names) { + if (!this.isSafeHuggingFacePathSegment(name)) { + return new Error('Enclave artifact_sidecars keys must be safe names.'); + } + const sidecar = sidecars[name]; + const shapeError = this.validateExactObjectKeys( + sidecar, + ['source', 'path', 'artifact_root', 'artifact_root_kind', 'weights_bytes', 'source_sha256'], + `enclave artifact_sidecars.${name}` + ); + if (shapeError) return shapeError; + const sourceError = this.validateHuggingFaceArtifactSource( + sidecar.source, + `enclave artifact_sidecars.${name}.source` + ); + if (sourceError) return sourceError; + if (!this.isSafeHuggingFacePath(sidecar.path)) { + return new Error(`Enclave artifact_sidecars.${name}.path must be a safe relative Hugging Face artifact path.`); + } + if (sidecar.source.path !== sidecar.path) { + return new Error(`Enclave artifact_sidecars.${name}.source.path must match path.`); + } + if (!this.isHexBytes(sidecar.artifact_root, 32)) { + return new Error(`Enclave artifact_sidecars.${name}.artifact_root must be a 32-byte hex Merkle root.`); + } + if (sidecar.artifact_root_kind !== ENCLAVE_ARTIFACT_ROOT_KIND) { + return new Error(`Enclave artifact_sidecars.${name}.artifact_root_kind must be ${ENCLAVE_ARTIFACT_ROOT_KIND}.`); + } + if (!Number.isSafeInteger(sidecar.weights_bytes) || sidecar.weights_bytes <= 0) { + return new Error(`Enclave artifact_sidecars.${name}.weights_bytes must be a positive integer.`); + } + if (!this.isHexBytes(sidecar.source_sha256, 32)) { + return new Error(`Enclave artifact_sidecars.${name}.source_sha256 must be 32-byte hex.`); + } + } + return null; + } + + validateHuggingFaceArtifactSource(source, label = 'enclave artifact_source') { + const shapeError = this.validateExactObjectKeys( + source, + ['kind', 'repo', 'revision', 'path'], + label + ); + if (shapeError) return shapeError; + if (source.kind !== 'huggingface') { + return new Error(`${label}.kind must be huggingface.`); + } + if (!this.isSafeHuggingFaceRepo(source.repo)) { + return new Error(`${label}.repo must be a safe namespace/name repo id.`); + } + if (!this.isHexBytes(source.revision, 20)) { + return new Error(`${label}.revision must be a 20-byte git commit hex.`); + } + if (!this.isSafeHuggingFacePath(source.path)) { + return new Error(`${label}.path must be a safe relative Hugging Face artifact path.`); + } + return null; + } + + validateRoomPolicy(policy) { + if (!policy || typeof policy !== 'object' || Array.isArray(policy)) { + return new Error('Room policy must be an object.'); + } + const unknown = Object.keys(policy).filter((key) => !ROOM_POLICY_FIELDS.has(key)).sort(); + if (unknown.length > 0) { + return new Error(`Unsupported room policy field: ${unknown.join(', ')}.`); + } + for (const key of ['region_hint', 'canary_set']) { + if ( + hasOwn(policy, key) && + ( + typeof policy[key] !== 'string' || + policy[key].length === 0 || + policy[key].length > 128 + ) + ) { + return new Error(`Room policy ${key} must be a non-empty string.`); + } + } + if ( + hasOwn(policy, 'min_reputation') && + ( + typeof policy.min_reputation !== 'number' || + !Number.isFinite(policy.min_reputation) || + policy.min_reputation < 0 || + policy.min_reputation > 1 + ) + ) { + return new Error('Room policy min_reputation must be between 0 and 1.'); + } + if ( + hasOwn(policy, 'max_price_mult') && + ( + typeof policy.max_price_mult !== 'number' || + !Number.isFinite(policy.max_price_mult) || + policy.max_price_mult <= 0 + ) + ) { + return new Error('Room policy max_price_mult must be positive.'); + } + return null; + } + + async priceSchedule(key, enclave, ctxMeta = null) { + const existing = await this.get(key); + if (existing) return existing; + return { + enclave_id: enclave.enclave_id, + model_id: enclave.model_id, + denom: PRICE_DENOMINATION, + ...(ctxMeta ? { + ctx_bracket: ctxMeta.ctx_bracket, + ctx_bracket_table_ver: ctxMeta.ctx_bracket_table_ver, + } : {}), + current: null, + pending: null, + }; + } + + priceScheduleAt(schedule, at) { + const updated = cloneValue(schedule); + if (updated.pending && updated.pending.effective_at <= at) { + updated.current = updated.pending; + updated.pending = null; + } + return updated; + } + + priceActiveEntry(schedule, at) { + if (schedule.pending && schedule.pending.effective_at <= at) return cloneValue(schedule.pending); + return schedule.current ? cloneValue(schedule.current) : null; + } + + priceLatestEntry(schedule) { + return schedule.pending ?? schedule.current; + } + + priceSeedSnapshot(record) { + if (!record) return null; + return { + enclave_id: record.enclave_id, + model_id: record.model_id, + denom: record.denom, + ver: record.seed_ver ?? record.ver, + ...(record.ctx_bracket ? { ctx_bracket: record.ctx_bracket } : {}), + ...(record.ctx_bracket_table_ver ? { ctx_bracket_table_ver: record.ctx_bracket_table_ver } : {}), + rate_map: cloneValue(record.seed_rate_map ?? record.rate_map), + per_req_au: record.seed_per_req_au ?? record.per_req_au, + min_session_au: record.seed_min_session_au ?? record.min_session_au, + effective_at: record.seed_effective_at ?? record.effective_at, + effective_from: record.seed_effective_from ?? record.effective_from, + updated_at: record.seed_updated_at ?? record.updated_at, + set_by: record.seed_by ?? record.set_by, + set_by_role: record.seed_by_role ?? record.set_by_role, + }; + } + + priceSeedEntry(record) { + return record?.seed ? cloneValue(record.seed) : this.priceSeedSnapshot(record); + } + + priceLatestSeedEntry(schedule) { + if (schedule.pending) return this.priceSeedEntry(schedule.pending); + if (schedule.current) return this.priceSeedEntry(schedule.current); + return null; + } + + sanitizeMarketBoundRecord(key, record) { + if (key !== 'price_min_bps' && key !== 'price_max_bps') return record; + const safe = (entry) => Number.isSafeInteger(entry?.value) && + entry.value >= 2_500 && entry.value <= 40_000; + const next = cloneValue(record); + if (!safe(next.current)) next.current = { + ...next.current, value: PARAM_DEFINITIONS[key].default, + policy_repair: 'market_activity_v2_hard_bounds', + }; + if (next.pending && !safe(next.pending)) next.pending = null; + return next; + } + + async migrateMarketPricing() { + const adminError = await this.requireAdmin(); + if (adminError) return adminError; + const shape = this.validateExactCommandValue(['op', 'at', 'markets'], 'migrate_market_pricing'); + if (shape) return shape; + if (!Number.isSafeInteger(this.value.at) || this.value.at < 0 || + !Array.isArray(this.value.markets) || this.value.markets.length > 128) { + return new Error('Migration requires a timestamp and at most 128 active market rows.'); + } + const key = 'market/activity/migration-v3'; + const existing = await this.get(key); + const state = await this.epochApplyStateRecord(); + if (state.pending_epoch !== null && state.pending_epoch !== undefined) { + return new Error('Market pricing migration requires a completed epoch boundary.'); + } + const priorIndex = await this.get('market/activity/index') ?? []; + const index = new Map(priorIndex.map((row) => [ + this.priceMarketKey(row.enclave_id, row.ctx_bracket ?? null), row, + ])); + const seen = new Set(); + for (const row of this.value.markets) { + const fields = ['enclave_id', ...(row?.ctx_bracket !== undefined + ? ['ctx_bracket', 'ctx_bracket_table_ver'] : [])]; + const rowShape = this.validateExactObjectKeys(row, fields, 'Migration market'); + if (rowShape) return rowShape; + if (!this.isSafeKeyPart(row.enclave_id)) return new Error('Invalid migration market enclave.'); + const enclave = await this.get(`enclave/${row.enclave_id}`); + if (!enclave || enclave.status !== 'active') return new Error('Migration market enclave is not active.'); + const ctx = await this.priceCtxMetaForEnclave(enclave, row.ctx_bracket, this.value.at, 'Migration market'); + if (ctx instanceof Error) return ctx; + if ((ctx?.ctx_bracket_table_ver ?? null) !== (row.ctx_bracket_table_ver ?? null)) { + return new Error('Migration market context table version is not active.'); + } + const marketKey = this.priceMarketKey(row.enclave_id, row.ctx_bracket ?? null); + if (seen.has(marketKey)) return new Error('Duplicate migration market.'); + seen.add(marketKey); + const schedule = await this.priceSchedule(this.priceScheduleKey(row.enclave_id, row.ctx_bracket ?? null), enclave, ctx); + const price = this.priceActiveEntry(schedule, this.value.at); + if (!price || this.priceSeedEntry(price)?.set_by_role !== 'admin' || + !(await this.get(`modelref/${enclave.model_id}`))) { + return new Error('Migration market requires an active admin price and model reference.'); + } + index.set(marketKey, cloneValue(row)); + } + if (index.size > DEFAULT_MAX_MARKET_USAGE_ENTRIES) return new Error('Migration market index capacity exceeded.'); + const repairs = []; + for (const name of ['price_min_bps', 'price_max_bps']) { + const before = await this.get(`params/${name}`); + if (!before) continue; + const after = this.sanitizeMarketBoundRecord(name, before); + if (stableJson(before) !== stableJson(after)) repairs.push({ key: name, before, after }); + } + const nextIndex = Array.from(index.values()).sort((a, b) => + compareCodepoint(a.enclave_id, b.enclave_id) || compareCodepoint(a.ctx_bracket ?? '', b.ctx_bracket ?? '')); + if (existing && repairs.length === 0 && stableJson(priorIndex) === stableJson(nextIndex)) { + return { ok: true, op: 'migrateMarketPricing', idempotent: true, market_count: index.size }; + } + // Complete validation before any writes. Historical params/update records stay immutable. + for (const repair of repairs) await this.put(`params/${repair.key}`, repair.after); + await this.put('market/activity/index', nextIndex); + await this.put(key, { + schema_version: 3, contract_version: CONTRACT_VERSION, + hard_min_bps: 2_500, hard_max_bps: 40_000, + low_utilization_bps: MARKET_UTILIZATION_LOW_BPS, + high_utilization_bps: MARKET_UTILIZATION_HIGH_BPS, + price_step_bps: MARKET_UTILIZATION_STEP_BPS, + previous_applied_epoch: state.updated_epoch ?? 0, + repairs: [...(existing?.repairs ?? []), ...repairs], market_count: index.size, + migrated_at: existing?.migrated_at ?? this.tx, updated_at: this.tx, migrated_by: this.address, + }); + return { ok: true, op: 'migrateMarketPricing', idempotent: false, repaired: repairs.length, market_count: index.size }; + } + + validateActivityCalibration(value, modelClass, rateMap = null) { + const shape = this.validateExactObjectKeys(value, + ['schema_version', 'source_hash', 'dimensions'], 'Activity calibration'); + if (shape) return shape; + if (value.schema_version !== 1 || !this.isHexBytes(value.source_hash, 32) || + !Array.isArray(value.dimensions) || value.dimensions.length < 1 || + value.dimensions.length > RATE_MAP_MAX_ENTRIES) { + return new Error('Invalid activity calibration metadata.'); + } + const allowed = MODEL_CLASS_RATE_UNITS[modelClass]; + const seen = new Set(); + let positive = false; + for (const row of value.dimensions) { + const error = this.validateExactObjectKeys(row, ['unit', 'units', 'work_us'], + 'Activity calibration dimension'); + if (error) return error; + if (!allowed?.has(row.unit) || seen.has(row.unit) || + !/^[1-9][0-9]{0,17}$/.test(row.units) || + !/^[1-9][0-9]{0,17}$/.test(row.work_us)) { + return new Error('Invalid activity calibration dimension.'); + } + seen.add(row.unit); + positive ||= BigInt(row.work_us) > 0n; + if ((row.unit === 'input_token' || row.unit === 'output_token') && + BigInt(row.work_us) === 0n) { + return new Error('Token activity calibration requires positive prefill/decode work.'); + } + } + if (rateMap && stableJson([...seen].sort(compareCodepoint)) !== + stableJson(rateMap.map((row) => row.unit).sort(compareCodepoint))) { + return new Error('Activity calibration must cover every model reference rate unit exactly.'); + } + if (!positive || (modelClass === DEFAULT_MODEL_CLASS && + (!seen.has('input_token') || !seen.has('output_token')))) { + return new Error('Activity calibration requires calibrated workload dimensions.'); + } + if (stableJson(value.dimensions) !== stableJson(value.dimensions.slice().sort( + (a, b) => compareCodepoint(a.unit, b.unit)))) { + return new Error('Activity calibration dimensions must be sorted.'); + } + return null; + } + + incrementalSettledUsage(body) { + const usage = this.normalizeReceiptUsage(body.usage); + const prior = this.normalizeReceiptUsage(body.billing_prior_usage); + if (usage instanceof Error || prior instanceof Error) { + return new Error('Canonical receipt activity usage is invalid.'); + } + const billed = this.normalizeLockedRateMap(body.locked_rate_map, 'activity locked rates'); + if (billed instanceof Error) return billed; + const paidUnits = new Set(billed.map((row) => row.unit)); + const result = {}; + for (const unit of new Set([...Object.keys(usage), ...Object.keys(prior)])) { + const count = BigInt(usage[unit] ?? 0) - BigInt(prior[unit] ?? 0); + if (count < 0n) return new Error('Canonical receipt activity regressed below billing baseline.'); + if (count > 0n && paidUnits.has(unit)) result[unit] = count.toString(); + } + return stableValue(result); + } + + addSettledUsage(left, right) { + if (!left || !right || typeof left !== 'object' || typeof right !== 'object' || + Array.isArray(left) || Array.isArray(right)) return new Error('Invalid settled activity units.'); + const result = {}; + for (const unit of new Set([...Object.keys(left), ...Object.keys(right)])) { + if (!this.isSafeKeyPart(unit)) return new Error('Invalid settled activity unit.'); + const a = this.parseAu(left[unit] ?? '0', 'settled activity count'); + const b = this.parseAu(right[unit] ?? '0', 'settled activity count'); + if (a instanceof Error || b instanceof Error) return new Error('Invalid settled activity count.'); + const sum = this.safeAddAu(a.toString(), b.toString()); + if (sum instanceof Error) return sum; + if (sum !== '0') result[unit] = sum; + } + return stableValue(result); + } + + calibratedActivityWork(usage, calibration) { + const dimensions = new Map(calibration.dimensions.map((row) => [row.unit, row])); + let work = 0n; + for (const [unit, count] of Object.entries(usage)) { + const dimension = dimensions.get(unit); + if (!dimension) return new Error('Settled unit is missing its signed activity calibration.'); + const n = this.parseAu(count, 'settled activity count'); + if (n instanceof Error) return n; + // Round once per epoch/axis, not per receipt or page (split-resistant). + work += (n * BigInt(dimension.work_us) * 1_000_000n) / BigInt(dimension.units); + } + return work.toString(); // picoseconds of calibrated reference work. + } + + async activityMarketEntries(usageMap, includeDormant) { + const entries = this.mapMarketUsageEntriesForHash(usageMap); + const known = await this.get('market/activity/index') ?? []; + if (!Array.isArray(known) || known.length > DEFAULT_MAX_MARKET_USAGE_ENTRIES) { + return new Error('Market activity index exceeds its deterministic bound.'); + } + const keys = new Set(entries.map((row) => this.priceMarketKey(row.enclave_id, row.ctx_bracket ?? null))); + for (const row of known) { + const key = this.priceMarketKey(row.enclave_id, row.ctx_bracket ?? null); + if (includeDormant && !keys.has(key)) entries.push({ + ...row, + demand_au: '0', + session_count: 0, + provider_count: 0, + compute_ms: '0', + capacity_slot_count: 0, + legacy_receipt_count: 0, + _activity_dormant: true, + }); + } + if (new Set([...keys, ...known.map((row) => this.priceMarketKey(row.enclave_id, row.ctx_bracket ?? null))]).size > DEFAULT_MAX_MARKET_USAGE_ENTRIES) { + return new Error('Market activity index capacity exceeded.'); + } + return entries.sort((a, b) => compareCodepoint(a.enclave_id, b.enclave_id) || + compareCodepoint(a.ctx_bracket ?? '', b.ctx_bracket ?? '')); + } + + async writeActivityMarketIndex(updates) { + const index = new Map((await this.get('market/activity/index') ?? []).map((row) => [ + this.priceMarketKey(row.enclave_id, row.ctx_bracket ?? null), row, + ])); + for (const update of updates) { + if (!update.record.market.activity_initialized) continue; + index.set(update.market_key, { + enclave_id: update.enclave_id, + ...(update.ctx_bracket ? { ctx_bracket: update.ctx_bracket, + ctx_bracket_table_ver: update.ctx_bracket_table_ver } : {}), + }); + } + if (index.size > DEFAULT_MAX_MARKET_USAGE_ENTRIES) return new Error('Market activity index overflow.'); + if (updates.length) await this.put('market/activity/index', Array.from(index.values()).sort( + (a, b) => compareCodepoint(a.enclave_id, b.enclave_id) || + compareCodepoint(a.ctx_bracket ?? '', b.ctx_bracket ?? ''))); + } + + marketPriceParamKeys() { + return ['price_min_bps', 'price_max_bps']; + } + + marketPriceConstants() { + return { + schema_version: 3, + low_utilization_bps: MARKET_UTILIZATION_LOW_BPS, + high_utilization_bps: MARKET_UTILIZATION_HIGH_BPS, + price_step_bps: MARKET_UTILIZATION_STEP_BPS, + }; + } + + marketUtilizationBps(computeMs, capacitySlotCount, epochSeconds) { + const busy = this.parseAu(computeMs, 'market compute duration'); + if (busy instanceof Error || !Number.isSafeInteger(capacitySlotCount) || + capacitySlotCount < 0 || !Number.isSafeInteger(epochSeconds) || epochSeconds < 1) { + return new Error('Invalid market utilization evidence.'); + } + if (capacitySlotCount === 0) { + return busy === 0n ? 0 : new Error('Market compute duration requires execution capacity.'); + } + const available = BigInt(capacitySlotCount) * BigInt(epochSeconds) * 1_000n; + const utilization = busy * 10_000n / available; + return Number(utilization > 10_000n ? 10_000n : utilization); + } + + marketUtilizationMultiplier(utilizationBps) { + if (!Number.isSafeInteger(utilizationBps) || utilizationBps < 0 || utilizationBps > 10_000) { + return new Error('Invalid market utilization.'); + } + if (utilizationBps >= MARKET_UTILIZATION_HIGH_BPS) { + return 10_000 + MARKET_UTILIZATION_STEP_BPS; + } + if (utilizationBps <= MARKET_UTILIZATION_LOW_BPS) { + return 10_000 - MARKET_UTILIZATION_STEP_BPS; + } + return 10_000; + } + + scalePriceTerm(term, multiplierBps) { + const amount = this.parseAu(term, 'price term'); + if (amount instanceof Error || !Number.isSafeInteger(multiplierBps) || multiplierBps < 0) { + return new Error('Invalid price term.'); + } + if (amount === 0n) return ZERO_AU; + const scaled = (amount * BigInt(multiplierBps) + 5_000n) / 10_000n; + return this.canonicalAu(scaled > 0n ? scaled : 1n); + } + + scaleRateMap(rateMap, multiplierBps) { + const scaled = []; + for (const entry of rateMap) { + const perUnitAu = this.scalePriceTerm(entry.per_unit_au, multiplierBps); + if (perUnitAu instanceof Error) return perUnitAu; + scaled.push({ ...entry, per_unit_au: perUnitAu }); + } + return this.normalizeRateMap(scaled); + } + + clampRateMapBounds(priceRateMap, referenceRateMap, params) { + const priceByUnit = this.rateMapByUnit(priceRateMap); + const referenceByUnit = this.rateMapByUnit(referenceRateMap); + if (priceByUnit.size !== referenceByUnit.size) { + return new Error('Market price rate_map units must match model reference rate_map units.'); + } + const bounded = []; + for (const entry of priceRateMap) { + const reference = referenceByUnit.get(entry.unit); + const price = this.parseAu(entry.per_unit_au, 'market price per_unit_au'); + const referencePrice = this.parseAu( + reference?.per_unit_au, + 'market reference per_unit_au', + { allowZero: false } + ); + if ( + price instanceof Error || + referencePrice instanceof Error || + !Number.isSafeInteger(entry.granularity) || + entry.granularity <= 0 || + !Number.isSafeInteger(reference?.granularity) || + reference.granularity <= 0 + ) { + return new Error('Invalid market price rate_map bounds.'); + } + const denominator = BigInt(reference.granularity) * 10_000n; + const scaledReference = referencePrice * BigInt(entry.granularity); + const lowerNumerator = scaledReference * BigInt(Math.max(2_500, params.price_min_bps)); + const upperNumerator = scaledReference * BigInt(Math.min(40_000, params.price_max_bps)); + const lower = (lowerNumerator + denominator - 1n) / denominator; + const upper = upperNumerator / denominator; + if (lower > upper) return new Error(`Model reference bounds cannot represent unit ${entry.unit}.`); + const clamped = price < lower ? lower : (price > upper ? upper : price); + bounded.push({ ...entry, per_unit_au: this.canonicalAu(clamped) }); + } + return this.normalizeRateMap(bounded); + } + + priceTermsEqual(left, right) { + return ( + stableJson(left.rate_map) === stableJson(right.rate_map) && + this.compareAu(left.per_req_au, right.per_req_au) === 0 && + this.compareAu(left.min_session_au, right.min_session_au) === 0 + ); + } + + async computeMarketPriceUpdates(marketUsageMap, context = {}) { + const at = context.at ?? this.value.at; + const epoch = context.epoch ?? this.value.epoch; + const epochSeconds = context.epochSeconds ?? null; + const tx = context.tx ?? this.tx; + if (!Number.isSafeInteger(epochSeconds) || epochSeconds < 1) { + return new Error('Market activity requires a calibrated epoch duration.'); + } + const marketParams = await this.activeParamsAt(at, this.marketPriceParamKeys()); + const constants = this.marketPriceConstants(marketParams); + const entries = await this.activityMarketEntries(marketUsageMap, context.includeDormant === true); + if (entries instanceof Error) return entries; + const updates = []; + for (const usage of entries) { + const marketKey = this.priceMarketKey(usage.enclave_id, usage.ctx_bracket ?? null); + const enclave = await this.get(`enclave/${usage.enclave_id}`); + // Retired markets have no new orders and leave their immutable history intact. + if ((!enclave || enclave.status !== 'active') && usage.session_count === 0) continue; + if (!enclave || enclave.status !== 'active') return new Error('Market usage enclave is not active.'); + const ctxMeta = await this.priceCtxMetaForEnclave(enclave, usage.ctx_bracket, at, 'Market usage'); + if (ctxMeta instanceof Error) { + if (usage._activity_dormant) continue; + return ctxMeta; + } + if (ctxMeta && usage.ctx_bracket_table_ver !== undefined && usage.ctx_bracket_table_ver !== ctxMeta.ctx_bracket_table_ver) { + if (usage._activity_dormant) continue; + return new Error('Market usage context bracket table version is not active for the epoch.'); + } + const scheduleKey = this.priceScheduleKey(usage.enclave_id, ctxMeta?.ctx_bracket ?? null); + const schedule = this.priceScheduleAt(await this.priceSchedule(scheduleKey, enclave, ctxMeta), at); + const current = this.priceActiveEntry(schedule, at); + if (!current) return new Error('Market usage enclave has no admin price seed.'); + const seed = this.priceSeedEntry(current); + if (!seed || seed.set_by_role !== 'admin') return new Error('Market price requires an admin seed.'); + const modelRef = await this.get(`modelref/${enclave.model_id}`); + if (!modelRef) return new Error('Market price model reference not found.'); + const previousMarket = current.market ?? {}; + if (Number.isSafeInteger(previousMarket.epoch) && previousMarket.epoch >= epoch) { + return new Error('Market activity epoch must increase exactly once per settled epoch.'); + } + const canonical = context.canonicalActivity?.get(marketKey) ?? + (context.includeDormant && usage.session_count === 0 + ? { settled_usage: {}, compute_ms: '0', capacity_slot_count: 0, + legacy_receipt_count: 0 } + : usage); + const settledUsage = canonical?.settled_usage ?? {}; + const computeMs = canonical?.compute_ms ?? usage.compute_ms; + const capacitySlotCount = canonical?.capacity_slot_count ?? usage.capacity_slot_count; + const legacyReceiptCount = canonical?.legacy_receipt_count ?? + usage.legacy_receipt_count; + if (canonical && canonical.session_count !== undefined && + (canonical.session_count !== usage.session_count || + canonical.demand_au !== usage.demand_au || + canonical.compute_ms !== usage.compute_ms || + canonical.capacity_slot_count !== usage.capacity_slot_count || + (canonical.legacy_receipt_count ?? 0) !== usage.legacy_receipt_count || + (canonical.provider_count ?? canonical.providers?.length) !== usage.provider_count)) { + return new Error('Market activity totals do not match canonical receipt evidence.'); + } + if (computeMs === undefined || capacitySlotCount === undefined || + !Number.isSafeInteger(legacyReceiptCount) || legacyReceiptCount < 0) { + return new Error('Market utilization requires canonical signed compute evidence.'); + } + const legacyHold = legacyReceiptCount > 0; + const utilizationBps = legacyHold ? null : this.marketUtilizationBps( + computeMs, capacitySlotCount, epochSeconds); + if (utilizationBps instanceof Error) return utilizationBps; + const multiplierBps = legacyHold ? 10_000 : + this.marketUtilizationMultiplier(utilizationBps); + if (multiplierBps instanceof Error) return multiplierBps; + const activeSupply = usage.provider_count; + // Apply the fixed utilization step directly. No previous-hour activity, + // revenue target, EMA, or demand AU enters the direction decision. + const desiredRateMap = this.scaleRateMap(current.rate_map, multiplierBps); + const desiredPerReqAu = this.scalePriceTerm(current.per_req_au, multiplierBps); + const desiredMinSessionAu = this.scalePriceTerm(current.min_session_au, multiplierBps); + const nextTerms = { + rate_map: desiredRateMap, + per_req_au: desiredPerReqAu, + min_session_au: desiredMinSessionAu, + }; + for (const term of Object.values(nextTerms)) if (term instanceof Error) return term; + for (const field of ['per_req_au', 'min_session_au']) { + const lower = BigInt(this.scalePriceTerm(seed[field], 2_500)); + const upper = BigInt(this.scalePriceTerm(seed[field], 40_000)); + const amount = BigInt(nextTerms[field]); + nextTerms[field] = (amount < lower ? lower : amount > upper ? upper : amount).toString(); + } + nextTerms.rate_map = this.clampRateMapBounds(nextTerms.rate_map, modelRef.rate_map, marketParams); + if (nextTerms.rate_map instanceof Error) return nextTerms.rate_map; + const record = { + enclave_id: current.enclave_id, model_id: current.model_id, denom: PRICE_DENOMINATION, + ver: (this.priceLatestEntry(schedule)?.ver ?? 0) + 1, + ...nextTerms, effective_at: at, effective_from: tx, updated_at: tx, + set_by: seed.set_by, set_by_role: 'admin', + ...(ctxMeta ? { ctx_bracket: ctxMeta.ctx_bracket, + ctx_bracket_table_ver: ctxMeta.ctx_bracket_table_ver } : {}), + price_source: 'market_utilization', seed, + market: { + schema_version: 3, + source: legacyHold ? 'canonical_legacy_receipt_hold' : + 'canonical_signed_slot_time', + epoch, + epoch_seconds: epochSeconds, + active_supply: activeSupply, + capacity_slot_count: capacitySlotCount, + compute_ms: computeMs, + legacy_receipt_count: legacyReceiptCount, + utilization_bps: utilizationBps, + // Gross AU and metered units remain accounting evidence only. + active_demand_au: usage.demand_au, session_count: usage.session_count, + settled_usage: settledUsage, + activity_basis: legacyHold ? 'legacy_receipt_hold_v1' : + 'signed_slot_time_v1', + modelref_ver: modelRef.ver ?? null, + activity_initialized: true, multiplier_bps: multiplierBps, constants, + desired_rate_map: desiredRateMap, desired_per_req_au: desiredPerReqAu, + desired_min_session_au: desiredMinSessionAu, + previous_price_ver: current.ver, previous_rate_map: cloneValue(current.rate_map), + previous_per_req_au: current.per_req_au, previous_min_session_au: current.min_session_au, + seed_price_ver: seed.ver, + }, + }; + updates.push({ + enclave_id: usage.enclave_id, + ...(ctxMeta ? { ctx_bracket: ctxMeta.ctx_bracket, + ctx_bracket_table_ver: ctxMeta.ctx_bracket_table_ver } : {}), + market_key: marketKey, ver: record.ver, rate_map: record.rate_map, + utilization_bps: utilizationBps, multiplier_bps: multiplierBps, + active_supply: activeSupply, capacity_slot_count: capacitySlotCount, + compute_ms: computeMs, legacy_receipt_count: legacyReceiptCount, + active_demand_au: usage.demand_au, frozen: false, + schedule_key: scheduleKey, schedule: { ...schedule, current: record }, + record_key: this.priceRecordKey(usage.enclave_id, record.ver, ctxMeta?.ctx_bracket ?? null), record, + }); + } + return updates; + } + + modelClassFor(value) { + if (!value || !hasOwn(value, 'model_class') || value.model_class === null || value.model_class === undefined) { + return undefined; + } + return value.model_class; + } + + validateModelClass(modelClass, label) { + if (typeof modelClass !== 'string' || modelClass.length === 0 || modelClass.length > 64) { + return new Error(`${label} must be a non-empty string.`); + } + if (!MODEL_CLASSES.has(modelClass)) return new Error(`Unsupported ${label}.`); + return null; + } + + validateLaunchEnclaveAttestationTier(attTier) { + if (attTier <= MAX_LAUNCH_ENCLAVE_ATTESTATION_TIER) return null; + return new Error( + `Enclave attestation tiers above ${MAX_LAUNCH_ENCLAVE_ATTESTATION_TIER} are not launch-advertisable; Tier 4 is provider KYB, not enclave hardware.` + ); + } + + async currentAppliedEpoch() { + const state = await this.get('epoch/apply/state'); + const epoch = state?.epoch ?? 0; + return Number.isSafeInteger(epoch) && epoch >= 0 ? epoch : 0; + } + + async enclaveMinTierPolicy(enclave, currentEpoch = null) { + const epoch = currentEpoch ?? await this.currentAppliedEpoch(); + const policy = await this.get(`tierpolicy/enclave/${enclave.enclave_id}`); + const baseMinTier = policy?.current_min_att_tier ?? enclave.min_att_tier ?? enclave.att_tier ?? 1; + const pending = policy?.pending ?? enclave.pending_min_att_tier ?? null; + if (pending && pending.effective_epoch <= epoch) { + return { + min_att_tier: pending.min_att_tier, + pending: null, + effective_epoch: pending.effective_epoch, + current_epoch: epoch, + }; + } + return { + min_att_tier: baseMinTier, + pending, + current_epoch: epoch, + }; + } + + validateRateMap(rateMap, modelClass, label, { allowZeroPrice = false } = {}) { + if (!Array.isArray(rateMap) || rateMap.length === 0 || rateMap.length > RATE_MAP_MAX_ENTRIES) { + return new Error(`${label} must be a non-empty array with at most ${RATE_MAP_MAX_ENTRIES} entries.`); + } + const validUnits = MODEL_CLASS_RATE_UNITS[modelClass]; + if (!validUnits) return new Error(`No rate units configured for model_class ${modelClass}.`); + const seen = new Set(); + for (const entry of rateMap) { + const shapeError = this.validateExactObjectKeys(entry, ['unit', 'per_unit_au', 'granularity'], `${label} entry`); + if (shapeError) return shapeError; + if (typeof entry.unit !== 'string' || entry.unit.length === 0 || entry.unit.length > 64) { + return new Error(`${label} unit must be a non-empty string.`); + } + if (!validUnits.has(entry.unit)) return new Error(`${label} unit ${entry.unit} is not allowed for model_class ${modelClass}.`); + if (seen.has(entry.unit)) return new Error(`${label} has duplicate unit ${entry.unit}.`); + seen.add(entry.unit); + const perUnitAu = this.normalizeAu(entry.per_unit_au, `${label} per_unit_au`, { allowZero: allowZeroPrice }); + if (perUnitAu instanceof Error || (!allowZeroPrice && this.isZeroAu(perUnitAu))) { + return new Error(`${label} per_unit_au must be ${allowZeroPrice ? 'a non-negative' : 'a positive'} integer.`); + } + if (!Number.isSafeInteger(entry.granularity) || entry.granularity <= 0) { + return new Error(`${label} granularity must be a positive integer.`); + } + } + return null; + } + + validateEnclaveModalityRateMap(enclave, rateMap) { + const modalityError = this.validateModalitySet( + enclave?.caps?.modality_set, + 'Enclave caps modality_set' + ); + if (modalityError) return modalityError; + const modelClass = this.modelClassFor(enclave); + const required = new Set(); + if (modelClass === DEFAULT_MODEL_CLASS) { + required.add('input_token'); + required.add('output_token'); + } else if (modelClass === 'embedding') { + required.add('input_token'); + } else if (modelClass === 'image-generation') { + required.add('image'); + required.add('step'); + } else if (modelClass === 'video-generation') { + required.add('video_second'); + required.add('frame'); + } else if (modelClass === 'tts' || modelClass === 'audio-generation' || modelClass === 'music-generation') { + required.add('input_character'); + required.add('audio_second'); + } else if (modelClass === 'stt') { + required.add('audio_second'); + } + const units = new Set(rateMap.map((entry) => entry.unit)); + for (const unit of required) { + if (!units.has(unit)) { + return new Error(`Enclave price rate_map is missing required modality unit ${unit}.`); + } + } + return null; + } + + normalizeRateMap(rateMap) { + return rateMap + .map((entry) => ({ + unit: entry.unit, + per_unit_au: this.normalizeAu(entry.per_unit_au), + granularity: entry.granularity, + })) + .sort((left, right) => compareCodepoint(left.unit, right.unit)); + } + + normalizeLockedRateMap(rateMap, label) { + if (!Array.isArray(rateMap) || rateMap.length === 0 || rateMap.length > RATE_MAP_MAX_ENTRIES) { + return new Error(`${label} must be a non-empty array with at most ${RATE_MAP_MAX_ENTRIES} entries.`); + } + const seen = new Set(); + const normalized = []; + for (const entry of rateMap) { + const shapeError = this.validateExactObjectKeys(entry, ['unit', 'per_unit_au', 'granularity'], `${label} entry`); + if (shapeError) return shapeError; + if (typeof entry.unit !== 'string' || entry.unit.length === 0 || entry.unit.length > 64) { + return new Error(`${label} unit must be a non-empty string.`); + } + const unit = this.canonicalUsageUnit(entry.unit); + if (!this.isSafeKeyPart(unit)) return new Error(`${label} unit is invalid.`); + if (seen.has(unit)) return new Error(`${label} has duplicate unit ${unit}.`); + seen.add(unit); + const perUnitAu = this.normalizeAu(entry.per_unit_au, `${label} per_unit_au`, { allowZero: false }); + if (perUnitAu instanceof Error) { + return new Error(`${label} per_unit_au must be a positive integer.`); + } + if (!Number.isSafeInteger(entry.granularity) || entry.granularity <= 0) { + return new Error(`${label} granularity must be a positive integer.`); + } + normalized.push({ + unit, + per_unit_au: perUnitAu, + granularity: entry.granularity, + }); + } + return normalized.sort((left, right) => compareCodepoint(left.unit, right.unit)); + } + + rateMapByUnit(rateMap) { + const byUnit = new Map(); + for (const entry of rateMap ?? []) byUnit.set(entry.unit, entry); + return byUnit; + } + + validateRateMapBounds(priceRateMap, referenceRateMap, params) { + const priceByUnit = this.rateMapByUnit(priceRateMap); + const referenceByUnit = this.rateMapByUnit(referenceRateMap); + if (priceByUnit.size !== referenceByUnit.size) { + return new Error('Price rate_map units must match model reference rate_map units.'); + } + for (const [unit, ref] of referenceByUnit.entries()) { + const price = priceByUnit.get(unit); + if (!price) return new Error(`Price rate_map is missing model reference unit ${unit}.`); + if (!this.rateWithinBounds(price, ref, params)) { + return new Error(`Price rate_map unit ${unit} outside model reference bounds.`); + } + } + return null; + } + + rateWithinBounds(price, ref, params = { + price_min_bps: PARAM_DEFINITIONS.price_min_bps.default, + price_max_bps: PARAM_DEFINITIONS.price_max_bps.default, + }) { + const refPerUnit = this.parseAu(ref?.per_unit_au, 'reference rate per_unit_au', { allowZero: false }); + const pricePerUnit = this.parseAu(price?.per_unit_au, 'price rate per_unit_au'); + if ( + refPerUnit instanceof Error || + !Number.isSafeInteger(ref?.granularity) || + ref.granularity <= 0 || + pricePerUnit instanceof Error || + !Number.isSafeInteger(price?.granularity) || + price.granularity <= 0 + ) { + return false; + } + const priceScaled = pricePerUnit * BigInt(ref.granularity) * 10_000n; + const refScaled = refPerUnit * BigInt(price.granularity); + return ( + priceScaled >= refScaled * BigInt(Math.max(2_500, params.price_min_bps)) && + priceScaled <= refScaled * BigInt(Math.min(40_000, params.price_max_bps)) + ); + } + + validateReputationEvent(value) { + if (!this.isSafeKeyPart(value.event_id)) return new Error('Invalid reputation event id.'); + if (!REPUTATION_EVENT_KINDS.has(value.kind)) return new Error('Unsupported reputation event kind.'); + if ( + (value.kind === 'session_ok' || value.kind === 'session_partial') && + this.normalizeAu(value.paid_au, 'reputation paid amount') instanceof Error + ) { + return new Error('Reputation event requires paid_au.'); + } + if ( + value.kind === 'session_fail' && + this.normalizeAu(value.max_spend_au, 'reputation max spend') instanceof Error + ) { + return new Error('Reputation event requires max_spend_au.'); + } + return null; + } + + validateReputationAnchor(value) { + if (!value || typeof value !== 'object' || Array.isArray(value)) { + return new Error('Invalid reputation anchor payload.'); + } + if (value.op !== 'anchor_reputation') return new Error('Invalid reputation anchor op.'); + if (!this.isSafeKeyPart(value.provider)) return new Error('Invalid reputation provider.'); + if (!Number.isSafeInteger(value.epoch) || value.epoch < 0) { + return new Error('Invalid reputation anchor epoch.'); + } + if (!Number.isSafeInteger(value.folded_at) || value.folded_at < 0) { + return new Error('Invalid reputation anchor folded_at.'); + } + if (!this.isHexBytes(value.events_head, 32)) return new Error('Invalid reputation events head.'); + if (!Number.isSafeInteger(value.r_bps) || value.r_bps < 0 || value.r_bps > 10_000) { + return new Error('Invalid reputation r_bps.'); + } + if (!Number.isSafeInteger(value.raw_milli)) return new Error('Invalid reputation raw_milli.'); + if (!Number.isSafeInteger(value.successful_sessions) || value.successful_sessions < 0) { + return new Error('Invalid reputation successful_sessions.'); + } + if ( + value.provenance_violation !== undefined && + typeof value.provenance_violation !== 'boolean' + ) { + return new Error('Invalid reputation provenance_violation.'); + } + return null; + } + + validateProbeResult(value) { + if (!this.isSafeKeyPart(value.probe_id)) return new Error('Invalid probe id.'); + if (!PROBE_KINDS.has(value.probe_kind)) return new Error('Unsupported probe kind.'); + if (value.probe_kind === 'canary') { + if (!value.enclave_id) return new Error('Canary probe requires enclave_id.'); + if (!value.binary_hash) return new Error('Canary probe requires binary_hash.'); + if (!Number.isInteger(value.match_bps)) return new Error('Canary probe requires match_bps.'); + if (typeof value.pass !== 'boolean') return new Error('Canary probe requires pass.'); + if (!value.canary_set) return new Error('Canary probe requires canary_set.'); + if (!value.canary_prompt_id) return new Error('Canary probe requires canary_prompt_id.'); + if (value.challenge_epoch === undefined) return new Error('Canary probe requires challenge_epoch.'); + if (!value.challenge_apply_hash) return new Error('Canary probe requires challenge_apply_hash.'); + if (!value.challenge_seed) return new Error('Canary probe requires challenge_seed.'); + if (!value.verification_method) return new Error('Canary probe requires verification_method.'); + if (!PROBE_VERIFICATION_METHODS.has(value.verification_method)) { + return new Error('Unsupported canary verification_method.'); + } + if (!value.session_receipt_hash) return new Error('Canary probe requires session_receipt_hash.'); + if (!value.evidence_hash) return new Error('Canary probe requires evidence_hash.'); + if (!value.auditor_sig) return new Error('Canary probe requires auditor_sig.'); + if (!this.isSafeKeyPart(value.enclave_id)) return new Error('Invalid canary enclave id.'); + if (!this.isHexBytes(value.binary_hash, 32)) return new Error('Invalid canary binary hash.'); + if (!this.isHexBytes(value.session_receipt_hash, 32)) { + return new Error('Invalid canary session receipt hash.'); + } + if (!this.isHexBytes(value.evidence_hash, 32)) return new Error('Invalid canary evidence hash.'); + if (!this.isHexBytes(value.auditor_sig, 64)) return new Error('Invalid canary auditor signature.'); + if (!this.isSafeKeyPart(value.canary_prompt_id)) return new Error('Invalid canary prompt id.'); + if (!Number.isSafeInteger(value.challenge_epoch) || value.challenge_epoch < 0) { + return new Error('Invalid canary challenge epoch.'); + } + if (!this.isHexBytes(value.challenge_apply_hash, 32)) { + return new Error('Invalid canary challenge apply hash.'); + } + if (!this.isHexBytes(value.challenge_seed, 32)) return new Error('Invalid canary challenge seed.'); + } + return null; + } + + async normalizeSpendVoucherForReserve(voucher) { + const voucherFields = [ + 'schema_version', + 'session_id', + 'billing_id', + 'billing_attempt', + 'billing_prior_usage', + 'billing_prior_au_owed_cum', + 'billing_epoch', + 'reservation_id', + 'reservation_expires_after_epoch', + 'reservation_receipt_grace_epochs', + 'user', + 'provider', + 'payout_revision', + 'rail', + 'enclave_id', + 'model_id', + 'price_ver', + 'locked_rate_map', + 'locked_per_req_au', + 'locked_min_session_au', + 'served_ctx', + 'required_modalities', + 'ctx_bracket', + 'ctx_bracket_table_ver', + 'rules_ver', + 'max_spend_au', + 'checkpoint_every', + 'user_sig', + ]; + if (hasOwn(voucher, 'required_specialities')) voucherFields.push('required_specialities'); + if (hasOwn(voucher, 'workflow')) voucherFields.push('workflow'); + const shapeError = this.validateExactObjectKeys( + voucher, + voucherFields, + 'spend voucher' + ); + if (shapeError) return shapeError; + if (voucher.schema_version !== SPEND_VOUCHER_SCHEMA_VERSION) { + return new Error('Unsupported spend voucher schema version.'); + } + const checkpointError = this.validateExactObjectKeys( + voucher.checkpoint_every, + ['tokens', 'ms'], + 'spend voucher checkpoint policy' + ); + if (checkpointError) return checkpointError; + const rail = this.normalizeLedgerRail(voucher.rail, 'spend voucher rail'); + if (rail instanceof Error) return rail; + if (!this.isHexBytes(voucher.session_id, 32)) return new Error('Invalid spend voucher session id.'); + if (!this.isHexBytes(voucher.billing_id, 32)) return new Error('Invalid spend voucher billing id.'); + if (!Number.isSafeInteger(voucher.billing_attempt) || voucher.billing_attempt < 0) { + return new Error('Invalid spend voucher billing attempt.'); + } + if (!Number.isSafeInteger(voucher.billing_epoch) || voucher.billing_epoch < 1) { + return new Error('Invalid spend voucher billing epoch.'); + } + if (!this.isHexBytes(voucher.reservation_id, 32)) { + return new Error('Invalid spend voucher reservation id.'); + } + if (!Number.isSafeInteger(voucher.reservation_expires_after_epoch) || + voucher.reservation_expires_after_epoch <= voucher.billing_epoch || + !Number.isSafeInteger(voucher.reservation_receipt_grace_epochs) || + voucher.reservation_receipt_grace_epochs < 0) { + return new Error('Invalid spend voucher reservation expiry policy.'); + } + if (!this.isHexBytes(voucher.user, 32)) return new Error('Invalid spend voucher user.'); + if (!this.isHexBytes(voucher.provider, 32)) return new Error('Invalid spend voucher provider.'); + if (!this.isHexBytes(voucher.payout_revision, 32)) { + return new Error('Invalid spend voucher payout revision.'); + } + const billingPriorUsage = this.normalizeReceiptUsage(voucher.billing_prior_usage); + if (billingPriorUsage instanceof Error) return billingPriorUsage; + if (stableJson(billingPriorUsage) !== stableJson(voucher.billing_prior_usage)) { + return new Error('Spend voucher billing prior usage must be canonical.'); + } + const billingPriorAuOwedCum = this.normalizeAu( + voucher.billing_prior_au_owed_cum, + 'spend voucher billing prior cumulative amount' + ); + if (billingPriorAuOwedCum instanceof Error) { + return new Error('Invalid spend voucher billing prior cumulative amount.'); + } + if ( + voucher.billing_attempt === 0 && + (Object.keys(billingPriorUsage).length > 0 || !this.isZeroAu(billingPriorAuOwedCum)) + ) { + return new Error('Initial spend voucher billing attempt must have an empty baseline.'); + } + if (this.isZeroAu(billingPriorAuOwedCum) && Object.keys(billingPriorUsage).length > 0) { + return new Error('Spend voucher billing prior usage requires a prior cumulative amount.'); + } + if (!this.isHexBytes(voucher.enclave_id, 32)) return new Error('Invalid spend voucher enclave id.'); + if (typeof voucher.model_id !== 'string' || + voucher.model_id.length === 0 || + voucher.model_id.length > 256) { + return new Error('Invalid spend voucher model id.'); + } + if (!Number.isSafeInteger(voucher.price_ver) || voucher.price_ver < 1) { + return new Error('Invalid spend voucher price version.'); + } + if (!Number.isSafeInteger(voucher.rules_ver) || voucher.rules_ver < 1) { + return new Error('Invalid spend voucher rules version.'); + } + const lockedRateMap = this.normalizeLockedRateMap(voucher.locked_rate_map, 'spend voucher locked_rate_map'); + if (lockedRateMap instanceof Error) return lockedRateMap; + const lockedPerReqAu = this.normalizeAu(voucher.locked_per_req_au, 'spend voucher locked per-request price'); + if (lockedPerReqAu instanceof Error) { + return new Error('Invalid spend voucher locked per-request price.'); + } + const lockedMinSessionAu = this.normalizeAu(voucher.locked_min_session_au, 'spend voucher locked minimum session price'); + if (lockedMinSessionAu instanceof Error) { + return new Error('Invalid spend voucher locked minimum session price.'); + } + if (!Number.isSafeInteger(voucher.served_ctx) || voucher.served_ctx < 0) { + return new Error('Invalid spend voucher served context.'); + } + const modalityError = this.validateModalitySet( + voucher.required_modalities, + 'spend voucher required_modalities' + ); + if (modalityError) return modalityError; + const requiredSpecialities = voucher.required_specialities ?? {}; + const specialitiesError = this.validateSpecialitySelection( + requiredSpecialities, + 'spend voucher required_specialities' + ); + if (specialitiesError) return specialitiesError; + const normalizedSpecialities = Object.fromEntries( + Object.entries(requiredSpecialities).sort(([left], [right]) => compareCodepoint(left, right)) + ); + let workflow = null; + if (hasOwn(voucher, 'workflow')) { + workflow = this.normalizeWorkflowBinding( + voucher.workflow, + 'spend voucher workflow', + lockedRateMap + ); + if (workflow instanceof Error) return workflow; + if (stableJson(workflow) !== stableJson(canonicalWorkflowBinding(voucher.workflow))) { + return new Error('Spend voucher workflow must be canonical.'); + } + } + const table = voucher.ctx_bracket_table_ver === null || voucher.ctx_bracket_table_ver === undefined + ? null + : await this.ctxBracketTableByVersion(voucher.ctx_bracket_table_ver); + if (table instanceof Error) return table; + const ctxMeta = await this.normalizeCtxBracketEvidenceForEnclave( + voucher.enclave_id.toLowerCase(), + voucher.served_ctx, + voucher.ctx_bracket, + voucher.ctx_bracket_table_ver, + table, + 'spend voucher' + ); + if (ctxMeta instanceof Error) return ctxMeta; + const maxSpendAu = this.normalizeAu(voucher.max_spend_au, 'spend voucher max spend', { allowZero: false }); + if (maxSpendAu instanceof Error) { + return new Error('Invalid spend voucher max spend.'); + } + if (!Number.isSafeInteger(voucher.checkpoint_every.tokens) || voucher.checkpoint_every.tokens < 1) { + return new Error('Invalid spend voucher checkpoint tokens.'); + } + if (!Number.isSafeInteger(voucher.checkpoint_every.ms) || voucher.checkpoint_every.ms < 1) { + return new Error('Invalid spend voucher checkpoint milliseconds.'); + } + if (!this.isHexBytes(voucher.user_sig, 64)) return new Error('Invalid spend voucher user signature.'); + const body = { + schema_version: SPEND_VOUCHER_SCHEMA_VERSION, + session_id: voucher.session_id.toLowerCase(), + billing_id: voucher.billing_id.toLowerCase(), + billing_attempt: voucher.billing_attempt, + billing_prior_usage: billingPriorUsage, + billing_prior_au_owed_cum: billingPriorAuOwedCum, + billing_epoch: voucher.billing_epoch, + reservation_id: voucher.reservation_id.toLowerCase(), + reservation_expires_after_epoch: voucher.reservation_expires_after_epoch, + reservation_receipt_grace_epochs: voucher.reservation_receipt_grace_epochs, + user: voucher.user.toLowerCase(), + provider: voucher.provider.toLowerCase(), + payout_revision: voucher.payout_revision.toLowerCase(), + rail, + enclave_id: voucher.enclave_id.toLowerCase(), + model_id: voucher.model_id, + price_ver: voucher.price_ver, + locked_rate_map: lockedRateMap, + locked_per_req_au: lockedPerReqAu, + locked_min_session_au: lockedMinSessionAu, + served_ctx: voucher.served_ctx, + required_modalities: voucher.required_modalities.slice(), + required_specialities: normalizedSpecialities, + ...(workflow ? { workflow } : {}), + ctx_bracket: ctxMeta.ctx_bracket, + ctx_bracket_table_ver: ctxMeta.ctx_bracket_table_ver, + rules_ver: voucher.rules_ver, + max_spend_au: maxSpendAu, + checkpoint_every: { + tokens: voucher.checkpoint_every.tokens, + ms: voucher.checkpoint_every.ms, + }, + }; + return { + ...body, + user_sig: voucher.user_sig.toLowerCase(), + body, + }; + } + + async normalizeTargetedSpendReserveValue(value) { + const reserveFields = [ + 'op', + 'payout_revision', + 'contract_version', + 'session_id', + 'reservation_id', + 'reservation_expires_after_epoch', + 'reservation_receipt_grace_epochs', + 'epoch', + 'at', + 'rail', + 'user', + 'provider', + 'enclave_id', + 'enclave_pubkey', + 'model_id', + 'price_ver', + 'rules_ver', + 'served_ctx', + 'required_modalities', + 'ctx_bracket', + 'ctx_bracket_table_ver', + 'max_spend_au', + 'voucher', + 'provider_sig', + ]; + if (hasOwn(value, 'required_specialities')) reserveFields.push('required_specialities'); + if (hasOwn(value, 'workflow')) reserveFields.push('workflow'); + const shapeError = this.validateExactObjectKeys( + value, + reserveFields, + 'spend reservation feature' + ); + if (shapeError) return shapeError; + if (value.op !== 'spend_reserve_targeted') return new Error('Invalid targeted spend reservation op.'); + if (!this.isHexBytes(value.payout_revision, 32) || + value.payout_revision !== value.payout_revision.toLowerCase()) { + return new Error('Invalid targeted spend reservation payout revision.'); + } + if (value.contract_version !== CONTRACT_VERSION) return new Error('Invalid spend reservation contract version.'); + if (!this.isHexBytes(value.session_id, 32)) return new Error('Invalid spend reservation session id.'); + if (!this.isHexBytes(value.reservation_id, 32) || + value.reservation_id !== value.reservation_id.toLowerCase()) { + return new Error('Invalid spend reservation reservation id.'); + } + if (!Number.isSafeInteger(value.reservation_expires_after_epoch) || + value.reservation_expires_after_epoch <= value.epoch || + !Number.isSafeInteger(value.reservation_receipt_grace_epochs) || + value.reservation_receipt_grace_epochs < 0) { + return new Error('Invalid spend reservation expiry policy.'); + } + if (!Number.isSafeInteger(value.epoch) || value.epoch < 1) return new Error('Invalid spend reservation epoch.'); + if (!Number.isSafeInteger(value.at) || value.at < 0) return new Error('Invalid spend reservation timestamp.'); + const rail = this.normalizeLedgerRail(value.rail, 'spend reservation rail'); + if (rail instanceof Error) return rail; + if (!this.isHexBytes(value.user, 32)) return new Error('Invalid spend reservation user.'); + if (!this.isHexBytes(value.provider, 32)) return new Error('Invalid spend reservation provider.'); + if (!this.isHexBytes(value.enclave_id, 32)) return new Error('Invalid spend reservation enclave.'); + if (!this.isHexBytes(value.enclave_pubkey, 32)) { + return new Error('Invalid spend reservation enclave public key.'); + } + if (typeof value.model_id !== 'string' || + value.model_id.length === 0 || + value.model_id.length > 256) { + return new Error('Invalid spend reservation model id.'); + } + if (!Number.isSafeInteger(value.price_ver) || value.price_ver < 1) { + return new Error('Invalid spend reservation price version.'); + } + if (!Number.isSafeInteger(value.rules_ver) || value.rules_ver < 1) { + return new Error('Invalid spend reservation rules version.'); + } + if (!Number.isSafeInteger(value.served_ctx) || value.served_ctx < 0) { + return new Error('Invalid spend reservation served context.'); + } + const modalityError = this.validateModalitySet( + value.required_modalities, + 'spend reservation required_modalities' + ); + if (modalityError) return modalityError; + const requiredSpecialities = value.required_specialities ?? {}; + const specialitiesError = this.validateSpecialitySelection( + requiredSpecialities, + 'spend reservation required_specialities' + ); + if (specialitiesError) return specialitiesError; + const normalizedSpecialities = Object.fromEntries( + Object.entries(requiredSpecialities).sort(([left], [right]) => compareCodepoint(left, right)) + ); + const activeTable = value.ctx_bracket_table_ver === null || value.ctx_bracket_table_ver === undefined + ? null + : await this.ctxBracketTableAt(value.at); + if (activeTable instanceof Error) return activeTable; + if (activeTable && value.ctx_bracket_table_ver !== activeTable.ver) { + return new Error('Spend reservation context bracket table is not active.'); + } + const ctxMeta = await this.normalizeCtxBracketEvidenceForEnclave( + value.enclave_id.toLowerCase(), + value.served_ctx, + value.ctx_bracket, + value.ctx_bracket_table_ver, + activeTable, + 'spend reservation' + ); + if (ctxMeta instanceof Error) return ctxMeta; + const maxSpendAu = this.normalizeAu(value.max_spend_au, 'spend reservation max spend', { allowZero: false }); + if (maxSpendAu instanceof Error) { + return new Error('Invalid spend reservation max spend.'); + } + if (!this.isHexBytes(value.provider_sig, 64)) { + return new Error('Invalid spend reservation provider signature.'); + } + const voucher = await this.normalizeSpendVoucherForReserve(value.voucher); + if (voucher instanceof Error) return voucher; + if (voucher.session_id !== value.session_id.toLowerCase()) { + return new Error('Spend reservation voucher session mismatch.'); + } + if (voucher.reservation_id !== value.reservation_id.toLowerCase()) { + return new Error('Spend reservation voucher reservation mismatch.'); + } + if (voucher.reservation_expires_after_epoch !== value.reservation_expires_after_epoch || + voucher.reservation_receipt_grace_epochs !== value.reservation_receipt_grace_epochs) { + return new Error('Spend reservation voucher expiry policy mismatch.'); + } + if (voucher.billing_epoch !== value.epoch) { + return new Error('Spend reservation voucher billing epoch mismatch.'); + } + if (voucher.user !== value.user.toLowerCase()) { + return new Error('Spend reservation voucher user mismatch.'); + } + if (voucher.provider !== value.provider.toLowerCase()) { + return new Error('Spend reservation voucher provider mismatch.'); + } + if (voucher.payout_revision !== value.payout_revision.toLowerCase()) { + return new Error('Spend reservation voucher payout revision mismatch.'); + } + if (voucher.rail !== rail) return new Error('Spend reservation voucher rail mismatch.'); + if (voucher.enclave_id !== value.enclave_id.toLowerCase()) { + return new Error('Spend reservation voucher enclave mismatch.'); + } + if (voucher.model_id !== value.model_id) { + return new Error('Spend reservation voucher model mismatch.'); + } + if (voucher.price_ver !== value.price_ver) { + return new Error('Spend reservation voucher price mismatch.'); + } + if (voucher.body.served_ctx !== value.served_ctx) { + return new Error('Spend reservation voucher served context mismatch.'); + } + if (stableJson(voucher.body.required_modalities) !== stableJson(value.required_modalities)) { + return new Error('Spend reservation voucher required modalities mismatch.'); + } + if (stableJson(voucher.body.required_specialities) !== stableJson(normalizedSpecialities)) { + return new Error('Spend reservation voucher required specialities mismatch.'); + } + let workflow = null; + if (hasOwn(value, 'workflow')) { + workflow = this.normalizeWorkflowBinding( + value.workflow, + 'spend reservation workflow', + voucher.body.locked_rate_map + ); + if (workflow instanceof Error) return workflow; + if (stableJson(workflow) !== stableJson(value.workflow)) { + return new Error('Spend reservation workflow must be canonical.'); + } + } + if (stableJson(voucher.body.workflow ?? null) !== stableJson(workflow)) { + return new Error('Spend reservation voucher workflow mismatch.'); + } + if (voucher.body.ctx_bracket !== value.ctx_bracket) { + return new Error('Spend reservation voucher context bracket mismatch.'); + } + if (voucher.body.ctx_bracket_table_ver !== value.ctx_bracket_table_ver) { + return new Error('Spend reservation voucher context bracket table version mismatch.'); + } + if (voucher.rules_ver !== value.rules_ver) { + return new Error('Spend reservation voucher rules version mismatch.'); + } + if (this.compareAu(voucher.max_spend_au, maxSpendAu) !== 0) { + return new Error('Spend reservation voucher max spend mismatch.'); + } + const reservationParams = await this.activeParamsAt(value.at, [ + 'reservation_max_lifetime_epochs', + 'reservation_receipt_grace_epochs', + ]); + if ( + value.epoch > Number.MAX_SAFE_INTEGER - reservationParams.reservation_max_lifetime_epochs || + value.reservation_expires_after_epoch !== + value.epoch + reservationParams.reservation_max_lifetime_epochs || + value.reservation_receipt_grace_epochs !== + reservationParams.reservation_receipt_grace_epochs + ) { + return new Error('Spend reservation expiry policy does not match active parameters.'); + } + return { + op: 'spend_reserve_targeted', + payout_revision: value.payout_revision, + contract_version: CONTRACT_VERSION, + session_id: value.session_id.toLowerCase(), + reservation_id: value.reservation_id.toLowerCase(), + reservation_expires_after_epoch: value.reservation_expires_after_epoch, + reservation_receipt_grace_epochs: value.reservation_receipt_grace_epochs, + epoch: value.epoch, + at: value.at, + rail, + user: value.user.toLowerCase(), + provider: value.provider.toLowerCase(), + enclave_id: value.enclave_id.toLowerCase(), + enclave_pubkey: value.enclave_pubkey.toLowerCase(), + model_id: value.model_id, + price_ver: value.price_ver, + locked_rate_map: voucher.body.locked_rate_map, + locked_per_req_au: voucher.body.locked_per_req_au, + locked_min_session_au: voucher.body.locked_min_session_au, + served_ctx: value.served_ctx, + required_modalities: value.required_modalities.slice(), + required_specialities: normalizedSpecialities, + ...(workflow ? { workflow } : {}), + ctx_bracket: ctxMeta.ctx_bracket, + ctx_bracket_table_ver: ctxMeta.ctx_bracket_table_ver, + rules_ver: value.rules_ver, + max_spend_au: maxSpendAu, + voucher: { ...cloneValue(voucher.body), user_sig: voucher.user_sig }, + voucher_body: voucher.body, + provider_sig: value.provider_sig.toLowerCase(), + }; + } + + + + async normalizeSpendReserveValue(value) { + if (CONTRACT_VERSION >= 17) { + return new Error('Aggregate spend reservations are disabled; use spend_reserve_targeted.'); + } + const reserveFields = [ + 'op', + 'contract_version', + 'session_id', + 'epoch', + 'at', + 'rail', + 'user', + 'provider', + 'enclave_id', + 'price_ver', + 'rules_ver', + 'served_ctx', + 'required_modalities', + 'ctx_bracket', + 'ctx_bracket_table_ver', + 'max_spend_au', + 'voucher', + 'provider_sig', + ]; + if (hasOwn(value, 'required_specialities')) reserveFields.push('required_specialities'); + const shapeError = this.validateExactObjectKeys( + value, + reserveFields, + 'spend reservation feature' + ); + if (shapeError) return shapeError; + if (value.op !== 'spend_reserve') return new Error('Invalid spend reservation op.'); + if (value.contract_version !== CONTRACT_VERSION) return new Error('Invalid spend reservation contract version.'); + if (!this.isHexBytes(value.session_id, 32)) return new Error('Invalid spend reservation session id.'); + if (!Number.isSafeInteger(value.epoch) || value.epoch < 1) return new Error('Invalid spend reservation epoch.'); + if (!Number.isSafeInteger(value.at) || value.at < 0) return new Error('Invalid spend reservation timestamp.'); + const rail = this.normalizeLedgerRail(value.rail, 'spend reservation rail'); + if (rail instanceof Error) return rail; + if (!this.isHexBytes(value.user, 32)) return new Error('Invalid spend reservation user.'); + if (!this.isHexBytes(value.provider, 32)) return new Error('Invalid spend reservation provider.'); + if (!this.isHexBytes(value.enclave_id, 32)) return new Error('Invalid spend reservation enclave.'); + if (!Number.isSafeInteger(value.price_ver) || value.price_ver < 1) { + return new Error('Invalid spend reservation price version.'); + } + if (!Number.isSafeInteger(value.rules_ver) || value.rules_ver < 1) { + return new Error('Invalid spend reservation rules version.'); + } + if (!Number.isSafeInteger(value.served_ctx) || value.served_ctx < 0) { + return new Error('Invalid spend reservation served context.'); + } + const modalityError = this.validateModalitySet( + value.required_modalities, + 'spend reservation required_modalities' + ); + if (modalityError) return modalityError; + const requiredSpecialities = value.required_specialities ?? {}; + const specialitiesError = this.validateSpecialitySelection( + requiredSpecialities, + 'spend reservation required_specialities' + ); + if (specialitiesError) return specialitiesError; + const normalizedSpecialities = Object.fromEntries( + Object.entries(requiredSpecialities).sort(([left], [right]) => compareCodepoint(left, right)) + ); + const activeTable = value.ctx_bracket_table_ver === null || value.ctx_bracket_table_ver === undefined + ? null + : await this.ctxBracketTableAt(value.at); + if (activeTable instanceof Error) return activeTable; + if (activeTable && value.ctx_bracket_table_ver !== activeTable.ver) { + return new Error('Spend reservation context bracket table is not active.'); + } + const ctxMeta = await this.normalizeCtxBracketEvidenceForEnclave( + value.enclave_id.toLowerCase(), + value.served_ctx, + value.ctx_bracket, + value.ctx_bracket_table_ver, + activeTable, + 'spend reservation' + ); + if (ctxMeta instanceof Error) return ctxMeta; + const maxSpendAu = this.normalizeAu(value.max_spend_au, 'spend reservation max spend', { allowZero: false }); + if (maxSpendAu instanceof Error) { + return new Error('Invalid spend reservation max spend.'); + } + if (!this.isHexBytes(value.provider_sig, 64)) { + return new Error('Invalid spend reservation provider signature.'); + } + const voucher = await this.normalizeSpendVoucherForReserve(value.voucher); + if (voucher instanceof Error) return voucher; + if (voucher.session_id !== value.session_id.toLowerCase()) { + return new Error('Spend reservation voucher session mismatch.'); + } + if (voucher.rail !== rail) return new Error('Spend reservation voucher rail mismatch.'); + if (voucher.enclave_id !== value.enclave_id.toLowerCase()) { + return new Error('Spend reservation voucher enclave mismatch.'); + } + if (voucher.price_ver !== value.price_ver) { + return new Error('Spend reservation voucher price mismatch.'); + } + if (voucher.body.served_ctx !== value.served_ctx) { + return new Error('Spend reservation voucher served context mismatch.'); + } + if (stableJson(voucher.body.required_modalities) !== stableJson(value.required_modalities)) { + return new Error('Spend reservation voucher required modalities mismatch.'); + } + if (stableJson(voucher.body.required_specialities) !== stableJson(normalizedSpecialities)) { + return new Error('Spend reservation voucher required specialities mismatch.'); + } + let workflow = null; + if (hasOwn(value, 'workflow')) { + workflow = this.normalizeWorkflowBinding( + value.workflow, + 'spend reservation workflow', + voucher.body.locked_rate_map + ); + if (workflow instanceof Error) return workflow; + if (stableJson(workflow) !== stableJson(value.workflow)) { + return new Error('Spend reservation workflow must be canonical.'); + } + } + if (stableJson(voucher.body.workflow ?? null) !== stableJson(workflow)) { + return new Error('Spend reservation voucher workflow mismatch.'); + } + if (voucher.body.ctx_bracket !== value.ctx_bracket) { + return new Error('Spend reservation voucher context bracket mismatch.'); + } + if (voucher.body.ctx_bracket_table_ver !== value.ctx_bracket_table_ver) { + return new Error('Spend reservation voucher context bracket table version mismatch.'); + } + if (this.compareAu(voucher.max_spend_au, maxSpendAu) !== 0) { + return new Error('Spend reservation voucher max spend mismatch.'); + } + return { + op: 'spend_reserve', + contract_version: CONTRACT_VERSION, + session_id: value.session_id.toLowerCase(), + epoch: value.epoch, + at: value.at, + rail, + user: value.user.toLowerCase(), + provider: value.provider.toLowerCase(), + enclave_id: value.enclave_id.toLowerCase(), + price_ver: value.price_ver, + locked_rate_map: voucher.body.locked_rate_map, + locked_per_req_au: voucher.body.locked_per_req_au, + locked_min_session_au: voucher.body.locked_min_session_au, + served_ctx: value.served_ctx, + required_modalities: value.required_modalities.slice(), + required_specialities: normalizedSpecialities, + ...(workflow ? { workflow } : {}), + ctx_bracket: ctxMeta.ctx_bracket, + ctx_bracket_table_ver: ctxMeta.ctx_bracket_table_ver, + rules_ver: value.rules_ver, + max_spend_au: maxSpendAu, + voucher: { + session_id: voucher.body.session_id, + billing_id: voucher.body.billing_id, + billing_attempt: voucher.body.billing_attempt, + billing_prior_usage: voucher.body.billing_prior_usage, + billing_prior_au_owed_cum: voucher.body.billing_prior_au_owed_cum, + rail: voucher.body.rail, + enclave_id: voucher.body.enclave_id, + price_ver: voucher.body.price_ver, + locked_rate_map: voucher.body.locked_rate_map, + locked_per_req_au: voucher.body.locked_per_req_au, + locked_min_session_au: voucher.body.locked_min_session_au, + served_ctx: voucher.body.served_ctx, + required_modalities: voucher.body.required_modalities, + required_specialities: voucher.body.required_specialities, + ...(voucher.body.workflow ? { workflow: voucher.body.workflow } : {}), + ctx_bracket: voucher.body.ctx_bracket, + ctx_bracket_table_ver: voucher.body.ctx_bracket_table_ver, + max_spend_au: voucher.body.max_spend_au, + checkpoint_every: voucher.body.checkpoint_every, + user_sig: voucher.user_sig, + }, + voucher_body: voucher.body, + provider_sig: value.provider_sig.toLowerCase(), + }; + } + + billingReservationIdentity(normalized) { + return { + billing_id: normalized.voucher_body.billing_id, + billing_attempt: normalized.voucher_body.billing_attempt, + billing_epoch: normalized.epoch, + reservation_id: normalized.voucher_body.reservation_id, + reservation_expires_after_epoch: normalized.reservation_expires_after_epoch, + reservation_receipt_grace_epochs: normalized.reservation_receipt_grace_epochs, + session_id: normalized.session_id, + user: normalized.user, + provider: normalized.provider, + rail: normalized.rail, + payout_revision: normalized.payout_revision, + }; + } + + async validateExistingBillingReservation(normalized) { + const identity = this.billingReservationIdentity(normalized); + const anchor = await this.get(this.receiptBillingKey(identity.billing_id)); + if (!anchor || + anchor.type !== 'receipt_billing_anchor' || + anchor.billing_id !== identity.billing_id || + anchor.user !== identity.user || + anchor.rail !== identity.rail || + anchor.epoch !== identity.billing_epoch || + !Number.isSafeInteger(anchor.latest_attempt) || + anchor.latest_attempt < identity.billing_attempt) { + return new Error('Billing reservation anchor is missing or inconsistent.'); + } + const reservation = await this.get(this.receiptReservationKey(identity.reservation_id)); + if (!reservation || + reservation.type !== 'receipt_reservation_identity' || + reservation.status !== 'active' || + stableJson({ + billing_id: reservation.billing_id, + billing_attempt: reservation.billing_attempt, + billing_epoch: reservation.billing_epoch, + reservation_id: reservation.reservation_id, + reservation_expires_after_epoch: reservation.reservation_expires_after_epoch, + reservation_receipt_grace_epochs: reservation.reservation_receipt_grace_epochs, + session_id: reservation.session_id, + user: reservation.user, + provider: reservation.provider, + rail: reservation.rail, + payout_revision: reservation.payout_revision, + }) !== stableJson(identity)) { + return new Error('Reservation identity is missing or inconsistent.'); + } + return null; + } + + async prepareBillingReservation(normalized, key) { + const identity = this.billingReservationIdentity(normalized); + const anchorKey = this.receiptBillingKey(identity.billing_id); + const existingAnchor = await this.get(anchorKey); + let nextAnchor; + if (existingAnchor === null) { + if (identity.billing_attempt !== 0) { + return new Error('Initial billing reservation attempt must be zero.'); + } + nextAnchor = { + type: 'receipt_billing_anchor', + billing_id: identity.billing_id, + user: identity.user, + rail: identity.rail, + epoch: identity.billing_epoch, + latest_attempt: 0, + created_at: key, + updated_at: key, + }; + } else { + if (existingAnchor.type !== 'receipt_billing_anchor' || + existingAnchor.billing_id !== identity.billing_id || + existingAnchor.user !== identity.user || + existingAnchor.rail !== identity.rail || + existingAnchor.epoch !== identity.billing_epoch || + !Number.isSafeInteger(existingAnchor.latest_attempt) || + existingAnchor.latest_attempt < 0) { + return new Error('Billing id cannot move across user, rail, or epoch.'); + } + if (identity.billing_attempt === existingAnchor.latest_attempt) { + const currentHead = await this.get( + this.receiptHeadKey(identity.billing_id, identity.billing_attempt) + ); + if (currentHead) { + return new Error('Billing attempt must advance exactly once.'); + } + const hold = await this.normalizeTargetedSpendHoldRecord( + (await this.get(this.targetedSpendHoldKey(identity.user, identity.rail))) ?? null, + identity.user, + identity.rail + ); + if (hold instanceof Error) return hold; + const billingAttemptIndex = this.normalizeTargetedSpendReservationIndexRecord( + await this.get( + this.targetedSpendBillingAttemptKey( + identity.user, + identity.rail, + identity.billing_id, + identity.billing_attempt + ) + ), + { + user: identity.user, + rail: identity.rail, + billingId: identity.billing_id, + billingAttempt: identity.billing_attempt, + } + ); + if (billingAttemptIndex instanceof Error) return billingAttemptIndex; + let activeLegacyReservation = false; + for (const session of hold.sessions) { + if (session.billing_id !== identity.billing_id || + session.billing_attempt !== identity.billing_attempt) { + continue; + } + const reservation = await this.get(this.receiptReservationKey(session.reservation_id)); + if (!reservation || + reservation.type !== 'receipt_reservation_identity' || + reservation.status === 'active') { + activeLegacyReservation = true; + break; + } + } + if (activeLegacyReservation || billingAttemptIndex !== null) { + return new Error('Billing attempt already has an active reservation.'); + } + nextAnchor = { + ...existingAnchor, + updated_at: key, + }; + } else { + if (identity.billing_attempt !== existingAnchor.latest_attempt + 1) { + return new Error('Billing attempt must advance exactly once.'); + } + const priorHead = await this.get( + this.receiptHeadKey(identity.billing_id, identity.billing_attempt - 1) + ); + if (!priorHead || + priorHead.type !== 'canonical_receipt_head' || + priorHead.billing_id !== identity.billing_id || + priorHead.billing_attempt !== identity.billing_attempt - 1) { + return new Error('Higher billing attempt requires the prior canonical receipt head.'); + } + if (stableJson(normalized.voucher_body.billing_prior_usage) !== + stableJson(priorHead.receipt.body.usage) || + this.compareAu( + normalized.voucher_body.billing_prior_au_owed_cum, + priorHead.receipt.body.au_owed_cum + ) !== 0) { + return new Error('Higher billing attempt does not exactly chain from the prior attempt.'); + } + nextAnchor = { + ...existingAnchor, + latest_attempt: identity.billing_attempt, + updated_at: key, + }; + } + } + + const reservationKey = this.receiptReservationKey(identity.reservation_id); + if ((await this.get(reservationKey)) !== null) { + return new Error('Reservation id is already in use.'); + } + return { + anchor_key: anchorKey, + anchor: nextAnchor, + reservation_key: reservationKey, + reservation: { + type: 'receipt_reservation_identity', + ...identity, + status: 'active', + closed_at: null, + close_record_key: null, + recorded_at: key, + }, + }; + } + + async normalizeSpendHoldRecord(record, user, rail, epoch, { targeted = false } = {}) { + if (!record) { + const empty = { + user, + rail, + denom: PRICE_DENOMINATION, + reserved_au: ZERO_AU, + balance_au_at_last_reserve: null, + sessions: [], + updated_at: null, + }; + return targeted + ? { type: 'targeted_spend_hold', ...empty } + : { ...empty, epoch }; + } + if (typeof record !== 'object' || Array.isArray(record)) { + return new Error('Spend hold record must be an object.'); + } + if (record.user !== user || record.rail !== rail || + (targeted + ? record.type !== 'targeted_spend_hold' || hasOwn(record, 'epoch') + : record.epoch !== epoch)) { + return new Error('Spend hold record key mismatch.'); + } + if (record.denom !== PRICE_DENOMINATION) return new Error('Spend hold denomination mismatch.'); + const reservedAu = this.normalizeAu(record.reserved_au, 'spend hold reserved amount'); + if (reservedAu instanceof Error) { + return new Error('Invalid spend hold reserved amount.'); + } + if (!Array.isArray(record.sessions)) return new Error('Spend hold sessions must be an array.'); + let targetedReservedAu = ZERO_AU; + for (const session of record.sessions) { + if (!session || typeof session !== 'object' || Array.isArray(session)) { + return new Error('Invalid spend hold session.'); + } + if (!this.isHexBytes(session.session_id, 32)) return new Error('Invalid spend hold session id.'); + if (!this.isHexBytes(session.billing_id, 32)) return new Error('Invalid spend hold billing id.'); + if (!Number.isSafeInteger(session.billing_attempt) || session.billing_attempt < 0) { + return new Error('Invalid spend hold billing attempt.'); + } + if (!Number.isSafeInteger(session.billing_epoch) || + session.billing_epoch < 1 || + (!targeted && session.billing_epoch !== epoch)) { + return new Error('Invalid spend hold billing epoch.'); + } + if (!this.isHexBytes(session.reservation_id, 32)) { + return new Error('Invalid spend hold reservation id.'); + } + if (!Number.isSafeInteger(session.reservation_expires_after_epoch) || + session.reservation_expires_after_epoch <= session.billing_epoch || + !Number.isSafeInteger(session.reservation_receipt_grace_epochs) || + session.reservation_receipt_grace_epochs < 0) { + return new Error('Invalid spend hold reservation expiry policy.'); + } + if (session.user !== user || session.rail !== rail) { + return new Error('Spend hold session user or rail mismatch.'); + } + if (!this.isHexBytes(session.provider, 32)) return new Error('Invalid spend hold provider.'); + if (!this.isHexBytes(session.payout_revision, 32)) { + return new Error('Invalid spend hold payout revision.'); + } + if (!this.isHexBytes(session.enclave_id, 32)) return new Error('Invalid spend hold enclave.'); + if (!this.isHexBytes(session.enclave_pubkey, 32)) { + return new Error('Invalid spend hold enclave public key.'); + } + if (typeof session.model_id !== 'string' || + session.model_id.length === 0 || + session.model_id.length > 256) { + return new Error('Invalid spend hold model id.'); + } + const billingPriorUsage = this.normalizeReceiptUsage(session.billing_prior_usage); + if (billingPriorUsage instanceof Error || + stableJson(billingPriorUsage) !== stableJson(session.billing_prior_usage)) { + return new Error('Invalid spend hold billing prior usage.'); + } + const billingPriorAuOwedCum = this.normalizeAu( + session.billing_prior_au_owed_cum, + 'spend hold billing prior cumulative amount' + ); + if (billingPriorAuOwedCum instanceof Error) { + return new Error('Invalid spend hold billing prior cumulative amount.'); + } + if (!Number.isSafeInteger(session.price_ver) || session.price_ver < 1) { + return new Error('Invalid spend hold price version.'); + } + if (!Number.isSafeInteger(session.rules_ver) || session.rules_ver < 1) { + return new Error('Invalid spend hold rules version.'); + } + const lockedRateMap = this.normalizeLockedRateMap( + session.locked_rate_map, + 'spend hold locked_rate_map' + ); + if (lockedRateMap instanceof Error) return lockedRateMap; + if (stableJson(lockedRateMap) !== stableJson(session.locked_rate_map)) { + return new Error('Spend hold locked_rate_map must be canonical.'); + } + const lockedPerReqAu = this.normalizeAu(session.locked_per_req_au, 'spend hold locked per-request price'); + if (lockedPerReqAu instanceof Error) { + return new Error('Invalid spend hold locked per-request price.'); + } + const lockedMinSessionAu = this.normalizeAu(session.locked_min_session_au, 'spend hold locked minimum session price'); + if (lockedMinSessionAu instanceof Error) { + return new Error('Invalid spend hold locked minimum session price.'); + } + if (!Number.isSafeInteger(session.served_ctx) || session.served_ctx < 0) { + return new Error('Invalid spend hold served context.'); + } + const modalityError = this.validateModalitySet( + session.required_modalities, + 'spend hold required_modalities' + ); + if (modalityError) return modalityError; + const specialitiesError = this.validateSpecialitySelection( + session.required_specialities ?? {}, + 'spend hold required_specialities' + ); + if (specialitiesError) return specialitiesError; + if (hasOwn(session, 'workflow')) { + const workflow = this.normalizeWorkflowBinding( + session.workflow, + 'spend hold workflow', + lockedRateMap + ); + if (workflow instanceof Error) return workflow; + if (stableJson(workflow) !== stableJson(session.workflow)) { + return new Error('Spend hold workflow must be canonical.'); + } + } + const table = session.ctx_bracket_table_ver === null || session.ctx_bracket_table_ver === undefined + ? null + : await this.ctxBracketTableByVersion(session.ctx_bracket_table_ver); + if (table instanceof Error) return table; + const ctxMeta = await this.normalizeCtxBracketEvidenceForEnclave( + session.enclave_id, + session.served_ctx, + session.ctx_bracket, + session.ctx_bracket_table_ver, + table, + 'spend hold' + ); + if (ctxMeta instanceof Error) return ctxMeta; + const maxSpendAu = this.normalizeAu(session.max_spend_au, 'spend hold max spend', { allowZero: false }); + if (maxSpendAu instanceof Error) { + return new Error('Invalid spend hold max spend.'); + } + if (targeted) { + targetedReservedAu = this.safeAddAu(targetedReservedAu, maxSpendAu); + if (targetedReservedAu instanceof Error) return targetedReservedAu; + } + if (!this.isHexBytes(session.voucher_hash, 32)) return new Error('Invalid spend hold voucher hash.'); + } + if (targeted && this.compareAu(targetedReservedAu, reservedAu) !== 0) { + return new Error('Targeted spend hold reserved amount does not equal its sessions.'); + } + return { + ...record, + reserved_au: reservedAu, + sessions: record.sessions.map((session) => ({ + ...session, + billing_prior_usage: this.normalizeReceiptUsage(session.billing_prior_usage), + billing_prior_au_owed_cum: this.normalizeAu( + session.billing_prior_au_owed_cum, + 'spend hold billing prior cumulative amount' + ), + locked_per_req_au: this.normalizeAu(session.locked_per_req_au, 'spend hold locked per-request price'), + locked_min_session_au: this.normalizeAu(session.locked_min_session_au, 'spend hold locked minimum session price'), + required_modalities: session.required_modalities.slice(), + required_specialities: cloneValue(session.required_specialities ?? {}), + ...(hasOwn(session, 'workflow') ? { + workflow: this.normalizeWorkflowBinding( + session.workflow, + 'spend hold workflow', + this.normalizeLockedRateMap(session.locked_rate_map, 'spend hold locked_rate_map') + ), + } : {}), + max_spend_au: this.normalizeAu(session.max_spend_au, 'spend hold max spend', { allowZero: false }), + })), + }; + } + + async normalizeTargetedSpendHoldRecord(record, user, rail) { + return await this.normalizeSpendHoldRecord( + record, + user, + rail, + null, + { targeted: true } + ); + } + + normalizePendingReservationDebitTotals(entries) { + const out = new Map(); + if (entries === null || entries === undefined) return out; + if (!Array.isArray(entries)) return new Error('Pending reservation debits must be an array.'); + for (const entry of entries) { + if (!entry || typeof entry !== 'object' || Array.isArray(entry)) { + return new Error('Invalid pending reservation debit entry.'); + } + const rail = this.normalizeLedgerRail(entry.rail, 'pending reservation debit rail'); + if (rail instanceof Error) return rail; + if (!this.isSafeKeyPart(entry.user)) return new Error('Invalid pending reservation debit user.'); + const au = this.normalizeAu(entry.au, 'pending reservation debit amount', { allowZero: false }); + if (au instanceof Error) return new Error('Invalid pending reservation debit amount.'); + const key = stableJson([rail, entry.user]); + if (out.has(key)) return new Error('Duplicate pending reservation debit entry.'); + out.set(key, { rail, user: entry.user, au }); + } + return out; + } + + reservationDebitTotalEntries(map) { + if (!map) return []; + return this.sortedRailRecords(map, 'user').map((entry) => ({ + rail: entry.rail, + user: entry.user, + au: entry.au, + })); + } + + nextReservationDebitTotals(applyState, epoch, page, debitMap) { + const base = page === 0 + ? new Map() + : this.normalizePendingReservationDebitTotals(applyState.pending_reserved_debits); + if (base instanceof Error) return base; + if (page > 0 && applyState.pending_epoch === epoch && !Array.isArray(applyState.pending_reserved_debits)) { + return new Error('Pending reservation debit state missing for paged epoch apply.'); + } + const out = new Map(base); + for (const debit of debitMap.values()) { + const key = stableJson([debit.rail, debit.user]); + const current = out.get(key) ?? { rail: debit.rail, user: debit.user, au: ZERO_AU }; + const nextAu = this.safeAddAu(current.au, debit.au); + if (nextAu instanceof Error) return nextAu; + out.set(key, { ...current, au: nextAu }); + } + return out; + } + + async validateEpochDebitReservations(epoch, debitTotals) { + for (const debit of debitTotals.values()) { + const hold = await this.normalizeSpendHoldRecord( + (await this.get(this.spendHoldKey(debit.user, debit.rail, epoch))) ?? null, + debit.user, + debit.rail, + epoch + ); + if (hold instanceof Error) return hold; + if (this.compareAu(hold.reserved_au, debit.au) < 0) { + return new Error('Epoch debit exceeds reserved spend hold.'); + } + } + return null; + } + + async requireBoundCanaryProbe(value, auditor) { + const catalogError = await this.requirePublishedCanarySet(value.canary_set); + if (catalogError) return catalogError; + + const enclave = await this.get(`enclave/${value.enclave_id}`); + if (!enclave) return new Error('Canary probe enclave not found.'); + const approvedBinaryHashes = this.normalizeApprovedBinaryHashes( + enclave.binary_hash, + enclave.approved_binary_hashes + ); + if (!approvedBinaryHashes.includes(value.binary_hash.toLowerCase())) { + return new Error('Canary probe binary_hash is not approved for enclave.'); + } + + const challengeError = await this.requireCanaryChallenge(value, auditor); + if (challengeError) return challengeError; + + if (!this.verifyProbeResultSignature(auditor, value)) { + return new Error('Invalid canary auditor signature.'); + } + return null; + } + + async requirePublishedCanarySet(canarySet) { + const catalog = await this.get('catalog/current'); + if (!catalog || catalog.status !== 'active') { + return new Error('Published catalog required for canary probe.'); + } + if (!Array.isArray(catalog.canaries) || !catalog.canaries.some((entry) => entry.set_id === canarySet)) { + return new Error('Canary set is not published in the active catalog.'); + } + return null; + } + + async requireCanaryChallenge(value, auditor) { + if (value.epoch !== value.challenge_epoch + 1) { + return new Error('Canary probe epoch must immediately follow its challenge epoch.'); + } + const anchor = await this.canaryChallengeAnchor(value.challenge_epoch); + if (!anchor) return new Error('Canary challenge epoch is not anchored.'); + if (anchor.apply_hash !== value.challenge_apply_hash.toLowerCase()) { + return new Error('Canary challenge apply hash mismatch.'); + } + const catalog = await this.get('catalog/current'); + const canary = catalog?.canaries?.find((entry) => entry.set_id === value.canary_set); + if (!canary) return new Error('Published canary challenge set not found.'); + const expectedSeed = await this.opaqueHash('mayhem-canary-challenge-v1', { + challenge_epoch: value.challenge_epoch, + challenge_apply_hash: anchor.apply_hash, + probe_epoch: value.epoch, + auditor, + provider: value.provider, + enclave_id: value.enclave_id, + canary_set: value.canary_set, + catalog_hash: catalog.catalog_hash, + }); + if (expectedSeed !== value.challenge_seed.toLowerCase()) { + return new Error('Canary challenge seed mismatch.'); + } + const selectedIndex = Number(BigInt(`0x${expectedSeed}`) % BigInt(canary.prompt_ids.length)); + if (value.canary_prompt_id !== canary.prompt_ids[selectedIndex]) { + return new Error('Canary prompt does not match the unpredictable challenge selection.'); + } + return null; + } + + validateDisputeOpen(value) { + const rail = this.normalizeLedgerRail(value.rail, 'dispute rail'); + if (rail instanceof Error) return rail; + if (!this.isHexBytes(value.session_id, 32)) return new Error('Invalid dispute session id.'); + if (!this.isSafeKeyPart(value.reason)) return new Error('Invalid dispute reason.'); + for (const key of ['provider', 'enclave_id']) { + if (!this.isHexBytes(value[key], 32)) return new Error(`Invalid dispute ${key}.`); + } + for (const key of ['counterparty']) { + if (value[key] !== undefined && !this.isSafeKeyPart(value[key])) { + return new Error(`Invalid dispute ${key}.`); + } + } + if (value.evidence !== undefined) { + const bytes = b4a.from(stableJson(value.evidence)).byteLength; + if (bytes > DISPUTE_EVIDENCE_MAX_BYTES) { + return new Error('Dispute evidence bundle is too large.'); + } + } + return null; + } + + validateEpochApplyShape(value) { + const arrays = [ + ['debits', value.debits], + ['earnings', value.earnings], + ]; + if (value.market_usage !== undefined) arrays.push(['market_usage', value.market_usage]); + if (value.earning_finals !== undefined) arrays.push(['earning_finals', value.earning_finals]); + for (const [name, entries] of arrays) { + if (!Array.isArray(entries)) return new Error(`Epoch apply ${name} must be an array.`); + } + return null; + } + + epochApplyPage(value) { + if (!hasOwn(value, 'page')) return 0; + if (!Number.isSafeInteger(value.page) || value.page < 0) { + return new Error('Invalid epochApply page.'); + } + return value.page; + } + + epochApplyLastPage(value) { + if (!hasOwn(value, 'last_page')) return !hasOwn(value, 'page'); + if (typeof value.last_page !== 'boolean') { + return new Error('Invalid epochApply last_page.'); + } + return value.last_page; + } + + isEpochApplyPagePositionReplay(applyState, epoch, page, lastPage) { + if (applyState.last_page !== page) return false; + if (lastPage) { + return applyState.updated_epoch === epoch && (applyState.pending_epoch ?? null) === null; + } + return ( + applyState.pending_epoch === epoch && + applyState.pending_next_page === page + 1 + ); + } + + isIdempotentEpochApplyPage(applyState, epoch, page, lastPage, applyHash) { + if (applyState.last_apply_hash !== applyHash) return false; + return this.isEpochApplyPagePositionReplay(applyState, epoch, page, lastPage); + } + + validateEpochApplyPageOrder(applyState, epoch, page) { + const pendingEpoch = applyState.pending_epoch ?? null; + if (page === 0) { + if (pendingEpoch !== null) return new Error('Guardian monotonic epoch invariant failed: pending epoch apply page exists.'); + if (epoch <= applyState.updated_epoch) return new Error('Guardian monotonic epoch invariant failed.'); + if (epoch !== applyState.updated_epoch + 1) { + return new Error('Guardian monotonic epoch invariant failed: epoch apply must be contiguous.'); + } + return null; + } + if (pendingEpoch !== epoch) { + return new Error('Guardian monotonic epoch invariant failed: epoch apply page has no pending predecessor.'); + } + if (epoch !== applyState.updated_epoch + 1) { + return new Error('Guardian monotonic epoch invariant failed: epoch apply must be contiguous.'); + } + if (applyState.pending_next_page !== page) { + return new Error('Guardian monotonic epoch invariant failed: epoch apply page is not contiguous.'); + } + if (!this.isHexBytes(applyState.last_apply_hash, 32)) { + return new Error('Guardian monotonic epoch invariant failed: pending epoch apply hash missing.'); + } + return null; + } + + async validateEpochCadenceTime(applyState, epoch, page, settledAt, epochSeconds) { + if (!Number.isSafeInteger(settledAt) || settledAt < 0 || + !Number.isSafeInteger(epochSeconds) || epochSeconds < 1) { + return new Error('Invalid canonical epoch settlement time.'); + } + if (page > 0) { + if (applyState.pending_epoch !== epoch || + applyState.pending_settlement_unix !== settledAt) { + return new Error('Paged epoch apply settlement time changed between pages.'); + } + return null; + } + if (applyState.updated_epoch === 0) return null; + const priorSettlementUnix = await this.priorEpochSettlementUnix(applyState); + if (priorSettlementUnix instanceof Error) return priorSettlementUnix; + if (priorSettlementUnix > Number.MAX_SAFE_INTEGER - epochSeconds) { + return new Error('Canonical epoch settlement time overflow.'); + } + if (settledAt < priorSettlementUnix + epochSeconds) { + return new Error('Epoch settlement cadence has not matured.'); + } + return null; + } + + async priorEpochSettlementUnix(applyState) { + if (Number.isSafeInteger(applyState.last_settlement_unix) && + applyState.last_settlement_unix >= 0) { + return applyState.last_settlement_unix; + } + const epoch = applyState.updated_epoch; + if (!Number.isSafeInteger(epoch) || epoch < 1 || + !this.isHexBytes(applyState.last_apply_hash, 32)) { + return new Error('Prior canonical epoch settlement identity is invalid.'); + } + const seal = await this.get(`epoch/seal/${epoch}`); + if (seal !== null) { + if (seal.type !== 'epoch_empty_seal' || + seal.epoch !== epoch || + seal.seal_hash !== applyState.last_apply_hash || + !Number.isSafeInteger(seal.at) || + seal.at < 0) { + return new Error('Prior canonical empty-seal settlement identity is invalid.'); + } + const expectedSealHash = await this.epochEmptySealHash( + this.epochEmptySealHashValue(seal) + ); + if (expectedSealHash !== seal.seal_hash) { + return new Error('Prior canonical empty-seal settlement hash is invalid.'); + } + return seal.at; + } + const commit = await this.get(`epoch/commit/${epoch}`); + if (!commit || + commit.type !== 'epoch_commit' || + commit.epoch !== epoch || + !Number.isSafeInteger(commit.at) || + commit.at < 0 || + !this.isHexBytes(commit.commit_hash, 32)) { + return new Error('Prior canonical epoch commit settlement identity is invalid.'); + } + const expectedCommitHash = await this.epochCommitHash({ + epoch, + epoch_seconds: commit.epoch_seconds, + roots: commit.roots, + totals: commit.totals, + }); + if (expectedCommitHash !== commit.commit_hash || + (applyState.last_receipt_commit_hash != null && + applyState.last_receipt_commit_hash !== commit.commit_hash)) { + return new Error('Prior canonical epoch commit settlement hash is invalid.'); + } + const usageRoot = await this.get(`ev/use/${epoch}`); + if (!usageRoot || + usageRoot.type !== 'usage_root' || + usageRoot.epoch !== epoch || + usageRoot.ts !== commit.at || + usageRoot.merkle_root !== commit.roots?.use) { + return new Error('Prior canonical epoch apply evidence is missing.'); + } + return commit.at; + } + + nextEpochApplyState({ + applyState, + epoch, + page, + lastPage, + applyHash, + epochSeconds, + settledAt, + reservationDebitTotals = null, + receiptApply = null, + }) { + const base = { + ...applyState, + updated_at: this.tx, + last_apply_previous_hash: applyState.last_apply_hash ?? null, + last_apply_hash: applyHash, + last_epoch_seconds: epochSeconds, + ...(receiptApply ? { + last_receipt_index_count: receiptApply.index_count, + last_receipt_index_revision: receiptApply.index_revision, + last_receipt_index_page_count: receiptApply.index_page_count, + last_receipt_index_updated_at: receiptApply.index_updated_at, + last_receipt_commit_hash: receiptApply.commit_hash, + last_receipt_allocation_count: receiptApply.allocation_count, + last_receipt_provider_count: receiptApply.provider_count, + last_receipt_market_count: receiptApply.market_count, + last_receipt_earn_cum_au: receiptApply.earn_cum_au, + last_receipt_fee_au: receiptApply.fee_au, + last_receipt_burn_au: receiptApply.burn_au, + } : {}), + }; + if (lastPage) { + return { + ...base, + updated_epoch: epoch, + last_settlement_unix: settledAt, + pending_epoch: null, + pending_next_page: 0, + pending_settlement_unix: null, + pending_reserved_debits: null, + ...(receiptApply ? { + pending_receipt_index_count: null, + pending_receipt_index_revision: null, + pending_receipt_index_page_count: null, + pending_receipt_index_updated_at: null, + pending_receipt_commit_hash: null, + pending_receipt_allocation_count: null, + pending_receipt_provider_count: null, + pending_receipt_market_count: null, + pending_receipt_earn_cum_au: null, + pending_receipt_fee_au: null, + pending_receipt_burn_au: null, + } : {}), + last_page: page, + }; + } + return { + ...base, + pending_epoch: epoch, + pending_next_page: page + 1, + pending_settlement_unix: settledAt, + pending_reserved_debits: this.reservationDebitTotalEntries(reservationDebitTotals), + ...(receiptApply ? { + pending_receipt_index_count: receiptApply.index_count, + pending_receipt_index_revision: receiptApply.index_revision, + pending_receipt_index_page_count: receiptApply.index_page_count, + pending_receipt_index_updated_at: receiptApply.index_updated_at, + pending_receipt_commit_hash: receiptApply.commit_hash, + pending_receipt_allocation_count: receiptApply.allocation_count, + pending_receipt_provider_count: receiptApply.provider_count, + pending_receipt_market_count: receiptApply.market_count, + pending_receipt_earn_cum_au: receiptApply.earn_cum_au, + pending_receipt_fee_au: receiptApply.fee_au, + pending_receipt_burn_au: receiptApply.burn_au, + } : {}), + updated_epoch: applyState.updated_epoch, + last_page: page, + }; + } + + validateEpochApplyFeatureValue(value) { + if (CONTRACT_VERSION >= 17) { + return new Error('Aggregate epoch_apply is disabled; use receipt-bound apply_targeted_epoch.'); + } + if (!value || typeof value !== 'object' || Array.isArray(value)) { + return new Error('epochApply feature value must be an object.'); + } + const required = ['op', 'epoch', 'at', 'debits', 'earnings']; + const allowed = new Set([...required, 'market_usage', 'roots', 'totals', 'page', 'last_page']); + const unknown = Object.keys(value).filter((key) => !allowed.has(key)).sort(); + if (unknown.length > 0) { + return new Error(`epochApply feature does not accept fields: ${unknown.join(', ')}.`); + } + for (const key of required) { + if (!hasOwn(value, key)) return new Error(`epochApply feature is missing ${key}.`); + } + if (value.op !== 'epoch_apply') return new Error('Invalid epochApply feature op.'); + if (!Number.isSafeInteger(value.epoch) || value.epoch < 1) { + return new Error('Invalid epochApply feature epoch.'); + } + if (!Number.isSafeInteger(value.at) || value.at < 0) { + return new Error('Invalid epochApply feature timestamp.'); + } + const page = this.epochApplyPage(value); + if (page instanceof Error) return page; + const lastPage = this.epochApplyLastPage(value); + if (lastPage instanceof Error) return lastPage; + return this.validateEpochApplyShape(value); + } + + async normalizeCommitTargetedEpochPageZeroFeatureValue(value) { + if (!value || typeof value !== 'object' || Array.isArray(value)) { + return new Error('Commit-plus-page-zero feature value must be an object.'); + } + const required = [ + 'op', + 'epoch', + 'at', + 'epoch_commit_hash', + 'receipt_index', + 'debits', + 'earnings', + 'allocations', + 'last_page', + 'roots', + 'totals', + ]; + const allowed = new Set([ + ...required, + 'earning_finals', + 'market_usage', + 'supersedes_commit_hash', + ]); + const unknown = Object.keys(value).filter((key) => !allowed.has(key)).sort(); + if (unknown.length > 0) { + return new Error(`Commit-plus-page-zero feature does not accept fields: ${unknown.join(', ')}.`); + } + for (const field of required) { + if (!hasOwn(value, field)) { + return new Error(`Commit-plus-page-zero feature is missing ${field}.`); + } + } + if (value.op !== 'commit_apply_targeted_epoch_page0') { + return new Error('Invalid commit-plus-page-zero feature op.'); + } + if (hasOwn(value, 'supersedes_commit_hash') && + (!this.isHexBytes(value.supersedes_commit_hash, 32) || + value.supersedes_commit_hash !== value.supersedes_commit_hash.toLowerCase())) { + return new Error('Invalid superseded epoch commit hash.'); + } + const roots = this.normalizeEpochRoots(value.roots); + if (roots instanceof Error) return roots; + const totals = this.normalizeEpochTotals(value.totals); + if (totals instanceof Error) return totals; + if (totals.price_count !== 0) { + return new Error('Receipt settlement page zero cannot carry market price derivations.'); + } + const emptyPriceRoot = await this.priceDerivationRoot([]); + if (roots.price !== emptyPriceRoot) { + return new Error('Receipt settlement price root must be the canonical empty root.'); + } + const targetedValue = { + op: 'apply_targeted_epoch', + epoch: value.epoch, + at: value.at, + epoch_commit_hash: value.epoch_commit_hash, + receipt_index: value.receipt_index, + debits: value.debits, + earnings: value.earnings, + allocations: value.allocations, + ...(hasOwn(value, 'earning_finals') ? { earning_finals: value.earning_finals } : {}), + ...(hasOwn(value, 'market_usage') ? { market_usage: value.market_usage } : {}), + page: 0, + last_page: value.last_page, + }; + const normalized = await this.normalizeTargetedEpochFeatureValue(targetedValue); + if (normalized instanceof Error) return normalized; + return { + epoch: value.epoch, + at: value.at, + roots, + totals, + receipt_index: normalized.ledger_value.receipt_index, + epoch_commit_hash: value.epoch_commit_hash, + supersedes_commit_hash: value.supersedes_commit_hash ?? null, + targeted_value: targetedValue, + normalized, + }; + } + + async prepareTargetedEpochCommitTransition(prepared, applyState, featureKey) { + const currentReceiptIndex = this.normalizeReceiptEpochIndexMetadata( + await this.get(this.receiptEpochIndexKey(prepared.epoch)), + prepared.epoch + ); + if (currentReceiptIndex instanceof Error) return currentReceiptIndex; + if (stableJson(currentReceiptIndex) !== stableJson(prepared.receipt_index)) { + return new Error('Canonical receipt epoch index changed before commit-plus-page-zero.'); + } + if (prepared.totals.use_count !== currentReceiptIndex.count) { + return new Error('Epoch commit receipt count must match the canonical receipt index.'); + } + const params = await this.activeParamsAt(prepared.at, ['challenge_epochs', 'epoch_seconds']); + const commitHash = await this.epochCommitHash({ + epoch: prepared.epoch, + epoch_seconds: params.epoch_seconds, + roots: prepared.roots, + totals: prepared.totals, + }); + if (commitHash !== prepared.epoch_commit_hash) { + return new Error('Commit-plus-page-zero epoch commit hash is invalid.'); + } + const key = `epoch/commit/${prepared.epoch}`; + const existing = await this.get(key); + if (existing?.commit_hash === commitHash) { + if ( + existing.type !== 'epoch_commit' || + existing.status !== 'provisional' || + existing.apply_mode !== 'targeted_receipt_pages_v1' || + existing.epoch !== prepared.epoch || + existing.at !== prepared.at || + stableJson(existing.roots) !== stableJson(prepared.roots) || + stableJson(existing.totals) !== stableJson(prepared.totals) + ) { + return new Error('Existing epoch commit does not match commit-plus-page-zero.'); + } + return { key, record: existing, archive: null, write: false }; + } + if (existing !== null) { + if ( + existing.type !== 'epoch_commit' || + existing.status !== 'provisional' || + existing.epoch !== prepared.epoch || + !this.isHexBytes(existing.commit_hash, 32) || + existing.commit_hash !== existing.commit_hash.toLowerCase() || + (existing.replacement_count !== undefined && + (!Number.isSafeInteger(existing.replacement_count) || existing.replacement_count < 0)) + ) { + return new Error('Only a canonical provisional epoch commit can be superseded.'); + } + if (prepared.supersedes_commit_hash !== existing.commit_hash) { + return new Error('Commit-plus-page-zero must identify the current provisional commit.'); + } + if (applyState.updated_epoch !== prepared.epoch - 1 || + (applyState.pending_epoch ?? null) !== null) { + return new Error('Epoch commit cannot be superseded after targeted apply has started.'); + } + for (const evidenceKey of ['use', 'earn', 'fee', 'price']) { + if (await this.get(`ev/${evidenceKey}/${prepared.epoch}`)) { + return new Error('Epoch commit cannot be superseded after settlement evidence exists.'); + } + } + if (!Number.isSafeInteger(existing.totals?.use_count) || + prepared.totals.use_count <= existing.totals.use_count) { + return new Error('Replacement epoch commit must strictly extend the canonical receipt count.'); + } + if (!Number.isSafeInteger(existing.at) || prepared.at < existing.at) { + return new Error('Replacement epoch commit timestamp cannot precede the superseded commit.'); + } + } else if (prepared.supersedes_commit_hash !== null) { + return new Error('Epoch commit has nothing to supersede.'); + } + const record = { + type: 'epoch_commit', + epoch: prepared.epoch, + epoch_seconds: params.epoch_seconds, + apply_mode: 'targeted_receipt_pages_v1', + roots: prepared.roots, + totals: prepared.totals, + status: 'provisional', + challenge_epochs: params.challenge_epochs, + provisional_until_epoch: prepared.epoch + params.challenge_epochs, + commit_hash: commitHash, + submitted_by: this.address, + submitted_at: featureKey, + at: prepared.at, + ...(existing ? { + supersedes: existing.commit_hash, + replacement_count: (existing.replacement_count ?? 0) + 1, + } : {}), + }; + const archive = existing ? { + key: `epoch/commit/superseded/${prepared.epoch}/${existing.commit_hash}`, + value: { + ...existing, + status: 'superseded', + superseded_by: commitHash, + superseded_at: featureKey, + }, + } : null; + return { key, record, archive, write: true }; + } + + async normalizeTargetedEpochFeatureValue(value) { + if (!value || typeof value !== 'object' || Array.isArray(value)) { + return new Error('Targeted epoch feature value must be an object.'); + } + const required = [ + 'op', + 'epoch', + 'at', + 'epoch_commit_hash', + 'receipt_index', + 'debits', + 'earnings', + 'allocations', + 'page', + 'last_page', + ]; + const allowed = new Set([...required, 'market_usage', 'earning_finals']); + const unknown = Object.keys(value).filter((key) => !allowed.has(key)).sort(); + if (unknown.length > 0) { + return new Error(`Targeted epoch feature does not accept fields: ${unknown.join(', ')}.`); + } + for (const key of required) { + if (!hasOwn(value, key)) return new Error(`Targeted epoch feature is missing ${key}.`); + } + if (value.op !== 'apply_targeted_epoch') { + return new Error('Invalid targeted epoch feature op.'); + } + if (!this.isHexBytes(value.epoch_commit_hash, 32) || + value.epoch_commit_hash !== value.epoch_commit_hash.toLowerCase()) { + return new Error('Invalid targeted epoch commit hash.'); + } + const receiptIndex = this.normalizeReceiptEpochIndexMetadata( + value.receipt_index, + value.epoch + ); + if (receiptIndex instanceof Error) return receiptIndex; + if (stableJson(receiptIndex) !== stableJson(value.receipt_index)) { + return new Error('Targeted epoch receipt index snapshot must be canonical.'); + } + if (!Array.isArray(value.earnings)) { + return new Error('Targeted epoch earnings must be an array.'); + } + if (!Array.isArray(value.allocations) || value.allocations.length === 0) { + return new Error('Targeted epoch allocations must be a non-empty array.'); + } + const allocations = []; + const allocationSessions = new Set(); + for (const entry of value.allocations) { + const entryError = this.validateExactObjectKeys( + entry, + [ + 'session_id', + 'billing_id', + 'billing_attempt', + 'billing_epoch', + 'receipt_seq', + 'receipt_hash', + 'user', + 'rail', + 'provider', + 'payout_revision', + 'au', + ], + 'targeted epoch allocation' + ); + if (entryError) return entryError; + const rail = this.normalizeLedgerRail(entry.rail, 'targeted epoch allocation rail'); + if (rail instanceof Error) return rail; + if (!this.isHexBytes(entry.session_id, 32) || + !this.isHexBytes(entry.billing_id, 32) || + !this.isHexBytes(entry.receipt_hash, 32) || + !this.isHexBytes(entry.user, 32) || + !this.isHexBytes(entry.provider, 32) || + !this.isHexBytes(entry.payout_revision, 32)) { + return new Error('Invalid targeted epoch allocation identity.'); + } + if (!Number.isSafeInteger(entry.billing_attempt) || entry.billing_attempt < 0 || + !Number.isSafeInteger(entry.billing_epoch) || entry.billing_epoch < 1 || + !Number.isSafeInteger(entry.receipt_seq) || entry.receipt_seq < 0) { + return new Error('Invalid targeted epoch allocation receipt position.'); + } + const attemptIdentity = `${entry.billing_id}:${entry.billing_attempt}`; + if (allocationSessions.has(entry.session_id) || + allocationSessions.has(attemptIdentity)) { + return new Error('Duplicate targeted epoch receipt allocation.'); + } + allocationSessions.add(entry.session_id); + allocationSessions.add(attemptIdentity); + const au = this.normalizeAu( + entry.au, + 'targeted epoch allocation amount', + { allowZero: false } + ); + if (au instanceof Error) return au; + allocations.push({ + session_id: entry.session_id, + billing_id: entry.billing_id, + billing_attempt: entry.billing_attempt, + billing_epoch: entry.billing_epoch, + receipt_seq: entry.receipt_seq, + receipt_hash: entry.receipt_hash, + user: entry.user, + rail, + provider: entry.provider, + payout_revision: entry.payout_revision, + au, + }); + } + allocations.sort((left, right) => ( + PROVIDER_PAYOUT_BINDING_RAIL_ORDER.indexOf(left.rail) - + PROVIDER_PAYOUT_BINDING_RAIL_ORDER.indexOf(right.rail) || + compareCodepoint(left.user, right.user) || + left.billing_epoch - right.billing_epoch || + compareCodepoint(left.billing_id, right.billing_id) || + left.billing_attempt - right.billing_attempt || + compareCodepoint(left.session_id, right.session_id) + )); + if (stableJson(allocations) !== stableJson(value.allocations)) { + return new Error('Targeted epoch allocations must be canonical.'); + } + const aggregated = new Map(); + const providerRails = new Map(); + for (const entry of value.earnings) { + const entryError = this.validateExactObjectKeys( + entry, + ['rail', 'provider', 'gross_au', 'payout_revision'], + 'targeted epoch earning' + ); + if (entryError) return entryError; + const rail = this.normalizeLedgerRail(entry.rail, 'targeted epoch earning rail'); + if (rail instanceof Error) return rail; + if (!PROVIDER_PAYOUT_BINDING_RAILS.has(rail)) { + return new Error('Targeted epoch earning rail has no payout binding path.'); + } + if (!this.isHexBytes(entry.provider, 32) || + entry.provider !== entry.provider.toLowerCase()) { + return new Error('Invalid targeted epoch earning provider.'); + } + if (!this.isHexBytes(entry.payout_revision, 32) || + entry.payout_revision !== entry.payout_revision.toLowerCase()) { + return new Error('Invalid targeted epoch payout revision.'); + } + const grossAu = this.normalizeAu( + entry.gross_au, + 'targeted epoch gross earning', + { allowZero: false } + ); + if (grossAu instanceof Error) return grossAu; + const providerRailKey = stableJson([rail, entry.provider]); + const selected = providerRails.get(providerRailKey); + if (selected && selected !== entry.payout_revision) { + return new Error('Targeted epoch provider rail cannot substitute payout revisions.'); + } + providerRails.set(providerRailKey, entry.payout_revision); + const key = stableJson([rail, entry.provider, entry.payout_revision]); + const current = aggregated.get(key) ?? { + rail, + provider: entry.provider, + gross_au: ZERO_AU, + payout_revision: entry.payout_revision, + }; + const next = this.safeAddAu(current.gross_au, grossAu); + if (next instanceof Error) return next; + aggregated.set(key, { ...current, gross_au: next }); + } + const targetedEarnings = Array.from(aggregated.values()).sort((left, right) => ( + PROVIDER_PAYOUT_BINDING_RAIL_ORDER.indexOf(left.rail) - + PROVIDER_PAYOUT_BINDING_RAIL_ORDER.indexOf(right.rail) || + compareCodepoint(left.provider, right.provider) || + compareCodepoint(left.payout_revision, right.payout_revision) + )); + let earningFinals = null; + if (hasOwn(value, 'earning_finals')) { + if (value.last_page !== true || !Array.isArray(value.earning_finals) || + value.earning_finals.length === 0) { + return new Error('Targeted epoch earning_finals require a non-empty final page.'); + } + earningFinals = []; + const identities = new Set(); + for (const entry of value.earning_finals) { + const entryError = this.validateExactObjectKeys( + entry, + ['rail', 'provider', 'gross_au', 'net_au', 'cumulative_au'], + 'targeted epoch final earning' + ); + if (entryError) return entryError; + const rail = this.normalizeLedgerRail(entry.rail, 'targeted epoch final earning rail'); + if (rail instanceof Error) return rail; + if (!this.isHexBytes(entry.provider, 32) || + entry.provider !== entry.provider.toLowerCase()) { + return new Error('Invalid targeted epoch final earning provider.'); + } + const identity = stableJson([rail, entry.provider]); + if (identities.has(identity)) { + return new Error('Duplicate targeted epoch final earning provider.'); + } + identities.add(identity); + const grossAu = this.normalizeAu( + entry.gross_au, + 'targeted epoch final gross earning', + { allowZero: false } + ); + const netAu = this.normalizeAu( + entry.net_au, + 'targeted epoch final net earning', + { allowZero: false } + ); + const cumulativeAu = this.normalizeAu( + entry.cumulative_au, + 'targeted epoch final cumulative earning', + { allowZero: false } + ); + if (grossAu instanceof Error || netAu instanceof Error || cumulativeAu instanceof Error) { + return new Error('Invalid targeted epoch final earning amount.'); + } + if (this.compareAu(netAu, grossAu) > 0 || this.compareAu(cumulativeAu, netAu) < 0) { + return new Error('Targeted epoch final earning totals are inconsistent.'); + } + earningFinals.push({ + rail, + provider: entry.provider, + gross_au: grossAu, + net_au: netAu, + cumulative_au: cumulativeAu, + }); + } + earningFinals.sort((left, right) => ( + PROVIDER_PAYOUT_BINDING_RAIL_ORDER.indexOf(left.rail) - + PROVIDER_PAYOUT_BINDING_RAIL_ORDER.indexOf(right.rail) || + compareCodepoint(left.provider, right.provider) + )); + if (stableJson(earningFinals) !== stableJson(value.earning_finals)) { + return new Error('Targeted epoch final earnings must be canonical.'); + } + } + const { + allocations: _allocations, + op: _op, + earnings: _earnings, + earning_finals: _earningFinals, + ...ledgerFields + } = value; + const ledgerValue = { + ...ledgerFields, + receipt_index: receiptIndex, + earnings: targetedEarnings.map(({ payout_revision: _revision, ...earning }) => earning), + ...(earningFinals ? { earning_finals: earningFinals } : {}), + }; + if (!Number.isSafeInteger(ledgerValue.epoch) || ledgerValue.epoch < 1) { + return new Error('Invalid targeted epoch.'); + } + if (!Number.isSafeInteger(ledgerValue.at) || ledgerValue.at < 0) { + return new Error('Invalid targeted epoch timestamp.'); + } + const page = this.epochApplyPage(ledgerValue); + if (page instanceof Error) return page; + const lastPage = this.epochApplyLastPage(ledgerValue); + if (lastPage instanceof Error) return lastPage; + const ledgerError = this.validateEpochApplyShape(ledgerValue); + if (ledgerError) return ledgerError; + return { + ledger_value: ledgerValue, + allocations, + targeted_earnings: targetedEarnings, + earning_finals: earningFinals, + revision_bindings: targetedEarnings.map((earning) => ({ + provider: earning.provider, + rail: earning.rail, + revision: earning.payout_revision, + })), + }; + } + + async validateProviderPayoutBindingIntent(intent, { currentState = true } = {}) { + const shapeError = this.validateExactObjectKeys( + intent, + [ + 'op', + 'network', + 'admin', + 'bootstrap', + 'context_revision', + 'provider', + 'rail', + 'currency', + 'chain_id', + 'target', + 'target_wallet', + 'target_signature', + 'previous_revision', + 'payment_config_version', + 'nonce', + 'expires_after_epoch', + ], + 'provider payout binding intent' + ); + if (shapeError) return shapeError; + if (intent.op !== 'bind_provider_payout') { + return new Error('Invalid provider payout binding intent op.'); + } + for (const field of ['admin', 'bootstrap', 'provider', 'nonce']) { + if (!this.isHexBytes(intent[field], 32) || intent[field] !== intent[field].toLowerCase()) { + return new Error(`Invalid provider payout binding ${field}.`); + } + } + if (!this.isHexBytes(intent.context_revision, 32) || + intent.context_revision !== intent.context_revision.toLowerCase()) { + return new Error('Invalid provider payout binding context revision.'); + } + if (!this.isSafeKeyPart(intent.network)) { + return new Error('Invalid provider payout binding network.'); + } + if (!PROVIDER_PAYOUT_BINDING_RAILS.has(intent.rail)) { + return new Error('Invalid provider payout binding rail.'); + } + if (intent.rail !== 'fiat' && intent.currency !== null) { + return new Error('TAP/TNK payout binding currency must be null.'); + } + if ( + intent.previous_revision !== null && + (!this.isHexBytes(intent.previous_revision, 32) || + intent.previous_revision !== intent.previous_revision.toLowerCase()) + ) { + return new Error('Invalid previous provider payout binding revision.'); + } + if (!Number.isSafeInteger(intent.payment_config_version) || + intent.payment_config_version < 1) { + return new Error('Invalid provider payout payment config version.'); + } + if (!Number.isSafeInteger(intent.expires_after_epoch) || + intent.expires_after_epoch < 1) { + return new Error('Invalid provider payout binding expiry epoch.'); + } + + if (intent.rail === 'fiat') { + const currency = this.normalizeFiatCurrency(intent.currency); + if (currency instanceof Error || + currency !== intent.currency || + intent.chain_id !== null || + intent.target_wallet !== null || + intent.target_signature !== null || + typeof intent.target !== 'string' || + !/^acct_[A-Za-z0-9._-]+$/.test(intent.target)) { + return new Error('Invalid verified Stripe payout target.'); + } + } else if (intent.rail === 'tap') { + if (!Number.isSafeInteger(intent.chain_id) || intent.chain_id < 1) { + return new Error('Invalid TAP payout binding chain id.'); + } + if (!this.isEthHexBytes(intent.target, 20) || + intent.target !== intent.target.toLowerCase()) { + return new Error('Invalid TAP payout target.'); + } + if (intent.target_wallet !== null) { + return new Error('TAP payout binding target_wallet must be null.'); + } + if (!this.isEthHexBytes(intent.target_signature, 65) || + intent.target_signature !== intent.target_signature.toLowerCase()) { + return new Error('Invalid TAP payout target ownership signature.'); + } + } else { + if (intent.chain_id !== null) { + return new Error('TNK payout binding chain_id must be null.'); + } + if (!this.isSafeKeyPart(intent.target) || intent.target !== intent.target.toLowerCase()) { + return new Error('Invalid TNK payout target.'); + } + if (!this.isHexBytes(intent.target_wallet, 32) || + intent.target_wallet !== intent.target_wallet.toLowerCase()) { + return new Error('Invalid TNK payout target wallet.'); + } + if (!this.isHexBytes(intent.target_signature, 64) || + intent.target_signature !== intent.target_signature.toLowerCase()) { + return new Error('Invalid TNK payout target ownership signature.'); + } + const address = this.msbAddressForPublicKey(intent.target_wallet, intent.network); + if (address instanceof Error) return address; + if (intent.target !== address) { + return new Error('TNK payout target does not match target wallet.'); + } + } + if (!currentState) return null; + + const admin = await this.get('admin'); + if (intent.admin !== admin) { + return new Error('Provider payout binding admin does not match canonical admin.'); + } + const payments = await this.get('payments/current'); + if (!payments || payments.set_by !== admin || payments.set_by_role !== 'admin') { + return new Error('Canonical payment configuration required.'); + } + if (intent.payment_config_version !== payments.ver) { + return new Error('Provider payout binding payment config version is stale.'); + } + if (!Array.isArray(payments.rails) || !payments.rails.includes(intent.rail)) { + return new Error('Provider payout binding rail is not enabled.'); + } + if (intent.network !== payments.tnk?.network) { + return new Error('Provider payout binding network is not canonical.'); + } + + if (intent.rail === 'fiat') { + if (!payments.fiat?.payout_currencies?.includes(intent.currency)) { + return new Error('Invalid verified Stripe payout target.'); + } + const verification = await this.providerStripePayoutVerificationForTarget( + intent.provider, + intent.target + ); + if (!verification || + verification.type !== 'stripe_payout_verification' || + verification.provider !== intent.provider || + verification.target !== intent.target || + verification.currency !== intent.currency || + verification.context_revision !== intent.context_revision || + verification.payment_config_version !== intent.payment_config_version || + verification.details_submitted !== true || + verification.payouts_enabled !== true || + verification.transfers_enabled !== true || + verification.verified_by !== admin || + verification.verified_by_role !== 'admin') { + return new Error('Current ready provider-scoped Stripe verification required.'); + } + return null; + } + + if (intent.rail === 'tap') { + if (intent.chain_id !== payments.tap?.chain_id) { + return new Error('TAP payout binding chain id is not canonical.'); + } + return null; + } + return null; + } + + validateDepositTnkIntent(intent) { + const fields = [ + 'op', + 'memo_hash', + 'treasury_address', + 'tnk_e18', + 'quoted_au', + 'rate_tnk_usd_au', + 'rate_source', + ]; + if (hasOwn(intent, 'rate_ts')) fields.push('rate_ts'); + if (hasOwn(intent, 'rate_record_key')) fields.push('rate_record_key'); + const shapeError = this.validateExactObjectKeys( + intent, + fields, + 'deposit TNK intent' + ); + if (shapeError) return shapeError; + if (intent.op !== 'deposit_tnk') return new Error('Invalid deposit TNK intent op.'); + if (!this.isSafeKeyPart(intent.memo_hash)) return new Error('Invalid deposit memo hash.'); + if (!this.isSafeKeyPart(intent.treasury_address)) return new Error('Invalid TNK treasury address.'); + const quotedAu = this.normalizeAu(intent.quoted_au, 'TNK quoted credit', { allowZero: false }); + if (quotedAu instanceof Error) { + return new Error('Invalid TNK quoted credit.'); + } + const rate = this.normalizeAu(intent.rate_tnk_usd_au, 'TNK quoted rate', { allowZero: false }); + if (rate instanceof Error) { + return new Error('Invalid TNK quoted rate.'); + } + if (!this.isSafeKeyPart(intent.rate_source)) return new Error('Invalid TNK rate source.'); + const hasRateTs = hasOwn(intent, 'rate_ts'); + const hasRateRecordKey = hasOwn(intent, 'rate_record_key'); + if (hasRateTs !== hasRateRecordKey) { + return new Error('TNK rate timestamp and record key must be paired.'); + } + if (hasRateTs && + (!Number.isSafeInteger(intent.rate_ts) || intent.rate_ts < 0)) { + return new Error('Invalid TNK rate timestamp.'); + } + if (hasRateRecordKey) { + const prefix = `rate/tnk/${intent.rate_ts}/`; + if (typeof intent.rate_record_key !== 'string' || + !intent.rate_record_key.startsWith(prefix) || + !this.isHexBytes(intent.rate_record_key.slice(prefix.length), 32)) { + return new Error('Invalid TNK rate record key.'); + } + } + const tnkE18 = this.parseTnkE18(intent.tnk_e18); + if (tnkE18 instanceof Error) return tnkE18; + return null; + } + + guardianValidateBalanceRecord(record, user = null, rail = null) { + if (!record || typeof record !== 'object' || Array.isArray(record)) { + return new Error('Guardian non-negative balance invariant failed.'); + } + if (rail !== null && record.rail !== rail) { + return new Error('Guardian balance rail invariant failed.'); + } + if (record.denom !== PRICE_DENOMINATION) { + return new Error('Guardian balance denomination invariant failed.'); + } + if (user !== null && record.user !== user) { + return new Error('Guardian balance owner invariant failed.'); + } + if (this.normalizeAu(record.au, 'balance au') instanceof Error) { + return new Error('Guardian non-negative balance invariant failed.'); + } + if (!Number.isSafeInteger(record.updated_epoch) || record.updated_epoch < 0) { + return new Error('Guardian balance epoch invariant failed.'); + } + if (record.rail === 'tap') { + const scopeError = this.guardianValidateTapScope(record, ['au'], 'balance'); + if (scopeError) return scopeError; + } + return null; + } + + guardianValidateEarningRecord(record, provider = null, rail = null) { + if (!record || typeof record !== 'object' || Array.isArray(record)) { + return new Error('Guardian non-negative earnings invariant failed.'); + } + if (rail !== null && record.rail !== rail) { + return new Error('Guardian earnings rail invariant failed.'); + } + if (record.denom !== PRICE_DENOMINATION) { + return new Error('Guardian earnings denomination invariant failed.'); + } + if (provider !== null && record.provider !== provider) { + return new Error('Guardian earnings owner invariant failed.'); + } + for (const key of ['total_au', 'held_au', 'paid_cum_au']) { + if (this.normalizeAu(record[key], `earning ${key}`) instanceof Error) { + return new Error('Guardian non-negative earnings invariant failed.'); + } + } + if (!Number.isSafeInteger(record.updated_epoch) || record.updated_epoch < 0) { + return new Error('Guardian earnings epoch invariant failed.'); + } + const payableAu = this.safeSubAu(record.total_au, record.held_au); + if (payableAu instanceof Error) return payableAu; + if (this.compareAu(record.held_au, record.total_au) > 0 || this.compareAu(record.paid_cum_au, payableAu) > 0) { + return new Error('Guardian earnings conservation invariant failed.'); + } + if (record.holdbacks !== undefined) { + const holdbacks = this.normalizeHoldbackBuckets(record); + if (holdbacks instanceof Error) return holdbacks; + const heldAu = this.holdbackBucketTotal(holdbacks); + if (heldAu instanceof Error) return heldAu; + if (this.compareAu(heldAu, record.held_au) !== 0) { + return new Error('Guardian earnings conservation invariant failed.'); + } + } + if (record.rail === 'tap') { + const scopeError = this.guardianValidateTapScope( + record, + ['total_au', 'held_au', 'paid_cum_au'], + 'earning' + ); + if (scopeError) return scopeError; + } + return null; + } + + guardianValidatePayoutLiabilityRecord( + record, + provider = null, + rail = null, + revision = null + ) { + if (!record || + typeof record !== 'object' || + Array.isArray(record) || + record.type !== 'provider_payout_liability') { + return new Error('Guardian payout liability shape invariant failed.'); + } + if (!PROVIDER_PAYOUT_BINDING_RAILS.has(record.rail) || + (rail !== null && record.rail !== rail)) { + return new Error('Guardian payout liability rail invariant failed.'); + } + if (!this.isHexBytes(record.provider, 32) || + (provider !== null && record.provider !== provider)) { + return new Error('Guardian payout liability owner invariant failed.'); + } + if (!this.isHexBytes(record.revision, 32) || + (revision !== null && record.revision !== revision)) { + return new Error('Guardian payout liability revision invariant failed.'); + } + if (!this.isSafeKeyPart(record.target)) { + return new Error('Guardian payout liability target invariant failed.'); + } + if (record.rail === 'fiat') { + if (!this.isSafeKeyPart(record.currency) || record.chain_id !== null) { + return new Error('Guardian payout liability fiat scope invariant failed.'); + } + } else if (record.rail === 'tap') { + if (record.currency !== null || + !Number.isSafeInteger(record.chain_id) || + record.chain_id < 1 || + !this.isEthHexBytes(record.target, 20)) { + return new Error('Guardian payout liability TAP scope invariant failed.'); + } + } else if (record.currency !== null || record.chain_id !== null) { + return new Error('Guardian payout liability TNK scope invariant failed.'); + } + for (const key of ['total_au', 'held_au', 'paid_cum_au']) { + if (this.normalizeAu(record[key], `payout liability ${key}`) instanceof Error) { + return new Error('Guardian non-negative payout liability invariant failed.'); + } + } + if (!Number.isSafeInteger(record.updated_epoch) || record.updated_epoch < 0) { + return new Error('Guardian payout liability epoch invariant failed.'); + } + const payableAu = this.safeSubAu(record.total_au, record.held_au); + if (payableAu instanceof Error || + this.compareAu(record.held_au, record.total_au) > 0 || + this.compareAu(record.paid_cum_au, payableAu) > 0) { + return new Error('Guardian payout liability conservation invariant failed.'); + } + const holdbacks = this.normalizeHoldbackBuckets(record); + if (holdbacks instanceof Error) return holdbacks; + const heldAu = this.holdbackBucketTotal(holdbacks); + if (heldAu instanceof Error || + this.compareAu(heldAu, record.held_au) !== 0) { + return new Error('Guardian payout liability conservation invariant failed.'); + } + return null; + } + + guardianValidateFeeRecord(record, rail = null) { + if (!record || typeof record !== 'object' || Array.isArray(record)) { + return new Error('Guardian fee conservation invariant failed.'); + } + if (rail !== null && record.rail !== rail) { + return new Error('Guardian fee rail invariant failed.'); + } + if (record.denom !== PRICE_DENOMINATION) { + return new Error('Guardian fee denomination invariant failed.'); + } + for (const key of ['cum_au', 'swept_cum_au']) { + if (this.normalizeAu(record[key], `fee ${key}`) instanceof Error) { + return new Error('Guardian fee conservation invariant failed.'); + } + } + if (!Number.isSafeInteger(record.updated_epoch) || record.updated_epoch < 0) { + return new Error('Guardian monotonic epoch invariant failed.'); + } + if (this.compareAu(record.swept_cum_au, record.cum_au) > 0) { + return new Error('Guardian fee conservation invariant failed.'); + } + const settledCumAu = record.settled_cum_au ?? record.cum_au; + if ( + this.normalizeAu(settledCumAu, 'fee settled cumulative amount') instanceof Error || + this.compareAu(settledCumAu, record.cum_au) < 0 + ) { + return new Error('Guardian conservation invariant failed.'); + } + if (record.rail === 'tap') { + const scopeError = this.guardianValidateTapScope( + record, + ['cum_au', 'swept_cum_au', 'settled_cum_au'], + 'fee' + ); + if (scopeError) return scopeError; + } + return null; + } + + guardianValidateBurnRecord(record, rail = null) { + if (!record || typeof record !== 'object' || Array.isArray(record)) { + return new Error('Guardian burn conservation invariant failed.'); + } + if (rail !== null && record.rail !== rail) { + return new Error('Guardian burn rail invariant failed.'); + } + if (record.denom !== PRICE_DENOMINATION) { + return new Error('Guardian burn denomination invariant failed.'); + } + if (this.normalizeAu(record.cum_au, 'burn cumulative amount') instanceof Error) { + return new Error('Guardian burn conservation invariant failed.'); + } + if (!Number.isSafeInteger(record.updated_epoch) || record.updated_epoch < 0) { + return new Error('Guardian monotonic epoch invariant failed.'); + } + const expectedBps = record.rail === 'tap' ? TAP_BURN_BPS : 0; + if (record.burn_bps !== expectedBps) { + return new Error('Guardian burn policy invariant failed.'); + } + if (record.rail === 'tap') { + const scopeError = this.guardianValidateTapScope(record, ['cum_au'], 'burn'); + if (scopeError) return scopeError; + } + return null; + } + + guardianCheckEpochApply({ + epoch, + applyState, + feeRecords, + burnRecords, + debitTotal, + debitRailTotals, + feeDeltaByRail, + burnDeltaByRail, + providerDeltaTotal, + nextFeeRecords, + nextBurnRecords, + balances, + earnings, + }) { + if (!applyState || !Number.isSafeInteger(applyState.updated_epoch) || applyState.updated_epoch < 0) { + return new Error('Guardian monotonic epoch invariant failed.'); + } + if (epoch <= applyState.updated_epoch || epoch !== applyState.updated_epoch + 1) { + return new Error('Guardian monotonic epoch invariant failed.'); + } + let feeDeltaAu = ZERO_AU; + let burnDeltaAu = ZERO_AU; + for (const rail of PROVIDER_ACCEPTED_RAIL_ORDER) { + const fee = feeRecords.get(rail); + const feeError = this.guardianValidateFeeRecord(fee, rail); + if (feeError) return feeError; + const nextFee = nextFeeRecords.get(rail); + const nextFeeError = this.guardianValidateFeeRecord(nextFee, rail); + if (nextFeeError) return nextFeeError; + const railFeeDelta = feeDeltaByRail.get(rail) ?? ZERO_AU; + feeDeltaAu = this.safeAddAu(feeDeltaAu, railFeeDelta); + if (feeDeltaAu instanceof Error) return feeDeltaAu; + const expectedFeeCumAu = this.safeAddAu(fee.cum_au, railFeeDelta); + if (expectedFeeCumAu instanceof Error) return expectedFeeCumAu; + if (this.compareAu(nextFee.cum_au, expectedFeeCumAu) !== 0) { + return new Error('Guardian fee conservation invariant failed.'); + } + const burn = burnRecords.get(rail); + const burnError = this.guardianValidateBurnRecord(burn, rail); + if (burnError) return burnError; + const nextBurn = nextBurnRecords.get(rail); + const nextBurnError = this.guardianValidateBurnRecord(nextBurn, rail); + if (nextBurnError) return nextBurnError; + const railBurnDelta = burnDeltaByRail.get(rail) ?? ZERO_AU; + burnDeltaAu = this.safeAddAu(burnDeltaAu, railBurnDelta); + if (burnDeltaAu instanceof Error) return burnDeltaAu; + const expectedBurnCumAu = this.safeAddAu(burn.cum_au, railBurnDelta); + if (expectedBurnCumAu instanceof Error) return expectedBurnCumAu; + if (this.compareAu(nextBurn.cum_au, expectedBurnCumAu) !== 0) { + return new Error('Guardian burn conservation invariant failed.'); + } + } + const providerAndFeeAu = this.safeAddAu(providerDeltaTotal, feeDeltaAu); + if (providerAndFeeAu instanceof Error) return providerAndFeeAu; + const grossDeltaTotal = this.safeAddAu(providerAndFeeAu, burnDeltaAu); + if (grossDeltaTotal instanceof Error) return grossDeltaTotal; + if (this.compareAu(grossDeltaTotal, debitTotal) !== 0) { + return new Error('Guardian conservation invariant failed.'); + } + + for (const balance of balances.values()) { + const balanceError = this.guardianValidateBalanceRecord(balance, balance.user, balance.rail); + if (balanceError) return balanceError; + } + for (const earning of earnings.values()) { + const earningError = this.guardianValidateEarningRecord(earning, earning.provider, earning.rail); + if (earningError) return earningError; + } + + const nextSettledCumByRail = new Map(); + for (const rail of PROVIDER_ACCEPTED_RAIL_ORDER) { + const fee = feeRecords.get(rail); + const nextFee = nextFeeRecords.get(rail); + const priorSettledCumAu = fee.settled_cum_au ?? fee.cum_au; + const nextSettledCumAu = this.safeAddAu(priorSettledCumAu, debitRailTotals.get(rail) ?? ZERO_AU); + if (nextSettledCumAu instanceof Error) return nextSettledCumAu; + if (this.compareAu(nextFee.cum_au, nextSettledCumAu) > 0) { + return new Error('Guardian conservation invariant failed.'); + } + nextSettledCumByRail.set(rail, nextSettledCumAu); + } + return { ok: true, next_settled_cum_by_rail: nextSettledCumByRail }; + } + + lockedEarningEpochs(params) { + return Math.max(params.holdback_epochs ?? 0, params.challenge_epochs ?? 0); + } + + providerLockedEarningEpochs(provider, params) { + const normalHoldback = params.holdback_epochs ?? 0; + const newProviderHoldback = params.new_provider_holdback_epochs ?? normalHoldback; + const threshold = params.probation_successful_sessions ?? 0; + const successfulSessions = provider?.probation?.successful_sessions ?? 0; + if ( + !Number.isSafeInteger(normalHoldback) || + !Number.isSafeInteger(newProviderHoldback) || + !Number.isSafeInteger(threshold) || + !Number.isSafeInteger(successfulSessions) || + normalHoldback < 0 || + newProviderHoldback < 0 || + threshold < 0 || + successfulSessions < 0 + ) { + return new Error('Invalid provider holdback probation state.'); + } + const providerHoldback = successfulSessions < threshold + ? Math.max(normalHoldback, newProviderHoldback) + : normalHoldback; + return Math.max(providerHoldback, params.challenge_epochs ?? 0); + } + + async recordCanaryProbePass(value, auditor) { + const key = `probe/pass/${value.provider}/${value.epoch}`; + const current = await this.get(key); + const auditors = current?.auditors ?? []; + const probes = current?.probes ?? []; + if (!Array.isArray(auditors) || !Array.isArray(probes) || auditors.length !== probes.length) { + return new Error('Invalid canary pass record.'); + } + if (auditors.includes(auditor)) { + return new Error('Auditor already supplied a canary pass for this provider epoch.'); + } + const next = [...probes, { + auditor, + probe_id: value.probe_id, + evidence_hash: value.evidence_hash, + challenge_seed: value.challenge_seed, + }].sort((left, right) => compareCodepoint(left.auditor, right.auditor)); + const record = { + provider: value.provider, + epoch: value.epoch, + pass_count: next.length, + auditors: next.map((entry) => entry.auditor), + probes: next, + last_probe_id: value.probe_id, + last_evidence_hash: value.evidence_hash ?? null, + updated_at: this.tx, + }; + await this.put(key, record); + return record; + } + + async probeGateForEarning(provider, earning, params) { + const holdbackBps = params.canary_probe_holdback_bps ?? 0; + const requiredPasses = params.canary_probe_release_min_passes ?? 0; + if (holdbackBps <= 0 || requiredPasses <= 0) return null; + if (!Number.isSafeInteger(holdbackBps) || holdbackBps < 0 || holdbackBps > 10_000) { + return new Error('Invalid canary probe holdback bps.'); + } + if (!Number.isSafeInteger(requiredPasses) || requiredPasses < 2) { + return new Error('Invalid canary probe release threshold.'); + } + const holdbacks = this.normalizeHoldbackBuckets(earning); + if (holdbacks instanceof Error) return holdbacks; + const passedEpochs = new Set(); + for (const epoch of [...new Set(holdbacks.map((bucket) => bucket.epoch))]) { + const passRecord = await this.get(`probe/pass/${provider}/${epoch}`); + const distinctAuditors = new Set(passRecord?.auditors ?? []); + let activeAuditors = 0; + for (const auditor of distinctAuditors) { + if ((await this.get(`auditor/${auditor}`))?.status === 'active') activeAuditors += 1; + } + if ( + distinctAuditors.size === (passRecord?.pass_count ?? 0) && + activeAuditors >= requiredPasses + ) { + passedEpochs.add(epoch); + } + } + return { + holdback_bps: holdbackBps, + required_passes: requiredPasses, + passed_epochs: passedEpochs, + }; + } + + normalizeHoldbackBuckets(record) { + if (!Array.isArray(record.holdbacks)) { + const heldAu = this.normalizeAu(record.held_au, 'earning held amount'); + if (heldAu instanceof Error) { + return new Error('Guardian non-negative earnings invariant failed.'); + } + if (this.isZeroAu(heldAu)) return []; + return [{ + epoch: Number.isSafeInteger(record.updated_epoch) ? record.updated_epoch : 0, + au: heldAu, + }]; + } + + const byEpoch = new Map(); + for (const bucket of record.holdbacks) { + if (!bucket || typeof bucket !== 'object' || Array.isArray(bucket)) { + return new Error('Guardian earnings conservation invariant failed.'); + } + if (!Number.isSafeInteger(bucket.epoch) || bucket.epoch < 0) { + return new Error('Guardian monotonic epoch invariant failed.'); + } + const bucketAu = this.normalizeAu(bucket.au, 'holdback bucket amount', { allowZero: false }); + if (bucketAu instanceof Error) { + return new Error('Guardian non-negative earnings invariant failed.'); + } + const lockedEpochs = hasOwn(bucket, 'locked_epochs') ? bucket.locked_epochs : null; + if (lockedEpochs !== null && (!Number.isSafeInteger(lockedEpochs) || lockedEpochs < 0)) { + return new Error('Guardian monotonic epoch invariant failed.'); + } + const key = `${bucket.epoch}:${bucket.probe_gate === true ? 'probe' : 'time'}:${lockedEpochs ?? 'default'}`; + const next = this.safeAddAu(byEpoch.get(key)?.au ?? ZERO_AU, bucketAu); + if (next instanceof Error) return next; + byEpoch.set(key, { + epoch: bucket.epoch, + au: next, + probe_gate: bucket.probe_gate === true, + ...(lockedEpochs !== null ? { locked_epochs: lockedEpochs } : {}), + }); + } + return Array.from(byEpoch.values()) + .sort((a, b) => ( + a.epoch - b.epoch || + (a.locked_epochs ?? -1) - (b.locked_epochs ?? -1) || + Number(a.probe_gate) - Number(b.probe_gate) + )) + .map((bucket) => ( + bucket.probe_gate + ? { + epoch: bucket.epoch, + au: bucket.au, + probe_gate: true, + ...(hasOwn(bucket, 'locked_epochs') ? { locked_epochs: bucket.locked_epochs } : {}), + } + : { + epoch: bucket.epoch, + au: bucket.au, + ...(hasOwn(bucket, 'locked_epochs') ? { locked_epochs: bucket.locked_epochs } : {}), + } + )); + } + + holdbackBucketTotal(holdbacks) { + let total = ZERO_AU; + for (const bucket of holdbacks) { + const next = this.safeAddAu(total, bucket.au); + if (next instanceof Error) return next; + total = next; + } + return total; + } + + refreshEarningHoldback(record, currentEpoch, lockedEpochs, probeGate = null, disputeGate = false) { + if (!Number.isSafeInteger(currentEpoch) || currentEpoch < 0) { + return new Error('Guardian monotonic epoch invariant failed.'); + } + if (!Number.isSafeInteger(lockedEpochs) || lockedEpochs < 0) { + return new Error('Guardian monotonic epoch invariant failed.'); + } + const holdbacks = this.normalizeHoldbackBuckets(record); + if (holdbacks instanceof Error) return holdbacks; + const kept = []; + for (const bucket of holdbacks) { + const bucketLockedEpochs = hasOwn(bucket, 'locked_epochs') ? bucket.locked_epochs : lockedEpochs; + if (!Number.isSafeInteger(bucketLockedEpochs) || bucketLockedEpochs < 0) { + return new Error('Guardian monotonic epoch invariant failed.'); + } + if (disputeGate || bucket.epoch + bucketLockedEpochs > currentEpoch) { + kept.push(bucket); + continue; + } + if (!probeGate) continue; + if (probeGate.passed_epochs.has(bucket.epoch)) continue; + if (bucket.probe_gate === true) { + kept.push(bucket); + continue; + } + const gatedAu = this.safeMulDivAu(bucket.au, probeGate.holdback_bps, 10_000); + if (gatedAu instanceof Error) return gatedAu; + if (this.compareAu(gatedAu, ZERO_AU) > 0) { + kept.push({ + epoch: bucket.epoch, + au: gatedAu, + probe_gate: true, + ...(hasOwn(bucket, 'locked_epochs') ? { locked_epochs: bucket.locked_epochs } : {}), + }); + } + } + const heldAu = this.holdbackBucketTotal(kept); + if (heldAu instanceof Error) return heldAu; + return { + ...record, + held_au: heldAu, + holdbacks: kept, + last_holdback_release_epoch: currentEpoch, + }; + } + + slashHoldbackBuckets(holdbacks, slashAu) { + const slashAmountAu = this.normalizeAu(slashAu, 'slash amount'); + if (slashAmountAu instanceof Error) { + return new Error('Guardian non-negative earnings invariant failed.'); + } + if (this.isZeroAu(slashAmountAu)) return holdbacks; + + let remaining = slashAmountAu; + const kept = []; + for (let idx = holdbacks.length - 1; idx >= 0; idx -= 1) { + const bucket = holdbacks[idx]; + if (this.isZeroAu(remaining)) { + kept.push(bucket); + continue; + } + if (this.compareAu(bucket.au, remaining) <= 0) { + remaining = this.safeSubAu(remaining, bucket.au); + if (remaining instanceof Error) return remaining; + continue; + } + const reducedAu = this.safeSubAu(bucket.au, remaining); + if (reducedAu instanceof Error) return reducedAu; + kept.push({ + ...bucket, + epoch: bucket.epoch, + au: reducedAu, + }); + remaining = ZERO_AU; + } + if (!this.isZeroAu(remaining)) return new Error('Guardian earnings conservation invariant failed.'); + return kept.reverse(); + } + + slashAmount(heldAu, slashBps) { + if (this.normalizeAu(heldAu, 'held amount') instanceof Error) { + return new Error('Guardian non-negative earnings invariant failed.'); + } + if (!Number.isSafeInteger(slashBps) || slashBps < 0 || slashBps > 10_000) { + return new Error('Invalid slash bps.'); + } + return this.safeMulDivAu(heldAu, slashBps, 10_000); + } + + providerActiveEnclaves(provider) { + if (!provider || !Array.isArray(provider.enclaves)) return []; + return [...new Set(provider.enclaves.filter((enclaveId) => this.isSafeKeyPart(enclaveId)))].sort(); + } + + providerEnclavesWith(provider, enclaveId) { + const enclaves = this.providerActiveEnclaves(provider); + if (!enclaves.includes(enclaveId)) enclaves.push(enclaveId); + return enclaves.sort(); + } + + providerEnclavesWithout(provider, enclaveId) { + return this.providerActiveEnclaves(provider).filter((activeEnclaveId) => activeEnclaveId !== enclaveId); + } + + enclaveActiveProviders(enclave) { + if (!enclave || !Array.isArray(enclave.providers)) return []; + return [...new Set(enclave.providers.filter((providerId) => this.isSafeKeyPart(providerId)))].sort(); + } + + enclaveProvidersWith(enclave, providerId) { + const providers = this.enclaveActiveProviders(enclave); + if (!providers.includes(providerId)) providers.push(providerId); + return providers.sort(); + } + + enclaveProvidersWithout(enclave, providerId) { + return this.enclaveActiveProviders(enclave).filter((activeProviderId) => activeProviderId !== providerId); + } + + roomServingEntries(room) { + if (!room || !Array.isArray(room.serves)) return []; + const entries = new Map(); + for (const entry of room.serves) { + if (!entry || typeof entry !== 'object' || Array.isArray(entry)) continue; + if (!this.isSafeKeyPart(entry.provider) || !this.isSafeKeyPart(entry.enclave_id)) continue; + entries.set(JSON.stringify([entry.provider, entry.enclave_id]), { + provider: entry.provider, + enclave_id: entry.enclave_id, + }); + } + return Array.from(entries.values()).sort((a, b) => ( + compareCodepoint(a.provider, b.provider) || compareCodepoint(a.enclave_id, b.enclave_id) + )); + } + + roomServesWith(room, providerId, enclaveId) { + const entries = this.roomServingEntries(room); + if (!entries.some((entry) => entry.provider === providerId && entry.enclave_id === enclaveId)) { + entries.push({ provider: providerId, enclave_id: enclaveId }); + } + return entries.sort((a, b) => ( + compareCodepoint(a.provider, b.provider) || compareCodepoint(a.enclave_id, b.enclave_id) + )); + } + + roomServesWithout(room, providerId, enclaveId = null) { + return this.roomServingEntries(room).filter((entry) => ( + entry.provider !== providerId || (enclaveId !== null && entry.enclave_id !== enclaveId) + )); + } + + async tombstoneRoomServes(roomId, entries, evidenceHash) { + if (!this.isSafeKeyPart(roomId)) return new Error('Invalid room id.'); + const tombstones = []; + for (const entry of entries) { + const tombstone = await this.tombstoneRoomServing( + roomId, + entry.provider, + entry.enclave_id, + evidenceHash + ); + if (tombstone instanceof Error) return tombstone; + tombstones.push(tombstone); + } + return tombstones; + } + + async tombstoneRoomServing(roomId, providerId, enclaveId, evidenceHash) { + if (!this.isSafeKeyPart(roomId)) return new Error('Invalid room id.'); + if (!this.isSafeKeyPart(providerId)) return new Error('Invalid provider id.'); + if (!this.isSafeKeyPart(enclaveId)) return new Error('Invalid enclave id.'); + + const roomServeKey = `roomserve/${roomId}/${providerId}/${enclaveId}`; + const roomServing = await this.get(roomServeKey); + const roomKey = `room/${roomId}`; + const room = await this.get(roomKey); + const servingKey = `serve/${providerId}/${enclaveId}`; + const serving = await this.get(servingKey); + + if (room) { + await this.put(roomKey, { + ...room, + serves: this.roomServesWithout(room, providerId, enclaveId), + serves_updated_at: this.tx, + }); + } + if (serving) { + await this.put(servingKey, { + ...serving, + rooms: Array.isArray(serving.rooms) + ? serving.rooms.filter((activeRoomId) => activeRoomId !== roomId) + : [], + updated_at: this.tx, + }); + } + if (!roomServing || roomServing.status !== 'active') { + return { + room_id: roomId, + provider: providerId, + enclave_id: enclaveId, + roomserve_tombstoned: false, + }; + } + + await this.put(roomServeKey, { + ...roomServing, + status: 'tombstoned', + updated_at: this.tx, + tombstoned_at: this.tx, + tombstone_reason_hash: evidenceHash, + }); + return { + room_id: roomId, + provider: providerId, + enclave_id: enclaveId, + roomserve_tombstoned: true, + }; + } + + async tombstoneEnclaveProviders(enclaveId, providerIds, evidenceHash) { + if (!this.isSafeKeyPart(enclaveId)) return new Error('Invalid enclave id.'); + const tombstones = []; + for (const providerId of [...new Set(providerIds)].sort()) { + const tombstone = await this.tombstoneProviderEnclave(providerId, enclaveId, evidenceHash); + if (tombstone instanceof Error) return tombstone; + tombstones.push(tombstone); + } + return tombstones; + } + + async tombstoneProviderEnclaves(providerId, enclaveIds, evidenceHash) { + if (!this.isSafeKeyPart(providerId)) return new Error('Invalid provider id.'); + const tombstones = []; + for (const enclaveId of [...new Set(enclaveIds)].sort()) { + const tombstone = await this.tombstoneProviderEnclave(providerId, enclaveId, evidenceHash); + if (tombstone instanceof Error) return tombstone; + tombstones.push(tombstone); + } + return tombstones; + } + + async tombstoneProviderEnclave(providerId, enclaveId, evidenceHash) { + if (!this.isSafeKeyPart(providerId)) return new Error('Invalid provider id.'); + if (!enclaveId) { + return { + provider: providerId, + enclave_id: null, + serve_tombstoned: false, + rooms_tombstoned: [], + }; + } + if (!this.isSafeKeyPart(enclaveId)) return new Error('Invalid enclave id.'); + + const serveKey = `serve/${providerId}/${enclaveId}`; + const serving = await this.get(serveKey); + if (!serving) { + return { + provider: providerId, + enclave_id: enclaveId, + serve_tombstoned: false, + rooms_tombstoned: [], + }; + } + + const rooms = Array.isArray(serving.rooms) ? serving.rooms.slice().sort() : []; + const tombstonedRooms = []; + for (const roomId of rooms) { + const tombstone = await this.tombstoneRoomServing(roomId, providerId, enclaveId, evidenceHash); + if (tombstone instanceof Error) return tombstone; + if (tombstone.roomserve_tombstoned) tombstonedRooms.push(roomId); + } + + await this.put(serveKey, { + ...serving, + status: 'tombstoned', + rooms: [], + updated_at: this.tx, + tombstoned_at: this.tx, + tombstone_reason_hash: evidenceHash, + }); + const provider = await this.get(`prov/${providerId}`); + if (provider) { + await this.put(`prov/${providerId}`, { + ...provider, + enclaves: this.providerEnclavesWithout(provider, enclaveId), + updated_at: this.tx, + }); + } + const enclave = await this.get(`enclave/${enclaveId}`); + if (enclave) { + await this.put(`enclave/${enclaveId}`, { + ...enclave, + providers: this.enclaveProvidersWithout(enclave, providerId), + updated_at: this.tx, + }); + } + return { + provider: providerId, + enclave_id: enclaveId, + serve_tombstoned: true, + rooms_tombstoned: tombstonedRooms, + }; + } + + async applyProviderSlash({ + providerId, + source, + reason, + evidenceHash, + epoch, + at, + slashBps, + beneficiary = null, + enclaveId = null, + probeId = null, + eventId = null, + banProvider = false, + tombstoneEnclave = false, + }) { + if (!this.isSafeKeyPart(providerId)) return new Error('Invalid provider id.'); + if (beneficiary !== null && !this.isSafeKeyPart(beneficiary)) { + return new Error('Invalid slash beneficiary.'); + } + + const providerKey = `prov/${providerId}`; + const provider = await this.get(providerKey); + if (!provider) return new Error('Provider not found.'); + + const updatedEarnings = new Map(); + const beneficiaryBalances = new Map(); + const updatedFees = new Map(); + const railSlashRecords = []; + let heldBeforeAu = ZERO_AU; + let heldAfterAu = ZERO_AU; + let forfeitedAu = ZERO_AU; + let reporterAu = ZERO_AU; + let treasuryAu = ZERO_AU; + + for (const rail of PROVIDER_ACCEPTED_RAIL_ORDER) { + const earning = await this.earningRecord(providerId, rail); + if (earning instanceof Error) return earning; + const earningError = this.guardianValidateEarningRecord(earning, providerId, rail); + if (earningError) return earningError; + const holdbacks = this.normalizeHoldbackBuckets(earning); + if (holdbacks instanceof Error) return holdbacks; + + const railForfeitedAu = this.slashAmount(earning.held_au, slashBps); + if (railForfeitedAu instanceof Error) return railForfeitedAu; + const railReporterAu = beneficiary === null ? ZERO_AU : this.safeMulDivAu(railForfeitedAu, 1, 2); + if (railReporterAu instanceof Error) return railReporterAu; + const railTreasuryAu = this.safeSubAu(railForfeitedAu, railReporterAu); + if (railTreasuryAu instanceof Error) return railTreasuryAu; + const remainingHoldbacks = this.slashHoldbackBuckets(holdbacks, railForfeitedAu); + if (remainingHoldbacks instanceof Error) return remainingHoldbacks; + const railHeldAfterAu = this.safeSubAu(earning.held_au, railForfeitedAu); + if (railHeldAfterAu instanceof Error) return railHeldAfterAu; + const railTotalAu = this.safeSubAu(earning.total_au, railForfeitedAu); + if (railTotalAu instanceof Error) return railTotalAu; + const slashedCumAu = this.safeAddAu(earning.slashed_cum_au ?? ZERO_AU, railForfeitedAu); + if (slashedCumAu instanceof Error) return slashedCumAu; + + heldBeforeAu = this.safeAddAu(heldBeforeAu, earning.held_au); + if (heldBeforeAu instanceof Error) return heldBeforeAu; + heldAfterAu = this.safeAddAu(heldAfterAu, railHeldAfterAu); + if (heldAfterAu instanceof Error) return heldAfterAu; + forfeitedAu = this.safeAddAu(forfeitedAu, railForfeitedAu); + if (forfeitedAu instanceof Error) return forfeitedAu; + reporterAu = this.safeAddAu(reporterAu, railReporterAu); + if (reporterAu instanceof Error) return reporterAu; + treasuryAu = this.safeAddAu(treasuryAu, railTreasuryAu); + if (treasuryAu instanceof Error) return treasuryAu; + + if (this.compareAu(earning.total_au, ZERO_AU) > 0 || this.compareAu(railForfeitedAu, ZERO_AU) > 0) { + const updatedEarning = { + ...earning, + rail, + total_au: railTotalAu, + held_au: railHeldAfterAu, + holdbacks: remainingHoldbacks, + slashed_cum_au: slashedCumAu, + last_slash_at: this.tx, + updated_at: this.tx, + }; + const updatedEarningError = this.guardianValidateEarningRecord(updatedEarning, providerId, rail); + if (updatedEarningError) return updatedEarningError; + updatedEarnings.set(rail, updatedEarning); + } + + if (this.compareAu(railReporterAu, ZERO_AU) > 0) { + const currentBalance = await this.balanceRecord(beneficiary, rail); + if (currentBalance instanceof Error) return currentBalance; + const balanceError = this.guardianValidateBalanceRecord(currentBalance, beneficiary, rail); + if (balanceError) return balanceError; + const nextAu = this.safeAddAu(currentBalance.au, railReporterAu); + if (nextAu instanceof Error) return nextAu; + beneficiaryBalances.set(rail, { + ...currentBalance, + rail, + au: nextAu, + updated_epoch: Math.max(currentBalance.updated_epoch, epoch), + updated_at: this.tx, + }); + } + + if (this.compareAu(railTreasuryAu, ZERO_AU) > 0) { + const fee = await this.feeCumRecord(rail); + if (fee instanceof Error) return fee; + const feeError = this.guardianValidateFeeRecord(fee, rail); + if (feeError) return feeError; + const cumAu = this.safeAddAu(fee.cum_au, railTreasuryAu); + if (cumAu instanceof Error) return cumAu; + const settledCumAu = this.safeAddAu(fee.settled_cum_au ?? fee.cum_au, railTreasuryAu); + if (settledCumAu instanceof Error) return settledCumAu; + const updatedFee = { + ...fee, + rail, + cum_au: cumAu, + settled_cum_au: settledCumAu, + updated_epoch: Math.max(fee.updated_epoch, epoch), + updated_at: this.tx, + last_slash_at: this.tx, + }; + const updatedFeeError = this.guardianValidateFeeRecord(updatedFee, rail); + if (updatedFeeError) return updatedFeeError; + updatedFees.set(rail, updatedFee); + } + + if (this.compareAu(earning.held_au, ZERO_AU) > 0 || this.compareAu(railForfeitedAu, ZERO_AU) > 0) { + railSlashRecords.push({ + rail, + held_before_au: earning.held_au, + held_after_au: railHeldAfterAu, + forfeited_au: railForfeitedAu, + beneficiary_au: railReporterAu, + treasury_au: railTreasuryAu, + }); + } + } + + const tombstone = tombstoneEnclave + ? await this.tombstoneProviderEnclave(providerId, enclaveId, evidenceHash) + : { + enclave_id: enclaveId, + serve_tombstoned: false, + rooms_tombstoned: [], + }; + if (tombstone instanceof Error) return tombstone; + const banTombstones = banProvider + ? await this.tombstoneProviderEnclaves( + providerId, + this.providerActiveEnclaves(provider).filter((activeEnclaveId) => activeEnclaveId !== enclaveId), + evidenceHash + ) + : []; + if (banTombstones instanceof Error) return banTombstones; + + const slash = { + type: 'slash', + provider: providerId, + source, + reason, + evidence_hash: evidenceHash, + epoch, + at, + tx: this.tx, + slashed_by: this.address, + beneficiary, + enclave_id: enclaveId, + probe_id: probeId, + event_id: eventId, + slash_bps: slashBps, + held_before_au: heldBeforeAu, + held_after_au: heldAfterAu, + forfeited_au: forfeitedAu, + beneficiary_au: reporterAu, + treasury_au: treasuryAu, + rails: railSlashRecords, + tombstone, + ban_tombstones: banTombstones, + provider_banned: banProvider, + }; + slash.slash_hash = await this.opaqueHash('mayhem-slash-v1', slash); + + const updatedProvider = banProvider + ? { + ...provider, + status: 'banned', + enclaves: [], + tombstoned_enclaves: [tombstone, ...banTombstones] + .filter((entry) => entry.enclave_id) + .map((entry) => entry.enclave_id), + banned_at: provider.banned_at ?? this.tx, + banned_by: provider.banned_by ?? this.address, + ban_reason_hash: provider.ban_reason_hash ?? evidenceHash, + updated_at: this.tx, + } + : { + ...provider, + enclaves: tombstone.serve_tombstoned + ? this.providerEnclavesWithout(provider, tombstone.enclave_id) + : this.providerActiveEnclaves(provider), + updated_at: this.tx, + }; + + for (const [rail, updatedEarning] of updatedEarnings) { + await this.put(this.earningKey(providerId, rail), updatedEarning); + } + for (const [rail, beneficiaryBalance] of beneficiaryBalances) { + await this.put(this.balanceKey(beneficiary, rail), beneficiaryBalance); + } + for (const [rail, updatedFee] of updatedFees) { + await this.put(this.feeCumKey(rail), updatedFee); + } + await this.put(providerKey, updatedProvider); + await this.put(`ev/slash/${providerId}/${this.tx}`, slash); + return slash; + } + + appendHoldbackBucket(holdbacks, epoch, au, lockedEpochs = null) { + if (!Number.isSafeInteger(epoch) || epoch < 0) { + return new Error('Guardian monotonic epoch invariant failed.'); + } + const amount = this.normalizeAu(au, 'holdback amount', { allowZero: false }); + if (amount instanceof Error) { + return new Error('Guardian non-negative earnings invariant failed.'); + } + if (lockedEpochs !== null && (!Number.isSafeInteger(lockedEpochs) || lockedEpochs < 0)) { + return new Error('Guardian monotonic epoch invariant failed.'); + } + const normalized = this.normalizeHoldbackBuckets({ holdbacks }); + if (normalized instanceof Error) return normalized; + const gated = normalized.filter((bucket) => bucket.probe_gate === true); + const byEpoch = new Map( + normalized + .filter((bucket) => bucket.probe_gate !== true) + .map((bucket) => [ + `${bucket.epoch}:${hasOwn(bucket, 'locked_epochs') ? bucket.locked_epochs : 'default'}`, + bucket, + ]) + ); + const key = `${epoch}:${lockedEpochs ?? 'default'}`; + const current = byEpoch.get(key); + const next = this.safeAddAu(current?.au ?? ZERO_AU, amount); + if (next instanceof Error) return next; + byEpoch.set(key, { + epoch, + au: next, + ...(lockedEpochs !== null ? { locked_epochs: lockedEpochs } : {}), + }); + return [ + ...Array.from(byEpoch.values()) + .map((bucket) => ({ + epoch: bucket.epoch, + au: bucket.au, + ...(hasOwn(bucket, 'locked_epochs') ? { locked_epochs: bucket.locked_epochs } : {}), + })), + ...gated, + ] + .sort((a, b) => ( + a.epoch - b.epoch || + (a.locked_epochs ?? -1) - (b.locked_epochs ?? -1) || + Number(a.probe_gate === true) - Number(b.probe_gate === true) + )); + } + + normalizeEpochRoots(value) { + if (!value || typeof value !== 'object' || Array.isArray(value)) { + return new Error('Epoch roots must be an object.'); + } + const keys = Object.keys(value).sort(); + if ( + keys.length !== EPOCH_ROOT_KEYS.length || + keys.some((key, idx) => key !== EPOCH_ROOT_KEYS.slice().sort()[idx]) + ) { + return new Error('Epoch roots must include dep, use, earn, fee, and price.'); + } + const roots = {}; + for (const key of EPOCH_ROOT_KEYS) { + const root = value[key]; + if (typeof root !== 'string' || !/^[0-9a-fA-F]{64}$/.test(root)) { + return new Error(`Invalid epoch ${key} root.`); + } + roots[key] = root.toLowerCase(); + } + return roots; + } + + normalizeEpochTotals(value) { + if (!value || typeof value !== 'object' || Array.isArray(value)) { + return new Error('Epoch totals must be an object.'); + } + const expected = EPOCH_TOTAL_KEYS.slice().sort(); + const keys = Object.keys(value).sort(); + if (keys.length !== expected.length || keys.some((key, idx) => key !== expected[idx])) { + return new Error('Epoch totals have an invalid shape.'); + } + const totals = {}; + for (const key of EPOCH_TOTAL_KEYS) { + const total = value[key]; + if (EPOCH_TOTAL_MONEY_KEYS.has(key)) { + const au = this.normalizeAu(total, `epoch total ${key}`); + if (au instanceof Error) { + return new Error(`Invalid epoch total ${key}.`); + } + totals[key] = au; + continue; + } + if (!Number.isSafeInteger(total) || total < 0) { + return new Error(`Invalid epoch total ${key}.`); + } + totals[key] = total; + } + return totals; + } + + canonicalUsageUnit(unit) { + switch (unit) { + case 'in': + case 'in_tokens': + case 'input': + case 'input_tokens': + case 'prompt_tokens': + case 'input_token': + return 'input_token'; + case 'cached_input': + case 'cached_inputs': + case 'cached_input_tokens': + case 'cached_prompt_tokens': + case 'cached_tokens': + case 'cached_input_token': + return 'cached_input_token'; + case 'out': + case 'out_tokens': + case 'output': + case 'output_tokens': + case 'completion_tokens': + case 'output_token': + return 'output_token'; + case 'images': + case 'image': + return 'image'; + case 'steps': + case 'step': + return 'step'; + default: + return unit; + } + } + + normalizeReceiptUsage(usageSource) { + if (!usageSource || typeof usageSource !== 'object' || Array.isArray(usageSource)) { + return new Error('Fraud proof receipt usage must be an object.'); + } + const usage = {}; + for (const [rawUnit, count] of Object.entries(usageSource)) { + if (typeof rawUnit !== 'string' || rawUnit.length === 0 || rawUnit.length > 64) { + return new Error('Invalid receipt usage unit.'); + } + if (!Number.isSafeInteger(count) || count < 0) { + return new Error('Invalid receipt usage count.'); + } + if (count === 0) continue; + const unit = this.canonicalUsageUnit(rawUnit); + if (!this.isSafeKeyPart(unit)) return new Error('Invalid receipt usage unit.'); + const next = this.safeAddCount(usage[unit] ?? 0, count, 'receipt usage count'); + if (next instanceof Error) return next; + usage[unit] = next; + } + return Object.fromEntries(Object.entries(usage).sort(([left], [right]) => compareCodepoint(left, right))); + } + + normalizeWorkflowBinding(source, label, rateMap = null) { + const shapeError = this.validateExactObjectKeys( + source, + ['endpoint_family', 'graph_hash', 'runtime_id', 'outcome_class', 'quoted_usage'], + label + ); + if (shapeError) return shapeError; + if (!this.isSafeKeyPart(source.endpoint_family)) { + return new Error(`${label} endpoint_family is invalid.`); + } + if (!this.isHexBytes(source.graph_hash, 32)) { + return new Error(`${label} graph_hash is invalid.`); + } + if (!this.isSafeKeyPart(source.runtime_id)) { + return new Error(`${label} runtime_id is invalid.`); + } + if (!this.isSafeKeyPart(source.outcome_class)) { + return new Error(`${label} outcome_class is invalid.`); + } + const quotedUsage = this.normalizeReceiptUsage(source.quoted_usage); + if (quotedUsage instanceof Error || Object.keys(quotedUsage).length === 0) { + return new Error(`${label} quoted_usage is invalid.`); + } + if (rateMap !== null) { + const quotedAu = this.usageAuForRateMap(rateMap, quotedUsage); + if (quotedAu instanceof Error) return new Error(`${label} quoted_usage is not priced by the locked rate_map.`); + } + return { + endpoint_family: source.endpoint_family, + graph_hash: source.graph_hash.toLowerCase(), + runtime_id: source.runtime_id, + outcome_class: source.outcome_class, + quoted_usage: quotedUsage, + }; + } + + normalizeWorkflowOutputBinding(source, label) { + const shapeError = this.validateExactObjectKeys( + source, + ['output_modalities', 'metrics'], + label + ); + if (shapeError) return shapeError; + const modalityError = this.validateModalitySet(source.output_modalities, `${label} output_modalities`); + if (modalityError) return modalityError; + if (!source.metrics || typeof source.metrics !== 'object' || Array.isArray(source.metrics)) { + return new Error(`${label} metrics must be an object.`); + } + const metricEntries = Object.entries(source.metrics); + if (metricEntries.length === 0 || metricEntries.length > 32) { + return new Error(`${label} metrics must contain between 1 and 32 entries.`); + } + const metrics = {}; + for (const [key, count] of metricEntries) { + if (!this.isSafeKeyPart(key)) return new Error(`${label} metric key is invalid.`); + if (!Number.isSafeInteger(count) || count <= 0) { + return new Error(`${label} metric count is invalid.`); + } + metrics[key] = count; + } + return { + output_modalities: source.output_modalities.slice(), + metrics: Object.fromEntries( + Object.entries(metrics).sort(([left], [right]) => compareCodepoint(left, right)) + ), + }; + } + + normalizeReceiptUsageAttribution(source) { + if (source === undefined || source === null) return {}; + if (!source || typeof source !== 'object' || Array.isArray(source)) { + return new Error('Receipt usage attribution must be an object.'); + } + const allowed = new Set([ + 'context_input_tokens', + 'reasoning_output_tokens', + 'vision_input_tokens', + 'audio_input_tokens', + ]); + const normalized = {}; + for (const [axis, count] of Object.entries(source)) { + if (!allowed.has(axis)) return new Error(`Unsupported receipt usage attribution ${axis}.`); + if (!Number.isSafeInteger(count) || count < 0) { + return new Error('Invalid receipt usage attribution count.'); + } + if (count > 0) normalized[axis] = count; + } + return Object.fromEntries( + Object.entries(normalized).sort(([left], [right]) => compareCodepoint(left, right)) + ); + } + + async normalizeReceiptEnvelope(value, options = {}) { + const targetSchemaVersion = options.targetSchemaVersion ?? SESSION_RECEIPT_SCHEMA_VERSION; + if (!value || typeof value !== 'object' || Array.isArray(value)) { + return new Error('Fraud proof receipt must be an object.'); + } + const receipt = value.receipt ?? value; + if (!receipt || typeof receipt !== 'object' || Array.isArray(receipt)) { + return new Error('Fraud proof receipt must be an object.'); + } + const bodySource = receipt.body ?? receipt; + if (!bodySource || typeof bodySource !== 'object' || Array.isArray(bodySource)) { + return new Error('Fraud proof receipt body must be an object.'); + } + const body = { + schema_version: bodySource.schema_version, + session_id: bodySource.session_id, + billing_id: bodySource.billing_id, + billing_attempt: bodySource.billing_attempt, + billing_prior_usage: cloneValue(bodySource.billing_prior_usage), + billing_prior_au_owed_cum: bodySource.billing_prior_au_owed_cum, + billing_epoch: bodySource.billing_epoch, + reservation_id: bodySource.reservation_id, + reservation_expires_after_epoch: bodySource.reservation_expires_after_epoch, + reservation_receipt_grace_epochs: bodySource.reservation_receipt_grace_epochs, + payout_revision: bodySource.payout_revision, + seq: bodySource.seq, + final: bodySource.final, + rail: bodySource.rail, + user: bodySource.user, + provider: bodySource.provider, + enclave_id: bodySource.enclave_id, + model_id: bodySource.model_id, + price_ver: bodySource.price_ver, + locked_rate_map: cloneValue(bodySource.locked_rate_map), + rules_ver: bodySource.rules_ver, + usage: cloneValue(bodySource.usage), + au_owed_cum: bodySource.au_owed_cum, + prompt_hash: bodySource.prompt_hash, + ts: bodySource.ts, + }; + if (targetSchemaVersion >= 12) { + body.compute_ms = bodySource.compute_ms; + body.capacity_slots = bodySource.capacity_slots; + } + if (hasOwn(bodySource, 'usage_attribution')) { + body.usage_attribution = cloneValue(bodySource.usage_attribution); + } + if (hasOwn(bodySource, 'locked_per_req_au')) body.locked_per_req_au = bodySource.locked_per_req_au; + if (hasOwn(bodySource, 'locked_min_session_au')) body.locked_min_session_au = bodySource.locked_min_session_au; + if (hasOwn(bodySource, 'served_ctx')) body.served_ctx = bodySource.served_ctx; + if (hasOwn(bodySource, 'ctx_bracket')) body.ctx_bracket = bodySource.ctx_bracket; + if (hasOwn(bodySource, 'ctx_bracket_table_ver')) { + body.ctx_bracket_table_ver = bodySource.ctx_bracket_table_ver; + } + if (hasOwn(bodySource, 'workflow')) { + body.workflow = cloneValue(bodySource.workflow); + } + if (hasOwn(bodySource, 'workflow_output')) { + body.workflow_output = cloneValue(bodySource.workflow_output); + } + + if (body.schema_version !== targetSchemaVersion) { + return new Error('Unsupported receipt schema version.'); + } + + const bodyError = await this.validateReceiptBody(body, targetSchemaVersion); + if (bodyError) return bodyError; + + const envelope = { + body, + enclave_sig: receipt.enclave_sig ?? value.enclave_sig, + user_sig: receipt.user_sig ?? value.user_sig, + enclave_pubkey: receipt.enclave_pubkey ?? value.enclave_pubkey ?? bodySource.enclave_pubkey ?? null, + }; + if (!this.isHexBytes(envelope.enclave_sig, 64)) return new Error('Invalid enclave receipt signature.'); + if (!this.isHexBytes(envelope.user_sig, 64)) return new Error('Invalid user receipt signature.'); + if (!this.isHexBytes(envelope.enclave_pubkey, 32)) { + return new Error('Invalid enclave receipt public key.'); + } + envelope.enclave_pubkey = envelope.enclave_pubkey.toLowerCase(); + return envelope; + } + + async validateReceiptBody(body, expectedSchemaVersion = SESSION_RECEIPT_SCHEMA_VERSION) { + if (body.schema_version !== expectedSchemaVersion) { + return new Error('Unsupported receipt schema version.'); + } + for (const field of ['session_id', 'user', 'provider', 'enclave_id', 'model_id', 'prompt_hash']) { + if (typeof body[field] !== 'string' || body[field].length === 0 || body[field].length > 256) { + return new Error(`Invalid receipt ${field}.`); + } + } + if (!this.isHexBytes(body.billing_id, 32)) return new Error('Invalid receipt billing id.'); + if (!Number.isSafeInteger(body.billing_attempt) || body.billing_attempt < 0) { + return new Error('Invalid receipt billing attempt.'); + } + if (!Number.isSafeInteger(body.billing_epoch) || body.billing_epoch < 1) { + return new Error('Invalid receipt billing epoch.'); + } + if (!this.isHexBytes(body.reservation_id, 32)) { + return new Error('Invalid receipt reservation id.'); + } + if (!Number.isSafeInteger(body.reservation_expires_after_epoch) || + body.reservation_expires_after_epoch <= body.billing_epoch || + !Number.isSafeInteger(body.reservation_receipt_grace_epochs) || + body.reservation_receipt_grace_epochs < 0) { + return new Error('Invalid receipt reservation expiry policy.'); + } + if (!this.isHexBytes(body.payout_revision, 32)) { + return new Error('Invalid receipt payout revision.'); + } + const billingPriorUsage = this.normalizeReceiptUsage(body.billing_prior_usage); + if (billingPriorUsage instanceof Error) return billingPriorUsage; + if (stableJson(billingPriorUsage) !== stableJson(body.billing_prior_usage)) { + return new Error('Receipt billing prior usage must be canonical.'); + } + const billingPriorAuOwedCum = this.normalizeAu( + body.billing_prior_au_owed_cum, + 'receipt billing prior cumulative amount' + ); + if (billingPriorAuOwedCum instanceof Error) { + return new Error('Invalid receipt billing prior cumulative amount.'); + } + if ( + body.billing_attempt === 0 && + (Object.keys(billingPriorUsage).length > 0 || !this.isZeroAu(billingPriorAuOwedCum)) + ) { + return new Error('Initial receipt billing attempt must have an empty baseline.'); + } + if (this.isZeroAu(billingPriorAuOwedCum) && Object.keys(billingPriorUsage).length > 0) { + return new Error('Receipt billing prior usage requires a prior cumulative amount.'); + } + if (!this.isHexBytes(body.user, 32)) return new Error('Invalid receipt user public key.'); + if (!this.isHexBytes(body.provider, 32)) return new Error('Invalid receipt provider public key.'); + const rail = this.normalizeLedgerRail(body.rail, 'receipt rail'); + if (rail instanceof Error) return rail; + if (body.rail !== rail) return new Error('Receipt rail must be canonical.'); + if (!Number.isSafeInteger(body.seq) || body.seq < 0) return new Error('Invalid receipt sequence.'); + if (typeof body.final !== 'boolean') return new Error('Invalid receipt final flag.'); + if (!Number.isSafeInteger(body.price_ver) || body.price_ver < 1) { + return new Error('Invalid receipt price version.'); + } + const lockedRateMap = this.normalizeLockedRateMap(body.locked_rate_map, 'receipt locked_rate_map'); + if (lockedRateMap instanceof Error) return lockedRateMap; + if (stableJson(lockedRateMap) !== stableJson(body.locked_rate_map)) { + return new Error('Receipt locked_rate_map must be canonical.'); + } + const lockedPerReqAu = this.normalizeAu(body.locked_per_req_au, 'receipt locked per-request price'); + if (lockedPerReqAu instanceof Error) { + return new Error('Invalid receipt locked per-request price.'); + } + const lockedMinSessionAu = this.normalizeAu(body.locked_min_session_au, 'receipt locked minimum session price'); + if (lockedMinSessionAu instanceof Error) { + return new Error('Invalid receipt locked minimum session price.'); + } + if (!Number.isSafeInteger(body.served_ctx) || body.served_ctx < 0) { + return new Error('Invalid receipt served context.'); + } + if (expectedSchemaVersion >= 12) { + if (!Number.isSafeInteger(body.compute_ms) || body.compute_ms < 1) { + return new Error('Invalid receipt compute duration.'); + } + if (!Number.isSafeInteger(body.capacity_slots) || body.capacity_slots < 1 || + body.capacity_slots > 1_000_000) { + return new Error('Invalid receipt execution capacity.'); + } + } + const table = body.ctx_bracket_table_ver === null || body.ctx_bracket_table_ver === undefined + ? null + : await this.ctxBracketTableByVersion(body.ctx_bracket_table_ver); + if (table instanceof Error) return table; + const ctxMeta = await this.normalizeCtxBracketEvidenceForEnclave( + body.enclave_id, + body.served_ctx, + body.ctx_bracket, + body.ctx_bracket_table_ver, + table, + 'receipt' + ); + if (ctxMeta instanceof Error) return ctxMeta; + if (!Number.isSafeInteger(body.rules_ver) || body.rules_ver < 1) { + return new Error('Invalid receipt rules version.'); + } + let workflow = null; + if (hasOwn(body, 'workflow')) { + workflow = this.normalizeWorkflowBinding(body.workflow, 'receipt workflow', lockedRateMap); + if (workflow instanceof Error) return workflow; + if (stableJson(workflow) !== stableJson(body.workflow)) { + return new Error('Receipt workflow must be canonical.'); + } + } + if (hasOwn(body, 'workflow_output')) { + if (!workflow) return new Error('Receipt workflow_output requires workflow.'); + const workflowOutput = this.normalizeWorkflowOutputBinding( + body.workflow_output, + 'receipt workflow_output' + ); + if (workflowOutput instanceof Error) return workflowOutput; + if (stableJson(workflowOutput) !== stableJson(body.workflow_output)) { + return new Error('Receipt workflow_output must be canonical.'); + } + } else if (workflow) { + return new Error('Receipt workflow requires workflow_output.'); + } + const usage = this.normalizeReceiptUsage(body.usage); + if (usage instanceof Error) return usage; + if (stableJson(usage) !== stableJson(body.usage)) { + return new Error('Receipt usage must be canonical.'); + } + const usageAttribution = this.normalizeReceiptUsageAttribution(body.usage_attribution); + if (usageAttribution instanceof Error) return usageAttribution; + if (stableJson(usageAttribution) !== stableJson(body.usage_attribution ?? {})) { + return new Error('Receipt usage attribution must be canonical.'); + } + // Rendered context telemetry is not a billable usage axis. It may exceed + // canonical input units, but cannot exceed the signed served context. + if ((usageAttribution.context_input_tokens ?? 0) > body.served_ctx) { + return new Error('Receipt context attribution exceeds served context.'); + } + if ((usageAttribution.reasoning_output_tokens ?? 0) > (usage.output_token ?? 0)) { + return new Error('Receipt reasoning attribution exceeds billed output tokens.'); + } + if ((usageAttribution.vision_input_tokens ?? 0) > (usage.input_token ?? 0)) { + return new Error('Receipt vision attribution exceeds billed input tokens.'); + } + if ((usageAttribution.audio_input_tokens ?? 0) > (usage.input_token ?? 0)) { + return new Error('Receipt audio attribution exceeds billed input tokens.'); + } + const auOwedCum = this.normalizeAu(body.au_owed_cum, 'receipt cumulative amount'); + if (auOwedCum instanceof Error) { + return new Error('Invalid receipt cumulative amount.'); + } + const lockedAu = this.logicalCumulativeAuForLockedTerms( + body.locked_rate_map, + lockedPerReqAu, + lockedMinSessionAu, + billingPriorUsage, + billingPriorAuOwedCum, + usage + ); + if (lockedAu instanceof Error) return lockedAu; + if (this.compareAu(auOwedCum, lockedAu) !== 0) { + return new Error('Receipt cumulative amount does not match locked price terms.'); + } + if (!Number.isSafeInteger(body.ts) || body.ts < 0) return new Error('Invalid receipt timestamp.'); + return null; + } + + verifyReceiptEnvelope(envelope) { + const signedBody = envelope.body; + if (!signedBody || typeof signedBody !== 'object' || Array.isArray(signedBody)) return false; + const enclaveKey = envelope.enclave_pubkey ?? ( + this.isHexBytes(signedBody.enclave_id, 32) ? signedBody.enclave_id : null + ); + if (!enclaveKey) return false; + const message = receiptMessage(signedBody); + return ( + verifyEd25519Hex(envelope.enclave_sig, message, enclaveKey) && + verifyEd25519Hex(envelope.user_sig, message, signedBody.user) + ); + } + + receiptLeafEnvelope(envelope) { + return { + body: cloneValue(envelope.body), + enclave_sig: envelope.enclave_sig, + user_sig: envelope.user_sig, + }; + } + + async usageLeafHash(envelope) { + return await this.opaqueHash('mayhem-usage-leaf-v1', this.receiptLeafEnvelope(envelope)); + } + + async fraudProofHash(value) { + return await this.opaqueHash('mayhem-fraud-proof-v1', value); + } + + async validateOverCreditFraudProof(commit, receipt) { + if (commit.status === 'void') return new Error('Epoch commit is already void.'); + if (this.value.proof_epoch > commit.provisional_until_epoch) { + return new Error('Epoch commit challenge window has closed.'); + } + if (commit.totals.use_count !== 1) { + return new Error('Over-credit proof requires a single committed receipt.'); + } + + const previousAu = this.normalizeAu(this.value.previous_au_owed_cum ?? ZERO_AU, 'previous receipt amount'); + if (previousAu instanceof Error) { + return new Error('Invalid previous receipt amount.'); + } + const claimedCum = this.normalizeAu(this.value.claimed_au_owed_cum, 'claimed receipt amount'); + if (claimedCum instanceof Error) return new Error('Invalid claimed receipt amount.'); + if (this.compareAu(previousAu, receipt.body.au_owed_cum) > 0 || this.compareAu(previousAu, claimedCum) > 0) { + return new Error('Previous receipt amount exceeds cumulative amount.'); + } + const actualAu = this.safeSubAu(receipt.body.au_owed_cum, previousAu); + if (actualAu instanceof Error) return actualAu; + const claimedAu = this.safeSubAu(claimedCum, previousAu); + if (claimedAu instanceof Error) return claimedAu; + if (this.compareAu(claimedAu, actualAu) <= 0) return new Error('Receipt does not contradict committed usage.'); + if (this.compareAu(commit.totals.use_au, claimedAu) !== 0) { + return new Error('Fraud proof claimed amount does not match committed usage total.'); + } + + const claimedReceipt = { + ...receipt, + body: { + ...receipt.body, + au_owed_cum: claimedCum, + }, + }; + const claimedUseRoot = await this.usageLeafHash(claimedReceipt); + if (commit.roots.use !== claimedUseRoot) { + return new Error('Fraud proof does not match committed usage root.'); + } + + return { + actual_au: actualAu, + claimed_au: claimedAu, + receipt_hash: await this.usageLeafHash(receipt), + }; + } + + priceTermsSnapshot(record) { + return { + ver: record.ver, + ...(record.ctx_bracket ? { ctx_bracket: record.ctx_bracket } : {}), + ...(record.ctx_bracket_table_ver ? { ctx_bracket_table_ver: record.ctx_bracket_table_ver } : {}), + rate_map: cloneValue(record.rate_map), + per_req_au: record.per_req_au, + min_session_au: record.min_session_au, + }; + } + + priceDerivationLeafValue(derivation) { + const { + derivation_hash: _derivationHash, + price_root: _priceRoot, + updated_at: _updatedAt, + ...leaf + } = derivation; + return leaf; + } + + priceDerivationFromMarketUpdate(update, { epoch, at, epochSeconds = null, usageRoot = null } = {}) { + const record = update.record; + const market = record.market; + if (!record || !market || typeof market !== 'object') { + return new Error('Market price update is missing derivation data.'); + } + return { + type: 'price_derivation', + schema_version: 3, + epoch, + at, + epoch_seconds: epochSeconds, + enclave_id: record.enclave_id, + ...(record.ctx_bracket ? { + ctx_bracket: record.ctx_bracket, + ctx_bracket_table_ver: record.ctx_bracket_table_ver, + } : {}), + model_id: record.model_id, + denom: record.denom, + price_ver: record.ver, + price_source: record.price_source, + usage: { + usage_root: usageRoot, + settled_usage: cloneValue(market.settled_usage), + compute_ms: market.compute_ms, + capacity_slot_count: market.capacity_slot_count, + legacy_receipt_count: market.legacy_receipt_count, + active_demand_au: market.active_demand_au, + session_count: market.session_count, + ...(record.ctx_bracket ? { + ctx_bracket: record.ctx_bracket, + ctx_bracket_table_ver: record.ctx_bracket_table_ver, + } : {}), + }, + controller: { + source: market.source, + active_supply: market.active_supply, + activity_basis: market.activity_basis, + utilization_bps: market.utilization_bps, + activity_initialized: market.activity_initialized, + modelref_ver: market.modelref_ver, + multiplier_bps: market.multiplier_bps, + constants: cloneValue(market.constants), + }, + seed_price: this.priceTermsSnapshot(record.seed), + previous_price: { + ver: market.previous_price_ver, + rate_map: cloneValue(market.previous_rate_map), + per_req_au: market.previous_per_req_au, + min_session_au: market.previous_min_session_au, + }, + desired_price: { + rate_map: cloneValue(market.desired_rate_map), + per_req_au: market.desired_per_req_au, + min_session_au: market.desired_min_session_au, + }, + result_price: this.priceTermsSnapshot(record), + }; + } + + async priceDerivationsFromMarketUpdates(updates, context = {}) { + const derivations = []; + const sorted = updates.slice().sort((left, right) => ( + compareCodepoint(left.enclave_id, right.enclave_id) || + compareCodepoint(left.ctx_bracket ?? '', right.ctx_bracket ?? '') + )); + for (const update of sorted) { + const derivation = this.priceDerivationFromMarketUpdate(update, context); + if (derivation instanceof Error) return derivation; + const derivationHash = await this.priceDerivationLeafHash(derivation); + update.derivation_hash = derivationHash; + derivations.push({ + ...derivation, + derivation_hash: derivationHash, + }); + } + const priceRoot = await this.priceDerivationRoot(derivations); + for (const derivation of derivations) { + derivation.price_root = priceRoot; + } + return derivations; + } + + async priceDerivationLeafHash(derivation) { + return await this.opaqueHash('mayhem-price-derivation-leaf-v1', this.priceDerivationLeafValue(derivation)); + } + + async merkleRoot(kind, leaves) { + if (leaves.length === 0) return await this.opaqueHash(`mayhem-${kind}-empty-root-v1`, {}); + let level = leaves.slice().sort(); + while (level.length > 1) { + const next = []; + for (let idx = 0; idx < level.length; idx += 2) { + const left = level[idx]; + const right = idx + 1 < level.length ? level[idx + 1] : left; + next.push(await this.opaqueHash(`mayhem-${kind}-node-v1`, { left, right })); + } + level = next; + } + return level[0]; + } + + async priceDerivationRoot(derivations) { + const leaves = []; + for (const derivation of derivations) { + leaves.push(await this.priceDerivationLeafHash(derivation)); + } + return await this.merkleRoot('price', leaves); + } + + normalizePriceProofUsage(value) { + const usageMap = this.aggregateMarketUsageEntries([value]); + if (usageMap instanceof Error) return usageMap; + const entries = this.mapMarketUsageEntriesForHash(usageMap); + if (entries.length !== 1) return new Error('Price derivation proof requires one market usage entry.'); + return entries[0]; + } + + async prepareCommittedActivityEvidence({ epoch, at, epochSeconds, roots, totals }) { + if (totals.price_count === 0) return null; + if (totals.price_count !== 1) { + return new Error('Nonempty activity price commitments require one market; use bounded receipt pages for larger settlements.'); + } + const index = this.normalizeReceiptEpochIndexMetadata( + await this.get(this.receiptEpochIndexKey(epoch)), epoch); + if (index instanceof Error) return index; + if (index.count > 128 || index.count !== totals.use_count) { + return new Error('Activity price commitment requires at most 128 canonical receipts; use bounded receipt pages.'); + } + const freeze = await this.validateFrozenEpoch(epoch, at, index); + if (freeze) return freeze; + const markets = new Map(); const leaves = []; const seen = new Set(); + for (let page = 0; page < index.page_count; page++) { + const record = await this.get(this.receiptEpochPageKey(epoch, page)); + if (record?.type !== 'canonical_receipt_epoch_page' || record.epoch !== epoch || + record.page !== page || !Array.isArray(record.identities)) { + return new Error('Activity commitment receipt page is invalid.'); + } + for (const identity of record.identities) { + const identityKey = `${identity.billing_id}/${identity.billing_attempt}`; + if (seen.has(identityKey)) return new Error('Activity commitment duplicates a canonical receipt.'); + seen.add(identityKey); + const head = await this.get(this.receiptHeadKey(identity.billing_id, identity.billing_attempt)); + if (head?.type !== 'canonical_receipt_head' || head.epoch !== epoch || + head.settlement_epoch !== epoch || head.settlement_ready !== true || + head.billing_id !== identity.billing_id || head.billing_attempt !== identity.billing_attempt) { + return new Error('Activity commitment requires canonical final receipt heads.'); + } + const body = head.receipt.body; + const marketKey = this.priceMarketKey(body.enclave_id, body.ctx_bracket ?? null); + const row = markets.get(marketKey) ?? { + enclave_id: body.enclave_id, + ...(body.ctx_bracket ? { ctx_bracket: body.ctx_bracket, + ctx_bracket_table_ver: body.ctx_bracket_table_ver } : {}), + demand_au: '0', session_count: 0, providers: new Set(), settled_usage: {}, + compute_ms: '0', legacy_receipt_count: 0, provider_capacities: new Map(), + }; + const increment = this.incrementalSettledUsage(body); + if (increment instanceof Error) return increment; + row.settled_usage = this.addSettledUsage(row.settled_usage, increment); + row.demand_au = this.safeAddAu(row.demand_au, head.incremental_au); + if (body.schema_version === SESSION_RECEIPT_SCHEMA_VERSION) { + row.compute_ms = this.safeAddAu(row.compute_ms, String(body.compute_ms)); + row.provider_capacities.set( + head.provider, + Math.max(row.provider_capacities.get(head.provider) ?? 0, body.capacity_slots) + ); + } else { + row.legacy_receipt_count = this.safeAddCount( + row.legacy_receipt_count, + 1, + 'activity commitment legacy receipt count' + ); + } + if (row.settled_usage instanceof Error || row.demand_au instanceof Error || + row.compute_ms instanceof Error || row.legacy_receipt_count instanceof Error) { + return new Error('Activity commitment work overflow.'); + } + row.session_count++; + row.providers.add(head.provider); + markets.set(marketKey, row); + leaves.push(await this.usageLeafHash(head.receipt)); + } + } + if (seen.size !== index.count || markets.size !== 1 || leaves.some((leaf) => leaf instanceof Error)) { + return new Error('Activity price commitment must cover exactly one complete canonical receipt market.'); + } + if (await this.merkleRoot('use', leaves) !== roots.use) { + return new Error('Activity price commitment usage root differs from canonical signed receipts.'); + } + const canonical = new Map(); const usage = new Map(); + for (const [key, row] of markets) { + const { providers, provider_capacities: providerCapacities, settled_usage, ...publicRow } = row; + publicRow.provider_count = providers.size; + publicRow.capacity_slot_count = Array.from(providerCapacities.values()) + .reduce((sum, slots) => sum + slots, 0); + if (publicRow.demand_au !== totals.use_au) return new Error('Activity commitment gross total mismatch.'); + usage.set(key, publicRow); + canonical.set(key, { ...publicRow, settled_usage }); + } + const updates = await this.computeMarketPriceUpdates(usage, { + epoch, at, epochSeconds, canonicalActivity: canonical, includeDormant: false, + }); + if (updates instanceof Error) return updates; + const derivations = await this.priceDerivationsFromMarketUpdates(updates, + { epoch, at, epochSeconds, usageRoot: roots.use }); + if (derivations instanceof Error) return derivations; + return { price_usage: this.mapMarketUsageEntriesForHash(usage)[0], + derivation: derivations[0], expected_price_root: await this.priceDerivationRoot(derivations) }; + } + + async validatePriceDerivationFraudProof(commit) { + if (commit.status === 'void') return new Error('Epoch commit is already void.'); + if (this.value.proof_epoch > commit.provisional_until_epoch) { + return new Error('Epoch commit challenge window has closed.'); + } + const evidence = commit.expected_activity_evidence; + if (commit.totals.price_count !== 1 || !evidence) { + return new Error('Price activity proof requires canonical work evidence pinned by its commitment.'); + } + const priceUsage = this.normalizePriceProofUsage(this.value.price_usage); + if (priceUsage instanceof Error) return priceUsage; + if (stableJson(priceUsage) !== stableJson(evidence.price_usage)) { + return new Error('Price activity proof usage differs from canonical commitment evidence.'); + } + const expected = await this.priceDerivationRoot([evidence.derivation]); + if (expected !== evidence.expected_price_root) return new Error('Pinned activity evidence is inconsistent.'); + if (expected === commit.roots.price) return new Error('Price activity proof does not contradict committed price root.'); + return { price_usage: priceUsage, enclave_id: priceUsage.enclave_id, + ...(priceUsage.ctx_bracket ? { ctx_bracket: priceUsage.ctx_bracket, + ctx_bracket_table_ver: priceUsage.ctx_bracket_table_ver } : {}), + expected_price_root: expected, committed_price_root: commit.roots.price, + price_derivation_hash: evidence.derivation.derivation_hash, + price_derivation: cloneValue(evidence.derivation) }; + } + + async validateEpochApplyTotals({ + epoch, + roots, + totals, + debitTotal, + feeDeltaAu, + nextFeeCum, + burnDeltaAu, + nextBurnCum, + providerCount, + earnCumTotal, + epochSeconds, + priceDerivations, + }) { + const commit = await this.get(`epoch/commit/${epoch}`); + if (!commit) return new Error('Epoch commit required before applying evidence roots.'); + if (commit.status === 'void') return new Error('Epoch commit is void.'); + if (commit.epoch_seconds !== epochSeconds) { + return new Error('Epoch apply epoch_seconds does not match committed epoch timing.'); + } + if ( + stableJson(commit.roots) !== stableJson(roots) || + stableJson(commit.totals) !== stableJson(totals) + ) { + return new Error('Epoch apply roots do not match committed roots.'); + } + if (this.compareAu(totals.use_au, debitTotal) !== 0) return new Error('Epoch usage total does not match debits.'); + if (this.compareAu(totals.earn_au, earnCumTotal) !== 0) { + return new Error('Epoch earn total does not match cumulative provider earnings.'); + } + if (this.compareAu(totals.fee_au, feeDeltaAu) !== 0) return new Error('Epoch fee total does not match computed fee.'); + if (this.compareAu(totals.fee_cum_au, nextFeeCum) !== 0) { + return new Error('Epoch cumulative fee total does not match fee state.'); + } + if (this.compareAu(totals.burn_au, burnDeltaAu) !== 0) { + return new Error('Epoch burn total does not match computed TAP burn.'); + } + if (this.compareAu(totals.burn_cum_au, nextBurnCum) !== 0) { + return new Error('Epoch cumulative burn total does not match burn state.'); + } + if (totals.provider_count !== providerCount) { + return new Error('Epoch provider count does not match earnings.'); + } + if (totals.price_count !== priceDerivations.length) { + return new Error('Epoch price derivation count does not match market updates.'); + } + const priceRoot = await this.priceDerivationRoot(priceDerivations); + if (roots.price !== priceRoot) { + return new Error('Epoch price root does not match recomputed price derivations.'); + } + + const depositRoot = await this.get(`ev/dep/${epoch}`); + if (depositRoot) { + if (depositRoot.type !== 'deposit_root') return new Error('Invalid deposit evidence root.'); + if ( + depositRoot.merkle_root !== roots.dep || + depositRoot.count !== totals.dep_count || + this.compareAu(depositRoot.au_total, totals.dep_au) !== 0 + ) { + return new Error('Committed deposit root does not match deposit evidence.'); + } + } + for (const key of ['use', 'earn', 'fee', 'price']) { + if ((await this.get(`ev/${key}/${epoch}`)) !== null) { + return new Error(`Epoch ${key} evidence root already exists.`); + } + } + return null; + } + + async validatePagedEpochCommitEvidence({ + commit, + feeCumAu, + burnCumAu, + priceDerivations, + }) { + if (commit.totals.price_count !== 0) { + return new Error('Paged receipt settlement cannot finalize market price derivations.'); + } + const emptyPriceRoot = await this.priceDerivationRoot([]); + if (commit.roots.price !== emptyPriceRoot) { + return new Error('Paged receipt settlement requires the canonical empty price root.'); + } + if (this.compareAu(commit.totals.fee_cum_au, feeCumAu) !== 0) { + return new Error('Epoch cumulative fee total does not match paged settlement state.'); + } + if (this.compareAu(commit.totals.burn_cum_au, burnCumAu) !== 0) { + return new Error('Epoch cumulative burn total does not match paged settlement state.'); + } + const depositRoot = await this.get(`ev/dep/${commit.epoch}`); + if (depositRoot && ( + depositRoot.type !== 'deposit_root' || + depositRoot.merkle_root !== commit.roots.dep || + depositRoot.count !== commit.totals.dep_count || + this.compareAu(depositRoot.au_total, commit.totals.dep_au) !== 0 + )) { + return new Error('Committed deposit root does not match deposit evidence.'); + } + for (const key of ['use', 'earn', 'fee', 'price']) { + if ((await this.get(`ev/${key}/${commit.epoch}`)) !== null) { + return new Error(`Epoch ${key} evidence root already exists.`); + } + } + if ((await this.get(`market/price/${commit.epoch}`)) !== null) { + return new Error('Bounded market price evidence already exists.'); + } + if (!Array.isArray(priceDerivations)) { + return new Error('Bounded market price derivations are invalid.'); + } + return null; + } + + async writeBoundedMarketPriceEvidence({ + epoch, + at, + epochSeconds, + usageRoot, + derivations, + }) { + const root = await this.priceDerivationRoot(derivations); + await this.put(`market/price/${epoch}`, { + type: 'bounded_market_price_root', + epoch, + epoch_seconds: epochSeconds, + usage_root: usageRoot, + price_root: root, + price_count: derivations.length, + ts: at, + updated_at: this.tx, + }); + for (const derivation of derivations) { + const suffix = derivation.ctx_bracket + ? `${derivation.enclave_id}/${derivation.ctx_bracket}` + : derivation.enclave_id; + await this.put(`market/price/${epoch}/${suffix}`, { + ...derivation, + price_root: root, + updated_at: this.tx, + }); + } + } + + async writePriceDerivationEvidence({ epoch, at, epoch_seconds, root, count, derivations }) { + await this.put(`ev/price/${epoch}`, { + type: 'price_root', + epoch, + epoch_seconds, + merkle_root: root, + price_count: count, + ts: at, + updated_at: this.tx, + }); + for (const derivation of derivations) { + const key = derivation.ctx_bracket + ? `ev/price/${epoch}/${derivation.enclave_id}/${derivation.ctx_bracket}` + : `ev/price/${epoch}/${derivation.enclave_id}`; + await this.put(key, { + ...derivation, + price_root: root, + updated_at: this.tx, + }); + } + } + + async writeEpochEvidenceRoots({ + epoch, + at, + epoch_seconds, + roots, + totals, + feeDeltaAu, + feeCumAu, + burnDeltaAu, + burnCumAu, + priceDerivations, + }) { + if ((await this.get(`ev/dep/${epoch}`)) === null) { + await this.put(`ev/dep/${epoch}`, { + type: 'deposit_root', + epoch, + epoch_seconds, + merkle_root: roots.dep, + count: totals.dep_count, + au_total: totals.dep_au, + ts: at, + updated_at: this.tx, + }); + } + await this.put(`ev/use/${epoch}`, { + type: 'usage_root', + epoch, + epoch_seconds, + merkle_root: roots.use, + sessions: totals.use_count, + au_total: totals.use_au, + providers: totals.provider_count, + ts: at, + updated_at: this.tx, + }); + await this.put(`ev/earn/${epoch}`, { + type: 'earn_root', + epoch, + epoch_seconds, + merkle_root: roots.earn, + provider_count: totals.provider_count, + au_cum_total: totals.earn_au, + ts: at, + updated_at: this.tx, + }); + await this.put(`ev/fee/${epoch}`, { + type: 'fee_root', + epoch, + epoch_seconds, + merkle_root: roots.fee, + au_fee_epoch: feeDeltaAu, + au_fee_cum: feeCumAu, + au_burn_epoch: burnDeltaAu, + au_burn_cum: burnCumAu, + tap_burn_bps: TAP_BURN_BPS, + sweep_msb_tx_hash: null, + ts: at, + updated_at: this.tx, + }); + await this.writePriceDerivationEvidence({ + epoch, + at, + epoch_seconds, + root: roots.price, + count: totals.price_count, + derivations: priceDerivations, + }); + } + + aggregateLedgerEntries(entries, idKey, amountKey, label) { + const out = new Map(); + for (const entry of entries) { + if (!entry || typeof entry !== 'object' || Array.isArray(entry)) { + return new Error(`Invalid ${label} entry.`); + } + const id = entry[idKey]; + const au = this.normalizeAu(entry[amountKey], `${label} amount`, { allowZero: false }); + if (au instanceof Error) return new Error(`Invalid ${label} amount.`); + if (!this.isSafeKeyPart(id)) return new Error(`Invalid ${label} ${idKey}.`); + const next = this.safeAddAu(out.get(id) ?? ZERO_AU, au); + if (next instanceof Error) return next; + out.set(id, next); + } + return out; + } + + aggregateRailLedgerEntries(entries, idKey, amountKey, label) { + const out = new Map(); + for (const entry of entries) { + if (!entry || typeof entry !== 'object' || Array.isArray(entry)) { + return new Error(`Invalid ${label} entry.`); + } + const rail = this.normalizeLedgerRail(entry.rail, `${label} rail`); + if (rail instanceof Error) return rail; + const id = entry[idKey]; + const au = this.normalizeAu(entry[amountKey], `${label} amount`, { allowZero: false }); + if (au instanceof Error) return new Error(`Invalid ${label} amount.`); + if (!this.isSafeKeyPart(id)) return new Error(`Invalid ${label} ${idKey}.`); + const key = stableJson([rail, id]); + const current = out.get(key) ?? { rail, [idKey]: id, [amountKey]: ZERO_AU }; + const next = this.safeAddAu(current[amountKey], au); + if (next instanceof Error) return next; + out.set(key, { ...current, [amountKey]: next }); + } + return out; + } + + aggregateMarketUsageEntries(entries) { + const out = new Map(); + for (const entry of entries) { + if (!entry || typeof entry !== 'object' || Array.isArray(entry)) { + return new Error('Invalid market usage entry.'); + } + const allowed = new Set([ + 'enclave_id', + 'ctx_bracket', + 'ctx_bracket_table_ver', + 'demand_au', + 'session_count', + 'provider_count', + 'compute_ms', + 'capacity_slot_count', + 'legacy_receipt_count', + ]); + const unknown = Object.keys(entry).filter((key) => !allowed.has(key)).sort(); + if (unknown.length > 0) { + return new Error(`market usage entry does not accept fields: ${unknown.join(', ')}.`); + } + for (const key of [ + 'enclave_id', + 'demand_au', + 'session_count', + 'provider_count', + 'compute_ms', + 'capacity_slot_count', + ]) { + if (!hasOwn(entry, key)) return new Error(`market usage entry is missing ${key}.`); + } + const enclaveId = entry.enclave_id; + if (!this.isSafeKeyPart(enclaveId)) return new Error('Invalid market usage enclave_id.'); + const ctxBracket = entry.ctx_bracket ?? null; + if (ctxBracket !== null && !this.isSafeKeyPart(ctxBracket)) { + return new Error('Invalid market usage context bracket.'); + } + if ( + hasOwn(entry, 'ctx_bracket_table_ver') && + (!Number.isSafeInteger(entry.ctx_bracket_table_ver) || entry.ctx_bracket_table_ver < 1) + ) { + return new Error('Invalid market usage context bracket table version.'); + } + const demandEntryAu = this.normalizeAu(entry.demand_au, 'market usage demand', { allowZero: false }); + if (demandEntryAu instanceof Error) { + return new Error('Invalid market usage demand.'); + } + if (!Number.isSafeInteger(entry.session_count) || entry.session_count <= 0) { + return new Error('Invalid market usage session_count.'); + } + if (!Number.isSafeInteger(entry.provider_count) || entry.provider_count <= 0) { + return new Error('Invalid market usage provider_count.'); + } + const legacyReceiptCount = entry.legacy_receipt_count ?? 0; + if (!Number.isSafeInteger(legacyReceiptCount) || legacyReceiptCount < 0 || + legacyReceiptCount > entry.session_count) { + return new Error('Invalid market usage legacy_receipt_count.'); + } + const computeMs = this.normalizeAu(entry.compute_ms, 'market usage compute duration'); + if (computeMs instanceof Error || + (legacyReceiptCount === 0 && this.isZeroAu(computeMs))) { + return new Error('Invalid market usage compute duration.'); + } + if (!Number.isSafeInteger(entry.capacity_slot_count) || + entry.capacity_slot_count < 0 || + (legacyReceiptCount === 0 && entry.capacity_slot_count === 0)) { + return new Error('Invalid market usage capacity_slot_count.'); + } + const key = this.priceMarketKey(enclaveId, ctxBracket); + const current = out.get(key) ?? { + enclave_id: enclaveId, + ...(ctxBracket ? { ctx_bracket: ctxBracket } : {}), + ...(entry.ctx_bracket_table_ver ? { ctx_bracket_table_ver: entry.ctx_bracket_table_ver } : {}), + demand_au: ZERO_AU, + session_count: 0, + provider_count: 0, + compute_ms: ZERO_AU, + capacity_slot_count: 0, + legacy_receipt_count: 0, + }; + if ((current.ctx_bracket_table_ver ?? null) !== (entry.ctx_bracket_table_ver ?? current.ctx_bracket_table_ver ?? null)) { + return new Error('Market usage context bracket table version mismatch.'); + } + const demandAu = this.safeAddAu(current.demand_au, demandEntryAu); + if (demandAu instanceof Error) return demandAu; + const sessionCount = this.safeAddCount(current.session_count, entry.session_count, 'market usage session_count'); + if (sessionCount instanceof Error) return sessionCount; + const providerCount = this.safeAddCount(current.provider_count, entry.provider_count, 'market usage provider_count'); + if (providerCount instanceof Error) return providerCount; + const totalComputeMs = this.safeAddAu(current.compute_ms, computeMs); + if (totalComputeMs instanceof Error) return totalComputeMs; + const capacitySlotCount = this.safeAddCount( + current.capacity_slot_count, + entry.capacity_slot_count, + 'market usage capacity slot count' + ); + if (capacitySlotCount instanceof Error) return capacitySlotCount; + const totalLegacyReceiptCount = this.safeAddCount( + current.legacy_receipt_count, + legacyReceiptCount, + 'market usage legacy receipt count' + ); + if (totalLegacyReceiptCount instanceof Error || + totalLegacyReceiptCount > sessionCount) { + return new Error('Invalid market usage legacy receipt total.'); + } + out.set(key, { + enclave_id: enclaveId, + ...(ctxBracket ? { ctx_bracket: ctxBracket } : {}), + ...(entry.ctx_bracket_table_ver ? { ctx_bracket_table_ver: entry.ctx_bracket_table_ver } : {}), + demand_au: demandAu, + session_count: sessionCount, + provider_count: providerCount, + compute_ms: totalComputeMs, + capacity_slot_count: capacitySlotCount, + legacy_receipt_count: totalLegacyReceiptCount, + }); + } + return out; + } + + sumAu(entries) { + let sum = ZERO_AU; + for (const [, au] of entries) { + const next = this.safeAddAu(sum, au); + if (next instanceof Error) return next; + sum = next; + } + return sum; + } + + sumRailAu(entries, amountKey) { + let sum = ZERO_AU; + for (const entry of entries.values()) { + const next = this.safeAddAu(sum, entry[amountKey]); + if (next instanceof Error) return next; + sum = next; + } + return sum; + } + + sumMarketDemandAu(entries) { + let sum = ZERO_AU; + for (const entry of entries.values()) { + const next = this.safeAddAu(sum, entry.demand_au); + if (next instanceof Error) return next; + sum = next; + } + return sum; + } + + usageAuForRateMap(rateMap, usage) { + const rates = this.rateMapByUnit(rateMap); + const priced = []; + for (const [unit, count] of Object.entries(usage ?? {})) { + if (!Number.isSafeInteger(count) || count < 0) return new Error('Invalid receipt usage count.'); + if (count === 0) continue; + const rate = rates.get(unit); + if (!rate) return new Error(`Receipt locked_rate_map missing usage unit ${unit}.`); + const perUnitAu = this.parseAu(rate.per_unit_au, 'locked rate per_unit_au', { allowZero: false }); + if (perUnitAu instanceof Error) { + return new Error('Invalid locked rate per_unit_au.'); + } + if (!Number.isSafeInteger(rate.granularity) || rate.granularity <= 0) { + return new Error('Invalid locked rate granularity.'); + } + priced.push({ + count: BigInt(count), + perUnitAu, + granularity: BigInt(rate.granularity), + }); + } + if (priced.length === 0) return ZERO_AU; + const sameGranularity = priced.every((entry) => entry.granularity === priced[0].granularity); + if (sameGranularity) { + const raw = priced.reduce((sum, entry) => sum + entry.count * entry.perUnitAu, 0n); + return this.canonicalAu(this.ceilDivBigInt(raw, priced[0].granularity)); + } + let total = 0n; + for (const entry of priced) { + total += this.ceilDivBigInt(entry.count * entry.perUnitAu, entry.granularity); + } + return this.canonicalAu(total); + } + + usageAuForLockedTerms(rateMap, perReqAu, minSessionAu, usage) { + const normalizedPerReqAu = this.normalizeAu(perReqAu, 'locked per-request price'); + if (normalizedPerReqAu instanceof Error) return new Error('Invalid locked per-request price.'); + const normalizedMinSessionAu = this.normalizeAu(minSessionAu, 'locked minimum session price'); + if (normalizedMinSessionAu instanceof Error) return new Error('Invalid locked minimum session price.'); + const usageAu = this.usageAuForRateMap(rateMap, usage); + if (usageAu instanceof Error) return usageAu; + const subtotal = this.safeAddAu(usageAu, normalizedPerReqAu); + if (subtotal instanceof Error) return subtotal; + return this.maxAu(subtotal, normalizedMinSessionAu); + } + + receiptUsageDelta(previous, current) { + const delta = {}; + for (const [unit, previousCount] of Object.entries(previous)) { + const currentCount = current[unit] ?? 0; + if (currentCount < previousCount) return new Error('Receipt cumulative usage regressed.'); + } + for (const [unit, currentCount] of Object.entries(current)) { + const count = currentCount - (previous[unit] ?? 0); + if (count > 0) delta[unit] = count; + } + return delta; + } + + logicalCumulativeAuForLockedTerms( + rateMap, + perReqAu, + minSessionAu, + priorUsage, + priorAuOwedCum, + currentUsage + ) { + const delta = this.receiptUsageDelta(priorUsage, currentUsage); + if (delta instanceof Error) return delta; + const increment = this.isZeroAu(priorAuOwedCum) + ? this.usageAuForLockedTerms(rateMap, perReqAu, minSessionAu, delta) + : this.usageAuForRateMap(rateMap, delta); + if (increment instanceof Error) return increment; + return this.safeAddAu(priorAuOwedCum, increment); + } + + ceilDivBigInt(value, divisor) { + if (value <= 0n) return 0n; + return (value + divisor - 1n) / divisor; + } + + railTotals(entries, amountKey) { + const out = new Map(PROVIDER_ACCEPTED_RAIL_ORDER.map((rail) => [rail, ZERO_AU])); + for (const entry of entries.values()) { + const next = this.safeAddAu(out.get(entry.rail) ?? ZERO_AU, entry[amountKey]); + if (next instanceof Error) return next; + out.set(entry.rail, next); + } + return out; + } + + assertMatchingRailTotals(left, right) { + for (const rail of PROVIDER_ACCEPTED_RAIL_ORDER) { + if (this.compareAu(left.get(rail) ?? ZERO_AU, right.get(rail) ?? ZERO_AU) !== 0) { + return new Error('Epoch debits must equal gross provider earnings per rail.'); + } + } + return null; + } + + safeAddAu(a, b) { + const left = this.parseAu(a, 'au value'); + if (left instanceof Error) return left; + const right = this.parseAu(b, 'au value'); + if (right instanceof Error) return right; + return this.canonicalAu(left + right); + } + + safeSubAu(a, b) { + const left = this.parseAu(a, 'au value'); + if (left instanceof Error) return left; + const right = this.parseAu(b, 'au value'); + if (right instanceof Error) return right; + if (right > left) return new Error('au value underflow.'); + return this.canonicalAu(left - right); + } + + safeMulDivAu(a, b, divisor) { + const amount = this.parseAu(a, 'au value'); + if (amount instanceof Error) return amount; + if ( + !Number.isSafeInteger(b) || + !Number.isSafeInteger(divisor) || + b < 0 || + divisor <= 0 + ) { + return new Error('Invalid au multiplier.'); + } + return this.canonicalAu((amount * BigInt(b)) / BigInt(divisor)); + } + + providerSettlementPageDelta({ grossAu, priorGrossAu, rail, feeBps }) { + const nextGrossAu = this.safeAddAu(priorGrossAu, grossAu); + if (nextGrossAu instanceof Error) return nextGrossAu; + const priorFeeAu = this.safeMulDivAu(priorGrossAu, feeBps, 10_000); + const nextFeeAu = this.safeMulDivAu(nextGrossAu, feeBps, 10_000); + if (priorFeeAu instanceof Error || nextFeeAu instanceof Error) { + return new Error('Provider settlement fee overflow.'); + } + const feeAu = this.safeSubAu(nextFeeAu, priorFeeAu); + if (feeAu instanceof Error) return feeAu; + const priorBurnAu = rail === 'tap' + ? this.safeMulDivAu(priorGrossAu, TAP_BURN_BPS, 10_000) + : ZERO_AU; + const nextBurnAu = rail === 'tap' + ? this.safeMulDivAu(nextGrossAu, TAP_BURN_BPS, 10_000) + : ZERO_AU; + if (priorBurnAu instanceof Error || nextBurnAu instanceof Error) { + return new Error('Provider settlement burn overflow.'); + } + const burnAu = this.safeSubAu(nextBurnAu, priorBurnAu); + if (burnAu instanceof Error) return burnAu; + const providerAu = this.safeSubAu( + this.safeSubAu(grossAu, feeAu), + burnAu + ); + if (providerAu instanceof Error) return providerAu; + return { + fee_au: feeAu, + burn_au: burnAu, + provider_au: providerAu, + }; + } + + safeAddCount(a, b, label = 'count') { + if (!Number.isSafeInteger(a) || !Number.isSafeInteger(b) || a < 0 || b < 0) { + return new Error(`Invalid ${label}.`); + } + const next = a + b; + if (!Number.isSafeInteger(next)) return new Error(`${label} overflow.`); + return next; + } + + parseAu(value, label = 'au value', { allowZero = true } = {}) { + if (typeof value === 'bigint') { + if (value < 0n || (!allowZero && value === 0n)) return new Error(`${label} must be positive.`); + return value; + } + if (typeof value !== 'string' || !/^(0|[1-9][0-9]*)$/.test(value)) { + return new Error(`${label} must be a canonical decimal string.`); + } + const parsed = BigInt(value); + if (!allowZero && parsed === 0n) return new Error(`${label} must be positive.`); + return parsed; + } + + normalizeAu(value, label = 'au value', options = {}) { + const parsed = this.parseAu(value, label, options); + if (parsed instanceof Error) return parsed; + return this.canonicalAu(parsed); + } + + canonicalAu(value) { + if (typeof value !== 'bigint' || value < 0n) return new Error('Invalid au value.'); + return value.toString(); + } + + compareAu(a, b) { + const left = this.parseAu(a, 'au value'); + if (left instanceof Error) return NaN; + const right = this.parseAu(b, 'au value'); + if (right instanceof Error) return NaN; + return left === right ? 0 : (left < right ? -1 : 1); + } + + maxAu(a, b) { + return this.compareAu(a, b) >= 0 ? this.normalizeAu(a) : this.normalizeAu(b); + } + + isZeroAu(value) { + return this.compareAu(value, ZERO_AU) === 0; + } + + sortedMapEntries(map) { + return Array.from(map.entries()).sort(([a], [b]) => compareCodepoint(a, b)); + } + + sortedRailRecords(map, idKey) { + return Array.from(map.values()).sort((a, b) => { + const railOrder = + PROVIDER_ACCEPTED_RAIL_ORDER.indexOf(a.rail) - PROVIDER_ACCEPTED_RAIL_ORDER.indexOf(b.rail); + if (railOrder !== 0) return railOrder; + return compareCodepoint(a[idKey], b[idKey]); + }); + } + + mapEntriesForHash(map, idKey, amountKey) { + return this.sortedMapEntries(map).map(([id, au]) => ({ + [idKey]: id, + [amountKey]: au, + })); + } + + mapRailEntriesForHash(map, idKey, amountKey) { + return this.sortedRailRecords(map, idKey).map((entry) => ({ + rail: entry.rail, + [idKey]: entry[idKey], + [amountKey]: entry[amountKey], + })); + } + + mapMarketUsageEntriesForHash(map) { + return Array.from(map.values()) + .sort((left, right) => ( + compareCodepoint(left.enclave_id, right.enclave_id) || + compareCodepoint(left.ctx_bracket ?? '', right.ctx_bracket ?? '') + )) + .map((entry) => ({ + enclave_id: entry.enclave_id, + ...(entry.ctx_bracket ? { ctx_bracket: entry.ctx_bracket } : {}), + ...(entry.ctx_bracket_table_ver ? { ctx_bracket_table_ver: entry.ctx_bracket_table_ver } : {}), + demand_au: entry.demand_au, + session_count: entry.session_count, + provider_count: entry.provider_count, + compute_ms: entry.compute_ms, + capacity_slot_count: entry.capacity_slot_count, + legacy_receipt_count: entry.legacy_receipt_count ?? 0, + })); + } + + async balanceRecord(user, rail) { + const normalizedRail = this.normalizeLedgerRail(rail, 'balance rail'); + if (normalizedRail instanceof Error) return normalizedRail; + const tap = normalizedRail === 'tap' + ? await this.canonicalTapPaymentConfig({ optional: true }) + : null; + if (tap instanceof Error) return tap; + const current = await this.get(this.balanceKey(user, normalizedRail)); + const scoped = tap === null || ( + current?.chain_id === tap.chain_id && + current?.pool_address === tap.pool_address + ) ? current : null; + return scoped ?? { + user, + rail: normalizedRail, + denom: PRICE_DENOMINATION, + au: ZERO_AU, + updated_epoch: 0, + updated_at: null, + ...(tap ?? {}), + }; + } + + async earningRecord(provider, rail) { + const normalizedRail = this.normalizeLedgerRail(rail, 'earning rail'); + if (normalizedRail instanceof Error) return normalizedRail; + const tap = normalizedRail === 'tap' + ? await this.canonicalTapPaymentConfig({ optional: true }) + : null; + if (tap instanceof Error) return tap; + const current = await this.get(this.earningKey(provider, normalizedRail)); + const scoped = tap === null || ( + current?.chain_id === tap.chain_id && + current?.pool_address === tap.pool_address + ) ? current : null; + return scoped ?? { + provider, + rail: normalizedRail, + denom: PRICE_DENOMINATION, + total_au: ZERO_AU, + held_au: ZERO_AU, + paid_cum_au: ZERO_AU, + updated_epoch: 0, + updated_at: null, + ...(tap ?? {}), + }; + } + + async feeCumRecord(rail) { + const normalizedRail = this.normalizeLedgerRail(rail, 'fee rail'); + if (normalizedRail instanceof Error) return normalizedRail; + const tap = normalizedRail === 'tap' + ? await this.canonicalTapPaymentConfig({ optional: true }) + : null; + if (tap instanceof Error) return tap; + const current = await this.get(this.feeCumKey(normalizedRail)); + const scoped = tap === null || ( + current?.chain_id === tap.chain_id && + current?.pool_address === tap.pool_address + ) ? current : null; + return scoped ?? { + rail: normalizedRail, + denom: PRICE_DENOMINATION, + cum_au: ZERO_AU, + swept_cum_au: ZERO_AU, + updated_epoch: 0, + updated_at: null, + last_apply_hash: null, + last_fee_bps: null, + ...(tap ?? {}), + }; + } + + async burnCumRecord(rail) { + const normalizedRail = this.normalizeLedgerRail(rail, 'burn rail'); + if (normalizedRail instanceof Error) return normalizedRail; + const tap = normalizedRail === 'tap' + ? await this.canonicalTapPaymentConfig({ optional: true }) + : null; + if (tap instanceof Error) return tap; + const current = await this.get(this.burnCumKey(normalizedRail)); + const scoped = tap === null || ( + current?.chain_id === tap.chain_id && + current?.pool_address === tap.pool_address + ) ? current : null; + return scoped ?? { + rail: normalizedRail, + denom: PRICE_DENOMINATION, + cum_au: ZERO_AU, + updated_epoch: 0, + updated_at: null, + last_apply_hash: null, + burn_bps: normalizedRail === 'tap' ? TAP_BURN_BPS : 0, + ...(tap ?? {}), + }; + } + + async epochApplyStateRecord() { + return (await this.get('epoch/apply/state')) ?? { + updated_epoch: 0, + updated_at: null, + last_apply_hash: null, + last_settlement_unix: null, + }; + } + + epochApplyAnchorKey(epoch) { + return `epoch/apply-anchor/${epoch}`; + } + + async prepareEpochApplyAnchor(state) { + if ( + !state || + !Number.isSafeInteger(state.updated_epoch) || + state.updated_epoch < 1 || + !this.isHexBytes(state.last_apply_hash, 32) || + !Number.isSafeInteger(state.last_settlement_unix) || + state.last_settlement_unix < 0 || + (state.pending_epoch ?? null) !== null + ) { + return new Error('Cannot anchor an incomplete epoch apply.'); + } + const key = this.epochApplyAnchorKey(state.updated_epoch); + const record = { + type: 'epoch_apply_anchor', + epoch: state.updated_epoch, + apply_hash: state.last_apply_hash.toLowerCase(), + settlement_unix: state.last_settlement_unix, + applied_at: state.updated_at, + }; + const existing = await this.get(key); + if (existing !== null) { + return stableJson(existing) === stableJson(record) + ? null + : new Error('Epoch apply anchor conflict.'); + } + return { key, record, write: true }; + } + + async rememberEpochApplyAnchor(state) { + const prepared = await this.prepareEpochApplyAnchor(state); + if (prepared instanceof Error) return prepared; + await this.writePreparedAnchor(prepared); + return null; + } + + async epochApplyAnchor(epoch) { + if (!Number.isSafeInteger(epoch) || epoch < 1) { + return new Error('Invalid epoch apply anchor epoch.'); + } + const historical = await this.get(this.epochApplyAnchorKey(epoch)); + if (historical !== null) { + if ( + historical.type !== 'epoch_apply_anchor' || + historical.epoch !== epoch || + !this.isHexBytes(historical.apply_hash, 32) || + !Number.isSafeInteger(historical.settlement_unix) || + historical.settlement_unix < 0 || + typeof historical.applied_at !== 'string' || + historical.applied_at.length === 0 + ) { + return new Error('Epoch apply anchor is invalid.'); + } + return historical; + } + const current = await this.epochApplyStateRecord(); + if ( + current.updated_epoch === epoch && + this.isHexBytes(current.last_apply_hash, 32) && + (current.pending_epoch ?? null) === null + ) { + const settlementUnix = await this.priorEpochSettlementUnix(current); + if (settlementUnix instanceof Error) return settlementUnix; + return { + type: 'epoch_apply_anchor', + epoch, + apply_hash: current.last_apply_hash.toLowerCase(), + settlement_unix: settlementUnix, + applied_at: current.updated_at, + }; + } + return null; + } + + async requireEpochApplyAnchor(epoch, applyHash, label) { + const anchor = await this.epochApplyAnchor(epoch); + if (anchor instanceof Error) return anchor; + if (!anchor || anchor.apply_hash !== applyHash) { + return new Error(`${label} apply hash mismatch.`); + } + return anchor; + } + + async prepareCanaryChallengeAnchor(state) { + if ( + !state || + !Number.isSafeInteger(state.updated_epoch) || + state.updated_epoch < 1 || + !this.isHexBytes(state.last_apply_hash, 32) || + (state.pending_epoch ?? null) !== null + ) { + return null; + } + const key = `epoch/challenge/${state.updated_epoch}`; + const record = { + epoch: state.updated_epoch, + apply_hash: state.last_apply_hash.toLowerCase(), + recorded_at: this.tx, + }; + const existing = await this.get(key); + if (existing !== null) { + if (existing.epoch !== record.epoch || existing.apply_hash !== record.apply_hash) { + return new Error('Canary challenge anchor conflict.'); + } + return { key, record, write: false }; + } + return { key, record, write: true }; + } + + async writePreparedAnchor(prepared) { + if (prepared?.write === true) { + await this.put(prepared.key, prepared.record); + } + } + + async rememberCanaryChallengeAnchor(state) { + const prepared = await this.prepareCanaryChallengeAnchor(state); + if (prepared instanceof Error) return prepared; + await this.writePreparedAnchor(prepared); + return null; + } + + async canaryChallengeAnchor(epoch) { + const historical = await this.get(`epoch/challenge/${epoch}`); + if (historical) return historical; + const current = await this.epochApplyStateRecord(); + if ( + current.updated_epoch === epoch && + this.isHexBytes(current.last_apply_hash, 32) && + (current.pending_epoch ?? null) === null + ) { + return { + epoch, + apply_hash: current.last_apply_hash.toLowerCase(), + recorded_at: current.updated_at, + }; + } + return null; + } + + parseTnkE18(value) { + if (typeof value !== 'string' || !/^[0-9]+$/.test(value)) { + return new Error('tnk_e18 must be a decimal integer string.'); + } + const parsed = BigInt(value); + if (parsed <= 0n) return new Error('tnk_e18 must be positive.'); + return parsed; + } + + parseTapWei(value, { allowZero = false } = {}) { + if (typeof value !== 'string' || !/^[0-9]+$/.test(value)) { + return new Error('tap_wei must be a decimal integer string.'); + } + const parsed = BigInt(value); + if (parsed < 0n || (!allowZero && parsed === 0n)) { + return new Error('tap_wei must be positive.'); + } + return parsed; + } + + normalizeFiatCurrency(value) { + if (typeof value !== 'string') return new Error('Invalid fiat currency.'); + const currency = value.trim().toLowerCase(); + if (!/^[a-z]{3}$/.test(currency)) return new Error('Unsupported fiat currency.'); + return currency; + } + + fiatEvidenceFields() { + if (this.value.fiat_currency === undefined || this.value.fiat_amount_minor === undefined) { + return new Error('Fiat evidence requires fiat_currency and fiat_amount_minor.'); + } + const currency = this.normalizeFiatCurrency(this.value.fiat_currency); + if (currency instanceof Error) return currency; + if ( + !Number.isSafeInteger(this.value.fiat_amount_minor) || + this.value.fiat_amount_minor <= 0 + ) { + return new Error('Invalid fiat amount.'); + } + return { + fiat_currency: currency, + fiat_amount_minor: this.value.fiat_amount_minor, + }; + } + + tnkE18ToAu(tnkE18, tnkUsdAu) { + const rate = this.parseAu(tnkUsdAu, 'TNK/USD atto-rate', { allowZero: false }); + if (rate instanceof Error) return new Error('Invalid TNK/USD atto-rate.'); + // Deposit conversion rounds down so credited AU never exceeds received TNK value. + return this.canonicalAu((tnkE18 * rate) / TNK_E18); + } + + tapWeiToAu(tapWei, tapUsdAu) { + const rate = this.parseAu(tapUsdAu, 'TAP/USD policy rate', { allowZero: false }); + if (rate instanceof Error) return new Error('Invalid TAP/USD policy rate.'); + // Deposit conversion rounds down so credited AU never exceeds received TAP value. + return this.canonicalAu((tapWei * rate) / TAP_WEI); + } + + async requireFreshRate(at) { + const rate = await this.get('rate/latest'); + if (!rate) return new Error('Fresh rate oracle required.'); + const admin = await this.get('admin'); + if (admin === null) return new Error('Fresh rate oracle requires a current admin key.'); + if (rate.posted_by !== admin || rate.posted_by_role !== 'admin') { + return new Error('Fresh rate oracle must be admin-posted.'); + } + if (rate.ts > at) return new Error('Rate oracle timestamp is in the future.'); + const params = await this.activeParamsAt(at, ['rate_staleness_seconds']); + if (at - rate.ts > params.rate_staleness_seconds) { + return new Error('Rate oracle is stale.'); + } + return rate; + } + + validateTnkRateRecord( + rate, + { + tnkUsdAu = null, + source = null, + ts = null, + updatedAt = null, + admin = null, + label = 'TNK rate', + } = {} + ) { + if (!rate || typeof rate !== 'object' || Array.isArray(rate)) { + return new Error(`${label} is missing.`); + } + const normalized = this.normalizeAu( + rate.tnk_usd_au, + `${label} TNK/USD atto-rate`, + { allowZero: false } + ); + if (normalized instanceof Error || normalized !== rate.tnk_usd_au || + rate.denom !== 'tnk_usd_au' || + typeof rate.source !== 'string' || + rate.source.length < 1 || + rate.source.length > 64 || + !Number.isSafeInteger(rate.ts) || + rate.ts < 0 || + typeof rate.updated_at !== 'string' || + !rate.updated_at.startsWith(`rate/tnk/${rate.ts}/`) || + !this.isHexBytes(rate.updated_at.slice(`rate/tnk/${rate.ts}/`.length), 32) || + !this.isHexBytes(rate.posted_by, 32) || + rate.posted_by !== rate.posted_by.toLowerCase() || + rate.posted_by_role !== 'admin') { + return new Error(`${label} is invalid.`); + } + if (admin !== null && rate.posted_by !== admin) { + return new Error(`${label} is not admin-posted.`); + } + if (tnkUsdAu !== null && this.compareAu(rate.tnk_usd_au, tnkUsdAu) !== 0) { + return new Error(`${label} amount mismatch.`); + } + if (source !== null && rate.source !== source) { + return new Error(`${label} source mismatch.`); + } + if (ts !== null && rate.ts !== ts) { + return new Error(`${label} timestamp mismatch.`); + } + if (updatedAt !== null && rate.updated_at !== updatedAt) { + return new Error(`${label} record key mismatch.`); + } + return null; + } + + async currentTnkRateRecord(label = 'Current TNK rate') { + const rate = await this.get('rate/latest'); + const admin = await this.get('admin'); + if (!rate || admin === null) return new Error(`${label} is missing.`); + const rateError = this.validateTnkRateRecord(rate, { admin, label }); + if (rateError) return rateError; + return rate; + } + + async guardianAcceptTnkDepositIntentRate(intent) { + const rate = await this.currentTnkRateRecord('TNK deposit rate'); + if (rate instanceof Error) return rate; + const exactError = this.validateTnkRateRecord(rate, { + tnkUsdAu: intent.rate_tnk_usd_au, + source: intent.rate_source, + label: 'TNK deposit rate', + }); + if (exactError) { + return new Error('TNK deposit rate does not match current oracle.'); + } + if ( + (hasOwn(intent, 'rate_ts') && intent.rate_ts !== rate.ts) || + (hasOwn(intent, 'rate_record_key') && intent.rate_record_key !== rate.updated_at) + ) { + return new Error('TNK deposit rate record is not current.'); + } + return rate; + } + + legacyTnkDepositRateCloseToCurrent(lockedRate, currentRate) { + const locked = this.parseAu(lockedRate, 'legacy TNK deposit locked rate', { allowZero: false }); + const current = this.parseAu(currentRate, 'current TNK deposit rate', { allowZero: false }); + if (locked instanceof Error || current instanceof Error) return false; + const diff = locked > current ? locked - current : current - locked; + const ceiling = locked > current ? locked : current; + return diff * 10_000n <= ceiling * LEGACY_TNK_DEPOSIT_RATE_DRIFT_BPS; + } + + async guardianRequireHistoricalTnkDepositRate(pending, at) { + const oracleValue = { + op: 'rate_oracle', + tnk_usd_au: pending.rate_tnk_usd_au, + source: pending.rate_source, + ts: pending.rate_ts, + }; + const key = await this.rateFeatureKey(oracleValue); + if (key instanceof Error) { + return new Error(`Guardian TNK deposit rate invariant failed: ${key.message}`); + } + if (key !== pending.rate_record_key) { + return new Error('Guardian TNK deposit rate invariant failed: rate record key mismatch.'); + } + const rate = await this.get(key); + const admin = await this.get('admin'); + if (!rate || admin === null) { + return new Error('Guardian TNK deposit rate invariant failed: exact admin-posted oracle history required.'); + } + const rateError = this.validateTnkRateRecord(rate, { + tnkUsdAu: pending.rate_tnk_usd_au, + source: pending.rate_source, + ts: pending.rate_ts, + updatedAt: pending.rate_record_key, + admin, + label: 'Guardian TNK deposit rate', + }); + if (rateError) { + return new Error(`Guardian TNK deposit rate invariant failed: ${rateError.message}`); + } + if (rate.ts > at) { + return new Error('Guardian TNK deposit rate invariant failed: oracle timestamp is in the future.'); + } + return rate; + } + + async guardianRequireTnkDepositRateLock(pending, at) { + const pendingRate = this.normalizeAu( + pending?.rate_tnk_usd_au, + 'pending TNK deposit rate', + { allowZero: false } + ); + if (pendingRate instanceof Error || pendingRate !== pending.rate_tnk_usd_au || + !this.isSafeKeyPart(pending.rate_source)) { + return new Error('Guardian TNK deposit rate invariant failed: pending rate is invalid.'); + } + const hasRateTs = hasOwn(pending, 'rate_ts'); + const hasRateRecordKey = hasOwn(pending, 'rate_record_key'); + if (hasRateTs || hasRateRecordKey) { + if (!hasRateTs || !hasRateRecordKey || + !Number.isSafeInteger(pending.rate_ts) || + pending.rate_ts < 0 || + typeof pending.rate_record_key !== 'string') { + return new Error('Guardian TNK deposit rate invariant failed: pending rate lock is invalid.'); + } + return await this.guardianRequireHistoricalTnkDepositRate(pending, at); + } + + const current = await this.guardianRequireFreshRate(at); + if (current instanceof Error) return current; + if (current.source !== pending.rate_source || + !this.legacyTnkDepositRateCloseToCurrent(pending.rate_tnk_usd_au, current.tnk_usd_au)) { + return new Error('TNK deposit rate does not match pending intent.'); + } + return { + ...current, + tnk_usd_au: pending.rate_tnk_usd_au, + source: pending.rate_source, + legacy_rate_lock: true, + }; + } + + async requireFreshTapRate(at) { + const rate = await this.get('tap/rate/latest'); + if (!rate) return new Error('Fresh TAP rate oracle required.'); + const admin = await this.get('admin'); + if (admin === null) return new Error('Fresh TAP rate oracle requires a current admin key.'); + if (rate.posted_by !== admin || rate.posted_by_role !== 'admin') { + return new Error('Fresh TAP rate oracle must be admin-posted.'); + } + if (rate.ts > at) return new Error('TAP rate oracle timestamp is in the future.'); + const params = await this.activeParamsAt(at, ['rate_staleness_seconds']); + if (at - rate.ts > params.rate_staleness_seconds) { + return new Error('TAP rate oracle is stale.'); + } + return rate; + } + + async guardianRequireFreshRate(at) { + const rate = await this.requireFreshRate(at); + if (rate instanceof Error) { + return new Error(`Guardian rate freshness invariant failed: ${rate.message}`); + } + return rate; + } + + async guardianRequireHistoricalTnkRate(settlement, at) { + const oracleValue = { + op: 'rate_oracle', + tnk_usd_au: settlement.rate_tnk_usd_au, + source: settlement.rate_source, + ts: settlement.rate_ts, + }; + const key = await this.rateFeatureKey(oracleValue); + if (key instanceof Error) { + return new Error(`Guardian TNK settlement rate invariant failed: ${key.message}`); + } + const rate = await this.get(key); + if (!rate) { + return new Error('Guardian TNK settlement rate invariant failed: exact admin-posted oracle history required.'); + } + if ( + rate.denom !== 'tnk_usd_au' || + this.compareAu(rate.tnk_usd_au, settlement.rate_tnk_usd_au) !== 0 || + rate.source !== settlement.rate_source || + rate.ts !== settlement.rate_ts || + rate.updated_at !== key || + rate.posted_by_role !== 'admin' || + !this.isHexBytes(rate.posted_by, 32) + ) { + return new Error('Guardian TNK settlement rate invariant failed: oracle history is invalid.'); + } + if (rate.ts > at) { + return new Error('Guardian TNK settlement rate invariant failed: oracle timestamp is in the future.'); + } + const params = await this.activeParamsAt(at, ['rate_staleness_seconds']); + if (at - rate.ts > params.rate_staleness_seconds) { + return new Error('Guardian TNK settlement rate invariant failed: oracle is stale.'); + } + return rate; + } + + async guardianRequireHistoricalTapRateLock(lock, at) { + const oracleValue = { + op: 'tap_rate_oracle', + tap_usd_au: lock.tap_usd_au, + source: lock.source, + ts: lock.rate_ts, + }; + const key = await this.rateFeatureKey(oracleValue); + if (key instanceof Error) { + return new Error(`Guardian TAP settlement rate invariant failed: ${key.message}`); + } + if (key !== lock.rate_record_key) { + return new Error('Guardian TAP settlement rate invariant failed: rate record key mismatch.'); + } + const rate = await this.get(key); + const admin = await this.get('admin'); + if (!rate || admin === null) { + return new Error('Guardian TAP settlement rate invariant failed: exact admin-posted oracle history required.'); + } + if ( + rate.denom !== 'tap_usd_au' || + this.compareAu(rate.tap_usd_au, lock.tap_usd_au) !== 0 || + rate.source !== lock.source || + rate.ts !== lock.rate_ts || + rate.updated_at !== key || + rate.posted_by !== admin || + rate.posted_by !== lock.posted_by || + rate.posted_by_role !== 'admin' + ) { + return new Error('Guardian TAP settlement rate invariant failed: oracle history is invalid.'); + } + if (rate.ts > at) { + return new Error('Guardian TAP settlement rate invariant failed: oracle timestamp is in the future.'); + } + const params = await this.activeParamsAt(at, ['rate_staleness_seconds']); + if (at - rate.ts > params.rate_staleness_seconds) { + return new Error('Guardian TAP settlement rate invariant failed: oracle is stale.'); + } + return rate; + } + + async guardianRequireFreshTapRate(at) { + const rate = await this.requireFreshTapRate(at); + if (rate instanceof Error) { + return new Error(`Guardian TAP rate freshness invariant failed: ${rate.message}`); + } + return rate; + } + + async opaqueHash(domain, value) { + const digest = await blake3(b4a.from(stableJson({ domain, value }))); + return b4a.toString(digest, 'hex'); + } + + async depositLeafHash(value) { + return await this.opaqueHash('mayhem-deposit-leaf-v1', value); + } + + async nextDepositRoot({ epoch, leaf, au, at }) { + const current = await this.get(`ev/dep/${epoch}`); + if (current && current.type !== 'deposit_root') { + return new Error('Invalid deposit evidence root.'); + } + const count = (current?.count ?? 0) + 1; + const auTotal = this.safeAddAu(current?.au_total ?? ZERO_AU, au); + if (auTotal instanceof Error) return auTotal; + const merkleRoot = current + ? await this.opaqueHash('mayhem-deposit-root-v1', { + previous_root: current.merkle_root, + leaf, + count, + }) + : leaf; + return { + type: 'deposit_root', + epoch, + merkle_root: merkleRoot, + count, + au_total: auTotal, + ts: at, + updated_at: this.tx, + }; + } + + async nextDepositReversalRoot({ epoch, leaf, disputedAu, clawbackAu, absorbedAu, at }) { + const current = await this.get(`ev/dep/${epoch}`); + if (current && current.type !== 'deposit_root') { + return new Error('Invalid deposit evidence root.'); + } + const count = (current?.count ?? 0) + 1; + const reversedAuTotal = this.safeAddAu(current?.reversed_au_total ?? ZERO_AU, disputedAu); + if (reversedAuTotal instanceof Error) return reversedAuTotal; + const clawbackAuTotal = this.safeAddAu(current?.clawback_au_total ?? ZERO_AU, clawbackAu); + if (clawbackAuTotal instanceof Error) return clawbackAuTotal; + const networkAbsorbedAuTotal = this.safeAddAu(current?.network_absorbed_au_total ?? ZERO_AU, absorbedAu); + if (networkAbsorbedAuTotal instanceof Error) return networkAbsorbedAuTotal; + const merkleRoot = current + ? await this.opaqueHash('mayhem-deposit-root-v1', { + previous_root: current.merkle_root, + leaf, + count, + }) + : leaf; + return { + ...(current ?? { + type: 'deposit_root', + epoch, + au_total: ZERO_AU, + }), + type: 'deposit_root', + epoch, + merkle_root: merkleRoot, + count, + reversed: true, + reversal_count: (current?.reversal_count ?? 0) + 1, + reversed_au_total: reversedAuTotal, + clawback_au_total: clawbackAuTotal, + network_absorbed_au_total: networkAbsorbedAuTotal, + ts: at, + updated_at: this.tx, + }; + } + + async epochApplyHash(value) { + const digest = await blake3(b4a.from(stableJson({ + domain: 'mayhem-epoch-apply-v1', + value, + }))); + return b4a.toString(digest, 'hex'); + } + + epochEmptySealHashValue(value) { + return { + type: value.type, + epoch: value.epoch, + at: value.at, + epoch_seconds: value.epoch_seconds, + previous_apply_hash: value.previous_apply_hash ?? null, + reason_hash: value.reason_hash, + sealed_by: value.sealed_by, + sealed_by_role: value.sealed_by_role, + totals: value.totals, + ...(value.market_price_root !== undefined ? { + market_price_root: value.market_price_root, + market_price_count: value.market_price_count, + } : {}), + }; + } + + async epochEmptySealHash(value) { + return await this.opaqueHash('mayhem-epoch-empty-seal-v1', this.epochEmptySealHashValue(value)); + } + + async epochApplyFeatureKey(value) { + const shapeError = this.validateEpochApplyFeatureValue(value); + if (shapeError) return shapeError; + const digest = await blake3(b4a.from(stableJson({ + domain: 'mayhem-epoch-apply-feature-v1', + value, + }))); + return `epoch/apply/${value.epoch}/${b4a.toString(digest, 'hex')}`; + } + + async tapAccountBindingFeatureKey(value) { + const normalized = this.normalizeTapAccountBinding(value); + if (normalized instanceof Error) return normalized; + const digest = await blake3(b4a.from(tapAccountBindingMessage(normalized))); + return `tap_account/${normalized.user}/${b4a.toString(digest, 'hex')}`; + } + + tapAccountBindingKey(user, chainId, poolAddress) { + return `tap/account/${chainId}/${poolAddress.toLowerCase()}/${user}`; + } + + tapAccountAddressKey(ethereumAddress, chainId, poolAddress) { + return `tap/account-by-address/${chainId}/${poolAddress.toLowerCase()}/${ethereumAddress.toLowerCase()}`; + } + + tapDepositIdentity(value) { + return [ + value.chain_id, + value.pool_address.toLowerCase(), + value.eth_tx_hash.toLowerCase(), + value.log_index, + value.block_hash.toLowerCase(), + ].join('/'); + } + + validateTapDepositIdentity(value) { + for (const key of ['chain_id', 'pool_address', 'eth_tx_hash', 'log_index', 'block_hash']) { + if (!hasOwn(value, key)) return new Error(`TAP deposit is missing ${key}.`); + } + if (!Number.isSafeInteger(value.chain_id) || value.chain_id < 1) { + return new Error('Invalid TAP chain id.'); + } + if (!this.isEthHexBytes(value.pool_address, 20)) return new Error('Invalid TAP pool address.'); + if (!this.isEthHexBytes(value.eth_tx_hash, 32)) return new Error('Invalid Ethereum tx hash.'); + if (!Number.isSafeInteger(value.log_index) || value.log_index < 0) { + return new Error('Invalid TAP deposit log index.'); + } + if (!this.isEthHexBytes(value.block_hash, 32)) return new Error('Invalid TAP deposit block hash.'); + return null; + } + + async depositFeatureKey(value) { + if (!value || typeof value !== 'object' || Array.isArray(value)) { + return new Error('Deposit feature value must be an object.'); + } + const digest = b4a.toString( + await blake3(b4a.from(stableJson({ + domain: 'mayhem-deposit-feature-v1', + value, + }))), + 'hex' + ); + if (value.op === 'deposit_tnk' && value.intent) { + const intentError = this.validateDepositTnkIntent(value.intent); + if (intentError) return intentError; + return `dep/tnk-intent/${value.intent.memo_hash}/${digest}`; + } + if (value.op === 'tnk_deposit') { + if (!this.isSafeKeyPart(value.memo_hash)) return new Error('Invalid deposit memo hash.'); + return `dep/tnk/${value.memo_hash}/${digest}`; + } + if (value.op === 'tap_deposit') { + const validationError = this.validateTapDepositIdentity(value); + if (validationError) return validationError; + return `dep/tap/${this.tapDepositIdentity(value)}`; + } + if (value.op === 'tap_deposit_reversal') { + const validationError = this.validateTapDepositReversalValue(value); + if (validationError) return validationError; + return `dep/tap/${this.tapDepositIdentity(value)}/reversal`; + } + if (value.op === 'fiat_deposit') { + if (!this.isSafeKeyPart(value.ext_ref_hash)) return new Error('Invalid external reference hash.'); + return `dep/fiat/${value.ext_ref_hash}`; + } + if (value.op === 'fiat_chargeback') { + if (!this.isSafeKeyPart(value.ext_ref_hash)) return new Error('Invalid external reference hash.'); + if (!this.isSafeKeyPart(value.dispute_ref_hash)) return new Error('Invalid dispute reference hash.'); + return `dep/fiat/${value.ext_ref_hash}/chargeback/${value.dispute_ref_hash}`; + } + return new Error('Unsupported deposit feature op.'); + } + + async rateFeatureKey(value) { + if (!value || typeof value !== 'object' || Array.isArray(value)) { + return new Error('Rate feature value must be an object.'); + } + let kind; + if (value.op === 'rate_oracle') { + const shapeError = this.validateRateOracleValue(value); + if (shapeError) return shapeError; + kind = 'tnk'; + } else if (value.op === 'tap_rate_oracle') { + const shapeError = this.validateTapRateOracleValue(value); + if (shapeError) return shapeError; + kind = 'tap'; + } else { + return new Error('Unsupported rate feature op.'); + } + this._mayhemApplyStage = 'rate:key:hash'; + const digest = b4a.toString( + await blake3(b4a.from(stableJson({ + domain: 'mayhem-rate-feature-v1', + value, + }))), + 'hex' + ); + this._mayhemApplyStage = 'rate:key:hashed'; + return `rate/${kind}/${value.ts}/${digest}`; + } + + async targetedPayoutPreparationFeatureKey(value) { + const normalized = await this.normalizeTargetedPayoutPreparationValue(value); + if (normalized instanceof Error) return normalized; + const digest = b4a.toString( + await blake3(b4a.from(stableJson({ + domain: 'mayhem-targeted-payout-preparation-feature-v1', + value: normalized, + }))), + 'hex' + ); + return ( + `payout/preparation-submit/${normalized.rail}/` + + `${normalized.economic_op_id}/${digest}` + ); + } + + async targetedPayoutEpochFeatureKey(value) { + const normalized = await this.normalizeTargetedPayoutEpochValue(value); + if (normalized instanceof Error) return normalized; + const digest = b4a.toString( + await blake3(b4a.from(stableJson({ + domain: 'mayhem-targeted-payout-epoch-feature-v1', + value: normalized, + }))), + 'hex' + ); + return `payout/epoch-plan-submit/${normalized.rail}/${normalized.epoch}/${digest}`; + } + + async targetedFiatAttemptFeatureKey(value) { + const normalized = await this.normalizePrepareTargetedFiatAttemptValue(value); + if (normalized instanceof Error) return normalized; + const digest = b4a.toString( + await blake3(b4a.from(stableJson({ + domain: 'mayhem-targeted-fiat-attempt-feature-v1', + value: normalized, + }))), + 'hex' + ); + return ( + `payout/attempt-submit/fiat/${normalized.economic_op_id}/` + + `${normalized.attempt_id}/${digest}` + ); + } + + async finalizeTargetedFiatAttemptFeatureKey(value) { + const normalized = this.normalizeFinalizeTargetedFiatAttemptValue(value); + if (normalized instanceof Error) return normalized; + const digest = b4a.toString( + await blake3(b4a.from(stableJson({ + domain: 'mayhem-finalize-targeted-fiat-attempt-feature-v1', + value: normalized, + }))), + 'hex' + ); + return ( + `payout/attempt-finalize/fiat/${normalized.economic_op_id}/` + + `${normalized.attempt_id}/${digest}` + ); + } + + async targetedTnkOutputFeatureKey(value) { + const normalized = this.normalizeTargetedTnkOutputSettlementValue(value); + if (normalized instanceof Error) return normalized; + const digest = b4a.toString( + await blake3(b4a.from(stableJson({ + domain: 'mayhem-targeted-tnk-output-feature-v1', + value: normalized, + }))), + 'hex' + ); + return ( + `settle/targeted/tnk/${normalized.epoch}/output/` + + `${normalized.economic_op_id}/${digest}` + ); + } + + async targetedFiatOutputFeatureKey(value) { + const normalized = this.normalizeTargetedFiatOutputSettlementValue(value); + if (normalized instanceof Error) return normalized; + const digest = b4a.toString( + await blake3(b4a.from(stableJson({ + domain: 'mayhem-targeted-fiat-output-feature-v1', + value: normalized, + }))), + 'hex' + ); + return ( + `settle/targeted/fiat/${normalized.epoch}/output/` + + `${normalized.economic_op_id}/${digest}` + ); + } + + async closeTargetedPayoutEpochFeatureKey(value) { + const normalized = this.normalizeCloseTargetedPayoutEpochValue(value); + if (normalized instanceof Error) return normalized; + const digest = b4a.toString( + await blake3(b4a.from(stableJson({ + domain: 'mayhem-close-targeted-payout-epoch-feature-v1', + value: normalized, + }))), + 'hex' + ); + return `settle/targeted/${normalized.rail}/${normalized.epoch}/close/${digest}`; + } + + async targetedTnkSettlementFeatureKey(value) { + const normalized = this.normalizeTargetedTnkSettlementValue(value); + if (normalized instanceof Error) return normalized; + const digest = b4a.toString( + await blake3(b4a.from(stableJson({ + domain: 'mayhem-targeted-tnk-settlement-feature-v1', + value, + }))), + 'hex' + ); + return `settle/targeted/tnk/${value.epoch}/${digest}`; + } + + async targetedTapSettlementFeatureKey(value) { + const normalized = this.normalizeTargetedTapSettlementValue(value); + if (normalized instanceof Error) return normalized; + const digest = b4a.toString( + await blake3(b4a.from(stableJson({ + domain: 'mayhem-targeted-tap-settlement-feature-v1', + value, + }))), + 'hex' + ); + return `settle/targeted/tap/${value.epoch}/${digest}`; + } + + async targetedFiatSettlementFeatureKey(value) { + const normalized = this.normalizeTargetedFiatSettlementValue(value); + if (normalized instanceof Error) return normalized; + const digest = b4a.toString( + await blake3(b4a.from(stableJson({ + domain: 'mayhem-targeted-fiat-settlement-feature-v2', + value, + }))), + 'hex' + ); + return `settle/targeted/fiat/${value.epoch}/${digest}`; + } + + async fiatDustSweepFeatureKey(value) { + const validationError = this.validateFiatDustSweepValue(value); + if (validationError) return validationError; + const digest = b4a.toString( + await blake3(b4a.from(stableJson({ + domain: 'mayhem-fiat-dust-sweep-feature-v1', + value, + }))), + 'hex' + ); + return `settle/fiat-dust/${value.provider}/${value.epoch}/${digest}`; + } + + async reputationAnchorFeatureKey(value) { + const validationError = this.validateReputationAnchor(value); + if (validationError) return validationError; + const digest = b4a.toString( + await blake3(b4a.from(stableJson({ + domain: 'mayhem-reputation-anchor-feature-v1', + value, + }))), + 'hex' + ); + return `rep/${value.provider}/${value.epoch}/${digest}`; + } + + async tier3MeasurementFeatureKey(value) { + const validationError = this.validateTier3MeasurementBlessValue(value); + if (validationError) return validationError; + const digest = b4a.toString( + await blake3(b4a.from(stableJson({ + domain: 'mayhem-tier3-measurement-feature-v1', + value, + }))), + 'hex' + ); + return `tier3/measurement/${value.platform}/${digest}`; + } + + async epochCommitHash(value) { + const digest = await blake3(b4a.from(stableJson({ + domain: 'mayhem-epoch-commit-v1', + value, + }))); + return b4a.toString(digest, 'hex'); + } + + probePass(value, params) { + if (value.probe_kind === 'uptime_tick') return true; + return value.match_bps >= params.canary_match_min_bps; + } + + async requireAuditorEligibility(auditor, atSeconds) { + const rep = await this.get(`rep/${auditor}`); + if (!rep) return new Error('Auditor reputation snapshot required.'); + const params = await this.activeParamsAt(atSeconds, [ + 'auditor_min_reputation_bps', + 'auditor_min_age_seconds', + ]); + if (rep.provenance_violation === true) return new Error('Auditor has a provenance violation.'); + if ((rep.r_bps ?? 0) < params.auditor_min_reputation_bps) { + return new Error('Auditor reputation too low.'); + } + const sinceSeconds = rep.probation?.since_seconds ?? 0; + if (atSeconds - sinceSeconds < params.auditor_min_age_seconds) { + return new Error('Auditor account age too low.'); + } + return null; + } + + async appendReputationEvent(event) { + if (!this.isSafeKeyPart(event.event_id)) return new Error('Invalid reputation event id.'); + const key = `ev/rep/${event.provider}/${event.event_id}`; + if ((await this.get(key)) !== null) return new Error('Reputation event already recorded.'); + + const headKey = `ev/rep/head/${event.provider}`; + const currentHead = await this.get(headKey); + const body = { + ...event, + paid_au: event.paid_au !== null && event.paid_au !== undefined + ? this.normalizeAu(event.paid_au, 'reputation paid amount') + : null, + max_spend_au: event.max_spend_au !== null && event.max_spend_au !== undefined + ? this.normalizeAu(event.max_spend_au, 'reputation max spend') + : null, + evidence_hash: event.evidence_hash ?? null, + recorded_at: this.tx, + recorded_by: this.address, + }; + const head = await this.reputationEventHead(currentHead?.head ?? null, body); + const foldKey = `ev/rep/fold/${event.provider}`; + const fold = this.advanceReputationFold(await this.get(foldKey), body, head); + if (fold instanceof Error) return fold; + const record = { + ...body, + head, + }; + const headRecord = { + provider: event.provider, + head, + count: (currentHead?.count ?? 0) + 1, + updated_at: this.tx, + }; + + await this.put(key, record); + await this.put(headKey, headRecord); + await this.put(foldKey, fold); + return record; + } + + parseSignedDecimal(value, label) { + if (typeof value !== 'string' || !/^(0|-?[1-9][0-9]*)$/.test(value)) { + return new Error(`${label} must be a canonical signed decimal string.`); + } + return BigInt(value); + } + + roundSignedRatio(value, divisor) { + if (typeof value !== 'bigint' || typeof divisor !== 'bigint' || divisor <= 0n) { + return new Error('Invalid signed ratio.'); + } + const negative = value < 0n; + const absolute = negative ? -value : value; + const rounded = (absolute + divisor / 2n) / divisor; + return negative ? -rounded : rounded; + } + + quantizePositiveReputation(value, scale, label) { + const scaled = value * Number(scale); + if (!Number.isFinite(scaled) || scaled < 0 || !Number.isSafeInteger(Math.floor(scaled + 0.5))) { + return new Error(`Invalid ${label}.`); + } + return BigInt(Math.floor(scaled + 0.5)); + } + + decayReputationRawNano(rawNano, fromSeconds, toSeconds) { + if ( + typeof rawNano !== 'bigint' || + !Number.isSafeInteger(fromSeconds) || + !Number.isSafeInteger(toSeconds) || + fromSeconds < 0 || + toSeconds < fromSeconds + ) { + return new Error('Invalid reputation decay range.'); + } + if (fromSeconds === toSeconds || rawNano === 0n) return rawNano; + const decay = 2 ** (-(toSeconds - fromSeconds) / REPUTATION_HALF_LIFE_SECONDS); + const decayPico = this.quantizePositiveReputation( + decay, + REPUTATION_DECAY_PICO_SCALE, + 'reputation decay' + ); + if (decayPico instanceof Error) return decayPico; + return this.roundSignedRatio( + rawNano * decayPico, + REPUTATION_DECAY_PICO_SCALE + ); + } + + reputationEventRawNano(event) { + let scoreQuarters = null; + let weightedAu = null; + if (event.kind === 'session_ok') { + scoreQuarters = 4n; + weightedAu = event.paid_au; + } else if (event.kind === 'session_partial') { + scoreQuarters = 1n; + weightedAu = event.paid_au; + } else if (event.kind === 'session_fail') { + scoreQuarters = -16n; + weightedAu = event.max_spend_au; + } + if (scoreQuarters !== null) { + const amount = this.parseAu(weightedAu, 'reputation weighted amount'); + if (amount instanceof Error) return amount; + const weight = Math.log10(1 + Number(amount)); + const weightNano = this.quantizePositiveReputation( + weight, + REPUTATION_RAW_NANO_SCALE, + 'reputation paid weight' + ); + if (weightNano instanceof Error) return weightNano; + return this.roundSignedRatio(weightNano * scoreQuarters, 4n); + } + const fixedScores = { + probe_ok: 500_000_000n, + probe_fail: -6_000_000_000n, + uptime_tick: 100_000_000n, + underdelivery: -6_000_000_000n, + dispute_lost: -20_000_000_000n, + provenance_violation: 0n, + }; + return fixedScores[event.kind] ?? new Error('Unsupported reputation event kind.'); + } + + advanceReputationFold(current, event, eventsHead) { + if (!Number.isSafeInteger(event.at) || event.at < 0) { + return new Error('Invalid reputation event time.'); + } + if (!Number.isSafeInteger(event.epoch) || event.epoch < 0) { + return new Error('Invalid reputation event epoch.'); + } + let rawNano = 0n; + let foldAt = event.at; + let successfulSessions = 0; + let provenanceViolation = false; + let eventCount = 0; + let maxEpoch = 0; + if (current !== null) { + rawNano = this.parseSignedDecimal(current.raw_nano, 'reputation raw_nano'); + if (rawNano instanceof Error) return rawNano; + if ( + !Number.isSafeInteger(current.at) || current.at < 0 || + !Number.isSafeInteger(current.successful_sessions) || current.successful_sessions < 0 || + !Number.isSafeInteger(current.event_count) || current.event_count < 0 || + !Number.isSafeInteger(current.max_epoch) || current.max_epoch < 0 || + typeof current.provenance_violation !== 'boolean' + ) { + return new Error('Invalid reputation fold state.'); + } + foldAt = Math.max(current.at, event.at); + rawNano = this.decayReputationRawNano(rawNano, current.at, foldAt); + if (rawNano instanceof Error) return rawNano; + successfulSessions = current.successful_sessions; + provenanceViolation = current.provenance_violation; + eventCount = current.event_count; + maxEpoch = current.max_epoch; + } + let contribution = this.reputationEventRawNano(event); + if (contribution instanceof Error) return contribution; + contribution = this.decayReputationRawNano(contribution, event.at, foldAt); + if (contribution instanceof Error) return contribution; + const nextSuccessfulSessions = this.safeAddCount( + successfulSessions, + event.kind === 'session_ok' ? 1 : 0, + 'successful reputation session count' + ); + if (nextSuccessfulSessions instanceof Error) return nextSuccessfulSessions; + const nextEventCount = this.safeAddCount(eventCount, 1, 'reputation event count'); + if (nextEventCount instanceof Error) return nextEventCount; + return { + provider: event.provider, + raw_nano: (rawNano + contribution).toString(), + at: foldAt, + max_epoch: Math.max(maxEpoch, event.epoch), + successful_sessions: nextSuccessfulSessions, + provenance_violation: provenanceViolation || event.kind === 'provenance_violation', + event_count: nextEventCount, + events_head: eventsHead, + updated_at: this.tx, + }; + } + + reputationFoldAt(fold, foldedAt) { + if (!Number.isSafeInteger(foldedAt) || foldedAt < fold.at) { + return new Error('Reputation folded_at precedes the latest event.'); + } + const rawNano = this.parseSignedDecimal(fold.raw_nano, 'reputation raw_nano'); + if (rawNano instanceof Error) return rawNano; + const decayed = this.decayReputationRawNano(rawNano, fold.at, foldedAt); + if (decayed instanceof Error) return decayed; + const rawMilliValue = this.roundSignedRatio(decayed, REPUTATION_RAW_NANO_PER_MILLI); + if (rawMilliValue instanceof Error) return rawMilliValue; + const rawMilli = Number(rawMilliValue); + if (!Number.isSafeInteger(rawMilli)) return new Error('Reputation raw_milli overflow.'); + const raw = rawMilli / 1_000; + const r = 1 / (1 + Math.exp(-raw / REPUTATION_KAPPA)); + const rBps = Math.floor(r * 10_000 + 0.5); + if (!Number.isSafeInteger(rBps) || rBps < 0 || rBps > 10_000) { + return new Error('Invalid folded reputation score.'); + } + return { + raw_milli: rawMilli, + r_bps: rBps, + successful_sessions: fold.successful_sessions, + provenance_violation: fold.provenance_violation, + }; + } + + isSafeKeyPart(value) { + return typeof value === 'string' && /^[a-zA-Z0-9._:-]{1,128}$/.test(value); + } + + isSafeModelId(value) { + return typeof value === 'string' && + /^[a-zA-Z0-9._:@/+~-]{1,256}$/.test(value) && + !value.startsWith('/') && + !value.endsWith('/') && + !value.includes('//'); + } + + isSafeHuggingFaceRepo(value) { + if (typeof value !== 'string') return false; + const parts = value.split('/'); + return parts.length === 2 && + parts.every((part) => this.isSafeHuggingFaceComponent(part)); + } + + isSafeHuggingFaceComponent(value) { + return typeof value === 'string' && + /^[A-Za-z0-9][A-Za-z0-9._-]{0,95}$/.test(value) && + !value.endsWith('.') && + !value.endsWith('-') && + !value.includes('..') && + !value.includes('--'); + } + + isSafeHuggingFacePath(value) { + return typeof value === 'string' && + value.length > 0 && + !value.startsWith('/') && + !value.startsWith('\\') && + !value.includes('\\') && + !value.includes('?') && + !value.includes('#') && + !value.includes('%') && + !/[\x00-\x1f\x7f]/.test(value) && + value.split('/').every((part) => this.isSafeHuggingFacePathSegment(part)); + } + + isSafeHuggingFacePathSegment(value) { + return typeof value === 'string' && + value.length > 0 && + value !== '.' && + value !== '..' && + /^[A-Za-z0-9._+-]+$/.test(value); + } + + isHttpsUrl(value) { + if (typeof value !== 'string' || value.length === 0 || value.length > 512) return false; + try { + const parsed = new URL(value); + return parsed.protocol === 'https:' && !!parsed.hostname; + } catch { + return false; + } + } + + isPinnedHuggingFaceResolveUrl(value) { + return this.pinnedHuggingFaceResolveRevision(value) !== null; + } + + pinnedHuggingFaceResolveRevision(value) { + if (!this.isHttpsUrl(value)) return null; + const parsed = new URL(value); + if (parsed.hostname !== 'huggingface.co') return null; + const parts = parsed.pathname.split('/').filter(Boolean); + const resolveIndex = parts.indexOf('resolve'); + if (resolveIndex < 0 || resolveIndex + 2 >= parts.length) return null; + return this.isHexBytes(parts[resolveIndex + 1], 20) ? parts[resolveIndex + 1] : null; + } + + isSafeExternalRef(value) { + return typeof value === 'string' && /^[a-zA-Z0-9._:-]{1,256}$/.test(value); + } + + normalizeProviderKybValue(value) { + if (!this.isHexBytes(value.provider, 32)) return new Error('Invalid provider id.'); + const legalName = value.legal_name.trim(); + if (!legalName || /[\x00-\x1f\x7f]/.test(legalName)) { + return new Error('Invalid provider KYB legal name.'); + } + const jurisdiction = value.jurisdiction.trim().toUpperCase(); + if (!/^[A-Z0-9._:-]{1,64}$/.test(jurisdiction)) { + return new Error('Invalid provider KYB jurisdiction.'); + } + const proofHash = value.proof_hash.toLowerCase(); + if (!this.isHexBytes(proofHash, 32)) return new Error('Invalid provider KYB proof hash.'); + const kybRef = value.kyb_ref.trim(); + if (!this.isSafeExternalRef(kybRef)) return new Error('Invalid provider KYB reference.'); + const schemaVersion = value.schema_version ?? 1; + if (!Number.isInteger(schemaVersion) || schemaVersion < 1) { + return new Error('Invalid provider KYB schema version.'); + } + const adminSig = value.admin_sig.toLowerCase(); + if (!this.isHexBytes(adminSig, 64)) return new Error('Invalid provider KYB admin signature.'); + return { + provider: value.provider.toLowerCase(), + legal_name: legalName, + jurisdiction, + proof_hash: proofHash, + kyb_ref: kybRef, + verified_at: value.verified_at, + schema_version: schemaVersion, + admin_sig: adminSig, + }; + } + + isHexBytes(value, bytes) { + return typeof value === 'string' && + value.length === bytes * 2 && + /^[0-9a-fA-F]+$/.test(value); + } + + isEthHexBytes(value, bytes) { + return typeof value === 'string' && + value.length === bytes * 2 + 2 && + /^0x[0-9a-fA-F]+$/.test(value); + } + + async reputationEventHead(previousHead, event) { + const payload = stableJson({ + domain: 'mayhem-reputation-event-v1', + previous_head: previousHead, + event, + }); + const digest = await blake3(b4a.from(payload)); + return b4a.toString(digest, 'hex'); + } + + verifyConsentSignature(sender, ver, hash, sig) { + const verify = this.protocol?.peer?.wallet?.verify; + if (typeof verify !== 'function') return false; + return verify.call(this.protocol.peer.wallet, sig, consentMessage(ver, hash), sender) === true; + } + + verifyProviderLifecycleSignature(provider, intent, sig) { + const verify = this.protocol?.peer?.wallet?.verify; + if (typeof verify !== 'function') return false; + return verify.call(this.protocol.peer.wallet, sig, providerLifecycleIntentMessage(intent), provider) === true; + } + + verifyProviderPayoutBindingSignature(provider, intent, signature) { + const verify = this.protocol?.peer?.wallet?.verify; + if (typeof verify !== 'function') return false; + return verify.call( + this.protocol.peer.wallet, + signature, + providerPayoutBindingMessage(intent), + provider + ) === true; + } + + verifyProviderPayoutTargetBindingSignature(intent) { + if (intent.rail === 'fiat') return intent.target_signature === null; + if (intent.rail === 'tnk') { + const verify = this.protocol?.peer?.wallet?.verify; + if (typeof verify !== 'function') return false; + return verify.call( + this.protocol.peer.wallet, + intent.target_signature, + providerPayoutTargetBindingMessage(intent), + intent.target_wallet + ) === true; + } + if (intent.rail !== 'tap') return false; + try { + const bytes = b4a.from(intent.target_signature.slice(2), 'hex'); + let recovery = bytes[64]; + if (recovery === 27 || recovery === 28) recovery -= 27; + if (recovery !== 0 && recovery !== 1) return false; + const signature = secp256k1.Signature + .fromCompact(bytes.subarray(0, 64)) + .addRecoveryBit(recovery); + if (signature.hasHighS()) return false; + const publicKey = signature + .recoverPublicKey( + ethereumPersonalMessageHash(providerPayoutTargetBindingMessage(intent)) + ) + .toRawBytes(false); + return ethereumAddressFromPublicKey(publicKey) === intent.target; + } catch { + return false; + } + } + + verifyDepositTnkSignature(sender, intent, sig) { + const verify = this.protocol?.peer?.wallet?.verify; + if (typeof verify !== 'function') return false; + return verify.call(this.protocol.peer.wallet, sig, depositTnkIntentMessage(intent), sender) === true; + } + + verifyTapAccountUserSignature(value) { + const verify = this.protocol?.peer?.wallet?.verify; + if (typeof verify !== 'function') return false; + return verify.call( + this.protocol.peer.wallet, + value.user_sig, + tapAccountBindingMessage(value), + value.user + ) === true; + } + + verifyTapAccountEthereumSignature(value) { + try { + const bytes = b4a.from(value.ethereum_sig.slice(2), 'hex'); + let recovery = bytes[64]; + if (recovery === 27 || recovery === 28) recovery -= 27; + if (recovery !== 0 && recovery !== 1) return false; + const signature = secp256k1.Signature + .fromCompact(bytes.subarray(0, 64)) + .addRecoveryBit(recovery); + if (signature.hasHighS()) return false; + const publicKey = signature + .recoverPublicKey(ethereumPersonalMessageHash(tapAccountBindingMessage(value))) + .toRawBytes(false); + return ethereumAddressFromPublicKey(publicKey) === value.ethereum_address.toLowerCase(); + } catch { + return false; + } + } + + verifySpendVoucherSignature(user, body, sig) { + const verify = this.protocol?.peer?.wallet?.verify; + if (typeof verify !== 'function') return false; + return verify.call(this.protocol.peer.wallet, sig, spendVoucherMessage(body), user) === true; + } + + verifySpendReservationSignature(provider, value) { + const verify = this.protocol?.peer?.wallet?.verify; + if (typeof verify !== 'function') return false; + return verify.call(this.protocol.peer.wallet, value.provider_sig, spendReservationMessage(value), provider) === true; + } + + verifyProbeResultSignature(auditor, value) { + const verify = this.protocol?.peer?.wallet?.verify; + if (typeof verify !== 'function') return false; + return verify.call(this.protocol.peer.wallet, value.auditor_sig, probeResultMessage(value, auditor), auditor) === true; + } + + async verifyProviderKybSignature(value) { + const admin = await this.get('admin'); + const verify = this.protocol?.peer?.wallet?.verify; + if (typeof admin !== 'string' || typeof verify !== 'function') return false; + return verify.call(this.protocol.peer.wallet, value.admin_sig, providerKybMessage(value), admin) === true; + } +} + +export default MayhemContract; diff --git a/intercom/contract/release.json b/intercom/contract/release.json index bb83f23f..86857f62 100644 --- a/intercom/contract/release.json +++ b/intercom/contract/release.json @@ -1,12 +1,12 @@ { "schema": 1, - "release_version": "0.2.197", - "contract_version": 25, - "contract_code_sha256": "cf2d19a4a4d36d7f08af86ee01fbbf90ad3734ef5b9ab7075445768f557d599c", + "release_version": "0.2.262", + "contract_version": 28, + "contract_code_sha256": "15bdb5d131993e499fdab5c601245237a32c5eadacc6a52d010be5ecca45eb98", "files": [ { "path": "contract/contract.js", - "sha256": "70f956c9163f6cfaea90a31197204785e74f54562b16cca816ee9faacbd2a087" + "sha256": "61615e2185e1ab7468991e455576cb4afd4b65828124feb5b8b606fede174981" }, { "path": "contract/history/v23.js", @@ -16,13 +16,25 @@ "path": "contract/history/v24.js", "sha256": "695c8010aa8f61fcedeb277f4251f0c6bd670bdb1c2a133b2da175900f45dd08" }, + { + "path": "contract/history/v25.js", + "sha256": "31d570bd8ab6e89b469e67f1d035f1cb943ff99ded8ed7824b490841ed733a4a" + }, + { + "path": "contract/history/v26.js", + "sha256": "ebf21e882b387d83b8dd85011b3800eecea2b4c22e418ad4bef1fc9a6157b33c" + }, + { + "path": "contract/history/v27.js", + "sha256": "72435496d4b37ebde029349369ac15d3072defb0d35348f5d91006e810e63d2f" + }, { "path": "contract/protocol.js", "sha256": "2ea38037623ddb799222f6958835fcd2c5a45de6ef1dcbac3dfe3c6e2449906b" }, { "path": "features/mayhem/index.js", - "sha256": "89fe96672e71e9df3c4cfcf275addd0f69b75a8518e6cef51c2941fc4028db9f" + "sha256": "10f189b71763014d77bd100506b3596c8aad1b49a01d160d32ab60f9b0af53af" }, { "path": "trac/trac-peer/src/artifacts/contract.js", diff --git a/intercom/features/mayhem/index.js b/intercom/features/mayhem/index.js index 62e62ffa..48ec56e9 100644 --- a/intercom/features/mayhem/index.js +++ b/intercom/features/mayhem/index.js @@ -369,12 +369,13 @@ const serviceParticipantFor = (service, value) => { return null; }; -const relayError = (message, requestId = null) => ({ +const relayError = (message, requestId = null, phase = null) => ({ ok: false, accepted: false, status: 'rejected', relayed: true, request_id: requestId, + ...(phase ? { phase } : {}), message, }); @@ -735,12 +736,18 @@ class MayhemFeature extends Feature { let connected = false; let sent = false; + let connectFailurePhase = 'transport_unavailable'; let attemptInFlight = false; const attempt = async () => { if (attemptInFlight || !pending.has(requestId)) return; attemptInFlight = true; try { - if (!(await this._connectAdminTransport(admin, sidechannel))) return; + if (!(await this._connectAdminTransport(admin, sidechannel))) { + connectFailurePhase = sidechannel + .directConnectFailure?.(admin, this.channel) + ?.phase ?? connectFailurePhase; + return; + } connected = true; if (sidechannel.broadcast(this.channel, message)) sent = true; } catch (_error) { @@ -776,7 +783,12 @@ class MayhemFeature extends Feature { : !sent ? unsentMessage : timeoutMessage; - current.resolve(relayError(messageText, requestId)); + const phase = !connected + ? connectFailurePhase + : !sent + ? 'request_send' + : 'admin_ack'; + current.resolve(relayError(messageText, requestId, phase)); }, this.timeoutMs) : null; void attempt(); @@ -1857,13 +1869,28 @@ class MayhemFeature extends Feature { ); } if (this.stopped && response?.status === 'pending') { - return relayError('Mayhem feature relay stopped before the canonical result appeared.', requestId); + return { + ...response, + ok: false, + accepted: true, + status: 'pending', + relayed: true, + request_id: requestId, + phase: 'admin_ack', + message: 'Mayhem feature relay stopped after accepting the append but before the canonical result appeared.', + }; } if (response?.status === 'pending') { - return relayError( - 'Mayhem feature relay accepted the append but no canonical result appeared before the relay result budget.', - requestId - ); + return { + ...response, + ok: false, + accepted: true, + status: 'pending', + relayed: true, + request_id: requestId, + phase: 'admin_ack', + message: 'Mayhem feature relay accepted the append but no canonical result appeared before the relay result budget.', + }; } return response; } diff --git a/intercom/features/sidechannel/index.js b/intercom/features/sidechannel/index.js index 75b89c11..16f1ec8f 100644 --- a/intercom/features/sidechannel/index.js +++ b/intercom/features/sidechannel/index.js @@ -20,6 +20,9 @@ const DEFAULT_ANNOUNCE_RETRY_DELAY_MS = 1_000; const MAX_ANNOUNCE_RETRY_DELAY_MS = 30_000; const DEFAULT_DIRECT_CONNECT_MAX_WAIT_MS = 120_000; const DEFAULT_DIRECT_CONNECT_POLL_MS = 100; +const DEFAULT_DIRECT_RECOVERY_HEALTH_TIMEOUT_MS = 2_000; +const DEFAULT_DIRECT_RECOVERY_BACKOFF_MS = 250; +const MAX_DIRECT_CONNECT_FAILURES = 256; const DEFAULT_MAX_CHANNELS = 1024; const DEFAULT_MAX_CHANNEL_NAME_BYTES = 256; const DEFAULT_CHANNEL_OPEN_TIMEOUT_MS = 120_000; @@ -98,6 +101,8 @@ class Sidechannel extends Feature { this.rateLimits = new Map(); this.preparedConnections = new WeakSet(); this.closedConnections = new WeakSet(); + this.directRecoveries = new Map(); + this.directConnectFailures = new Map(); this.started = false; this._startPromise = null; this._startGeneration = 0; @@ -144,6 +149,16 @@ class Sidechannel extends Feature { DEFAULT_DIRECT_CONNECT_POLL_MS, { min: 1 } ); + this.directRecoveryHealthTimeoutMs = safeIntegerOr( + config.directRecoveryHealthTimeoutMs, + DEFAULT_DIRECT_RECOVERY_HEALTH_TIMEOUT_MS, + { min: 1 } + ); + this.directRecoveryBackoffMs = safeIntegerOr( + config.directRecoveryBackoffMs, + DEFAULT_DIRECT_RECOVERY_BACKOFF_MS, + { min: 1 } + ); this.maxMessageBytes = Number.isSafeInteger(config.maxMessageBytes) && config.maxMessageBytes > 0 ? config.maxMessageBytes : 1_000_000; @@ -1658,11 +1673,119 @@ class Sidechannel extends Feature { return false; } + _directConnectFailureKey(remote, channel) { + return `${normalizeKeyHex(remote)}\u0000${normalizeChannel(channel)}`; + } + + _setDirectConnectFailure(remote, channel, phase) { + const key = this._directConnectFailureKey(remote, channel); + if (!phase) { + this.directConnectFailures.delete(key); + return; + } + if (!this.directConnectFailures.has(key) + && this.directConnectFailures.size >= MAX_DIRECT_CONNECT_FAILURES) { + this.directConnectFailures.delete(this.directConnectFailures.keys().next().value); + } + this.directConnectFailures.set(key, { + phase, + at: this._now(), + }); + } + + directConnectFailure(remote, channel) { + return this.directConnectFailures.get( + this._directConnectFailureKey(remote, channel) + ) ?? null; + } + + _connectionsForDirectPeer(remote) { + const target = normalizeKeyHex(remote); + if (!target) return []; + const swarmConnections = this.peer?.swarm?.connections + ? Array.from(this.peer.swarm.connections) + : []; + return swarmConnections.filter((connection) => ( + this._getRemoteKey(connection) === target + && connection?.destroyed !== true + && connection?.closed !== true + )); + } + + async _recoverUnresponsiveDirectPeer(remote, entry) { + const target = normalizeKeyHex(remote); + const existing = this.directRecoveries.get(target); + if (existing) return await existing; + + let recovery = null; + recovery = (async () => { + if (this._directPeerChannelReady(target, entry.name)) return 'connected'; + const connections = this._connectionsForDirectPeer(target); + if (connections.length === 0) { + this._setDirectConnectFailure(target, entry.name, 'transport_unavailable'); + return 'unavailable'; + } + const directSession = this.peer?.directSession; + if (typeof directSession?.proveConnection !== 'function') { + this._setDirectConnectFailure(target, entry.name, 'health_proof_unavailable'); + return 'health_unavailable'; + } + + const proofs = await Promise.allSettled(connections.map((connection) => ( + directSession.proveConnection(connection, this.directRecoveryHealthTimeoutMs) + ))); + if (proofs.some((proof) => proof.status === 'fulfilled')) { + this._setDirectConnectFailure(target, entry.name, 'protocol_incompatible'); + return 'healthy_transport'; + } + if (this._directPeerChannelReady(target, entry.name)) return 'connected'; + + const stillCurrent = new Set(this._connectionsForDirectPeer(target)); + const dead = connections.filter((connection) => stillCurrent.has(connection)); + const unprobed = Array.from(stillCurrent).filter( + (connection) => !connections.includes(connection) + ); + if (unprobed.length > 0) { + this._setDirectConnectFailure(target, entry.name, 'transport_changed'); + return 'changed'; + } + for (const connection of dead) { + this._dropConnection(connection); + try { + connection.destroy?.(); + } catch (_error) {} + } + + const key = b4a.from(target, 'hex'); + try { + this.peer.swarm.leavePeer?.(key); + } catch (_error) {} + await new Promise((resolve) => setTimeout(resolve, this.directRecoveryBackoffMs)); + try { + this.peer.swarm.joinPeer(key); + } catch (_error) { + this._setDirectConnectFailure(target, entry.name, 'transport_rejoin_failed'); + return 'rejoin_failed'; + } + this._setDirectConnectFailure(target, entry.name, 'transport_recovering'); + return 'recovering'; + })().finally(() => { + if (this.directRecoveries.get(target) === recovery) { + this.directRecoveries.delete(target); + } + }); + this.directRecoveries.set(target, recovery); + return await recovery; + } + async connectDirectPeer(remote, channel, waitMs = 15_000) { const target = normalizeKeyHex(remote); const entry = this._registerChannel(channel); if (!target || !entry || typeof this.peer?.swarm?.joinPeer !== 'function') return false; - if (this._directPeerChannelReady(target, entry.name)) return true; + if (this._directPeerChannelReady(target, entry.name)) { + this._setDirectConnectFailure(target, entry.name, null); + return true; + } try { this.peer.swarm.joinPeer(b4a.from(target, 'hex')); @@ -1681,9 +1804,13 @@ class Sidechannel extends Feature { this._openChannelForConnection(connection, entry); } } - if (this._directPeerChannelReady(target, entry.name)) return true; + if (this._directPeerChannelReady(target, entry.name)) { + this._setDirectConnectFailure(target, entry.name, null); + return true; + } await new Promise((resolve) => setTimeout(resolve, this.directConnectPollMs)); } + await this._recoverUnresponsiveDirectPeer(target, entry); return false; } @@ -1990,7 +2117,9 @@ class Sidechannel extends Feature { if (!connection || this.closedConnections.has(connection) || this._isBlocked(connection)) return; if (!this.preparedConnections.has(connection)) { this.preparedConnections.add(connection); - connection.on('close', () => this._dropConnection(connection)); + for (const event of ['error', 'end', 'close']) { + connection.on(event, () => this._dropConnection(connection)); + } } for (const entry of this.channels.values()) { this._openChannelForConnection(connection, entry); @@ -2029,6 +2158,8 @@ class Sidechannel extends Feature { } for (const connection of this.connections.keys()) this._dropConnection(connection); this.connections.clear(); + this.directRecoveries.clear(); + this.directConnectFailures.clear(); this.rateLimits.clear(); this.relaySourceLimits.clear(); } diff --git a/intercom/package-lock.json b/intercom/package-lock.json index 8a7b375e..a4f29cda 100644 --- a/intercom/package-lock.json +++ b/intercom/package-lock.json @@ -1,12 +1,12 @@ { "name": "mayhem-intercom", - "version": "0.2.46", + "version": "0.2.47", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "mayhem-intercom", - "version": "0.2.46", + "version": "0.2.47", "dependencies": { "@tracsystems/blake3": "^0.0.15", "b4a": "^1.6.7", @@ -18,6 +18,7 @@ "ethereum-cryptography": "^2.2.1", "fs": "npm:bare-node-fs", "fs-native-extensions": "1.5.0", + "hyperdht": "6.29.6", "hyperschema": "1.20.0", "path": "npm:bare-node-path", "pear-runtime": "1.3.1", @@ -2079,9 +2080,9 @@ } }, "node_modules/hyperdht": { - "version": "6.32.0", - "resolved": "https://registry.npmjs.org/hyperdht/-/hyperdht-6.32.0.tgz", - "integrity": "sha512-Y/SibEN7wue0E8ydh8lx9IX7SOE9o00b6tufPA7hRxAafXsisWUBrW36fIHdyxg148T4Z3olrooOGZDZ89LYag==", + "version": "6.29.6", + "resolved": "https://registry.npmjs.org/hyperdht/-/hyperdht-6.29.6.tgz", + "integrity": "sha512-bMMtw02fhiALdsTVLNZ/VFmMZuEY8kxb0/Rprl6oYF4/TCS653i3FDjtJeRbqtnAzwAj6dVZ/EwhoNOSCFGaYQ==", "license": "MIT", "dependencies": { "@hyperswarm/secret-stream": "^6.6.2", @@ -2089,11 +2090,10 @@ "bare-events": "^2.2.0", "blind-relay": "^1.3.0", "bogon": "^1.0.0", - "compact-encoding": "^3.0.0", + "compact-encoding": "^2.4.1", "dht-rpc": "^6.15.1", "hypercore-crypto": "^3.3.0", "hypercore-id-encoding": "^1.2.0", - "hyperdht-address": "^1.0.1", "noise-curve-ed": "^2.0.0", "noise-handshake": "^4.0.0", "record-cache": "^1.1.1", @@ -2108,46 +2108,6 @@ "hyperdht": "bin.js" } }, - "node_modules/hyperdht-address": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/hyperdht-address/-/hyperdht-address-1.1.1.tgz", - "integrity": "sha512-Mu/+7SW2cwvHxMXswa5mOrhrS5BJyntViAuB25k8d8wNtA8eAPs4LaIq6TwN1SS/KQY02h1r+gM8cQeN/k360w==", - "license": "Apache-2.0", - "dependencies": { - "compact-encoding": "^3.0.0", - "hyperschema": "^1.20.1" - } - }, - "node_modules/hyperdht-address/node_modules/compact-encoding": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/compact-encoding/-/compact-encoding-3.3.0.tgz", - "integrity": "sha512-e64XyzlBvTRJ3iScuU/U2w25Dglkm7fhrRbrGaHIwuTlNnzjRKMaQBCVl7mJRvZg7wI5a9wX1IVTXCuaAANNkw==", - "license": "Apache-2.0", - "dependencies": { - "b4a": "^1.3.0" - } - }, - "node_modules/hyperdht-address/node_modules/hyperschema": { - "version": "1.21.0", - "resolved": "https://registry.npmjs.org/hyperschema/-/hyperschema-1.21.0.tgz", - "integrity": "sha512-lEnIbLTUQf7w6FU6X+R3yUJhBwCzF4ewRnAaYOrPdcVWe1rbOf94PzMiXb5pBKxroCXzlrPLxVujxAsTrrHc8g==", - "license": "Apache-2.0", - "dependencies": { - "bare-fs": "^4.0.1", - "compact-encoding": "^3.0.0", - "generate-object-property": "^2.0.0", - "generate-string": "^1.0.1" - } - }, - "node_modules/hyperdht/node_modules/compact-encoding": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/compact-encoding/-/compact-encoding-3.3.0.tgz", - "integrity": "sha512-e64XyzlBvTRJ3iScuU/U2w25Dglkm7fhrRbrGaHIwuTlNnzjRKMaQBCVl7mJRvZg7wI5a9wX1IVTXCuaAANNkw==", - "license": "Apache-2.0", - "dependencies": { - "b4a": "^1.3.0" - } - }, "node_modules/hyperdrive": { "version": "13.3.3", "resolved": "https://registry.npmjs.org/hyperdrive/-/hyperdrive-13.3.3.tgz", @@ -3402,7 +3362,7 @@ "hyperbee": "2.26.5", "hypercore": "11.18.3", "hypercore-crypto": "3.6.1", - "hyperdht": "6.27.0", + "hyperdht": "6.29.6", "hyperschema": "1.17.1", "hyperswarm": "4.14.2", "lodash": "^4.18.1", @@ -3613,36 +3573,6 @@ "sodium-universal": "^5.0.0" } }, - "node_modules/trac-msb/node_modules/hyperdht": { - "version": "6.27.0", - "resolved": "https://registry.npmjs.org/hyperdht/-/hyperdht-6.27.0.tgz", - "integrity": "sha512-ZOXPSpsphn83hBxfFcZKNfXcgLLZH3HBWM2G0TAT0vl0hrBY287oEJ/2Nj0NimgxizpzA4J4t8wFsp/LSLs6Sw==", - "license": "MIT", - "dependencies": { - "@hyperswarm/secret-stream": "^6.6.2", - "b4a": "^1.3.1", - "bare-events": "^2.2.0", - "blind-relay": "^1.3.0", - "bogon": "^1.0.0", - "compact-encoding": "^2.4.1", - "compact-encoding-net": "^1.0.1", - "dht-rpc": "^6.15.1", - "hypercore-crypto": "^3.3.0", - "hypercore-id-encoding": "^1.2.0", - "noise-curve-ed": "^2.0.0", - "noise-handshake": "^4.0.0", - "record-cache": "^1.1.1", - "safety-catch": "^1.0.1", - "signal-promise": "^1.0.3", - "sodium-universal": "^5.0.1", - "streamx": "^2.16.1", - "unslab": "^1.3.0", - "xache": "^1.1.0" - }, - "bin": { - "hyperdht": "bin.js" - } - }, "node_modules/trac-msb/node_modules/hyperswarm": { "version": "4.14.2", "resolved": "https://registry.npmjs.org/hyperswarm/-/hyperswarm-4.14.2.tgz", @@ -3763,7 +3693,7 @@ "hyperbee": "^2.24.2", "hypercore": "11.8.3", "hypercore-crypto": "^3.4.0", - "hyperdht": "^6.20.5", + "hyperdht": "6.29.6", "hyperswarm": "^4.11.5", "inspector": "npm:bare-node-inspector", "is-options": "1.0.2", @@ -3785,7 +3715,7 @@ "timers": "npm:bare-node-timers", "tls": "npm:bare-node-tls", "trac-crypto-api": "^0.1.5", - "trac-msb": "^0.2.21", + "trac-msb": "file:../msb", "trac-wallet": "^1.0.4", "url": "npm:bare-node-url", "util": "npm:bare-node-util", diff --git a/intercom/package.json b/intercom/package.json index 71929560..efa02c3f 100644 --- a/intercom/package.json +++ b/intercom/package.json @@ -1,6 +1,6 @@ { "name": "mayhem-intercom", - "version": "0.2.46", + "version": "0.2.47", "type": "module", "main": "src/main.js", "scripts": { @@ -36,6 +36,7 @@ "ethereum-cryptography": "^2.2.1", "fs": "npm:bare-node-fs", "fs-native-extensions": "1.5.0", + "hyperdht": "6.29.6", "hyperschema": "1.20.0", "path": "npm:bare-node-path", "pear-runtime": "1.3.1", diff --git a/intercom/scripts/market-sim.mjs b/intercom/scripts/market-sim.mjs index f44fc76b..5dfa3ed1 100644 --- a/intercom/scripts/market-sim.mjs +++ b/intercom/scripts/market-sim.mjs @@ -9,35 +9,32 @@ const DEFAULTS = Object.fromEntries(Object.entries(contractParamDefinitions()).m export const marketConstants = () => new MayhemContract({}, {}).marketPriceConstants(DEFAULTS); const bpsDelta = (a,b) => Number((a>b?a-b:b-a)*10000n/b); -// Exogenous settled-work scenarios. Dollars/provider counts are varied only as -// evidence labels, never supplied to the contract's activity math. +// Exogenous signed slot-utilization scenarios. Dollars/provider counts are +// varied only as evidence labels, never supplied to the controller. export function runMarketSimulation() { const c = new MayhemContract({}, {}); const constants = marketConstants(); const definitions = { - stable: (e) => 1000n, - rise: (e) => e < 20 ? 1000n : 2000n, - fall: (e) => e < 20 ? 1000n : 500n, - empty: (e) => e < 20 ? 1000n : 0n, - one_provider: (e) => e < 20 ? 1000n : e < 40 ? 2000n : 500n, - adversarial_spike: (e) => e === 20 ? 1000000000000n : 1000n, - phantom_supply: (e) => 1000n, - spend_spike: (e) => 1000n, + stable: () => 5_000, + rise: (e) => e < 20 ? 5_000 : 8_500, + fall: (e) => e < 20 ? 5_000 : 1_500, + empty: (e) => e < 20 ? 5_000 : 0, + one_provider: (e) => e < 20 ? 5_000 : e < 40 ? 8_500 : 1_500, + adversarial_spike: (e) => e === 20 ? 10_000 : 5_000, + phantom_supply: () => 5_000, + spend_spike: () => 5_000, }; const scenarios = {}; - for (const [name, work] of Object.entries(definitions)) { - let price = SEED, ema = null, previousActivity = null; + for (const [name, utilization] of Object.entries(definitions)) { + let price = SEED; const rows = []; for (let epoch=1;epoch<=120;epoch++) { - const activity = work(epoch).toString(); - const multiplier = previousActivity === null ? 10000 : c.marketActivityMomentum(activity,previousActivity,constants); - previousActivity = activity; + const utilizationBps = utilization(epoch); + const multiplier = c.marketUtilizationMultiplier(utilizationBps); const previous = price; - const desired = c.scalePriceTerm(price.toString(),multiplier); - price = BigInt(c.stepPriceTerm(price.toString(),desired,constants)); + price = BigInt(c.scalePriceTerm(price.toString(), multiplier)); price = price < SEED/4n ? SEED/4n : price > SEED*4n ? SEED*4n : price; - ema = ema === null ? activity : c.marketActivityEma(ema,activity,constants); - rows.push({epoch,activity,momentum_bps:multiplier,ema_activity:ema,price_au:price.toString(), - step_bps:bpsDelta(price,previous),frozen:epoch===1, + rows.push({epoch,utilization_bps:utilizationBps,multiplier_bps:multiplier, + price_au:price.toString(),step_bps:bpsDelta(price,previous), provider_count:name==='one_provider'?1:name==='phantom_supply'&&epoch===20?1000000:2, settled_gross_au:name==='spend_spike'&&epoch===20?'1000000000000000000000':'100'}); } @@ -46,32 +43,32 @@ export function runMarketSimulation() { scenarios[name]={name,epochs:rows.length,rows,summary:{ final_price_au:price.toString(),max_step_bps:Math.max(...rows.map(r=>r.step_bps)), min_price_au:amounts.reduce((a,b)=>aa>b?a:b).toString(), - last_price_range_bps:bpsDelta(max,min),frozen_epochs:1, + last_price_range_bps:bpsDelta(max,min), }}; } - return {controller:'Settled activity momentum v2',seed_price_au:SEED.toString(),constants,scenarios}; + return {controller:'Signed slot utilization v3',seed_price_au:SEED.toString(),constants,scenarios}; } export function validateMarketSimulation(report) { const failures=[]; for(const s of Object.values(report.scenarios)) { - if(s.summary.max_step_bps>report.constants.max_step_bps)failures.push(s.name+': step exceeded'); + if(s.summary.max_step_bps>report.constants.price_step_bps)failures.push(s.name+': step exceeded'); if(BigInt(s.summary.min_price_au)SEED*4n)failures.push(s.name+': hard band exceeded'); if(s.summary.last_price_range_bps>350)failures.push(s.name+': final activity did not settle'); } for(const name of ['phantom_supply','spend_spike']) if(report.scenarios[name].rows.some(r=>r.price_au!==SEED.toString())) failures.push(name+': non-work input moved price'); - if(BigInt(report.scenarios.rise.rows[19].price_au)<=SEED)failures.push('rising work did not raise price'); - if(BigInt(report.scenarios.fall.rows[19].price_au)>=SEED)failures.push('falling work did not lower price'); + if(BigInt(report.scenarios.rise.rows[19].price_au)<=SEED)failures.push('high utilization did not raise price'); + if(BigInt(report.scenarios.fall.rows[19].price_au)>=SEED)failures.push('low utilization did not lower price'); return {ok:failures.length===0,failures}; } export function formatMarketSimulationMarkdown(report) { const validation=validateMarketSimulation(report); - return ['# Settled activity momentum simulation','', + return ['# Signed slot utilization simulation','', `Validation: ${validation.ok?'PASS':'FAIL'}`,'', - 'Aggregate work is compared with the immediately previous epoch. EMA is telemetry only. Initialized empty epochs keep decreasing toward the lower band. Provider counts and settled spend do not enter the controller.','', + 'Each epoch uses absolute signed slot utilization. At or above 80% the price rises 10%; at or below 20% it falls 10%; the middle band holds. Empty epochs keep decreasing toward the lower band. Provider counts and settled spend do not choose direction.','', '```json',JSON.stringify(report.constants,null,2),'```','', '| Scenario | Final price / seed | Maximum step (bps) |','| --- | ---: | ---: |', ...Object.values(report.scenarios).map(s=>`| ${s.name} | ${Number(BigInt(s.summary.final_price_au)*10000n/SEED)/10000} | ${s.summary.max_step_bps} |`), - '', 'A one-epoch spike is bounded; momentum pricing does not promise a return to a seed price or a dollar revenue target.',''].join('\n'); + '', 'A one-epoch spike moves one step. Later mid-band epochs hold that price; no dollar revenue target or previous-hour comparison is used.',''].join('\n'); } if (process.argv[1] && path.resolve(process.argv[1])===fileURLToPath(import.meta.url)) { const report=runMarketSimulation();const markdown=formatMarketSimulationMarkdown(report); diff --git a/intercom/scripts/msb-reader-catchup.mjs b/intercom/scripts/msb-reader-catchup.mjs new file mode 100644 index 00000000..b696351b --- /dev/null +++ b/intercom/scripts/msb-reader-catchup.mjs @@ -0,0 +1,17 @@ +/** + * Wait until a reused read-only MSB store reaches the caller's durable cursor. + * Opening the store only proves that it is readable; its signed length may + * still be nonzero and far behind the canonical writer after a long stop. + */ +export async function waitForMinimumSignedLength(state, { + minimumSignedLength, + timeoutSec, + sleepImpl, +}) { + let signedLength = state.getSignedLength(); + for (let waited = 0; signedLength < minimumSignedLength && waited < timeoutSec; waited += 1) { + await sleepImpl(1000); + signedLength = state.getSignedLength(); + } + return signedLength; +} diff --git a/intercom/scripts/prepare-market-activity-upgrade.mjs b/intercom/scripts/prepare-market-activity-upgrade.mjs index f7a98517..ffa18944 100644 --- a/intercom/scripts/prepare-market-activity-upgrade.mjs +++ b/intercom/scripts/prepare-market-activity-upgrade.mjs @@ -1,71 +1,108 @@ #!/usr/bin/env node -// Offline only: read canonical state exports, validate coverage, emit unsigned admin commands. +// Offline only: read a canonical state export and emit unsigned v28 migration commands. import fs from 'node:fs'; import path from 'node:path'; import crypto from 'node:crypto'; -import {fileURLToPath} from 'node:url'; +import { fileURLToPath } from 'node:url'; import assert from 'node:assert/strict'; -import MayhemContract from '../contract/contract.js'; -const compare = (a,b) => ab?1:0; +const compare = (a, b) => a < b ? -1 : a > b ? 1 : 0; + export function catalogActivityInventory(catalog) { return catalog.models.map((row) => ({ - model_id: row.model_id, model_class: row.model_class, - rate_units: (row.price_ref_au?.rate_map?.map((r)=>r.unit) ?? - (row.model_class==='text-generation'?['input_token','output_token']:[])).sort(compare), - mode: row.activity_calibration ? 'calibrated_work_v1' : 'relative_dimension_vector_v1', - calibration: row.activity_calibration ?? null, - })).sort((a,b)=>compare(a.model_id,b.model_id)); + model_id: row.model_id, + model_class: row.model_class, + rate_units: (row.price_ref_au?.rate_map?.map((rate) => rate.unit) ?? + (row.model_class === 'text-generation' + ? ['input_token', 'output_token'] + : (row.model_class === 'embedding' ? ['input_token'] : []))) + .sort(compare), + activity_basis: 'signed_slot_time_v1', + })).sort((left, right) => compare(left.model_id, right.model_id)); } -export function prepareMarketActivityUpgrade(snapshot, overrides = {}) { - assert(Number.isSafeInteger(snapshot.at) && snapshot.at>=0,'canonical snapshot at is required'); + +export function prepareMarketActivityUpgrade(snapshot) { + assert(Number.isSafeInteger(snapshot.at) && snapshot.at >= 0, + 'canonical snapshot at is required'); assert(snapshot.epoch_apply_state && snapshot.epoch_apply_state.pending_epoch == null, 'finish the prior-version paged epoch before preparing an upgrade'); - assert(Array.isArray(snapshot.pending_price_commits) && snapshot.pending_price_commits.length===0, - 'resolve all prior-version nonempty price-root commitments before upgrade; legacy AU proofs must not be reinterpreted'); + assert(Array.isArray(snapshot.pending_price_commits) && snapshot.pending_price_commits.length === 0, + 'resolve all prior-version nonempty price-root commitments before upgrade; old price proofs must not be reinterpreted'); assert(snapshot.modelrefs && snapshot.enclaves && Array.isArray(snapshot.prices), 'canonical modelrefs/enclaves maps and complete prices schedule array are required'); - const c=new MayhemContract({},{}); const markets=[];const seen=new Set(); - const inventory=[]; - for(const schedule of snapshot.prices) { - const current=schedule.pending?.effective_at<=snapshot.at?schedule.pending:schedule.current; - if(!current)continue; - const enclave=snapshot.enclaves[current.enclave_id]; - if(enclave?.status!=='active')continue; - assert.equal(current.model_id,enclave.model_id,'price/enclave model mismatch'); - assert.equal((current.seed??current).set_by_role,'admin','active price lacks admin provenance'); - const ref=snapshot.modelrefs[enclave.model_id];assert(ref,'active market modelref missing'); - const key=current.enclave_id+'/'+(current.ctx_bracket??'base');assert(!seen.has(key),'duplicate active schedule');seen.add(key); - markets.push({enclave_id:current.enclave_id,...(current.ctx_bracket?{ - ctx_bracket:current.ctx_bracket,ctx_bracket_table_ver:current.ctx_bracket_table_ver}:{} )}); + + const markets = []; + const seen = new Set(); + const inventory = Object.entries(snapshot.modelrefs) + .sort(([left], [right]) => compare(left, right)) + .map(([modelId, ref]) => ({ + model_id: modelId, + model_class: ref.model_class, + rate_units: ref.rate_map.map((rate) => rate.unit).sort(compare), + activity_basis: 'signed_slot_time_v1', + })); + + for (const schedule of snapshot.prices) { + const current = schedule.pending?.effective_at <= snapshot.at + ? schedule.pending + : schedule.current; + if (!current) continue; + const enclave = snapshot.enclaves[current.enclave_id]; + if (enclave?.status !== 'active') continue; + assert.equal(current.model_id, enclave.model_id, 'price/enclave model mismatch'); + assert.equal((current.seed ?? current).set_by_role, 'admin', + 'active price lacks admin provenance'); + assert(snapshot.modelrefs[enclave.model_id], 'active market modelref missing'); + const key = `${current.enclave_id}/${current.ctx_bracket ?? 'base'}`; + assert(!seen.has(key), 'duplicate active schedule'); + seen.add(key); + markets.push({ + enclave_id: current.enclave_id, + ...(current.ctx_bracket ? { + ctx_bracket: current.ctx_bracket, + ctx_bracket_table_ver: current.ctx_bracket_table_ver, + } : {}), + }); } - markets.sort((a,b)=>compare(a.enclave_id,b.enclave_id)||compare(a.ctx_bracket??'',b.ctx_bracket??'')); - assert(markets.length<=5000,'active market migration exceeds canonical index bound'); - const modelrefCommands=[]; - for(const [modelId,ref] of Object.entries(snapshot.modelrefs).sort(([a],[b])=>compare(a,b))) { - const calibration=overrides[modelId]??ref.activity_calibration; - if(calibration) { - const error=c.validateActivityCalibration(calibration,ref.model_class,ref.rate_map);assert(!error,error?.message); - if(overrides[modelId])modelrefCommands.push({op:'set_model_ref',model_id:modelId,model_class:ref.model_class, - rate_map:ref.rate_map,...(ref.source_hash?{source_hash:ref.source_hash}:{}),activity_calibration:calibration}); - } - inventory.push({model_id:modelId,model_class:ref.model_class,rate_units:ref.rate_map.map(r=>r.unit).sort(compare), - activity_basis:calibration?'calibrated_work_v1':'relative_dimension_vector_v1', - calibration_source_hash:calibration?.source_hash??null}); + markets.sort((left, right) => compare(left.enclave_id, right.enclave_id) || + compare(left.ctx_bracket ?? '', right.ctx_bracket ?? '')); + assert(markets.length <= 5_000, 'active market migration exceeds canonical index bound'); + + const commands = []; + for (let index = 0; index < markets.length || index === 0; index += 128) { + commands.push({ + op: 'migrate_market_pricing', + at: snapshot.at, + markets: markets.slice(index, index + 128), + }); } - for(const modelId of Object.keys(overrides))assert(snapshot.modelrefs[modelId],'calibration override has no canonical modelref'); - const commands=[]; - for(let i=0;i 1_000_000) throw new Error('receipt capacity_slots is too large'); + current.provider_capacities.set( + body.provider, + Math.max(current.provider_capacities.get(body.provider) ?? 0, capacitySlots) + ); + } else { + current.legacy_receipt_count += 1; + safeCount(current.legacy_receipt_count, 'legacy receipt count'); + } const paidUnits = new Set(body.locked_rate_map.map((row) => canonicalUsageUnit(row.unit))); const usage = normalizeReceiptUsage(body.usage); const prior = normalizeReceiptUsage(body.billing_prior_usage); @@ -190,8 +207,12 @@ function sortedMarketUsageEntries(map) { ...(entry.ctx_bracket ? { ctx_bracket: entry.ctx_bracket } : {}), ...(entry.ctx_bracket_table_ver ? { ctx_bracket_table_ver: entry.ctx_bracket_table_ver } : {}), demand_au: canonicalAu(entry.demand_au), + compute_ms: canonicalAu(entry.compute_ms), + legacy_receipt_count: entry.legacy_receipt_count, session_count: entry.sessions.size, provider_count: entry.providers.size, + capacity_slot_count: Array.from(entry.provider_capacities.values()) + .reduce((sum, slots) => sum + slots, 0), })); } @@ -299,6 +320,12 @@ function normalizeSettlementReceiptBody(body) { throw new Error('receipt billing prior usage requires a prior cumulative amount'); } assertUsageMonotonic(current.billing_prior_usage, current.usage); + if (current.schema_version === SESSION_RECEIPT_SCHEMA_VERSION) { + safeCount(current.compute_ms, 'receipt compute_ms'); + if (safeCount(current.capacity_slots, 'receipt capacity_slots') > 1_000_000) { + throw new Error('receipt capacity_slots is too large'); + } + } if (safeAu(current.au_owed_cum, 'receipt au_owed_cum') < safeAu(current.billing_prior_au_owed_cum, 'receipt billing_prior_au_owed_cum', { allowZero: true })) { throw new Error('receipt cumulative au regressed below its signed billing baseline'); diff --git a/intercom/scripts/retail-crypto-payment-worker.mjs b/intercom/scripts/retail-crypto-payment-worker.mjs new file mode 100644 index 00000000..1d10898c --- /dev/null +++ b/intercom/scripts/retail-crypto-payment-worker.mjs @@ -0,0 +1,1155 @@ +#!/usr/bin/env node +import { spawn } from 'node:child_process'; +import { createHash } from 'node:crypto'; +import fs from 'node:fs'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; + +import { MainSettlementBus } from 'trac-msb/src/index.js'; + +import { TAP_DEPOSIT_EVENT_SIGNATURE, tapDepositKey } from '../../contracts/scripts/tap-deposit-watcher.mjs'; +import { createLocalConfig, sleep } from './msb-local-common.mjs'; +import { waitForMinimumSignedLength } from './msb-reader-catchup.mjs'; +import { + ERC20_TRANSFER_TOPIC, + RetryWork, + ReviewWork, + addressTopic, + isBridgeFundingShortfall, + normalizeHex, + normalizeHex64, + normalizeTnkAddress, + parseHexInt, + summarizePayoutLiabilities, + tnkVerificationWindow, + uniqueIntentByAmount, + validateTapBridgePreflight, + verifyTapTransferReceipt, +} from './retail-crypto-verification.mjs'; +import { scanMsbTransfers } from './tnk-deposit-watcher.mjs'; + +const scriptPath = fileURLToPath(import.meta.url); +const repoRoot = path.resolve(path.dirname(scriptPath), '../..'); +const TOKEN_SCALE = 1_000_000_000_000_000_000n; + +function requiredEnv(name, env = process.env) { + const value = String(env[name] ?? '').trim(); + if (!value) throw new Error(`Missing ${name}`); + return value; +} + +function positiveInt(value, fallback, label) { + const parsed = Number(value ?? fallback); + if (!Number.isSafeInteger(parsed) || parsed <= 0) throw new Error(`${label} must be positive`); + return parsed; +} + +function atomicJson(file, value) { + fs.mkdirSync(path.dirname(file), { recursive: true, mode: 0o700 }); + const temporary = `${file}.tmp-${process.pid}-${Date.now()}`; + fs.writeFileSync(temporary, `${JSON.stringify(value, null, 2)}\n`, { mode: 0o600 }); + fs.renameSync(temporary, file); + fs.chmodSync(file, 0o600); +} + +function readJson(file, fallback = {}) { + if (!fs.existsSync(file)) return fallback; + return JSON.parse(fs.readFileSync(file, 'utf8')); +} + +async function closeMsb(msb) { + await Promise.race([ + Promise.resolve().then(() => msb.close()).catch(() => undefined), + sleep(10_000), + ]); +} + +async function openTnkReader(config) { + if (config.tnkMsb) return config.tnkMsb; + const msbConfig = createLocalConfig({ + network: config.tnkNetwork, + stateDir: path.join(config.stateDir, 'tnk-health-reader'), + storeName: `${config.tnkReaderStore}-health`, + channel: process.env.MSB_CHANNEL || undefined, + bootstrap: process.env.MSB_BOOTSTRAP || undefined, + dhtBootstrap: process.env.MSB_DHT_BOOTSTRAP || undefined, + enableWallet: false, + }); + const candidate = new MainSettlementBus(msbConfig); + try { + await Promise.race([ + candidate.ready(), + sleep(config.readerTimeoutSeconds * 1_000).then(() => { throw new RetryWork('reader_unavailable', 30); }), + ]); + config.tnkMsb = candidate; + return candidate; + } catch (error) { + await closeMsb(candidate); + throw error; + } +} + +async function withTnkReader(config, operation) { + const previous = config.tnkReaderQueue ?? Promise.resolve(); + let release; + const turn = new Promise((resolve) => { release = resolve; }); + config.tnkReaderQueue = previous.catch(() => undefined).then(() => turn); + await previous.catch(() => undefined); + try { + return await operation(await openTnkReader(config)); + } finally { + release(); + } +} + +async function caughtUpTnkSignedLength(config, msb) { + const minimumSignedLength = await coreMsbSignedLength(config.coreRpc); + const signedLength = await waitForMinimumSignedLength(msb.state, { + minimumSignedLength, + timeoutSec: config.readerTimeoutSeconds, + sleepImpl: sleep, + }); + if (signedLength < minimumSignedLength) throw new RetryWork('reader_unavailable', 30); + return signedLength; +} + +function sha256(value) { + return createHash('sha256').update(String(value)).digest('hex'); +} + +function ceilDiv(value, divisor) { + return (value + divisor - 1n) / divisor; +} + +function jsonOutput(stdout) { + const text = String(stdout ?? '').trim(); + try { return JSON.parse(text); } catch {} + const first = text.indexOf('{'); + const last = text.lastIndexOf('}'); + if (first === -1 || last <= first) throw new Error('command returned no JSON report'); + return JSON.parse(text.slice(first, last + 1)); +} + +class WorkerApi { + constructor(baseUrl, secret, workerId) { + this.base = new URL(baseUrl.endsWith('/') ? baseUrl : `${baseUrl}/`); + this.secret = secret; + this.workerId = workerId; + } + + async post(relative, body) { + const response = await fetch(new URL(relative, this.base), { + method: 'POST', + headers: { authorization: `Bearer ${this.secret}`, 'content-type': 'application/json' }, + body: JSON.stringify(body), + signal: AbortSignal.timeout(15_000), + }); + const payload = await response.json().catch(() => null); + if (!response.ok) throw new Error(`worker API ${relative} failed with HTTP ${response.status}`); + return payload; + } + + pull() { return this.post('internal/crypto-payment-worker/pull', { worker_id: this.workerId }); } + discovery(rail) { + return this.post('internal/crypto-payment-worker/discovery', { worker_id: this.workerId, rail }); + } + discovered(intent, transactionHash, observedAt) { + return this.post(`internal/crypto-payment-worker/${intent.id}/discover`, { + worker_id: this.workerId, + transaction_hash: transactionHash, + observed_at: observedAt.toISOString(), + }); + } + renew(work) { return this.post(`internal/crypto-payment-worker/${work.intent.id}/renew`, { lease_token: work.lease_token }); } + retry(work, error) { + return this.post(`internal/crypto-payment-worker/${work.intent.id}/retry`, { + lease_token: work.lease_token, + code: error.code, + retry_after_seconds: error.delaySeconds, + transfer_observed: error.transferObserved, + }); + } + review(work, reason, evidence = null) { + return this.post(`internal/crypto-payment-worker/${work.intent.id}/review`, { + lease_token: work.lease_token, + reason, + ...(evidence ? { evidence: serializeEvidence(evidence) } : {}), + }); + } + evidence(work, evidence) { + return this.post(`internal/crypto-payment-worker/${work.intent.id}/evidence`, { + lease_token: work.lease_token, + evidence: serializeEvidence({ ...evidence, intentId: work.intent.id }), + }); + } + status(report) { return this.post('internal/crypto-payment-worker/status', report); } +} + +function serializeEvidence(value) { + return JSON.parse(JSON.stringify(value, (_, item) => typeof item === 'bigint' ? item.toString() : item)); +} + +class TapRpc { + constructor(urls, expectedChainId) { + this.urls = urls; + this.expectedChainId = BigInt(expectedChainId); + this.selected = null; + this.selectedIndex = null; + this.nextId = 1; + } + + async select() { + for (const [index, url] of this.urls.entries()) { + try { + const chainId = parseHexInt(await this.callUrl(url, 'eth_chainId', []), 'Ethereum chain id'); + if (chainId === this.expectedChainId) { + this.selected = url; + this.selectedIndex = index; + return; + } + } catch {} + } + throw new RetryWork('rpc_unavailable', 30); + } + + async call(method, params) { + if (!this.selected) await this.select(); + const preferred = this.selectedIndex ?? 0; + const order = [preferred, ...this.urls.map((_url, index) => index).filter((index) => index !== preferred)]; + let lastError = null; + for (const index of order) { + const candidate = this.urls[index]; + try { + const chainId = parseHexInt(await this.callUrl(candidate, 'eth_chainId', []), 'Ethereum chain id'); + if (chainId !== this.expectedChainId) continue; + const result = await this.callUrl(candidate, method, params); + this.selected = candidate; + this.selectedIndex = index; + return result; + } catch (error) { + lastError = error; + } + } + this.selected = null; + this.selectedIndex = null; + throw lastError ?? new RetryWork('rpc_unavailable', 30); + } + + async callUrl(url, method, params) { + const response = await fetch(url, { + method: 'POST', + headers: { 'content-type': 'application/json' }, + body: JSON.stringify({ jsonrpc: '2.0', id: this.nextId++, method, params }), + signal: AbortSignal.timeout(10_000), + }); + const body = await response.json().catch(() => null); + if (!response.ok || body?.error || !Object.hasOwn(body ?? {}, 'result')) throw new Error('Ethereum RPC failed'); + return body.result; + } + + mode() { return this.selectedIndex === 0 ? 'primary' : 'fallback'; } +} + +function coreStateUrl(baseUrl, key, { prefix = false, signedLength } = {}) { + const base = new URL(baseUrl.endsWith('/') ? baseUrl : `${baseUrl}/`); + const url = new URL('state', base); + url.searchParams.set(prefix ? 'prefix' : 'key', key); + url.searchParams.set('confirmed', 'true'); + if (prefix) url.searchParams.set('limit', '1000'); + if (signedLength !== undefined) url.searchParams.set('signed_length', String(signedLength)); + return url; +} + +async function readCore(baseUrl, key, options = {}) { + const response = await fetch(coreStateUrl(baseUrl, key, options), { signal: AbortSignal.timeout(10_000) }); + const body = await response.json().catch(() => null); + if (!response.ok || body?.confirmed !== true || !Number.isSafeInteger(body?.signed_length)) { + throw new RetryWork('core_unavailable', 30); + } + if (options.signedLength !== undefined && body.signed_length !== options.signedLength) { + throw new RetryWork('core_unavailable', 30); + } + return body; +} + +async function coreMsbSignedLength(baseUrl) { + const base = new URL(baseUrl.endsWith('/') ? baseUrl : `${baseUrl}/`); + const response = await fetch(new URL('status', base), { signal: AbortSignal.timeout(10_000) }); + const body = await response.json().catch(() => null); + const value = Number(body?.msb?.signedLength); + if (!response.ok || !Number.isSafeInteger(value) || value <= 0) throw new RetryWork('core_unavailable', 30); + return value; +} + +async function coreWorkingFunds(config, rail) { + const name = rail.toLowerCase(); + const balance = await readCore(config.coreRpc, `bal/${config.platformBuyer}/${name}`); + const signedLength = balance.signed_length; + const holds = await Promise.all([ + 'targeted-outstanding', 'targeted-summary', 'targeted-legacy-release', + ].map((kind) => readCore(config.coreRpc, `hold/${kind}/${name}/${config.platformBuyer}`, { signedLength }))); + const value = balance.value; + if (!value || String(value.user).toLowerCase() !== config.platformBuyer || value.rail !== name || + value.denom !== 'au_usd' || !/^\d+$/.test(String(value.au))) { + throw new RetryWork('core_unavailable', 30); + } + const amount = (record, field) => { + if (!record.value) return 0n; + const text = String(record.value[field] ?? ''); + if (!/^\d+$/.test(text)) throw new RetryWork('core_unavailable', 30); + return BigInt(text); + }; + const legacy = amount(holds[0], 'reserved_au'); + const summary = amount(holds[1], 'reserved_au'); + const released = amount(holds[2], 'released_au'); + if (released > legacy) throw new RetryWork('core_unavailable', 30); + return { balanceAu: BigInt(value.au), heldAu: legacy - released + summary, signedLength }; +} + +function exactEpoch(values, prefix) { + let latest = 0; + for (const entry of values ?? []) { + const tail = String(entry?.key ?? '').slice(prefix.length); + if (/^[1-9][0-9]*$/.test(tail)) latest = Math.max(latest, Number(tail)); + } + return latest; +} + +async function coreSettlementState(config, rail) { + const name = rail.toLowerCase(); + const settlementPrefix = `settle/targeted/${name}/`; + const [liabilityRecords, settlementRecords, applyRecords] = await Promise.all([ + readCore(config.coreRpc, `payout/liability/${name}/`, { prefix: true }), + readCore(config.coreRpc, settlementPrefix, { prefix: true }), + readCore(config.coreRpc, 'epoch/apply-anchor/', { prefix: true }), + ]); + if (liabilityRecords.truncated || settlementRecords.truncated || applyRecords.truncated) { + throw new RetryWork('settlement_state_truncated', 60); + } + const liabilities = summarizePayoutLiabilities(liabilityRecords.values ?? [], rail); + const currentEpoch = exactEpoch(applyRecords.values, 'epoch/apply-anchor/'); + const lastSettledEpoch = exactEpoch(settlementRecords.values, settlementPrefix); + const lag = Math.max(0, currentEpoch - lastSettledEpoch); + const payoutStatus = liabilities.payableAu === 0n + ? 'current' + : lag <= config.settlementMaxEpochLag ? 'pending' : 'lagging'; + return { + ...liabilities, + currentEpoch, + lastSettledEpoch, + payoutStatus, + lag, + }; +} + +function erc20BalanceCall(address) { + return `0x70a08231${addressTopic(address).slice(2)}`; +} + +async function tapOperationalStatus(config) { + const rpc = new TapRpc(config.tapRpcUrls, config.tapChainId); + await rpc.select(); + const payments = await readCore(config.coreRpc, 'payments/current'); + const rate = await readCore(config.coreRpc, 'tap/rate/latest'); + const pool = normalizeHex(payments.value?.tap?.pool_address, 20, 'TAP pool'); + const rateAu = BigInt(String(rate.value?.tap_usd_au ?? '0')); + if (rateAu <= 0n) throw new RetryWork('rate_unavailable', 30); + const [{ latestNumber, finalizedNumber }, tokenBalance, gasBalance, settlementGasBalance, + poolBalance, core, payout] = await Promise.all([ + tapFinality(rpc), + rpc.call('eth_call', [{ to: config.tapToken, data: erc20BalanceCall(config.tapCollection) }, 'latest']), + rpc.call('eth_getBalance', [config.tapCollection, 'latest']), + rpc.call('eth_getBalance', [config.tapSettlementGasAddress, 'latest']), + rpc.call('eth_call', [{ to: config.tapToken, data: erc20BalanceCall(pool) }, 'latest']), + coreWorkingFunds(config, 'TAP'), + coreSettlementState(config, 'TAP'), + ]); + const collectionGasWei = parseHexInt(gasBalance, 'TAP collection gas balance'); + const settlementGasWei = parseHexInt(settlementGasBalance, 'TAP settlement gas balance'); + const poolBalanceWei = parseHexInt(poolBalance, 'TAP pool balance'); + const requiredWei = ceilDiv(payout.unsettledAu * TOKEN_SCALE, rateAu); + const gasReady = collectionGasWei >= config.tapMinimumGasWei && + settlementGasWei >= config.tapMinimumGasWei; + const assetsReady = poolBalanceWei >= requiredWei; + const settlementReady = payout.payoutStatus !== 'lagging' && gasReady && assetsReady; + return { + worker_id: config.workerId, + rail: 'TAP', + reader_healthy: true, + rpc_mode: rpc.mode(), + collection_balance_base_units: parseHexInt(tokenBalance, 'TAP collection balance').toString(), + gas_balance_base_units: collectionGasWei.toString(), + external_height: latestNumber.toString(), + external_finalized_height: finalizedNumber.toString(), + core_balance_au: core.balanceAu.toString(), + core_held_au: core.heldAu.toString(), + core_signed_length: String(core.signedLength), + settlement: { + ready: settlementReady, + payout_status: payout.payoutStatus, + current_epoch: payout.currentEpoch, + last_settled_epoch: payout.lastSettledEpoch || null, + epoch_lag: payout.lag, + liability_au: payout.unsettledAu.toString(), + held_au: payout.heldAu.toString(), + payable_au: payout.payableAu.toString(), + pool_balance_base_units: poolBalanceWei.toString(), + required_base_units: requiredWei.toString(), + sponsorship_ready: true, + gas_ready: gasReady, + settlement_gas_balance_base_units: settlementGasWei.toString(), + assets_ready: assetsReady, + }, + last_error: null, + checked_at: new Date().toISOString(), + }; +} + +async function tnkOperationalStatus(config) { + return withTnkReader(config, async (msb) => { + const signedLength = await caughtUpTnkSignedLength(config, msb); + const [payments, rate, payout] = await Promise.all([ + readCore(config.coreRpc, 'payments/current'), + readCore(config.coreRpc, 'rate/latest'), + coreSettlementState(config, 'TNK'), + ]); + const treasuryAddress = normalizeTnkAddress( + payments.value?.tnk?.treasury_address, + config.tnkNetwork, + 'TNK treasury address', + ); + const rateAu = BigInt(String(rate.value?.tnk_usd_au ?? '0')); + if (rateAu <= 0n) throw new RetryWork('rate_unavailable', 30); + let balance = null; + let treasuryBalance = null; + for (let waited = 0; waited <= config.readerTimeoutSeconds; waited += 1) { + balance = await msb.getBalance(config.tnkCollection, true); + treasuryBalance = await msb.getBalance(treasuryAddress, true); + if (balance && treasuryBalance) break; + await sleep(1_000); + } + if (!balance || !treasuryBalance || !/^\d+$/.test(String(balance.balance)) || + !/^\d+$/.test(String(treasuryBalance.balance))) throw new RetryWork('reader_unavailable', 30); + const core = await coreWorkingFunds(config, 'TNK'); + const requiredE18 = ceilDiv(payout.unsettledAu * TOKEN_SCALE, rateAu); + const treasuryE18 = BigInt(treasuryBalance.balance); + const sponsorshipReady = treasuryE18 >= requiredE18; + const settlementReady = payout.payoutStatus !== 'lagging' && sponsorshipReady; + return { + worker_id: config.workerId, + rail: 'TNK', + reader_healthy: true, + rpc_mode: 'msb', + collection_balance_base_units: String(balance.balance), + gas_balance_base_units: null, + external_height: String(signedLength), + external_finalized_height: String(Math.max(0, signedLength - config.tnkFinality)), + core_balance_au: core.balanceAu.toString(), + core_held_au: core.heldAu.toString(), + core_signed_length: String(core.signedLength), + settlement: { + ready: settlementReady, + payout_status: payout.payoutStatus, + current_epoch: payout.currentEpoch, + last_settled_epoch: payout.lastSettledEpoch || null, + epoch_lag: payout.lag, + liability_au: payout.unsettledAu.toString(), + held_au: payout.heldAu.toString(), + payable_au: payout.payableAu.toString(), + treasury_balance_base_units: treasuryE18.toString(), + required_base_units: requiredE18.toString(), + sponsorship_ready: sponsorshipReady, + gas_ready: true, + assets_ready: sponsorshipReady, + }, + last_error: null, + checked_at: new Date().toISOString(), + }; + }); +} + +async function reportOperationalStatus(config, rails = ['TAP', 'TNK']) { + for (const [rail, collect] of [['TAP', tapOperationalStatus], ['TNK', tnkOperationalStatus]]) { + if (!rails.includes(rail)) continue; + try { + await config.api.status(await collect(config)); + } catch (error) { + const detail = String(error?.message ?? error) + .replace(/https?:\/\/[^\s"']+/g, '') + .slice(0, 500); + console.error(JSON.stringify({ + event: 'crypto_payment_status_unavailable', + rail, + code: error instanceof RetryWork ? error.code : 'status_unavailable', + detail, + })); + const core = await coreWorkingFunds(config, rail).catch(() => ({ balanceAu: 0n, heldAu: 0n, signedLength: 0 })); + await config.api.status({ + worker_id: config.workerId, + rail, + reader_healthy: false, + rpc_mode: rail === 'TNK' ? 'msb' : null, + core_balance_au: core.balanceAu.toString(), + core_held_au: core.heldAu.toString(), + core_signed_length: String(core.signedLength), + settlement: null, + last_error: error instanceof RetryWork ? error.code : 'status_unavailable', + checked_at: new Date().toISOString(), + }).catch(() => undefined); + } + } +} + +async function runCommand(command, args, { timeoutMs = 1_200_000 } = {}) { + return new Promise((resolve, reject) => { + const child = spawn(command, args, { cwd: repoRoot, env: process.env, stdio: ['ignore', 'pipe', 'pipe'] }); + let stdout = ''; + let stderr = ''; + const append = (current, chunk) => (current + chunk.toString('utf8')).slice(-4_000_000); + child.stdout.on('data', (chunk) => { stdout = append(stdout, chunk); }); + child.stderr.on('data', (chunk) => { stderr = append(stderr, chunk); }); + const timeout = setTimeout(() => child.kill('SIGTERM'), timeoutMs); + child.on('error', (error) => { clearTimeout(timeout); reject(error); }); + child.on('close', (code, signal) => { + clearTimeout(timeout); + if (code === 0) resolve({ stdout, stderr }); + else { + const error = new Error(`command exited ${code ?? signal ?? 'unknown'}`); + error.stdout = stdout; + error.stderr = stderr; + reject(error); + } + }); + }); +} + +async function tapFinality(rpc) { + const latest = await rpc.call('eth_blockNumber', []); + let finalized = null; + try { finalized = await rpc.call('eth_getBlockByNumber', ['finalized', false]); } catch {} + const latestNumber = parseHexInt(latest, 'latest block'); + const finalizedNumber = finalized?.number + ? parseHexInt(finalized.number, 'finalized block') + : latestNumber > 12n ? latestNumber - 12n : 0n; + return { latestNumber, finalizedNumber }; +} + +async function tapBlockTime(rpc, blockNumber) { + const block = await rpc.call('eth_getBlockByNumber', [blockNumber, false]); + const timestamp = parseHexInt(block?.timestamp, 'TAP block timestamp'); + return new Date(Number(timestamp) * 1_000); +} + +async function discoverTapIncoming(config, intents) { + if (intents.length === 0) return; + const rpc = new TapRpc(config.tapRpcUrls, config.tapChainId); + await rpc.select(); + const latest = parseHexInt(await rpc.call('eth_blockNumber', []), 'latest block'); + const checkpointFile = config.discoveryCheckpointFile('tap'); + const checkpoint = readJson(checkpointFile, {}); + const stored = /^\d+$/.test(String(checkpoint.next_block ?? '')) + ? BigInt(checkpoint.next_block) + : null; + let start = stored === null || stored > latest + ? (latest > BigInt(config.tapBridgeLookbackBlocks) ? latest - BigInt(config.tapBridgeLookbackBlocks) : 0n) + : (stored > 12n ? stored - 12n : 0n); + const claimed = new Set(); + while (start <= latest) { + const end = start + 999n < latest ? start + 999n : latest; + const logs = await rpc.call('eth_getLogs', [{ + address: config.tapToken, + fromBlock: `0x${start.toString(16)}`, + toBlock: `0x${end.toString(16)}`, + topics: [ERC20_TRANSFER_TOPIC, null, addressTopic(config.tapCollection)], + }]); + for (const log of Array.isArray(logs) ? logs : []) { + const intent = uniqueIntentByAmount( + intents.filter((candidate) => !claimed.has(candidate.id)), + parseHexInt(log.data, 'TAP transfer amount'), + ); + if (!intent) continue; + const observedAt = await tapBlockTime(rpc, log.blockNumber); + await config.api.discovered(intent, normalizeHex(log.transactionHash, 32, 'TAP transaction'), observedAt); + claimed.add(intent.id); + } + atomicJson(checkpointFile, { next_block: (end + 1n).toString(), updated_at: new Date().toISOString() }); + start = end + 1n; + } +} + +async function discoverTnkIncoming(config, intents) { + if (intents.length === 0) return; + await withTnkReader(config, async (msb) => { + const current = await caughtUpTnkSignedLength(config, msb); + const checkpointFile = config.discoveryCheckpointFile('tnk'); + const checkpoint = readJson(checkpointFile, {}); + const stored = Number(checkpoint.next_signed_length); + const start = Number.isSafeInteger(stored) && stored >= 0 && stored <= current + ? Math.max(0, stored - Math.max(2, config.tnkFinality)) + : Math.max(0, current - config.tnkLookback); + // Discovery observes signed transfers immediately; the verification pass + // still enforces the configured finality before any credit is posted. + const scan = await scanMsbTransfers(msb, { + fromSignedLength: start, + finalitySignedLengths: 0, + chunkSize: 500, + timeoutSec: config.readerTimeoutSeconds, + minimumSignedLength: current, + }); + const claimed = new Set(); + for (const transfer of scan.transfers) { + if (String(transfer.to).toLowerCase() !== config.tnkCollection) continue; + const intent = uniqueIntentByAmount( + intents.filter((candidate) => !claimed.has(candidate.id)), + transfer.tnk_e18, + ); + if (!intent) continue; + await config.api.discovered(intent, normalizeHex64(transfer.hash, 'TNK transaction'), new Date()); + claimed.add(intent.id); + } + atomicJson(checkpointFile, { + next_signed_length: scan.confirmedLength, + updated_at: new Date().toISOString(), + }); + }); +} + +async function discoverIncoming(config, rails = ['TAP', 'TNK']) { + for (const rail of rails) { + try { + const intents = (await config.api.discovery(rail))?.intents ?? []; + if (rail === 'TAP') await discoverTapIncoming(config, intents); + else await discoverTnkIncoming(config, intents); + } catch (error) { + const detail = String(error?.message ?? error) + .replace(/https?:\/\/\S+/g, '') + .slice(0, 300); + console.error(JSON.stringify({ + event: 'crypto_payment_discovery_unavailable', rail, detail, + })); + } + } +} + +async function verifyTapCustomerTransfer(intent, rpc) { + const receipt = await rpc.call('eth_getTransactionReceipt', [intent.transaction_hash]); + const { latestNumber, finalizedNumber } = await tapFinality(rpc); + return verifyTapTransferReceipt(receipt, { + transactionHash: intent.transaction_hash, + token: intent.token_contract, + destination: intent.destination, + amountBaseUnits: intent.token_amount_base_units, + latestBlock: latestNumber, + finalizedBlock: finalizedNumber, + }); +} + +async function verifyTnkCustomerTransfer(intent, config) { + const hash = normalizeHex64(intent.transaction_hash, 'TNK transaction hash'); + return withTnkReader(config, async (msb) => { + // Compute the lookback only after the persistent reader reaches the Core + // frontier. Computing it from a stale startup position turns one lookup + // into an unbounded replay of the entire offline gap. + const confirmed = await caughtUpTnkSignedLength(config, msb); + const window = tnkVerificationWindow(confirmed, config.tnkLookback); + const scan = await scanMsbTransfers(msb, { + fromSignedLength: window.fromSignedLength, + finalitySignedLengths: config.tnkFinality, + chunkSize: 500, + timeoutSec: config.readerTimeoutSeconds, + minimumSignedLength: window.minimumSignedLength, + matchHash: hash, + }); + const transfer = scan.transfers.find((candidate) => candidate.hash === hash); + if (!transfer) throw new RetryWork('transfer_pending', 20); + const confirmations = Math.max(0, scan.confirmedLength - Number(transfer.confirmed_length) + 1); + const evidence = { + rail: 'TNK', + transactionHash: hash, + logIndex: -1, + blockNumber: BigInt(transfer.confirmed_length), + blockHash: sha256(`msb/${config.tnkNetwork}/${transfer.confirmed_length}`), + fromAddress: String(transfer.from), + toAddress: String(transfer.to), + tokenAmountBaseUnits: BigInt(transfer.tnk_e18), + confirmations, + finalized: Number(transfer.confirmed_length) <= scan.safeEnd, + observedAt: new Date(), + externalRecordKey: `tnk/${config.tnkNetwork}/${hash}`, + }; + if (!evidence.finalized) throw new RetryWork('awaiting_finality', 20, true); + if (String(transfer.to).toLowerCase() !== String(intent.destination).toLowerCase()) { + throw new ReviewWork('wrong_destination', evidence); + } + if (BigInt(transfer.tnk_e18) !== BigInt(intent.token_amount_base_units)) { + throw new ReviewWork('amount_mismatch', evidence); + } + return evidence; + }); +} + +function auToUsd(au) { + // Core's TNK deposit CLI accepts cents. Round the backing lot upward so + // every micro-valued retail credit is fully covered; the sub-cent remainder + // stays in the platform buyer's TNK balance for later purchases. + const centAu = 10_000_000_000_000_000n; + const cents = (BigInt(au) + centAu - 1n) / centAu; + const whole = cents / 100n; + const fraction = (cents % 100n).toString().padStart(2, '0'); + return `${whole}.${fraction}`; +} + +function e18ToDecimal(amount) { + const value = BigInt(amount); + if (value <= 0n) throw new RetryWork('core_unavailable', 60, true); + const whole = value / TOKEN_SCALE; + const fraction = (value % TOKEN_SCALE).toString().padStart(18, '0').replace(/0+$/, ''); + return fraction ? `${whole}.${fraction}` : whole.toString(); +} + +async function waitForCoreRecord(config, work, key, expectedAu, timeoutSeconds = 600) { + const deadline = Date.now() + timeoutSeconds * 1_000; + while (Date.now() <= deadline) { + const record = await readCore(config.coreRpc, key); + const value = record.value; + if (value && typeof value.au === 'string' && BigInt(value.au) >= BigInt(expectedAu)) { + return { value, signedLength: BigInt(record.signed_length), key }; + } + await config.api.renew(work); + await sleep(5_000); + } + throw new RetryWork('core_bridge_pending', 30, true); +} + +async function bridgeTnk(config, work, checkpoint) { + const intent = work.intent; + const nonce = sha256(`openmayhem-retail-tnk-v1/${intent.id}`); + const common = [ + 'pay', 'tnk', '--amount', auToUsd(intent.expected_core_au), '--nonce', nonce, + '--home', config.buyerHome, '--rpc-url', config.coreRpc, + '--wallet-password-file', config.walletPasswordFile, '--json', + ]; + let memo = checkpoint.tnk_memo_hash + ? normalizeHex64(checkpoint.tnk_memo_hash, 'TNK bridge memo') + : null; + let dry = null; + let creditedKey = memo ? `dep/tnk-credited/${memo}` : null; + let pendingKey = memo ? `dep/pending/${memo}` : null; + let credited = creditedKey ? await readCore(config.coreRpc, creditedKey) : null; + let pending = !credited?.value && pendingKey ? await readCore(config.coreRpc, pendingKey) : null; + if (!memo || (!credited?.value && !pending?.value)) { + dry = jsonOutput((await runCommand(config.mayhemBin, common, { api: config.api, work })).stdout); + if (String(dry.who).toLowerCase() !== config.platformBuyer) { + throw new RetryWork('core_unavailable', 60, true); + } + const derivedMemo = normalizeHex64(dry.memo_hash, 'TNK bridge memo'); + if (memo && derivedMemo !== memo) throw new RetryWork('core_unavailable', 60, true); + memo = derivedMemo; + checkpoint.tnk_memo_hash = memo; + atomicJson(config.checkpointFile(intent.id), checkpoint); + creditedKey = `dep/tnk-credited/${memo}`; + pendingKey = `dep/pending/${memo}`; + credited = await readCore(config.coreRpc, creditedKey); + pending = !credited.value ? await readCore(config.coreRpc, pendingKey) : null; + } + if (!credited.value) { + if (!pending.value) { + await runCommand(config.mayhemBin, [...common.slice(0, -1), '--submit-intent', '--json'], { api: config.api, work }); + const deadline = Date.now() + config.bridgeTimeoutSeconds * 1_000; + while (!pending.value && Date.now() <= deadline) { + await config.api.renew(work); + await sleep(5_000); + pending = await readCore(config.coreRpc, pendingKey); + } + } + const locked = pending.value; + const canonicalPayments = await readCore(config.coreRpc, 'payments/current'); + const canonicalTreasury = canonicalPayments.value?.tnk?.treasury_address; + if (!locked || String(locked.user).toLowerCase() !== config.platformBuyer || + String(locked.msb_network).toLowerCase() !== config.tnkNetwork || + normalizeTnkAddress(locked.msb_from, config.tnkNetwork, 'TNK bridge sender') !== config.tnkCollection || + normalizeTnkAddress(locked.treasury_address, config.tnkNetwork, 'TNK bridge treasury') !== + normalizeTnkAddress(canonicalTreasury, config.tnkNetwork, 'TNK bridge treasury') || + BigInt(locked.quoted_au ?? 0) < BigInt(intent.expected_core_au)) { + throw new RetryWork('core_unavailable', 60, true); + } + const lockedTnkE18 = BigInt(locked.tnk_e18 ?? 0); + if (checkpoint.tnk_bridge_amount_e18 && + BigInt(checkpoint.tnk_bridge_amount_e18) !== lockedTnkE18) { + throw new RetryWork('core_unavailable', 60, true); + } + if (!checkpoint.tnk_bridge_amount_e18) { + checkpoint.tnk_bridge_amount_e18 = lockedTnkE18.toString(); + checkpoint.tnk_bridge_rate_au = String(locked.rate_tnk_usd_au); + checkpoint.tnk_bridge_locked_at = new Date().toISOString(); + atomicJson(config.checkpointFile(intent.id), checkpoint); + } + const transferArgs = [ + path.join(repoRoot, 'crates/mayhem-cli/src/msb-transfer-helper.mjs'), + 'settlement-transfer', '--network', config.tnkNetwork, + '--stores-directory', config.msbStoresDirectory, '--store-name', config.msbStoreName, + '--to', locked.treasury_address, '--amount', e18ToDecimal(lockedTnkE18), + '--operation-id', sha256(`openmayhem-retail-tnk-bridge-v2/${intent.id}/${lockedTnkE18}`), + '--journal-file', path.join(config.stateDir, 'tnk-journals', `${intent.id}-${lockedTnkE18}.json`), + '--wallet-password-file', config.walletPasswordFile, + '--timeout-seconds', String(config.bridgeTimeoutSeconds), + ]; + try { + await runCommand(process.execPath, transferArgs, { api: config.api, work }); + } catch (error) { + if (isBridgeFundingShortfall(error, 'TNK')) { + throw new RetryWork('bridge_funding_shortfall', 30, true); + } + throw error; + } + credited = await readCore(config.coreRpc, creditedKey); + } + const record = credited.value + ? { value: credited.value, signedLength: BigInt(credited.signed_length), key: creditedKey } + : await waitForCoreRecord(config, work, creditedKey, intent.expected_core_au, config.bridgeTimeoutSeconds); + if (String(record.value.user).toLowerCase() !== config.platformBuyer || + BigInt(record.value.au) < BigInt(intent.expected_core_au)) { + throw new RetryWork('core_bridge_pending', 30, true); + } + checkpoint.core_record_key = record.key; + atomicJson(config.checkpointFile(intent.id), checkpoint); + return { backed: true, buyer: config.platformBuyer, rail: 'TNK', creditedAu: BigInt(record.value.au), + confirmedSignedLength: record.signedLength, recordKey: record.key }; +} + +function tapDust(intentId) { + // Keep deposits unique without materially changing what the platform funds. + // Ten hex digits provide about one trillion distinct wei values while the + // maximum surcharge stays below 0.0000011 TAP. + return BigInt(`0x${sha256(`openmayhem-retail-tap-dust-v1/${intentId}`).slice(0, 10)}`) + 1n; +} + +async function findTapDeposit(rpc, pool, buyer, amountWei, lookbackBlocks) { + const latestHex = await rpc.call('eth_blockNumber', []); + const latest = parseHexInt(latestHex, 'latest block'); + const from = latest > BigInt(lookbackBlocks) ? latest - BigInt(lookbackBlocks) : 0n; + const logs = await rpc.call('eth_getLogs', [{ + address: pool, + fromBlock: `0x${from.toString(16)}`, + toBlock: 'latest', + topics: [TAP_DEPOSIT_EVENT_SIGNATURE, addressTopic(buyer)], + }]); + const exact = (Array.isArray(logs) ? logs : []).filter((log) => parseHexInt(log.data, 'TAP deposit amount') === amountWei); + if (exact.length > 1) throw new ReviewWork('duplicate_transfer'); + return exact[0] ?? null; +} + +async function bridgeTap(config, work, checkpoint, rpc) { + const intent = work.intent; + if (!rpc.selected) await rpc.select(); + const [payments, rate] = await Promise.all([ + readCore(config.coreRpc, 'payments/current'), + readCore(config.coreRpc, 'tap/rate/latest'), + ]); + const tap = payments.value?.tap; + const rateAu = BigInt(String(rate.value?.tap_usd_au ?? '0')); + if (!tap || Number(tap.chain_id) !== config.tapChainId || rateAu <= 0n || + String(tap.token_address).toLowerCase() !== String(intent.token_contract).toLowerCase()) { + throw new RetryWork('core_unavailable', 60, true); + } + // Freeze the Core-side obligation at the first verified bridge attempt. A + // retry must recover that exact deposit instead of repricing an already + // matched customer transfer whenever the oracle moves before broadcast. + const amountWei = checkpoint.tap_bridge_amount_wei + ? BigInt(checkpoint.tap_bridge_amount_wei) + : ceilDiv(BigInt(intent.expected_core_au) * TOKEN_SCALE, rateAu) + tapDust(intent.id); + if (!checkpoint.tap_bridge_amount_wei) { + checkpoint.tap_bridge_amount_wei = amountWei.toString(); + checkpoint.tap_bridge_rate_au = rateAu.toString(); + checkpoint.tap_bridge_locked_at = new Date().toISOString(); + atomicJson(config.checkpointFile(intent.id), checkpoint); + } + let common = null; + let buyer = config.tapCollection; + if (!checkpoint.tap_submission_started_at) { + const preferred = rpc.selectedIndex ?? 0; + const rpcOrder = [preferred, ...config.tapRpcUrls.map((_url, index) => index).filter((index) => index !== preferred)]; + let dry = null; + let lastDryError = null; + for (const index of rpcOrder) { + const candidate = config.tapRpcUrls[index]; + try { + const chainId = parseHexInt(await rpc.callUrl(candidate, 'eth_chainId', []), 'Ethereum chain id'); + if (chainId !== BigInt(config.tapChainId)) continue; + const candidateArgs = [ + 'pay', 'tap', '--amount-wei', amountWei.toString(), '--home', config.buyerHome, + '--peer-rpc-url', config.coreRpc, '--wallet-password-file', config.walletPasswordFile, + '--eth-rpc', candidate, '--json', + ]; + dry = jsonOutput((await runCommand(config.mayhemBin, candidateArgs, { api: config.api, work })).stdout); + common = candidateArgs; + rpc.selected = candidate; + rpc.selectedIndex = index; + break; + } catch (error) { + if (isBridgeFundingShortfall(error, 'TAP')) { + throw new RetryWork('bridge_funding_shortfall', 30, true); + } + lastDryError = error; + } + } + if (!dry || !common) { + if (lastDryError instanceof ReviewWork || lastDryError instanceof RetryWork) throw lastDryError; + throw new RetryWork('rpc_unavailable', 30, true); + } + try { + ({ ethereumAccount: buyer } = validateTapBridgePreflight(dry, { + platformBuyer: config.platformBuyer, + collection: intent.destination, + coreRpc: config.coreRpc, + })); + } catch { + throw new RetryWork('core_unavailable', 60, true); + } + } + const pool = normalizeHex(tap.pool_address, 20, 'TAP pool'); + let deposit = await findTapDeposit(rpc, pool, buyer, amountWei, config.tapBridgeLookbackBlocks); + if (!deposit) { + // An Ethereum submission has a small uncertainty window: the signer can + // broadcast successfully and die before the child returns its hash. Never + // submit again from that state. Keep scanning for the uniquely dusted + // amount and surface the intent operationally until the chain resolves it. + if (checkpoint.tap_submission_started_at && !checkpoint.tap_bridge_tx_hash) { + throw new RetryWork('bridge_submission_uncertain', 30, true); + } + if (checkpoint.tap_bridge_tx_hash) { + const pendingReceipt = await rpc.call('eth_getTransactionReceipt', [checkpoint.tap_bridge_tx_hash]); + if (!pendingReceipt) throw new RetryWork('core_bridge_pending', 30, true); + if (parseHexInt(pendingReceipt.status, 'TAP bridge receipt status') !== 1n) { + throw new ReviewWork('bridge_transaction_failed'); + } + const matchingLogs = (Array.isArray(pendingReceipt.logs) ? pendingReceipt.logs : []).filter((log) => + String(log?.address).toLowerCase() === pool && + String(log?.topics?.[0]).toLowerCase() === TAP_DEPOSIT_EVENT_SIGNATURE && + String(log?.topics?.[1]).toLowerCase() === addressTopic(buyer) && + parseHexInt(log.data, 'TAP bridge amount') === amountWei); + if (matchingLogs.length !== 1) throw new ReviewWork('bridge_transaction_mismatch'); + deposit = matchingLogs[0]; + } + } + if (!deposit) { + if (!common) throw new RetryWork('bridge_submission_uncertain', 30, true); + checkpoint.tap_submission_started_at = new Date().toISOString(); + atomicJson(config.checkpointFile(intent.id), checkpoint); + const submitted = jsonOutput((await runCommand(config.mayhemBin, [...common.slice(0, -1), '--confirm', '--json'], { + api: config.api, work, + })).stdout); + checkpoint.tap_bridge_tx_hash = normalizeHex(submitted.deposit_tx_hash, 32, 'TAP bridge transaction'); + atomicJson(config.checkpointFile(intent.id), checkpoint); + const receipt = await rpc.call('eth_getTransactionReceipt', [checkpoint.tap_bridge_tx_hash]); + const logs = (Array.isArray(receipt?.logs) ? receipt.logs : []).filter((log) => + String(log?.address).toLowerCase() === pool && + String(log?.topics?.[0]).toLowerCase() === TAP_DEPOSIT_EVENT_SIGNATURE && + String(log?.topics?.[1]).toLowerCase() === addressTopic(buyer) && + parseHexInt(log.data, 'TAP bridge amount') === amountWei); + if (logs.length !== 1) throw new ReviewWork('bridge_transaction_mismatch'); + deposit = logs[0]; + } + const { finalizedNumber } = await tapFinality(rpc); + const blockNumber = parseHexInt(deposit.blockNumber, 'TAP bridge block'); + if (blockNumber > finalizedNumber) throw new RetryWork('core_bridge_pending', 20, true); + const normalized = { + chain_id: config.tapChainId, + pool_address: pool, + eth_tx_hash: normalizeHex(deposit.transactionHash, 32, 'TAP bridge transaction'), + log_index: Number(parseHexInt(deposit.logIndex, 'TAP bridge log index')), + block_hash: normalizeHex(deposit.blockHash, 32, 'TAP bridge block hash'), + }; + const key = `dep/tap/${tapDepositKey(normalized)}`; + const record = await waitForCoreRecord(config, work, key, intent.expected_core_au, config.bridgeTimeoutSeconds); + if (String(record.value.who).toLowerCase() !== config.platformBuyer || BigInt(record.value.au) < BigInt(intent.expected_core_au)) { + throw new RetryWork('core_bridge_pending', 30, true); + } + checkpoint.core_record_key = key; + atomicJson(config.checkpointFile(intent.id), checkpoint); + return { backed: true, buyer: config.platformBuyer, rail: 'TAP', creditedAu: BigInt(record.value.au), + confirmedSignedLength: record.signedLength, recordKey: key }; +} + +async function processWork(config, work) { + console.log(JSON.stringify({ event: 'crypto_payment_processing', intent_id: work.intent.id, rail: work.intent.rail })); + const checkpointFile = config.checkpointFile(work.intent.id); + const checkpoint = readJson(checkpointFile, { schema_version: 1, intent_id: work.intent.id }); + if (checkpoint.intent_id !== work.intent.id) throw new ReviewWork('malformed_transfer'); + let external; + let rpc = null; + if (checkpoint.external_evidence) { + external = reviveEvidence(checkpoint.external_evidence); + } else if (work.intent.rail === 'TAP') { + rpc = new TapRpc(config.tapRpcUrls, config.tapChainId); + await rpc.select(); + external = await verifyTapCustomerTransfer(work.intent, rpc); + } else { + external = await verifyTnkCustomerTransfer(work.intent, config); + } + checkpoint.external_evidence = serializeEvidence(external); + atomicJson(checkpointFile, checkpoint); + console.log(JSON.stringify({ event: 'crypto_payment_external_verified', intent_id: work.intent.id, rail: work.intent.rail })); + if (work.intent.late_submission) { + throw new ReviewWork('late_submission', external); + } + const core = work.intent.rail === 'TAP' + ? await bridgeTap(config, work, checkpoint, rpc ?? new TapRpc(config.tapRpcUrls, config.tapChainId)) + : await bridgeTnk(config, work, checkpoint); + console.log(JSON.stringify({ event: 'crypto_payment_core_backed', intent_id: work.intent.id, rail: work.intent.rail })); + await config.api.evidence(work, { ...external, core }); + checkpoint.credited = true; + checkpoint.credited_at = new Date().toISOString(); + atomicJson(checkpointFile, checkpoint); +} + +async function processWorkWithLease(config, work) { + const renew = setInterval( + () => void config.api.renew(work).catch(() => undefined), + 30_000, + ); + try { + await processWork(config, work); + } finally { + clearInterval(renew); + } +} + +function reviveEvidence(value) { + return { + ...value, + blockNumber: BigInt(value.blockNumber), + tokenAmountBaseUnits: BigInt(value.tokenAmountBaseUnits), + observedAt: new Date(value.observedAt), + }; +} + +function configuration(env = process.env) { + const stateDir = path.resolve(env.OPENMAYHEM_CRYPTO_WORKER_STATE_DIR || '/var/lib/openmayhem/retail-crypto-worker'); + const primary = requiredEnv('MAYHEM_TAP_ETH_RPC', env); + const fallbacks = String(env.OPENMAYHEM_TAP_ETH_RPC_FALLBACKS ?? '').split(/[;,\s]+/).filter(Boolean); + const workerId = env.OPENMAYHEM_CRYPTO_WORKER_ID || 'retail-crypto-worker'; + const tnkNetwork = env.MAYHEM_MSB_NETWORK || 'mainnet'; + const api = new WorkerApi( + requiredEnv('OPENMAYHEM_CRYPTO_API_URL', env), + requiredEnv('OPENMAYHEM_CRYPTO_WORKER_SECRET', env), + workerId, + ); + return { + api, + workerId, + stateDir, + coreRpc: requiredEnv('MAYHEM_PEER_RPC', env), + platformBuyer: normalizeHex64(requiredEnv('OPENMAYHEM_CRYPTO_PLATFORM_BUYER', env), 'platform buyer'), + buyerHome: path.resolve(requiredEnv('OPENMAYHEM_CRYPTO_BUYER_HOME', env)), + walletPasswordFile: path.resolve(requiredEnv('OPENMAYHEM_CRYPTO_WALLET_PASSWORD_FILE', env)), + mayhemBin: path.resolve(env.MAYHEM_BIN || path.join(repoRoot, 'target/release/mayhem')), + tapRpcUrls: [primary, ...fallbacks.filter((url) => url !== primary)], + tapChainId: positiveInt(env.MAYHEM_TAP_ETH_CHAIN_ID, 1, 'TAP chain id'), + tapToken: normalizeHex(requiredEnv('OPENMAYHEM_TAP_TOKEN_ADDRESS', env), 20, 'TAP token'), + tapCollection: normalizeHex(requiredEnv('OPENMAYHEM_TAP_COLLECTION_ADDRESS', env), 20, 'TAP collection'), + tapSettlementGasAddress: normalizeHex( + requiredEnv('OPENMAYHEM_TAP_SETTLEMENT_GAS_ADDRESS', env), + 20, + 'TAP settlement gas address', + ), + tapMinimumGasWei: BigInt(env.OPENMAYHEM_TAP_MINIMUM_GAS_WEI || '5000000000000000'), + settlementMaxEpochLag: positiveInt(env.OPENMAYHEM_SETTLEMENT_MAX_EPOCH_LAG, 2, 'settlement max epoch lag'), + tapBridgeLookbackBlocks: positiveInt(env.OPENMAYHEM_TAP_BRIDGE_LOOKBACK_BLOCKS, 7200, 'TAP bridge lookback'), + tnkNetwork, + tnkCollection: normalizeTnkAddress( + requiredEnv('OPENMAYHEM_TNK_COLLECTION_ADDRESS', env), + tnkNetwork, + 'TNK collection address', + ), + tnkFinality: positiveInt(env.OPENMAYHEM_TNK_FINALITY_SIGNED_LENGTHS, 2, 'TNK finality'), + tnkLookback: positiveInt(env.OPENMAYHEM_TNK_LOOKBACK_SIGNED_LENGTHS, 5000, 'TNK lookback'), + tnkReaderStore: env.OPENMAYHEM_TNK_READER_STORE || 'openmayhem-retail-crypto-reader', + msbStoresDirectory: path.resolve(requiredEnv('OPENMAYHEM_CRYPTO_MSB_STORES_DIRECTORY', env)), + msbStoreName: requiredEnv('OPENMAYHEM_CRYPTO_MSB_STORE_NAME', env), + readerTimeoutSeconds: positiveInt(env.OPENMAYHEM_CRYPTO_READER_TIMEOUT_SECONDS, 60, 'reader timeout'), + bridgeTimeoutSeconds: positiveInt(env.OPENMAYHEM_CRYPTO_BRIDGE_TIMEOUT_SECONDS, 900, 'bridge timeout'), + intervalSeconds: positiveInt(env.OPENMAYHEM_CRYPTO_WORKER_INTERVAL_SECONDS, 5, 'worker interval'), + statusIntervalSeconds: positiveInt(env.OPENMAYHEM_CRYPTO_STATUS_INTERVAL_SECONDS, 60, 'status interval'), + discoveryIntervalSeconds: positiveInt(env.OPENMAYHEM_CRYPTO_DISCOVERY_INTERVAL_SECONDS, 5, 'discovery interval'), + tnkDiscoveryReaderStore: env.OPENMAYHEM_TNK_DISCOVERY_READER_STORE || 'openmayhem-retail-crypto-discovery', + checkpointFile: (intentId) => path.join(stateDir, 'intents', `${intentId}.json`), + discoveryCheckpointFile: (rail) => path.join(stateDir, 'discovery', `${rail}.json`), + }; +} + +async function runPeriodicLoop(intervalSeconds, task) { + while (true) { + try { + await task(); + } catch { + console.error(JSON.stringify({ event: 'crypto_payment_periodic_task_unavailable' })); + } + await sleep(intervalSeconds * 1_000); + } +} + +async function runWorkLoop(config) { + while (true) { + let work = null; + try { + work = (await config.api.pull())?.work ?? null; + if (!work) { + await sleep(config.intervalSeconds * 1_000); + continue; + } + await processWorkWithLease(config, work); + console.log(JSON.stringify({ event: 'crypto_payment_credited', intent_id: work.intent.id, rail: work.intent.rail })); + } catch (error) { + if (work && error instanceof ReviewWork) { + await config.api.review(work, error.reason, error.evidence).catch(() => undefined); + console.error(JSON.stringify({ event: 'crypto_payment_review', intent_id: work.intent.id, rail: work.intent.rail, reason: error.reason })); + } else if (work) { + const retry = error instanceof RetryWork ? error : new RetryWork('core_unavailable', 30, true); + await config.api.retry(work, retry).catch(() => undefined); + console.error(JSON.stringify({ event: 'crypto_payment_retry', intent_id: work.intent.id, rail: work.intent.rail, code: retry.code })); + } else { + console.error(JSON.stringify({ event: 'crypto_payment_worker_unavailable' })); + } + await sleep(config.intervalSeconds * 1_000); + } + } +} + +async function main() { + const config = configuration(); + fs.mkdirSync(config.stateDir, { recursive: true, mode: 0o700 }); + + const shutdown = async () => { + if (config.tnkMsb) await closeMsb(config.tnkMsb); + process.exit(0); + }; + process.once('SIGINT', () => void shutdown()); + process.once('SIGTERM', () => void shutdown()); + + // These loops must stay independent. A customer payment can spend minutes in + // a Core bridge without pausing either rail's discovery or health heartbeat. + await Promise.all([ + runWorkLoop(config), + runPeriodicLoop(config.discoveryIntervalSeconds, () => discoverIncoming(config, ['TAP'])), + runPeriodicLoop(config.discoveryIntervalSeconds, () => discoverIncoming(config, ['TNK'])), + runPeriodicLoop(config.statusIntervalSeconds, () => reportOperationalStatus(config, ['TAP'])), + runPeriodicLoop(config.statusIntervalSeconds, () => reportOperationalStatus(config, ['TNK'])), + ]); +} + +function isDirectExecution(argument) { + if (!argument) return false; + try { + return fs.realpathSync(argument) === fs.realpathSync(scriptPath); + } catch { + return path.resolve(argument) === scriptPath; + } +} + +if (isDirectExecution(process.argv[1])) { + main().catch((error) => { + console.error(error?.message ?? String(error)); + process.exit(1); + }); +} diff --git a/intercom/scripts/retail-crypto-verification.mjs b/intercom/scripts/retail-crypto-verification.mjs new file mode 100644 index 00000000..a562800e --- /dev/null +++ b/intercom/scripts/retail-crypto-verification.mjs @@ -0,0 +1,211 @@ +export const ERC20_TRANSFER_TOPIC = '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef'; + +export function uniqueIntentByAmount(intents, amountBaseUnits) { + const amount = String(amountBaseUnits); + const matches = intents.filter((intent) => String(intent?.token_amount_base_units) === amount); + return matches.length === 1 ? matches[0] : null; +} + +export function tnkVerificationWindow(confirmedSignedLength, lookback) { + if (!Number.isSafeInteger(confirmedSignedLength) || confirmedSignedLength < 0 || + !Number.isSafeInteger(lookback) || lookback <= 0) { + throw new Error('TNK verification window requires a non-negative frontier and positive lookback'); + } + return { + fromSignedLength: Math.max(0, confirmedSignedLength - lookback), + minimumSignedLength: confirmedSignedLength, + }; +} + +export class RetryWork extends Error { + constructor(code, delaySeconds, transferObserved = false) { + super(code); + this.code = code; + this.delaySeconds = delaySeconds; + this.transferObserved = transferObserved; + } +} + +export class ReviewWork extends Error { + constructor(reason, evidence = null) { + super(reason); + this.reason = reason; + this.evidence = evidence; + } +} + +export function isBridgeFundingShortfall(error, rail) { + const output = `${error?.stdout ?? ''}\n${error?.stderr ?? ''}`.toLowerCase(); + const token = String(rail ?? '').toLowerCase(); + if (!['tap', 'tnk'].includes(token)) return false; + return output.includes(`not enough ${token}`) || + output.includes(`insufficient ${token}`) || + output.includes(`${token} balance is insufficient`); +} + +export function normalizeHex(value, bytes, label) { + const text = String(value ?? '').trim().toLowerCase(); + if (!new RegExp(`^0x[0-9a-f]{${bytes * 2}}$`).test(text)) throw new Error(`${label} is invalid`); + return text; +} + +export function normalizeHex64(value, label) { + const text = String(value ?? '').trim().replace(/^0x/i, '').toLowerCase(); + if (!/^[0-9a-f]{64}$/.test(text)) throw new Error(`${label} is invalid`); + return text; +} + +export function normalizeTnkAddress(value, network, label) { + const text = String(value ?? '').trim().toLowerCase(); + const prefix = network === 'mainnet' ? 'trac1' : network === 'testnet1' ? 'testtrac1' : null; + if (!prefix || !text.startsWith(prefix) || !/^[0-9a-z]{24,110}$/.test(text)) { + throw new Error(`${label} is invalid for ${network}`); + } + return text; +} + +export function validateTapBridgePreflight(report, { platformBuyer, collection, coreRpc }) { + const ethereumAccount = normalizeHex(report?.from, 20, 'TAP bridge account'); + const boundUser = normalizeHex64(report?.tap_account_binding?.user, 'TAP-bound platform buyer'); + if (ethereumAccount !== normalizeHex(collection, 20, 'TAP collection account') || + boundUser !== normalizeHex64(platformBuyer, 'platform buyer') || + String(report?.payment_config?.peer_rpc_url) !== coreRpc) { + throw new Error('TAP bridge preflight does not match the configured buyer'); + } + return { ethereumAccount, boundUser }; +} + +export function parseHexInt(value, label) { + if (typeof value !== 'string' || !/^0x[0-9a-f]+$/i.test(value)) throw new Error(`${label} is invalid`); + return BigInt(value); +} + +export function addressTopic(address) { + return `0x${'0'.repeat(24)}${normalizeHex(address, 20, 'address').slice(2)}`; +} + +export function summarizePayoutLiabilities(records, rail) { + let totalAu = 0n; + let heldAu = 0n; + let paidAu = 0n; + for (const entry of records) { + const value = entry?.value; + if (value?.type !== 'provider_payout_liability' || value.rail !== rail.toLowerCase()) { + throw new Error(`Invalid ${rail} payout liability record`); + } + const total = decimalInteger(value.total_au, 'total_au'); + const held = decimalInteger(value.held_au, 'held_au'); + const paid = decimalInteger(value.paid_cum_au, 'paid_cum_au'); + if (held > total || paid > total || held + paid > total) { + throw new Error(`Invalid ${rail} payout liability totals`); + } + totalAu += total; + heldAu += held; + paidAu += paid; + } + return { + totalAu, + heldAu, + paidAu, + unsettledAu: totalAu - paidAu, + payableAu: totalAu - heldAu - paidAu, + }; +} + +function decimalInteger(value, label) { + const text = String(value ?? ''); + if (!/^(0|[1-9][0-9]*)$/.test(text)) throw new Error(`${label} is invalid`); + return BigInt(text); +} + +function topicAddress(topic, label) { + const normalized = normalizeHex(topic, 32, label); + return `0x${normalized.slice(-40)}`; +} + +export function verifyTapTransferReceipt(receipt, { + transactionHash, + token, + destination, + amountBaseUnits, + latestBlock, + finalizedBlock, +}) { + const txHash = normalizeHex(transactionHash, 32, 'transaction hash'); + if (!receipt) throw new RetryWork('transfer_pending', 15); + if (normalizeHex(receipt.transactionHash, 32, 'receipt transaction hash') !== txHash || + parseHexInt(receipt.status, 'receipt status') !== 1n) { + throw new ReviewWork('malformed_transfer'); + } + const tokenAddress = normalizeHex(token, 20, 'token'); + const targetTopic = addressTopic(destination); + const tokenLogs = (Array.isArray(receipt.logs) ? receipt.logs : []).filter((log) => + String(log?.address ?? '').toLowerCase() === tokenAddress && + String(log?.topics?.[0] ?? '').toLowerCase() === ERC20_TRANSFER_TOPIC); + const destinationLogs = tokenLogs.filter((log) => String(log?.topics?.[2] ?? '').toLowerCase() === targetTopic); + const amount = BigInt(String(amountBaseUnits)); + const blockNumber = parseHexInt(receipt.blockNumber, 'receipt block number'); + const current = BigInt(latestBlock); + const finalized = BigInt(finalizedBlock); + if (blockNumber > finalized) throw new RetryWork('awaiting_finality', 20, true); + const exact = destinationLogs.find((log) => parseHexInt(log.data, 'transfer amount') === amount); + if (!exact) { + if (destinationLogs.length > 0) { + throw new ReviewWork('amount_mismatch', tapTransferEvidence(receipt, destinationLogs[0], { + txHash, + latestBlock, + finalizedBlock, + })); + } + if (tokenLogs.length > 0) { + throw new ReviewWork('wrong_destination', tapTransferEvidence(receipt, tokenLogs[0], { + txHash, + latestBlock, + finalizedBlock, + })); + } + throw new ReviewWork('wrong_token'); + } + const logIndex = Number(parseHexInt(exact.logIndex, 'transfer log index')); + if (!Number.isSafeInteger(logIndex)) throw new ReviewWork('malformed_transfer'); + return { + rail: 'TAP', + transactionHash: txHash, + logIndex, + blockNumber, + blockHash: normalizeHex(receipt.blockHash, 32, 'receipt block hash'), + fromAddress: topicAddress(exact.topics[1], 'transfer sender'), + toAddress: normalizeHex(destination, 20, 'destination'), + tokenAmountBaseUnits: amount, + confirmations: Number(current - blockNumber + 1n), + finalized: true, + observedAt: new Date(), + externalRecordKey: `tap/${txHash}/${logIndex}`, + }; +} + +function tapTransferEvidence(receipt, log, { txHash, latestBlock, finalizedBlock }) { + try { + const blockNumber = parseHexInt(receipt.blockNumber, 'receipt block number'); + const current = BigInt(latestBlock); + const finalized = BigInt(finalizedBlock); + const logIndex = Number(parseHexInt(log.logIndex, 'transfer log index')); + if (!Number.isSafeInteger(logIndex)) throw new Error('invalid log index'); + return { + rail: 'TAP', + transactionHash: txHash, + logIndex, + blockNumber, + blockHash: normalizeHex(receipt.blockHash, 32, 'receipt block hash'), + fromAddress: topicAddress(log.topics[1], 'transfer sender'), + toAddress: topicAddress(log.topics[2], 'transfer destination'), + tokenAmountBaseUnits: parseHexInt(log.data, 'transfer amount'), + confirmations: Number(current >= blockNumber ? current - blockNumber + 1n : 0n), + finalized: blockNumber <= finalized, + observedAt: new Date(), + externalRecordKey: `tap/${txHash}/${logIndex}`, + }; + } catch { + throw new ReviewWork('malformed_transfer'); + } +} diff --git a/intercom/scripts/tnk-deposit-watcher.mjs b/intercom/scripts/tnk-deposit-watcher.mjs index d338b2be..1070c656 100644 --- a/intercom/scripts/tnk-deposit-watcher.mjs +++ b/intercom/scripts/tnk-deposit-watcher.mjs @@ -17,9 +17,11 @@ import { readLocalNetwork, sleep, } from './msb-local-common.mjs'; +import { waitForMinimumSignedLength } from './msb-reader-catchup.mjs'; const scriptPath = fileURLToPath(import.meta.url); const DEFAULT_CURSOR = path.resolve('.mayhem-local', 'tnk-deposit-watcher.json'); +const MSB_CLOSE_TIMEOUT_MS = 5_000; const MAX_UNMATCHED_TRANSFERS = 1000; const isHex64 = (value) => /^[0-9a-f]{64}$/i.test(String(value ?? '')); @@ -100,6 +102,21 @@ export async function resolveActiveBillingEpoch(explicitEpoch, rpcUrl, { return epoch; } +export async function resolveMinimumMsbSignedLength(peerRpc, fallback, { + fetchImpl = globalThis.fetch, +} = {}) { + if (!Number.isSafeInteger(fallback) || fallback < 1) { + throw new Error('MSB reader fallback length must be a positive safe integer'); + } + if (!peerRpc) return fallback; + const status = await fetchJson(new URL('status', ensureRpcBase(peerRpc)), fetchImpl); + const advertised = Number(status?.msb?.signedLength); + if (!Number.isSafeInteger(advertised) || advertised < 1) { + throw new Error('Canonical peer did not report a valid MSB signed length'); + } + return Math.max(fallback, advertised); +} + function readJsonIfExists(filePath, fallback) { if (!filePath || !fs.existsSync(filePath)) return fallback; return JSON.parse(fs.readFileSync(filePath, 'utf8')); @@ -110,6 +127,24 @@ function writeJson(filePath, value) { fs.writeFileSync(filePath, `${JSON.stringify(value, null, 2)}\n`); } +async function closeMsbForExit(msb) { + let timer; + try { + await Promise.race([ + Promise.resolve().then(() => msb.close()), + new Promise((resolve) => { + timer = setTimeout(resolve, MSB_CLOSE_TIMEOUT_MS); + timer.unref?.(); + }), + ]); + } catch (_error) { + // Deposit evidence and the cursor are made durable before shutdown. A + // transport close failure must not suppress an already matched deposit. + } finally { + if (timer) clearTimeout(timer); + } +} + function normalizeCursor(raw) { const cursor = raw && typeof raw === 'object' ? raw : {}; return { @@ -399,17 +434,23 @@ export async function waitForDepositState(match, { return { verified: false, state }; } -async function scanMsbTransfers(msb, { +export async function scanMsbTransfers(msb, { fromSignedLength, finalitySignedLengths, chunkSize, timeoutSec, + minimumSignedLength = fromSignedLength + 1, + matchHash = null, + sleepImpl = sleep, }) { - let confirmedLength = msb.state.getSignedLength(); - for (let waited = 0; confirmedLength === 0 && waited < timeoutSec; waited += 1) { - await sleep(1000); - confirmedLength = msb.state.getSignedLength(); - } + // ready() means the local Core opened; it does not mean a reused reader + // store has caught up to the network. Waiting only for nonzero permanently + // stranded an old cursor after a long service stop. + const confirmedLength = await waitForMinimumSignedLength(msb.state, { + minimumSignedLength, + timeoutSec, + sleepImpl, + }); const safeEnd = Math.max(0, confirmedLength - finalitySignedLengths); if (safeEnd <= fromSignedLength) { return { confirmedLength, safeEnd, transfers: [] }; @@ -420,6 +461,7 @@ async function scanMsbTransfers(msb, { const end = Math.min(start + chunkSize, safeEnd); const { hashes } = await msb.getTxHashes(start, end); for (const hashEntry of hashes) { + if (matchHash && String(hashEntry.hash).toLowerCase() !== matchHash) continue; const details = await msb.getTxDetails(hashEntry.hash); const transfer = transferFromTxDetails(hashEntry, details); if (transfer) transfers.push(transfer); @@ -511,24 +553,21 @@ async function main() { const msb = new MainSettlementBus(config); await msb.ready(); - let scan; - let fromSignedLength; - try { - const confirmedLength = msb.state.getSignedLength(); - fromSignedLength = args['from-signed-length'] !== undefined - ? parsePositiveInt(args['from-signed-length'], '--from-signed-length') - : cursor.next_signed_length ?? Math.max(0, confirmedLength - lookback); - scan = await scanMsbTransfers(msb, { - fromSignedLength, - finalitySignedLengths, - chunkSize, - timeoutSec, - }); - } finally { - try { - await msb.close(); - } catch (_error) {} - } + const confirmedLength = msb.state.getSignedLength(); + const fromSignedLength = args['from-signed-length'] !== undefined + ? parsePositiveInt(args['from-signed-length'], '--from-signed-length') + : cursor.next_signed_length ?? Math.max(0, confirmedLength - lookback); + const minimumSignedLength = await resolveMinimumMsbSignedLength( + adminRpcUrl, + fromSignedLength + 1, + ); + const scan = await scanMsbTransfers(msb, { + fromSignedLength, + finalitySignedLengths, + chunkSize, + timeoutSec, + minimumSignedLength, + }); const pendingEntries = await readPendingIntents({ peerRpc: adminRpcUrl, @@ -682,12 +721,18 @@ async function main() { } } + // Do not close the reader until every matched deposit and the next cursor + // are durable. Some Hypercore transports can reject outstanding reads while + // closing; that must never prevent a confirmed deposit from being posted. + await closeMsbForExit(msb); if (!report.ok) process.exit(2); } if (process.argv[1] && path.resolve(process.argv[1]) === scriptPath) { - main().catch((error) => { - console.error(error?.stack || error?.message || String(error)); - process.exit(1); - }); + main() + .then(() => process.exit(0)) + .catch((error) => { + console.error(error?.stack || error?.message || String(error)); + process.exit(1); + }); } diff --git a/intercom/src/msb-settlement-transfer-helper.js b/intercom/src/msb-settlement-transfer-helper.js index 1f36e112..13cde885 100644 --- a/intercom/src/msb-settlement-transfer-helper.js +++ b/intercom/src/msb-settlement-transfer-helper.js @@ -31,6 +31,26 @@ const JOURNAL_SCHEMA_VERSION = 1; const MAX_TRANSFER_AMOUNT = 0xffffffffffffffffffffffffffffffffn; const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms)); +const MSB_CLOSE_TIMEOUT_MS = 5_000; + +async function closeMsbBounded(msb, timeoutMs = MSB_CLOSE_TIMEOUT_MS) { + let timer; + try { + await Promise.race([ + Promise.resolve().then(() => msb.close()), + new Promise((resolve) => { + timer = setTimeout(resolve, timeoutMs); + timer.unref?.(); + }), + ]); + } catch (_error) { + // The operation result is already durable before shutdown begins. A + // transport close failure must not turn a confirmed transfer into a + // permanently running payment job. + } finally { + if (timer) clearTimeout(timer); + } +} function fail(message) { throw new Error(message); @@ -54,7 +74,7 @@ function takeOption(args, name) { return value; } -function readWalletPasswordFile(file) { +export function readWalletPasswordFile(file) { if (file === null) return null; let stat; try { @@ -68,7 +88,10 @@ function readWalletPasswordFile(file) { if (!isWindows && (stat.mode & 0o077) !== 0) { fail('wallet password file must be owner-only (0600).'); } - return fs.readFileSync(file, 'utf8'); + // Password files created by standard shell tooling commonly end in one line + // break. Treat that delimiter as file formatting, while preserving every + // other byte so passwords containing spaces remain valid. + return fs.readFileSync(file, 'utf8').replace(/\r?\n$/, ''); } function normalizeNetwork(value) { @@ -652,9 +675,7 @@ export async function runSettlementTransferHelper(rawArgs, options = {}) { console.log = originalLog; console.info = originalInfo; if (!options.keepOpen && opened) { - try { - await msb.close(); - } catch (_error) {} + await closeMsbBounded(msb); } } } @@ -762,9 +783,7 @@ export async function runPreparedSettlementTransferHelper(command, rawArgs, opti console.log = originalLog; console.info = originalInfo; if (!options.keepOpen && opened) { - try { - await msb.close(); - } catch (_error) {} + await closeMsbBounded(msb); } } } @@ -919,9 +938,7 @@ export async function runTransferHelper(rawArgs, options = {}) { console.log = originalLog; console.info = originalInfo; if (!options.keepOpen && opened) { - try { - await msb.close(); - } catch (_error) {} + await closeMsbBounded(msb); } } } diff --git a/intercom/src/release-identity.js b/intercom/src/release-identity.js index 0c7903bb..0a46433c 100644 --- a/intercom/src/release-identity.js +++ b/intercom/src/release-identity.js @@ -8,6 +8,9 @@ export const CONTRACT_CODE_PATHS = Object.freeze([ 'contract/contract.js', 'contract/history/v23.js', 'contract/history/v24.js', + 'contract/history/v25.js', + 'contract/history/v26.js', + 'contract/history/v27.js', 'contract/protocol.js', 'features/mayhem/index.js', 'trac/trac-peer/src/artifacts/contract.js', diff --git a/intercom/tests/canonical-history-replay.test.js b/intercom/tests/canonical-history-replay.test.js index ec32d762..e8e63e46 100644 --- a/intercom/tests/canonical-history-replay.test.js +++ b/intercom/tests/canonical-history-replay.test.js @@ -13,7 +13,7 @@ import { FeatureOperation, FeatureCheck } from 'trac-peer/src/operations/feature import { canonicalReplayContext, consumeCanonicalReplayContext, canonicalReplayView } from 'trac-peer/src/base/canonical-replay.js'; import {adminWriterDiagnostics,appliedViewProof} from '../src/rpc.js'; import MayhemContract from '../contract/contract.js'; -import Contract23 from '../contract/history/v23.js';import Contract24 from '../contract/history/v24.js'; +import Contract23 from '../contract/history/v23.js';import Contract24 from '../contract/history/v24.js';import Contract25 from '../contract/history/v25.js';import Contract26 from '../contract/history/v26.js';import Contract27 from '../contract/history/v27.js'; import { MemoryStorage } from './helpers/contract.js'; const delay=ms=>new Promise(r=>setTimeout(r,ms)); const protocol={featMaxBytes:()=>1e6}; @@ -25,7 +25,7 @@ async function fixture(version,kind='feature'){ const config={bootstrap:'43'.repeat(32),maxMsbSignedLength:1e9,maxMsbSignedLengthFutureDelta:100000,maxMsbApplyOperationBytes:4096};let msbEntry; const msbClient={networkId:918,bootstrapHex:'44'.repeat(32),getTxvHex:async()=> '45'.repeat(32),getFee:()=>FEE,pubKeyHexToAddress:toAddress,addressToPubKeyHex:a=>b4a.toString(PeerWallet.decodeBech32mSafe(a),'hex'),getSignedLength:()=>100,waitForSignedLengthAtLeast:async()=>{},getSignedAtLength:async()=>msbEntry}; const peer={wallet,writerLocalKey:'42'.repeat(32),config,msbClient};const proto=new MayhemProtocol(peer,{},config); - const Implementation=version===23?Contract23:Contract24;const old=new Implementation(proto,{});let store,base,lastNode; + const Implementation=version===23?Contract23:version===24?Contract24:version===25?Contract25:version===26?Contract26:Contract27;const old=new Implementation(proto,{});let store,base,lastNode; const applyHandler=(contract,view)=>kind==='feature'?handler(wallet,contract,view):new TxOperation(new TxCheck(),{wallet,protocolInstance:proto,contractInstance:contract,canonicalView:view,msbClient,config}); async function open(){store=new Corestore(dir);base=new Autobase(store,null,{ackInterval:0,valueEncoding:'json',open:s=>new Hyperbee(s.get('view'),{extension:false,keyEncoding:'utf-8',valueEncoding:'json'}),async apply(nodes,view){const batch=view.batch();for(const node of nodes){if(node.value?.type==='seed'){await batch.put('admin',wallet.publicKey);continue;}if(node.value){lastNode=node;await applyHandler(old,view).handle(node.value,batch,base,node);}}await batch.flush();await batch.close();}});await base.ready();peer.base=base;} await open();await base.append({type:'seed'});await base.update(); @@ -35,7 +35,7 @@ async function fixture(version,kind='feature'){ op={type:'feature',key,value:{dispatch:{type:'mayhem_feature',contract_version:version,key,address:wallet.publicKey,value,nonce,hash:wallet.sign(JSON.stringify(value)+nonce)}}}; }else{ const original=proto.versionedTransactionObject;proto.versionedTransactionObject=d=>({...d,value:{...d.value,contract_version:version}}); - const paid=await proto.preparePaidTransaction({type:'setParams',value:{op:'set_params',submitted_at:0,effective_at:86400,values:{price_min_bps:1}}});proto.versionedTransactionObject=original; + const paid=await proto.preparePaidTransaction({type:'setParams',value:{op:'set_params',submitted_at:0,effective_at:86400,values:{price_min_bps:version>=25?2500:1}}});proto.versionedTransactionObject=original; const txo=Object.fromEntries(Object.entries(paid.payload.txo).map(([k,v])=>[k,b4a.from(v,'hex')]));txo.va=b4a.from(toAddress(wallet.publicKey));msbEntry={value:safeEncodeApplyOperation({type:12,address:b4a.from(paid.payload.address),txo})}; op={type:'tx',key:paid.surrogate.tx,value:{dispatch:paid.dispatch,ipk:wallet.publicKey,wp:wallet.publicKey,msbsl:100}}; } @@ -47,23 +47,23 @@ async function fixture(version,kind='feature'){ async function replay(){const replayStore=new Corestore(path.join(dir,'replay'));const view=new Hyperbee(replayStore.get({name:'view'}),{extension:false,keyEncoding:'utf-8',valueEncoding:'json'});await view.ready();await view.put('admin',wallet.publicKey);const batch=view.batch();const current=new MayhemContract(proto,{});await applyHandler(current,base.view).handle(op,batch,base,node);await batch.flush();await batch.close();assert.equal((await view.core.treeHash()).toString('hex'),expectedHash,'Full authenticated tree must match historical writer');const once=view.core.length;const duplicate=view.batch();await applyHandler(current,base.view).handle(op,duplicate,base,node);await duplicate.flush();await duplicate.close();assert.equal(view.core.length,once);const state=await collect(view);await view.close();await replayStore.close();return state;} return {wallet,op,node,proto,replay,get view(){return base.view;},expected,before:()=>new MemoryStorage({admin:wallet.publicKey}),async close(){await base.close();await store.close();fs.rmSync(dir,{recursive:true,force:true});}}; } -for(const version of [23,24])for(const kind of ['feature','tx'])test(`persisted signed v${version} ${kind} history preserves full tree under25 exactly once`,async()=>{ - const f=await fixture(version,kind);try{const replayed=await f.replay();assert.equal(replayed.snapshotBytes(),f.expected.snapshotBytes());if(kind==='tx')assert.equal((await replayed.get('params/price_min_bps')).value.pending.value,1,'Historical pre25 pricing bound must not be reinterpreted');}finally{await f.close();} +for(const version of [23,24,25,26,27])for(const kind of ['feature','tx'])test(`persisted signed v${version} ${kind} history preserves full tree under28 exactly once`,async()=>{ + const f=await fixture(version,kind);try{const replayed=await f.replay();assert.equal(replayed.snapshotBytes(),f.expected.snapshotBytes());if(kind==='tx')assert.equal((await replayed.get('params/price_min_bps')).value.pending.value,version>=25?2500:1,'Historical pricing bound must not be reinterpreted');}finally{await f.close();} }); test('fresh, unbound, mutated and wrong-version historical Feature calls remain rejected',async()=>{ const f=await fixture(24);try{const c=new MayhemContract({},{}); - await assert.rejects(c.execute(f.op,f.before()),/expected CONTRACT_VERSION 25, got 24/); - await assert.rejects(c.execute(f.op,f.before(),{replay:true}),/expected CONTRACT_VERSION 25, got 24/); + await assert.rejects(c.execute(f.op,f.before()),/expected CONTRACT_VERSION 28, got 24/); + await assert.rejects(c.execute(f.op,f.before(),{replay:true}),/expected CONTRACT_VERSION 28, got 24/); const fresh=structuredClone(f.op);fresh.value.dispatch.nonce='fresh';fresh.value.dispatch.hash=f.wallet.sign(JSON.stringify(fresh.value.dispatch.value)+'fresh'); - const unsignedNode={...f.node,value:fresh};await assert.rejects(handler(f.wallet,c,f.view).handle(fresh,f.before(),{},unsignedNode),/expected CONTRACT_VERSION 25, got 24/); - const bad=structuredClone(f.op);bad.value.dispatch.contract_version=23;await assert.rejects(handler(f.wallet,c,f.view).handle(bad,f.before(),{},{...f.node,value:bad}),/expected CONTRACT_VERSION 25, got 23/); + const unsignedNode={...f.node,value:fresh};await assert.rejects(handler(f.wallet,c,f.view).handle(fresh,f.before(),{},unsignedNode),/expected CONTRACT_VERSION 28, got 24/); + const bad=structuredClone(f.op);bad.value.dispatch.contract_version=23;await assert.rejects(handler(f.wallet,c,f.view).handle(bad,f.before(),{},{...f.node,value:bad}),/expected CONTRACT_VERSION 28, got 23/); const storage=f.before(),token=await canonicalReplayContext(f.op,storage,f.node,f.view);assert(token);assert.equal(consumeCanonicalReplayContext(token,f.op,f.before()),false);assert.equal(consumeCanonicalReplayContext(token,f.op,storage),false,'failed use consumes capability'); const token2=await canonicalReplayContext(f.op,storage,f.node,f.view);f.op.value.dispatch.key+='tampered';assert.equal(consumeCanonicalReplayContext(token2,f.op,storage),false); }finally{await f.close();} }); test('ordinary historical TX requires exact canonical index and original signed bytes',async()=>{ - const f=await fixture(24,'tx');try{const c=new MayhemContract(f.proto,{}),storage=f.before();await assert.rejects(c.execute(f.op,storage),/expected CONTRACT_VERSION 25, got 24/); + const f=await fixture(24,'tx');try{const c=new MayhemContract(f.proto,{}),storage=f.before();await assert.rejects(c.execute(f.op,storage),/expected CONTRACT_VERSION 28, got 24/); const noIndex={core:f.view.core,checkout:length=>{const view=f.view.checkout(length);return {close:()=>view.close(),get:async key=>key.startsWith('tx/')?null:view.get(key)};}}; assert.equal(await canonicalReplayContext(f.op,storage,f.node,noIndex),null); const badRecord={core:f.view.core,checkout:length=>{const view=f.view.checkout(length);return {close:()=>view.close(),get:async key=>{const row=await view.get(key);return key.startsWith('txi/')?{...row,value:{...row.value,ipk:'00'.repeat(32)}}:row;}};}}; @@ -73,10 +73,10 @@ test('ordinary historical TX requires exact canonical index and original signed }finally{await f.close();} }); -for (const version of [23, 24]) test(`persisted remote v${version} Feature reads signed default view ahead of apply batch`, async () => { +for (const version of [23, 24, 25, 26, 27]) test(`persisted remote v${version} Feature reads signed default view ahead of apply batch`, async () => { const dir=fs.mkdtempSync(path.join(os.tmpdir(),'mayhem-remote-history-')); const wallet=new Wallet();await wallet.ready;await wallet.generateKeyPair(); - const old=new (version===23?Contract23:Contract24)(protocol,{}); + const old=new (version===23?Contract23:version===24?Contract24:version===25?Contract25:version===26?Contract26:Contract27)(protocol,{}); const opened=new Set(), streams=[]; async function open(name,key,contract,broken=false){ const store=new Corestore(path.join(dir,name));let base; @@ -115,7 +115,7 @@ for (const version of [23, 24]) test(`persisted remote v${version} Feature reads await writer.base.append(op);await writer.base.update(); const expected=(await writer.base.view.core.treeHash()).toString('hex'); const broken=await open('broken',writer.base.key,new MayhemContract(protocol,{}),true);const disconnectBroken=connect(writer,broken); - await until(()=>broken.error);assert.match(broken.error.message,new RegExp('expected CONTRACT_VERSION 25, got '+version)); + await until(()=>broken.error);assert.match(broken.error.message,new RegExp('expected CONTRACT_VERSION 28, got '+version)); assert(broken.observations.some(x=>x.remote&&x.inputSignedLength>=x.nodeLength&&x.publicLength===x.applyLength)); disconnectBroken();await close(broken); const current=new MayhemContract(protocol,{}),fixed=await open('fixed',writer.base.key,current);connect(writer,fixed); diff --git a/intercom/tests/contract-epoch.test.js b/intercom/tests/contract-epoch.test.js index c6307e66..36023c5b 100644 --- a/intercom/tests/contract-epoch.test.js +++ b/intercom/tests/contract-epoch.test.js @@ -97,6 +97,8 @@ const defaultReceiptBody = (user, provider) => ({ locked_per_req_au: '0', locked_min_session_au: '0', served_ctx: 32768, + compute_ms: 1_800_000, + capacity_slots: 1, ctx_bracket: 'le32k', ctx_bracket_table_ver: 1, rules_ver: 1, @@ -468,6 +470,8 @@ const signedReceipt = (user, provider, enclave, overrides = {}) => { locked_per_req_au: '0', locked_min_session_au: '0', served_ctx: 32768, + compute_ms: 1_800_000, + capacity_slots: 1, ctx_bracket: 'le32k', ctx_bracket_table_ver: 1, rules_ver: 1, diff --git a/intercom/tests/contract-fiat-settlement.test.js b/intercom/tests/contract-fiat-settlement.test.js index 595082b2..0b2089c6 100644 --- a/intercom/tests/contract-fiat-settlement.test.js +++ b/intercom/tests/contract-fiat-settlement.test.js @@ -542,6 +542,14 @@ test('fiat outputs settle independently and an earlier recipient survives later assert.equal(retry.ok, true, retry.message); assert.equal(retry.idempotent, true); + const feeBefore = (await ctx.storage.get('fee/fiat/cum')).value; + await ctx.storage.put('fee/fiat/cum', { + ...feeBefore, + cum_au: (BigInt(feeBefore.cum_au) + 3n * CENT_AU).toString(), + settled_cum_au: (BigInt(feeBefore.settled_cum_au) + 3n * CENT_AU).toString(), + updated_epoch: 2, + }); + const feeOutput = ctx.outputs[1]; assert.equal((await prepareEconomicOutput(ctx, plan, feeOutput)).ok, true); const feeAttempt = await prepareAttempt(ctx, plan, feeOutput, 1); @@ -559,6 +567,12 @@ test('fiat outputs settle independently and an earlier recipient survives later (await settleOutput(ctx, plan, feeOutput, feeAttempt.value, feeTransfer)).result.ok, true ); + const feeAfter = (await ctx.storage.get('fee/fiat/cum')).value; + assert.equal(feeAfter.swept_cum_au, feeOutput.paid_au); + assert.equal( + BigInt(feeAfter.cum_au) - BigInt(feeAfter.swept_cum_au), + 3n * CENT_AU + ); assert.equal((await closeEpoch(ctx, plan)).ok, true); }); diff --git a/intercom/tests/contract-market-activity.test.js b/intercom/tests/contract-market-activity.test.js index 86cde96d..36db8915 100644 --- a/intercom/tests/contract-market-activity.test.js +++ b/intercom/tests/contract-market-activity.test.js @@ -11,32 +11,81 @@ const calibration = { { unit: 'output_token', units: '100', work_us: '1000000' }, ], }; -async function market({ calibrated = true, modelClass = 'text-generation', units = ['input_token', 'output_token'], fixedTerms = false } = {}) { + +async function market({ + calibrated = false, + modelClass = 'text-generation', + units = ['input_token', 'output_token'], + fixedTerms = false, +} = {}) { const admin = await makeIdentity(); const storage = new MemoryStorage({ admin: admin.publicKey }); const contract = new MayhemContract({}, {}); - contract.storage = storage; contract.address = admin.publicKey; contract.tx = 'aa'.repeat(32); + contract.storage = storage; + contract.address = admin.publicKey; + contract.tx = 'aa'.repeat(32); const ctxBracket = modelClass === 'text-generation' ? 'le8k' : null; const priceKey = `price/${ENCLAVE}${ctxBracket ? '/'+ctxBracket : ''}`; const rates = units.map((unit) => ({ unit, per_unit_au: '1000000', granularity: 1 })); await storage.put(`enclave/${ENCLAVE}`, { - enclave_id: ENCLAVE, model_id: MODEL, model_class: modelClass, status: 'active', + enclave_id: ENCLAVE, + model_id: MODEL, + model_class: modelClass, + status: 'active', caps: { ctx: 8192, modality_set: ['text'] }, }); - await storage.put(`modelref/${MODEL}`, { model_id: MODEL, model_class: modelClass, - ver: 1, rate_map: rates, ...(calibrated ? { activity_calibration: calibration } : {}) }); - await seedCurrentAdminPrice(storage, { enclaveId: ENCLAVE, modelId: MODEL, - admin: admin.publicKey, rateMap: rates, perReqAu: fixedTerms ? 1000000 : 0, minSessionAu: fixedTerms ? 2000000 : 0, ctxBracket, ctxBracketTableVer: ctxBracket ? 1 : null }); - async function step(epoch, counts, { gross = '100', providers = 2, seconds = 3600, persist = true } = {}) { - const row = { enclave_id: ENCLAVE, + await storage.put(`modelref/${MODEL}`, { + model_id: MODEL, + model_class: modelClass, + ver: 1, + rate_map: rates, + ...(calibrated ? { activity_calibration: calibration } : {}), + }); + await seedCurrentAdminPrice(storage, { + enclaveId: ENCLAVE, + modelId: MODEL, + admin: admin.publicKey, + rateMap: rates, + perReqAu: fixedTerms ? 1000000 : 0, + minSessionAu: fixedTerms ? 2000000 : 0, + ctxBracket, + ctxBracketTableVer: ctxBracket ? 1 : null, + }); + + async function step(epoch, utilizationBps, { + gross = '100', + providers = 1, + capacitySlots = providers, + seconds = 3600, + computeMs = null, + legacyReceiptCount = 0, + counts = { [units[0]]: '1' }, + persist = true, + } = {}) { + const calculatedBusyMs = BigInt(seconds) * 1000n * BigInt(capacitySlots) * + BigInt(utilizationBps) / 10000n; + const busyMs = computeMs === null + ? (calculatedBusyMs > 0n ? calculatedBusyMs : 1n).toString() + : String(computeMs); + const row = { + enclave_id: ENCLAVE, ...(ctxBracket ? { ctx_bracket: ctxBracket, ctx_bracket_table_ver: 1 } : {}), - demand_au: gross, session_count: 2, provider_count: providers }; + demand_au: gross, + session_count: 2, + provider_count: providers, + compute_ms: busyMs, + capacity_slot_count: capacitySlots, + legacy_receipt_count: legacyReceiptCount, + }; const usage = contract.aggregateMarketUsageEntries([row]); assert.ok(!(usage instanceof Error), usage.message); const key = contract.priceMarketKey(ENCLAVE, ctxBracket); const result = await contract.computeMarketPriceUpdates(usage, { - epoch, at: epoch * seconds, epochSeconds: seconds, - canonicalActivity: new Map([[key, { ...row, settled_usage: counts }]]), includeDormant: true, + epoch, + at: epoch * seconds, + epochSeconds: seconds, + canonicalActivity: new Map([[key, { ...row, settled_usage: counts }]]), + includeDormant: true, }); assert.ok(!(result instanceof Error), result.message); assert.equal(result.length, 1); @@ -47,276 +96,386 @@ async function market({ calibrated = true, modelClass = 'text-generation', units } return result[0]; } + return { contract, storage, admin, step, priceKey }; } + const amount = (update) => BigInt(update.rate_map[0].per_unit_au); -test('calibrated prefill/decode work drives direction independently of gross spend and provider count', async () => { - const a = await market(); const b = await market(); - await a.step(1, { input_token: '1000', output_token: '100' }); - await b.step(1, { input_token: '1000', output_token: '100' }, { gross: '999999999', providers: 200 }); - const up = await a.step(2, { input_token: '2000', output_token: '200' }, { gross: '1' }); - const expensive = await b.step(2, { input_token: '2000', output_token: '200' }, { gross: '999999999999', providers: 500 }); - assert.equal(up.record.market.calibrated_work_ps, '4000000000000'); - assert.equal(up.momentum_bps, 20000); assert.equal(amount(up), 1100000n); - assert.deepEqual(up.rate_map, expensive.rate_map); - const down = await a.step(3, { input_token: '500', output_token: '50' }, { gross: '999999999' }); - assert.ok(down.momentum_bps < 10000); assert.equal(amount(down), 990000n); - assert.ok(!Object.hasOwn(down.record.market.constants, 'provider_epoch_target_au')); +test('20 and 80 percent boundaries apply exact fixed price steps', async () => { + for (const [utilization, expected, multiplier] of [ + [0, 900000n, 9000], + [2000, 900000n, 9000], + [2001, 1000000n, 10000], + [7999, 1000000n, 10000], + [8000, 1100000n, 11000], + [10000, 1100000n, 11000], + ]) { + const ctx = await market(); + const update = await ctx.step(1, utilization); + assert.equal(amount(update), expected, String(utilization)); + assert.equal(update.utilization_bps, utilization); + assert.equal(update.multiplier_bps, multiplier); + assert.equal(update.record.market.activity_basis, 'signed_slot_time_v1'); + assert.equal(update.record.price_source, 'market_utilization'); + } }); -test('equal calibrated work with a different prompt/decode mix keeps the same price', async () => { - const ctx = await market(); - await ctx.step(1, { input_token: '1000', output_token: '100' }); - const stable = await ctx.step(2, { input_token: '2000' }); - assert.equal(stable.momentum_bps, 10000); assert.equal(amount(stable), 1000000n); -}); +test('sustained saturation rises every epoch and sustained idleness falls every epoch', async () => { + const rising = await market(); + let prior = 1000000n; + for (let epoch = 1; epoch <= 20; epoch++) { + const update = await rising.step(epoch, 8000); + assert.ok(amount(update) >= prior); + assert.ok(amount(update) - prior <= prior / 10n + 1n); + prior = amount(update); + } + assert.equal(prior, 4000000n); -test('missing calibration uses dimension-relative momentum for every model class', async () => { - for (const [modelClass, units] of [ - ['text-generation', ['input_token','output_token']], ['embedding', ['input_token','embedding']], - ['workflow', ['pixel_frame']], ['image-generation', ['image','step']], - ['video-generation', ['frame','video_second']], ['tts', ['audio_second','input_character']], - ['stt', ['audio_second']], ['audio-generation', ['audio_second','input_character']], - ['music-generation', ['audio_second','input_character']], - ]) { - const ctx = await market({ calibrated: false, modelClass, units }); - const first = Object.fromEntries(units.map((u,i) => [u, String((i+1)*100)])); - const twice = Object.fromEntries(units.map((u,i) => [u, String((i+1)*200)])); - await ctx.step(1, first); - const up = await ctx.step(2, twice); - assert.equal(up.record.market.activity_basis, 'relative_dimension_vector_v1', modelClass); - assert.equal(up.momentum_bps, 20000, modelClass); assert.equal(up.frozen, false, modelClass); - const down = await ctx.step(3, first); - assert.ok(amount(down) < amount(up), modelClass); + const falling = await market(); + prior = 1000000n; + for (let epoch = 1; epoch <= 30; epoch++) { + const update = await falling.step(epoch, 2000); + assert.ok(amount(update) <= prior); + assert.ok(prior - amount(update) <= prior / 10n + 1n); + prior = amount(update); } + assert.equal(prior, 250000n); }); -test('fixed work per second is invariant to epoch length, prices, and large exact counts', async () => { - const ctx = await market(); - await ctx.step(1, { input_token: '9007199254740993000' }, { seconds: 3600 }); - const sameRate = await ctx.step(2, { input_token: '4503599627370496500' }, { seconds: 1800 }); - assert.equal(sameRate.momentum_bps, 10000); assert.equal(amount(sameRate), 1000000n); +test('price direction depends on absolute utilization, not the previous hour', async () => { + const high = await market(); + const highOne = await high.step(1, 8500); + const highTwo = await high.step(2, 8500); + assert.equal(amount(highOne), 1100000n); + assert.equal(amount(highTwo), 1210000n); + + const low = await market(); + const lowOne = await low.step(1, 1500); + const lowTwo = await low.step(2, 1500); + assert.equal(amount(lowOne), 900000n); + assert.equal(amount(lowTwo), 810000n); }); -test('successive falls reach the lower band and consecutive empty epochs keep decreasing', async () => { +test('an epoch containing a pre-v27 receipt settles while holding price once', async () => { const ctx = await market(); - await ctx.step(1, { input_token: '1000' }); - let last; - for (let epoch = 2; epoch < 50; epoch++) { - last = await ctx.step(epoch, { input_token: (1000n * 4n**BigInt(epoch)).toString() }); - assert.ok(amount(last) <= 4000000n); - } - assert.equal(amount(last), 4000000n); - for (let epoch = 50; epoch < 95; epoch++) { - const next = await ctx.step(epoch, { input_token: (1000n * 4n**BigInt(98-epoch)).toString() }); - assert.ok(amount(next) >= 250000n); - assert.ok(amount(last)-amount(next) <= amount(last)/10n); - last = next; - } - assert.equal(amount(last), 250000n); - const dormant = await market(); - await dormant.step(1, { input_token: '1000' }); - const zero = await dormant.step(2, {}); - const equalZero = await dormant.step(3, {}); - assert.equal(amount(zero), 900000n); - assert.equal(amount(equalZero), 810000n); - assert.equal(equalZero.momentum_bps, 0); -}); + const first = await ctx.step(1, 8000); + assert.equal(amount(first), 1100000n); -test('canonical work excludes billing baselines and unpriced injected dimensions', () => { - const c = new MayhemContract({}, {}); - const body = { usage: { input_token: 120, output_token: 20, invented_work: 9999999 }, - billing_prior_usage: { input_token: 100, output_token: 10 }, - locked_rate_map: [{ unit: 'input_token', per_unit_au: '1', granularity: 1 }, - { unit: 'output_token', per_unit_au: '1', granularity: 1 }] }; - assert.deepEqual(c.incrementalSettledUsage(body), { input_token: '20', output_token: '10' }); - assert.ok(c.incrementalSettledUsage({ ...body, usage: { input_token: 99 } }) instanceof Error); - const whole = { input_token: '30', output_token: '20' }; - const pages = c.addSettledUsage({ input_token: '10', output_token: '13' }, - { input_token: '20', output_token: '7' }); - assert.equal(c.calibratedActivityWork(whole, calibration), c.calibratedActivityWork(pages, calibration)); -}); + const held = await ctx.step(2, 0, { + computeMs: 0, + capacitySlots: 0, + legacyReceiptCount: 1, + }); + assert.equal(amount(held), 1100000n); + assert.equal(held.utilization_bps, null); + assert.equal(held.multiplier_bps, 10000); + assert.equal(held.record.market.activity_basis, 'legacy_receipt_hold_v1'); + assert.equal(held.record.market.legacy_receipt_count, 1); -test('calibration changes bootstrap a new baseline and cannot create a price jump', async () => { - const ctx = await market(); await ctx.step(1, { input_token: '1000' }); - const ref = (await ctx.storage.get(`modelref/${MODEL}`)).value; - ref.activity_calibration.dimensions[0].units = '1'; - await ctx.storage.put(`modelref/${MODEL}`, ref); - const reset = await ctx.step(2, { input_token: '1000' }); - assert.equal(reset.record.market.frozen_reason, 'activity_baseline_bootstrap'); - assert.equal(amount(reset), 1000000n); + const resumed = await ctx.step(3, 8000); + assert.equal(amount(resumed), 1210000n); + assert.equal(resumed.record.market.activity_basis, 'signed_slot_time_v1'); }); -test('price derivation hashes bind work, calibration, baseline and the result', async () => { - const ctx = await market(); await ctx.step(1, { input_token: '1000' }); - const update = await ctx.step(2, { input_token: '2000' }); - const d = ctx.contract.priceDerivationFromMarketUpdate(update, { epoch: 2, at: 7200, epochSeconds: 3600, usageRoot: 'ab'.repeat(32) }); - const original = await ctx.contract.priceDerivationLeafHash(d); - for (const change of [ - (v) => {v.usage.settled_usage.input_token = '2001';}, - (v) => {v.controller.previous_activity_rate = '1';}, - (v) => {v.controller.previous_activity_vector.input_token = '1';}, - (v) => {v.controller.calibration_hash = 'cd'.repeat(32);}, - (v) => {v.result_price.rate_map[0].per_unit_au = '1';}, - ]) { const tampered = structuredClone(d); change(tampered); assert.notEqual(await ctx.contract.priceDerivationLeafHash(tampered), original); } +test('mid-band utilization holds regardless of demand AU or metered-unit changes', async () => { + const ctx = await market(); + const first = await ctx.step(1, 5000, { + gross: '1', + counts: { input_token: '1', output_token: '5000' }, + }); + const second = await ctx.step(2, 5000, { + gross: '999999999999999999', + counts: { input_token: '999999999', output_token: '1' }, + }); + assert.equal(amount(first), 1000000n); + assert.equal(amount(second), 1000000n); }); -test('hard-band migration removes unsafe pending records before their activation and preserves audit history', async () => { - const ctx = await market(); const records = {}; - for (const [key, active, pending] of [['price_min_bps',2500,1],['price_max_bps',40000,1000000]]) { - records[key] = { key, current: { value:active,ver:1,effective_at:0 }, - pending: { value:pending,ver:2,effective_at:86400 } }; - await ctx.storage.put(`params/${key}`,records[key]); - } - await ctx.storage.put('params/update/2',{ values:{price_min_bps:1,price_max_bps:1000000} }); - for (const at of [0,86400,9999999]) assert.deepEqual(await ctx.contract.activeParamsAt(at,['price_min_bps','price_max_bps']), {price_min_bps:2500,price_max_bps:40000}); - assert.ok(ctx.contract.validateParamValues({price_min_bps:1}) instanceof Error); - assert.ok(ctx.contract.validateParamValues({price_max_bps:1000000}) instanceof Error); - const outsider = await makeIdentity(); - const rejected = await execute(ctx.contract,ctx.storage,'migrateMarketPricing',{op:'migrate_market_pricing',at:0,markets:[]},outsider.publicKey,1); - assert.match(rejected.message,/admin/i); - const applied = await execute(ctx.contract,ctx.storage,'migrateMarketPricing',{op:'migrate_market_pricing',at:0,markets:[]},ctx.admin.publicKey,2); - assert.equal(applied.repaired,2); - for (const key of Object.keys(records)) assert.equal((await ctx.storage.get(`params/${key}`)).value.pending,null); - assert.deepEqual((await ctx.storage.get('params/update/2')).value.values,{price_min_bps:1,price_max_bps:1000000}); - const repeated = await execute(ctx.contract,ctx.storage,'migrateMarketPricing',{op:'migrate_market_pricing',at:0,markets:[]},ctx.admin.publicKey,3); - assert.equal(repeated.idempotent,true); +test('slot capacity normalizes the same compute time into different utilization', async () => { + const oneSlot = await market(); + const twoSlots = await market(); + const computeMs = 2_880_000; + const busy = await oneSlot.step(1, 0, { capacitySlots: 1, computeMs }); + const spare = await twoSlots.step(1, 0, { capacitySlots: 2, computeMs }); + assert.equal(busy.utilization_bps, 8000); + assert.equal(amount(busy), 1100000n); + assert.equal(spare.utilization_bps, 4000); + assert.equal(amount(spare), 1000000n); }); -test('migration rejects partial epoch upgrades without modifying parameters', async () => { - const ctx = await market(); - await ctx.storage.put('epoch/apply/state',{updated_epoch:1,pending_epoch:2,pending_next_page:1}); - const result = await execute(ctx.contract,ctx.storage,'migrateMarketPricing',{op:'migrate_market_pricing',at:0,markets:[]},ctx.admin.publicKey,1); - assert.match(result.message,/completed epoch boundary/); - assert.equal(await ctx.storage.get('market/activity/migration-v2'),null); +test('utilization is invariant to epoch length and provider count when slot time matches', async () => { + const short = await market(); + const long = await market(); + const a = await short.step(1, 8000, { seconds: 1800, providers: 1, capacitySlots: 2 }); + const b = await long.step(1, 8000, { seconds: 3600, providers: 4, capacitySlots: 8 }); + assert.equal(a.utilization_bps, b.utilization_bps); + assert.deepEqual(a.rate_map, b.rate_map); }); +test('every model class uses the same signed slot-time controller without calibration', async () => { + for (const [modelClass, units] of [ + ['text-generation', ['input_token','output_token']], + ['embedding', ['input_token','embedding']], + ['workflow', ['pixel_frame']], + ['image-generation', ['image','step']], + ['video-generation', ['frame','video_second']], + ['tts', ['audio_second','input_character']], + ['stt', ['audio_second']], + ['audio-generation', ['audio_second','input_character']], + ['music-generation', ['audio_second','input_character']], + ]) { + const ctx = await market({ calibrated: false, modelClass, units }); + const up = await ctx.step(1, 8000, { + counts: Object.fromEntries(units.map((unit) => [unit, '1'])), + }); + assert.equal(up.record.market.activity_basis, 'signed_slot_time_v1', modelClass); + assert.equal(amount(up), 1100000n, modelClass); + } +}); -test('one-provider markets rise after bootstrap and fixed terms stay inside seed bands', async () => { +test('consecutive empty epochs decay dormant markets to the hard floor', async () => { const ctx = await market({ fixedTerms: true }); - await ctx.step(1, { input_token: '1000' }, { providers: 1 }); + await ctx.step(1, 5000); let previous = 1000000n; - for (let epoch = 2; epoch <= 40; epoch++) { - const u = await ctx.step(epoch, { input_token: (1000n*4n**BigInt(epoch)).toString() }, { providers: 1 }); - assert.equal(u.frozen, false); - const current = BigInt(u.record.per_req_au); - assert.ok(current - previous <= previous/10n); - assert.ok(current <= 4000000n); - assert.ok(BigInt(u.record.min_session_au) <= 8000000n); - previous = current; - } - assert.equal(previous, 4000000n); - for (let epoch = 41; epoch <= 90; epoch++) { - const u = await ctx.step(epoch, { input_token: epoch < 81 ? (1000n * 4n**BigInt(80-epoch)).toString() : '0' }, { providers: 1 }); - assert.ok(BigInt(u.record.per_req_au) >= 250000n); - assert.ok(BigInt(u.record.min_session_au) >= 500000n); - previous = BigInt(u.record.per_req_au); + for (let epoch = 2; epoch <= 30; epoch++) { + const updates = await ctx.contract.computeMarketPriceUpdates(new Map(), { + epoch, + at: epoch * 3600, + epochSeconds: 3600, + canonicalActivity: new Map(), + includeDormant: true, + }); + assert.ok(!(updates instanceof Error), updates.message); + const next = updates[0]; + await ctx.storage.put(next.schedule_key, next.schedule); + assert.equal(next.utilization_bps, 0); + assert.equal(next.record.market.capacity_slot_count, 0); + assert.ok(amount(next) >= 250000n); + previous = amount(next); } assert.equal(previous, 250000n); }); -test('migration seeds active base and context markets before any new traffic', async () => { +test('canonical compute evidence must match the committed market totals', async () => { const ctx = await market(); - const base = 'ad'.repeat(32), baseModel = 'test/media'; - const rates = [{unit:'pixel_frame',per_unit_au:'1000',granularity:1}]; - await ctx.storage.put(`enclave/${base}`,{enclave_id:base,model_id:baseModel,model_class:'workflow',status:'active',caps:{}}); - await ctx.storage.put(`modelref/${baseModel}`,{model_id:baseModel,model_class:'workflow',rate_map:rates}); - await seedCurrentAdminPrice(ctx.storage,{enclaveId:base,modelId:baseModel,admin:ctx.admin.publicKey,rateMap:rates,ctxBracket:null}); - const markets=[{enclave_id:base},{enclave_id:ENCLAVE,ctx_bracket:'le8k',ctx_bracket_table_ver:1}]; - const migration=await execute(ctx.contract,ctx.storage,'migrateMarketPricing',{op:'migrate_market_pricing',at:0,markets},ctx.admin.publicKey,1); - assert.equal(migration.market_count,2); - ctx.contract.storage=ctx.storage; - const empty=await ctx.contract.computeMarketPriceUpdates(new Map(),{epoch:1,at:3600,epochSeconds:3600,includeDormant:true,canonicalActivity:new Map()}); - assert.equal(empty.length,2);assert.ok(empty.every(u=>u.record.market.activity_initialized)); - const before=ctx.storage.snapshotBytes(); - const bad=await execute(ctx.contract,ctx.storage,'migrateMarketPricing',{op:'migrate_market_pricing',at:0,markets:[{enclave_id:'af'.repeat(32)}]},ctx.admin.publicKey,2); - assert.match(bad.message,/not active/);assert.equal(ctx.storage.snapshotBytes(),before); + const row = { + enclave_id: ENCLAVE, + ctx_bracket: 'le8k', + ctx_bracket_table_ver: 1, + demand_au: '100', + session_count: 1, + provider_count: 1, + compute_ms: '2880000', + capacity_slot_count: 1, + }; + const usage = ctx.contract.aggregateMarketUsageEntries([row]); + const key = ctx.contract.priceMarketKey(ENCLAVE, 'le8k'); + const result = await ctx.contract.computeMarketPriceUpdates(usage, { + epoch: 1, + at: 3600, + epochSeconds: 3600, + canonicalActivity: new Map([[key, { + ...row, + compute_ms: '2879999', + settled_usage: { input_token: '1' }, + }]]), + }); + assert.match(result.message, /do not match canonical receipt evidence/); }); -test('calibration must cover every priced workload axis with positive work', () => { - const c=new MayhemContract({},{}); - const rates=[{unit:'input_token'},{unit:'output_token'}]; - assert.equal(c.validateActivityCalibration(calibration,'text-generation',rates),null); - const partial=structuredClone(calibration);partial.dimensions.pop(); - assert.match(c.validateActivityCalibration(partial,'text-generation',rates).message,/every model reference/); - const zero=structuredClone(calibration);zero.dimensions[0].work_us='0'; - assert.ok(c.validateActivityCalibration(zero,'text-generation',rates) instanceof Error); - assert.match(c.validateActivityCalibration(calibration,'text-generation',[...rates,{unit:'cached_input_token'}]).message,/every model reference/); +test('market usage rejects missing, zero, or malformed slot-time evidence', async () => { + const ctx = await market(); + const base = { + enclave_id: ENCLAVE, + ctx_bracket: 'le8k', + ctx_bracket_table_ver: 1, + demand_au: '1', + session_count: 1, + provider_count: 1, + compute_ms: '1', + capacity_slot_count: 1, + }; + for (const invalid of [ + (({ compute_ms, ...row }) => row)(base), + { ...base, compute_ms: '0' }, + { ...base, compute_ms: 'nope' }, + { ...base, capacity_slot_count: 0 }, + ]) { + assert.ok(ctx.contract.aggregateMarketUsageEntries([invalid]) instanceof Error); + } }); -test('activity index overflow fails before a price or settlement write', async () => { - const ctx=await market(); - await ctx.storage.put('market/activity/index',Array.from({length:5000},(_,i)=>({enclave_id:'market-'+i}))); - const before=ctx.storage.snapshotBytes(); - const row={enclave_id:ENCLAVE,ctx_bracket:'le8k',ctx_bracket_table_ver:1,demand_au:'10',session_count:1,provider_count:1}; - const result=await ctx.contract.computeMarketPriceUpdates(ctx.contract.aggregateMarketUsageEntries([row]),{epoch:1,at:3600,epochSeconds:3600}); - assert.match(result.message,/capacity exceeded/);assert.equal(ctx.storage.snapshotBytes(),before); +test('price derivation hashes bind compute time, capacity, utilization and result', async () => { + const ctx = await market(); + const update = await ctx.step(1, 8000); + const derivation = ctx.contract.priceDerivationFromMarketUpdate(update, { + epoch: 1, + at: 3600, + epochSeconds: 3600, + usageRoot: 'ab'.repeat(32), + }); + const original = await ctx.contract.priceDerivationLeafHash(derivation); + for (const change of [ + (value) => { value.usage.compute_ms = '1'; }, + (value) => { value.usage.capacity_slot_count = 9; }, + (value) => { value.controller.utilization_bps = 1; }, + (value) => { value.result_price.rate_map[0].per_unit_au = '1'; }, + ]) { + const tampered = structuredClone(derivation); + change(tampered); + assert.notEqual(await ctx.contract.priceDerivationLeafHash(tampered), original); + } }); +test('fixed request terms follow utilization and stay inside seed bounds', async () => { + const ctx = await market({ fixedTerms: true }); + let last; + for (let epoch = 1; epoch <= 30; epoch++) last = await ctx.step(epoch, 10000); + assert.equal(amount(last), 4000000n); + assert.equal(BigInt(last.record.per_req_au), 4000000n); + assert.equal(BigInt(last.record.min_session_au), 8000000n); + for (let epoch = 31; epoch <= 80; epoch++) last = await ctx.step(epoch, 0); + assert.equal(amount(last), 250000n); + assert.equal(BigInt(last.record.per_req_au), 250000n); + assert.equal(BigInt(last.record.min_session_au), 500000n); +}); -test('admin can clear calibration explicitly while omission preserves it', async () => { - const { contract, storage, admin, step } = await market(); - await step(1, { input_token: '1000', output_token: '100' }); - const ref = (await storage.get(`modelref/${MODEL}`)).value; - const value = { op: 'set_model_ref', model_id: MODEL, model_class: ref.model_class, rate_map: ref.rate_map }; - const preserved = await execute(contract, storage, 'setModelRef', value, admin.publicKey, 301); - assert.equal(preserved.ok, true, preserved.message); - assert.deepEqual((await storage.get(`modelref/${MODEL}`)).value.activity_calibration, calibration); - const cleared = await execute(contract, storage, 'setModelRef', { ...value, activity_calibration: null }, admin.publicKey, 302); - assert.equal(cleared.ok, true, cleared.message); - assert.equal((await storage.get(`modelref/${MODEL}`)).value.activity_calibration, undefined); - contract.storage = storage; contract.address = admin.publicKey; - const next = await step(2, { input_token: '9999', output_token: '9999' }); - assert.equal(next.record.market.activity_basis, 'relative_dimension_vector_v1'); - assert.equal(next.record.price_source, 'market_activity_hold'); +test('hard-band migration preserves history and records utilization policy', async () => { + const ctx = await market(); + const records = {}; + for (const [key, active, pending] of [ + ['price_min_bps', 2500, 1], + ['price_max_bps', 40000, 1000000], + ]) { + records[key] = { + key, + current: { value: active, ver: 1, effective_at: 0 }, + pending: { value: pending, ver: 2, effective_at: 86400 }, + }; + await ctx.storage.put(`params/${key}`, records[key]); + } + await ctx.storage.put('params/update/2', { + values: { price_min_bps: 1, price_max_bps: 1000000 }, + }); + const outsider = await makeIdentity(); + const rejected = await execute(ctx.contract, ctx.storage, 'migrateMarketPricing', { + op: 'migrate_market_pricing', at: 0, markets: [], + }, outsider.publicKey, 1); + assert.match(rejected.message, /admin/i); + const applied = await execute(ctx.contract, ctx.storage, 'migrateMarketPricing', { + op: 'migrate_market_pricing', at: 0, markets: [], + }, ctx.admin.publicKey, 2); + assert.equal(applied.repaired, 2); + const migration = (await ctx.storage.get('market/activity/migration-v3')).value; + assert.equal(migration.schema_version, 3); + assert.equal(migration.low_utilization_bps, 2000); + assert.equal(migration.high_utilization_bps, 8000); + assert.equal(migration.price_step_bps, 1000); + assert.deepEqual((await ctx.storage.get('params/update/2')).value.values, { + price_min_bps: 1, + price_max_bps: 1000000, + }); + const repeated = await execute(ctx.contract, ctx.storage, 'migrateMarketPricing', { + op: 'migrate_market_pricing', at: 0, markets: [], + }, ctx.admin.publicKey, 3); + assert.equal(repeated.idempotent, true); }); +test('migration rejects partial epoch upgrades without modifying parameters', async () => { + const ctx = await market(); + await ctx.storage.put('epoch/apply/state', { + updated_epoch: 1, + pending_epoch: 2, + pending_next_page: 1, + }); + const result = await execute(ctx.contract, ctx.storage, 'migrateMarketPricing', { + op: 'migrate_market_pricing', at: 0, markets: [], + }, ctx.admin.publicKey, 1); + assert.match(result.message, /completed epoch boundary/); + assert.equal(await ctx.storage.get('market/activity/migration-v3'), null); +}); -test('immediately previous activity controls rise, fall and equality despite opposing EMA', async () => { - for (const calibrated of [true, false]) { - const ctx = await market({ calibrated }); - const counts = (n) => ({ input_token: String(n * 10), output_token: String(n) }); - await ctx.step(1, counts(100)); - const surge = await ctx.step(2, counts(400)); - const lower = await ctx.step(3, counts(300)); - assert.ok(amount(lower) < amount(surge), '300 falls from 400 despite exceeding EMA 175'); - assert.ok(lower.momentum_bps >= 7499 && lower.momentum_bps <= 7500); - const equal = await ctx.step(4, counts(300)); - assert.equal(amount(equal), amount(lower), 'equal work holds despite lower EMA'); - const trough = await ctx.step(5, counts(10)); - const higher = await ctx.step(6, counts(20)); - assert.ok(amount(higher) > amount(trough), '20 rises from 10 despite remaining below EMA'); - assert.ok(higher.momentum_bps >= 20000 && higher.momentum_bps <= 20001); - assert.deepEqual(higher.record.market.previous_activity_vector, trough.record.market.activity_vector); - assert.equal(higher.record.market.previous_activity_rate, trough.record.market.activity_rate); - } +test('migration seeds active base and context markets before empty utilization updates', async () => { + const ctx = await market(); + const base = 'ad'.repeat(32); + const baseModel = 'test/media'; + const rates = [{ unit: 'pixel_frame', per_unit_au: '1000', granularity: 1 }]; + await ctx.storage.put(`enclave/${base}`, { + enclave_id: base, + model_id: baseModel, + model_class: 'workflow', + status: 'active', + caps: {}, + }); + await ctx.storage.put(`modelref/${baseModel}`, { + model_id: baseModel, + model_class: 'workflow', + rate_map: rates, + }); + await seedCurrentAdminPrice(ctx.storage, { + enclaveId: base, + modelId: baseModel, + admin: ctx.admin.publicKey, + rateMap: rates, + ctxBracket: null, + }); + const markets = [ + { enclave_id: base }, + { enclave_id: ENCLAVE, ctx_bracket: 'le8k', ctx_bracket_table_ver: 1 }, + ]; + const migration = await execute(ctx.contract, ctx.storage, 'migrateMarketPricing', { + op: 'migrate_market_pricing', at: 0, markets, + }, ctx.admin.publicKey, 1); + assert.equal(migration.market_count, 2); + ctx.contract.storage = ctx.storage; + const empty = await ctx.contract.computeMarketPriceUpdates(new Map(), { + epoch: 1, + at: 3600, + epochSeconds: 3600, + includeDormant: true, + canonicalActivity: new Map(), + }); + assert.equal(empty.length, 2); + assert.ok(empty.every((update) => update.utilization_bps === 0)); }); +test('activity calibration remains valid metadata but no longer controls direction', async () => { + const ctx = await market({ calibrated: true }); + const rates = [{ unit: 'input_token' }, { unit: 'output_token' }]; + assert.equal(ctx.contract.validateActivityCalibration(calibration, 'text-generation', rates), null); + const partial = structuredClone(calibration); + partial.dimensions.pop(); + assert.match( + ctx.contract.validateActivityCalibration(partial, 'text-generation', rates).message, + /every model reference/ + ); + const update = await ctx.step(1, 8000, { counts: { input_token: '1' } }); + assert.equal(update.record.market.activity_basis, 'signed_slot_time_v1'); + assert.equal(amount(update), 1100000n); +}); -test('initialized consecutive empty epochs decrease to the hard floor in both activity modes', async () => { - for (const calibrated of [true, false]) { - const ctx = await market({ calibrated, fixedTerms: true }); - await ctx.step(1, {}, { providers: 1 }); - let previous = 1000000n; - for (let epoch = 2; epoch <= 30; epoch++) { - const updates = await ctx.contract.computeMarketPriceUpdates(new Map(), { - epoch, at: epoch * 3600, epochSeconds: 3600, canonicalActivity: new Map(), includeDormant: true, - }); - assert.ok(!(updates instanceof Error), updates.message); - const next = updates[0]; - await ctx.storage.put(next.schedule_key, next.schedule); - assert.equal(next.record.market.active_supply, 0); - assert.equal(next.record.market.session_count, 0); - assert.equal(next.momentum_bps, 0); - assert.ok(amount(next) >= 250000n); - assert.ok(previous - amount(next) <= previous / 10n); - if (previous > 250000n) assert.ok(amount(next) < previous); - else assert.equal(amount(next), previous); - assert.ok(BigInt(next.record.per_req_au) >= 250000n); - assert.ok(BigInt(next.record.min_session_au) >= 500000n); - previous = amount(next); - } - assert.equal(previous, 250000n); - } +test('activity index overflow fails before a price write', async () => { + const ctx = await market(); + await ctx.storage.put( + 'market/activity/index', + Array.from({ length: 5000 }, (_, index) => ({ enclave_id: `market-${index}` })) + ); + const before = ctx.storage.snapshotBytes(); + const row = { + enclave_id: ENCLAVE, + ctx_bracket: 'le8k', + ctx_bracket_table_ver: 1, + demand_au: '10', + session_count: 1, + provider_count: 1, + compute_ms: '1', + capacity_slot_count: 1, + }; + const result = await ctx.contract.computeMarketPriceUpdates( + ctx.contract.aggregateMarketUsageEntries([row]), + { epoch: 1, at: 3600, epochSeconds: 3600 } + ); + assert.match(result.message, /capacity exceeded/); + assert.equal(ctx.storage.snapshotBytes(), before); }); diff --git a/intercom/tests/contract-params.test.js b/intercom/tests/contract-params.test.js index 3234d0a8..a5bfb73b 100644 --- a/intercom/tests/contract-params.test.js +++ b/intercom/tests/contract-params.test.js @@ -40,9 +40,6 @@ const EPOCH_OPERATING_PARAM_VALUES = { price_min_bps: 2_500, price_max_bps: 30_000, price_rate_limit_seconds: 900, - market_ema_alpha_bps: 4_000, - market_gain_bps: 6_000, - market_max_step_bps: 1_500, epoch_seconds: 7_200, challenge_epochs: 3, max_apply_batch: 2_500, @@ -291,8 +288,6 @@ test('MayhemContract setParams is admin-only and inert until the activation dela rate_staleness_seconds: 120, uptime_tick_seconds: 1_800, price_rate_limit_seconds: 900, - market_gain_bps: 7_500, - market_ema_alpha_bps: 2_000, param_activation_delay_seconds: 3_600, }, }), @@ -319,8 +314,6 @@ test('MayhemContract setParams is admin-only and inert until the activation dela 'rate_staleness_seconds', 'uptime_tick_seconds', 'price_rate_limit_seconds', - 'market_gain_bps', - 'market_ema_alpha_bps', 'param_activation_delay_seconds', ]), outsider.publicKey, @@ -340,10 +333,22 @@ test('MayhemContract setParams is admin-only and inert until the activation dela rate_staleness_seconds: 120, uptime_tick_seconds: 1_800, price_rate_limit_seconds: 900, - market_gain_bps: 7_500, - market_ema_alpha_bps: 2_000, param_activation_delay_seconds: 3_600, }); + + const deprecatedControllerKnob = await execute( + contract, + storage, + 'setParams', + makeSetParams({ + submitted_at: 2 * DAY_SECONDS, + effective_at: 3 * DAY_SECONDS, + values: { market_gain_bps: 7_500 }, + }), + admin.publicKey, + 11 + ); + assert.match(deprecatedControllerKnob.message, /deprecated and read-only/i); }); test('MayhemContract epoch and market epoch controls are admin-governed params', async () => { diff --git a/intercom/tests/contract-payout.test.js b/intercom/tests/contract-payout.test.js index 347b6e04..1889f2b7 100644 --- a/intercom/tests/contract-payout.test.js +++ b/intercom/tests/contract-payout.test.js @@ -428,6 +428,8 @@ async function seedCanonicalReceiptEpoch( enclave_id: voucher.enclave_id, ctx_bracket: voucher.ctx_bracket, ctx_bracket_table_ver: voucher.ctx_bracket_table_ver, + compute_ms: 1_000, + capacity_slots: 1, }, }, feature_key: updatedAt, @@ -687,6 +689,8 @@ const receiptBundle = (user, provider, overrides = {}) => ({ locked_per_req_au: '0', locked_min_session_au: '0', served_ctx: 8192, + compute_ms: 1_800_000, + capacity_slots: 1, ctx_bracket: 'le8k', ctx_bracket_table_ver: 1, rules_ver: 1, diff --git a/intercom/tests/contract-price.test.js b/intercom/tests/contract-price.test.js index f131b1ff..59c4413d 100644 --- a/intercom/tests/contract-price.test.js +++ b/intercom/tests/contract-price.test.js @@ -95,15 +95,21 @@ const makePrice = (overrides = {}) => ({ ...overrides, }); -const makeMarketUsage = (demandAu, sessionCount, overrides = {}) => ({ - enclave_id: enclaveId, - ctx_bracket: priceCtxBracket, - ctx_bracket_table_ver: priceCtxBracketTableVer, - demand_au: auString(demandAu), - session_count: sessionCount, - provider_count: 1, - ...overrides, -}); +const makeMarketUsage = (demandAu, sessionCount, overrides = {}) => { + const providerCount = overrides.provider_count ?? 1; + return { + enclave_id: enclaveId, + ctx_bracket: priceCtxBracket, + ctx_bracket_table_ver: priceCtxBracketTableVer, + demand_au: auString(demandAu), + session_count: sessionCount, + provider_count: providerCount, + compute_ms: auString(1_800_000 * providerCount), + capacity_slot_count: providerCount, + legacy_receipt_count: 0, + ...overrides, + }; +}; async function setupRegisteredEnclave() { const provider = await makeIdentity(); @@ -117,18 +123,6 @@ async function setupRegisteredEnclave() { model_class: 'text-generation', rate_map: textRateMap(20, 60), }); - await storage.put('params/market_provider_epoch_target_au', { - key: 'market_provider_epoch_target_au', - current: { - value: '1000000', - ver: 1, - submitted_at: 0, - effective_at: 0, - set_at: null, - }, - pending: null, - }); - for (const op of [ { type: 'setRules', @@ -380,7 +374,7 @@ test('MayhemContract bills multimodal LLM input through token rates only', async assert.match(doubleBill.message, /unit image is not allowed for model_class text-generation/i); }); -test('MayhemContract epochApply keeps cold-start markets pinned to the admin seed', async () => { +test('MayhemContract epochApply keeps mid-utilization markets at the current price', async () => { const { contract, storage, provider, admin } = await setupRegisteredEnclave(); const user = await makeIdentity(); @@ -421,12 +415,11 @@ test('MayhemContract epochApply keeps cold-start markets pinned to the admin see ctx_bracket: priceCtxBracket, ctx_bracket_table_ver: priceCtxBracketTableVer, ver: 2, - momentum_bps: 10_000, - activity_rate: null, - ema_activity_rate: null, + utilization_bps: 5_000, + multiplier_bps: 10_000, active_supply: 1, active_demand_au: '10000000', - frozen: true, + frozen: false, derivation_hash: '', } ); @@ -435,17 +428,18 @@ test('MayhemContract epochApply keeps cold-start markets pinned to the admin see const schedule = await storage.get(priceKey); assert.equal(schedule.value.current.ver, 2); - assert.equal(schedule.value.current.price_source, 'market_activity_hold'); + assert.equal(schedule.value.current.price_source, 'market_utilization'); assert.deepEqual(schedule.value.current.rate_map, textRateMap(18, 55)); const priceRoot = (await storage.get('ev/price/1')).value; assert.equal(priceRoot.merkle_root, applied.price_root); assert.equal(priceRoot.price_count, 1); const derivation = (await storage.get(priceEvidenceKey)).value; assert.equal(derivation.price_root, applied.price_root); - assert.equal(derivation.controller.frozen, true); + assert.equal(derivation.controller.utilization_bps, 5_000); + assert.equal(derivation.controller.multiplier_bps, 10_000); }); -test('MayhemContract epochApply counts settled-work supply, not idle joined wallets', async () => { +test('MayhemContract epochApply uses signed execution capacity, not idle joined wallets', async () => { const { contract, storage, provider, admin } = await setupRegisteredEnclave(); const user = await makeIdentity(); const idleProvider = await makeIdentity(); @@ -481,26 +475,14 @@ test('MayhemContract epochApply counts settled-work supply, not idle joined wall ); assert.equal(applied.ok, true, applied.message); assert.equal(applied.market_prices[0].active_supply, 1); - assert.equal(applied.market_prices[0].frozen, true); + assert.equal(applied.market_prices[0].utilization_bps, 5_000); + assert.equal(applied.market_prices[0].frozen, false); const derivation = (await storage.get(priceEvidenceKey)).value; assert.equal(derivation.controller.active_supply, 1); - assert.equal(derivation.controller.frozen, true); -}); - - - -test('MayhemContract market price math supports sub-micro atto price steps', async () => { - const { contract } = await setupRegisteredEnclave(); - const qwenEmbeddingPerTokenAu = '10000000'; - const next = contract.stepPriceTerm(qwenEmbeddingPerTokenAu, '10001000', { - gain_bps: 5_000, - max_step_bps: 1, - }); - - assert.equal(next, '10000500'); - assert.ok(BigInt(next) > BigInt(qwenEmbeddingPerTokenAu)); - assert.ok(BigInt(next) - BigInt(qwenEmbeddingPerTokenAu) < BigInt(qwenEmbeddingPerTokenAu) / 10_000n); + assert.equal(derivation.usage.capacity_slot_count, 1); + assert.equal(derivation.usage.compute_ms, '1800000'); + assert.equal(derivation.controller.utilization_bps, 5_000); }); test('MayhemContract keeps context brackets as independent price markets', async () => { @@ -580,13 +562,8 @@ test('MayhemContract keeps context brackets as independent price markets', async assert.equal(shortDerivation.price_root, longDerivation.price_root); }); -test('MayhemContract market price derivation uses active admin-tuned epoch params', async () => { - const { contract, storage, provider, admin } = await setupRegisteredEnclave(); - const user = await makeIdentity(); - const providerTwo = await makeIdentity(); - - const seeded = await execute(contract, storage, 'setPrice', makePrice(), admin.publicKey, 5); - assert.equal(seeded.ok, true, seeded.message); +test('MayhemContract rejects retired momentum controller parameters', async () => { + const { contract, storage, admin } = await setupRegisteredEnclave(); const tuned = await execute( contract, storage, @@ -601,50 +578,9 @@ test('MayhemContract market price derivation uses active admin-tuned epoch param }, }, admin.publicKey, - 6 - ); - assert.equal(tuned.ok, true, tuned.message); - const joined = await execute( - contract, - storage, - 'joinEnclave', - providerJoin, - provider.publicKey, - 7 - ); - assert.equal(joined.ok, true, joined.message); - await registerAndJoinExtraProvider(contract, storage, admin, providerTwo, 8); - await storage.put(`bal/${user.publicKey}/fiat`, seededBalance(user.publicKey, 10_000_000)); - - const applyValue = { - op: 'epoch_apply', - epoch: 1, - at: DAY_SECONDS + 1, - debits: [{ rail: 'fiat', user: user.publicKey, au: '2000000' }], - earnings: [ - { rail: 'fiat', provider: provider.publicKey, gross_au: '1000000' }, - { rail: 'fiat', provider: providerTwo.publicKey, gross_au: '1000000' }, - ], - market_usage: [makeMarketUsage(2_000_000, 4, { provider_count: 2 })], - }; - await seedSpendHoldsForApply(storage, applyValue); - const applied = await executeEpochApplyFeature( - contract, - storage, - applyValue, - admin.publicKey + 5 ); - assert.equal(applied.ok, true, applied.message); - const derivation = (await storage.get(priceEvidenceKey)).value; - assert.deepEqual(derivation.controller.constants, { - schema_version: 2, - ema_alpha_bps: 2_500, - gain_bps: 10_000, - max_step_bps: 10_000, - max_momentum_bps: 50_000, - }); - assert.equal(derivation.controller.momentum_bps, 10_000); - assert.equal(derivation.controller.frozen_reason, 'missing_canonical_activity'); + assert.match(tuned.message, /market_gain_bps is deprecated and read-only/i); }); @@ -737,12 +673,11 @@ test('MayhemContract keeps one enclave price while conserving mixed rail settlem ctx_bracket: priceCtxBracket, ctx_bracket_table_ver: priceCtxBracketTableVer, ver: 2, - momentum_bps: 10_000, - activity_rate: null, - ema_activity_rate: null, + utilization_bps: 5_000, + multiplier_bps: 10_000, active_supply: 2, active_demand_au: '1000000', - frozen: true, + frozen: false, derivation_hash: applied.market_prices[0].derivation_hash, }, ]); @@ -750,7 +685,7 @@ test('MayhemContract keeps one enclave price while conserving mixed rail settlem const schedule = await storage.get(priceKey); assert.equal(schedule.value.current.ver, 2); - assert.equal(schedule.value.current.price_source, 'market_activity_hold'); + assert.equal(schedule.value.current.price_source, 'market_utilization'); assert.equal(await storage.get(`price/${enclaveId}/fiat`), null); assert.equal(await storage.get(`price/${enclaveId}/tap`), null); assert.equal(await storage.get(`price/${enclaveId}`), null); diff --git a/intercom/tests/contract-receipt-settlement.test.js b/intercom/tests/contract-receipt-settlement.test.js index 71ce39ed..bead9de1 100644 --- a/intercom/tests/contract-receipt-settlement.test.js +++ b/intercom/tests/contract-receipt-settlement.test.js @@ -360,6 +360,7 @@ async function moveReservationsToLegacyHold(ctx, reservations, { txNo = 60 } = { } function receiptValue(ctx, reservation, { + schemaVersion = SESSION_RECEIPT_SCHEMA_VERSION, seq = 1, final = false, usage = { input_token: 10 }, @@ -371,7 +372,7 @@ function receiptValue(ctx, reservation, { } = {}) { const voucher = reservation.value.voucher; const body = { - schema_version: SESSION_RECEIPT_SCHEMA_VERSION, + schema_version: schemaVersion, session_id: voucher.session_id, billing_id: voucher.billing_id, billing_attempt: voucher.billing_attempt, @@ -394,6 +395,7 @@ function receiptValue(ctx, reservation, { locked_per_req_au: voucher.locked_per_req_au, locked_min_session_au: voucher.locked_min_session_au, served_ctx: voucher.served_ctx, + ...(schemaVersion >= 12 ? { compute_ms: 1_000, capacity_slots: 1 } : {}), ctx_bracket: voucher.ctx_bracket, ctx_bracket_table_ver: voucher.ctx_bracket_table_ver, rules_ver: voucher.rules_ver, @@ -906,10 +908,33 @@ test('non-final receipt heads stay unindexed until the final head becomes settle /sequence conflicts/i ); + const computeRegression = receiptValue(ctx, reservation, { + seq: 2, + usage: { input_token: 15 }, + auOwedCum: '150', + bodyOverrides: { compute_ms: 999 }, + }); + assert.match( + (await submitReceipt(ctx, computeRegression)).result.message, + /not monotonic/i + ); + + const capacityChange = receiptValue(ctx, reservation, { + seq: 2, + usage: { input_token: 15 }, + auOwedCum: '150', + bodyOverrides: { compute_ms: 2_000, capacity_slots: 2 }, + }); + assert.match( + (await submitReceipt(ctx, capacityChange)).result.message, + /immutable attempt terms/i + ); + const advanced = receiptValue(ctx, reservation, { seq: 2, usage: { input_token: 15 }, auOwedCum: '150', + bodyOverrides: { compute_ms: 2_000 }, }); const second = await submitReceipt(ctx, advanced); assert.equal(second.result.ok, true, second.result.message); @@ -924,6 +949,7 @@ test('non-final receipt heads stay unindexed until the final head becomes settle final: true, usage: { input_token: 20 }, auOwedCum: '200', + bodyOverrides: { compute_ms: 3_000 }, }); const finalized = await submitReceipt(ctx, final); assert.equal(finalized.result.ok, true, finalized.result.message); @@ -1625,6 +1651,8 @@ test('atomic commit plus page zero safely replaces an unapplied stale commit', a demand_au: '20', session_count: 2, provider_count: 1, + compute_ms: '2000', + capacity_slot_count: 1, }]; const falseMarketPage = structuredClone(page1); falseMarketPage.market_usage[0].session_count = 1; @@ -1667,7 +1695,7 @@ test('atomic commit plus page zero safely replaces an unapplied stale commit', a }, ctx.admin.publicKey, 301); assert.equal(empty.ok, true, empty.message); const dormant = (await ctx.storage.get(`price/${ENCLAVE_ID}/le8k`)).value.current; - assert.equal(dormant.rate_map[0].per_unit_au, '9'); + assert.equal(dormant.rate_map[0].per_unit_au, '8'); assert.deepEqual(dormant.market.settled_usage, {}); }); @@ -1905,11 +1933,13 @@ test('canonical receipt metadata rejects count and revision overflow', async () }); -test('v25 settles retained v23/v24 context receipts without rewriting signatures or billing', async () => { - for (const contractVersion of [23, 24, CONTRACT_VERSION]) { +test('v27 settles retained v23-v26 schema-11 receipts without rewriting signatures or billing', async () => { + for (const contractVersion of [23, 24, 25, 26, CONTRACT_VERSION]) { const ctx = await setupContract(); const reservation = await submitReservation(ctx); const value = receiptValue(ctx, reservation, { + schemaVersion: contractVersion === CONTRACT_VERSION ? + SESSION_RECEIPT_SCHEMA_VERSION : 11, final: true, bodyOverrides: { usage_attribution: { context_input_tokens: 1200 } }, outerOverrides: { contract_version: contractVersion }, @@ -1942,16 +1972,17 @@ test('v25 settles retained v23/v24 context receipts without rewriting signatures const rewritten = { ...recovered, contract_version: CONTRACT_VERSION }; const rejected = await submitReceipt(ctx, rewritten); assert.notEqual(rejected.result.ok, true); - assert.match(rejected.result.message, /signature/i); + assert.match(rejected.result.message, /schema|signature/i); } } }); -test('v24 context recovery bounds telemetry and admits no other legacy operations', async () => { +test('prior-version context recovery bounds telemetry and admits no other legacy operations', async () => { const ctx = await setupContract(); const reservation = await submitReservation(ctx); for (const count of [0, -1, 1.5, 8193, Number.MAX_SAFE_INTEGER + 1]) { const invalid = receiptValue(ctx, reservation, { + schemaVersion: 11, final: true, bodyOverrides: { usage_attribution: { context_input_tokens: count } }, outerOverrides: { contract_version: 23 }, @@ -1974,7 +2005,7 @@ test('v24 context recovery bounds telemetry and admits no other legacy operation assert.match(rejected.message, /contract version/i); }); -test('v25 price fraud proof pins signed canonical work and survives later calibration changes', async () => { +test('activity-price fraud proof pins signed canonical work and survives later calibration changes', async () => { const ctx = await setupContract(); const reservation = await submitReservation(ctx); const receipt = await submitReceipt(ctx, receiptValue(ctx, reservation, { final: true })); diff --git a/intercom/tests/contract-registry.test.js b/intercom/tests/contract-registry.test.js index 847b603e..3ab9010e 100644 --- a/intercom/tests/contract-registry.test.js +++ b/intercom/tests/contract-registry.test.js @@ -2333,6 +2333,65 @@ test('MayhemContract admin verifies and revokes provider KYB without raw documen ); assert.match(regrantRevokedIdentity.message, /kyb identity is banned or revoked/i); assert.equal(await storage.get(`kyb/${replacement.publicKey}`), null); + + const nonAdminKybUnban = await execute( + contract, + storage, + 'unban', + { + op: 'unban', + target_type: 'kyb', + target: provider.publicKey, + reason_hash: 'c'.repeat(64), + }, + provider.publicKey, + 13 + ); + assert.match(nonAdminKybUnban.message, /admin required/i); + + const restoredKybIdentity = await execute( + contract, + storage, + 'unban', + { + op: 'unban', + target_type: 'kyb', + target: provider.publicKey, + reason_hash: 'c'.repeat(64), + }, + admin.publicKey, + 14 + ); + assert.deepEqual(restoredKybIdentity, { + ok: true, + op: 'unban', + target_type: 'kyb', + target: provider.publicKey, + }); + const kybBanKeys = await contract.kybBanIndexKeys(revokedKyb.value); + assert.equal(kybBanKeys.length, 3); + for (const key of kybBanKeys) { + const record = await storage.get(key); + assert.equal(record.value.status, 'unbanned'); + assert.equal(record.value.unbanned_by, admin.publicKey); + assert.equal(record.value.unban_reason_hash, 'c'.repeat(64)); + } + + const reverified = await execute( + contract, + storage, + 'setProviderKyb', + signedKyb, + admin.publicKey, + 15 + ); + assert.deepEqual(reverified, { + ok: true, + op: 'setProviderKyb', + provider: provider.publicKey, + att_tier: 4, + }); + assert.equal((await storage.get(`kyb/${provider.publicKey}`)).value.status, 'verified'); }); test('MayhemContract lets providers narrow but never invent admin speciality levels', async () => { diff --git a/intercom/tests/contract-tnk-settlement.test.js b/intercom/tests/contract-tnk-settlement.test.js index 16c21d72..7703563f 100644 --- a/intercom/tests/contract-tnk-settlement.test.js +++ b/intercom/tests/contract-tnk-settlement.test.js @@ -448,6 +448,29 @@ test('TNK preparation binds the exact signed MSB payload and rejects effect reus assert.match(invalid.message, /canonical epoch plan|plan output/i); }); +test('TNK preparation refreshes a matured revision holdback before validating its liability', async () => { + const ctx = await setup(); + const liabilityKey = + `payout/liability/tnk/${ctx.provider.publicKey}/${ctx.revision}`; + const liability = (await ctx.storage.get(liabilityKey)).value; + await ctx.storage.put(liabilityKey, { + ...liability, + held_au: liability.total_au, + holdbacks: [{ epoch: 0, au: liability.total_au, locked_epochs: 1 }], + updated_epoch: 0, + last_holdback_release_epoch: 0, + }); + + const plan = await buildPlan(ctx, ctx.outputs); + const prepared = await prepareOutput( + ctx, + plan, + ctx.outputs[0], + '8'.repeat(64) + ); + assert.equal(prepared.ok, true, prepared.message); +}); + test('TNK carry-only epoch closes without fabricating an external effect', async () => { const ctx = await setup({ payoutMinAu: '1000000000000000000', diff --git a/intercom/tests/contract-versioning.test.js b/intercom/tests/contract-versioning.test.js index 77afdf40..28d1af1d 100644 --- a/intercom/tests/contract-versioning.test.js +++ b/intercom/tests/contract-versioning.test.js @@ -30,9 +30,9 @@ const versioningLockedRateMap = [ const versioningBillingId = 'bb'.repeat(32); test('launch version gates cover A16/A17/D6/D7/M5/M6/M8 deterministic changes', () => { - assert.equal(CONTRACT_VERSION, 25); + assert.equal(CONTRACT_VERSION, 28); assert.deepEqual(signingMessageVersions(), [2]); - assert.equal(SESSION_RECEIPT_SCHEMA_VERSION, 11); + assert.equal(SESSION_RECEIPT_SCHEMA_VERSION, 12); assert.equal(SPEND_VOUCHER_SCHEMA_VERSION, 11); }); @@ -87,6 +87,25 @@ test('contract keeps descriptive backends extensible while routing semantics sta modality_set: ['text', 'video', 'audio'], speciality_levels: {}, }, 'video-generation'), null); + assert.equal(contract.validateEnclaveCaps({ + chat: false, + tools: false, + json: true, + vision: false, + image: false, + video: false, + audio: false, + ctx: 1024, + ctx_max: 1024, + output_modality: 'text', + output_modalities: ['text'], + modality_set: ['text'], + speciality_levels: {}, + }, 'decision'), null); + assert.equal(contract.validateEnclaveModalityRateMap({ + model_class: 'decision', + caps: { modality_set: ['text'] }, + }, versioningLockedRateMap), null); }); test('contract accepts only the current consent signing version', async () => { @@ -192,6 +211,8 @@ test('receipt verifier accepts only the current signing payload', async () => { locked_per_req_au: '0', locked_min_session_au: '0', served_ctx: 8192, + compute_ms: 1_000, + capacity_slots: 1, ctx_bracket: 'le8k', ctx_bracket_table_ver: 1, rules_ver: 1, @@ -220,7 +241,7 @@ test('Rust atto money signing fixture matches JS canonical messages', () => { { unit: 'output_token', per_unit_au: '2500000000000000', granularity: 1000 }, ]; const voucher = { - schema_version: SESSION_RECEIPT_SCHEMA_VERSION, + schema_version: SPEND_VOUCHER_SCHEMA_VERSION, session_id: 'sess-au-roundtrip', billing_id: '44'.repeat(32), billing_attempt: 0, @@ -300,6 +321,8 @@ test('Rust atto money signing fixture matches JS canonical messages', () => { locked_per_req_au: '1', locked_min_session_au: '2000000000000000000000000', served_ctx: 131072, + compute_ms: 1_000, + capacity_slots: 1, ctx_bracket: 'le128k', ctx_bracket_table_ver: 1, rules_ver: 7, @@ -310,7 +333,7 @@ test('Rust atto money signing fixture matches JS canonical messages', () => { }; const expectedReceipt = [ '{"domain":"mayhem-session-receipt","signing_version":2,"body":{', - '"schema_version":11,"session_id":"sess-au-roundtrip","billing_id":"', + '"schema_version":12,"session_id":"sess-au-roundtrip","billing_id":"', '44'.repeat(32), '","billing_attempt":0,"billing_prior_usage":{},"billing_prior_au_owed_cum":"0",', '"billing_epoch":7,"reservation_id":"', @@ -328,7 +351,7 @@ test('Rust atto money signing fixture matches JS canonical messages', () => { '{"unit":"input_token","per_unit_au":"10000000","granularity":1},', '{"unit":"output_token","per_unit_au":"2500000000000000","granularity":1000}', '],"locked_per_req_au":"1","locked_min_session_au":"2000000000000000000000000",', - '"served_ctx":131072,"ctx_bracket":"le128k","ctx_bracket_table_ver":1,', + '"served_ctx":131072,"compute_ms":1000,"capacity_slots":1,"ctx_bracket":"le128k","ctx_bracket_table_ver":1,', '"rules_ver":7,"usage":{"input_token":3,"output_token":5},', '"au_owed_cum":"2000000000000000000000001","prompt_hash":"', '33'.repeat(32), @@ -417,6 +440,8 @@ test('workflow voucher and receipt terms are canonical signed evidence', async ( locked_per_req_au: '0', locked_min_session_au: '0', served_ctx: 0, + compute_ms: 1_000, + capacity_slots: 1, ctx_bracket: null, ctx_bracket_table_ver: null, rules_ver: voucher.rules_ver, @@ -498,6 +523,8 @@ test('receipt normalization rejects old schemas and non-canonical usage', async locked_per_req_au: '0', locked_min_session_au: '0', served_ctx: 8192, + compute_ms: 1_000, + capacity_slots: 1, ctx_bracket: 'le8k', ctx_bracket_table_ver: 1, rules_ver: 1, diff --git a/intercom/tests/market-activity-upgrade.test.js b/intercom/tests/market-activity-upgrade.test.js index f5cbbfa5..f96a04ed 100644 --- a/intercom/tests/market-activity-upgrade.test.js +++ b/intercom/tests/market-activity-upgrade.test.js @@ -11,7 +11,9 @@ test('offline upgrade generator includes every active base and context price, ne prices:[{current:{enclave_id:'a',model_id:'media',set_by_role:'admin'}},{current:{enclave_id:'b',model_id:'text',set_by_role:'admin',ctx_bracket:'le8k',ctx_bracket_table_ver:1}},{current:{enclave_id:'c',model_id:'media',set_by_role:'admin'}}]}; const before=JSON.stringify(snapshot);const plan=prepareMarketActivityUpgrade(snapshot); assert.equal(plan.active_market_count,2);assert.equal(plan.commands[0].markets.length,2); - assert.ok(plan.modelref_inventory.every(r=>r.activity_basis==='relative_dimension_vector_v1')); + assert.equal(plan.contract_version,28);assert.equal(plan.schema_version,2); + assert.ok(plan.modelref_inventory.every(r=>r.activity_basis==='signed_slot_time_v1')); + assert.deepEqual([plan.low_utilization_bps,plan.high_utilization_bps,plan.price_step_bps],[2000,8000,1000]); assert.equal(JSON.stringify(snapshot),before); const pending=structuredClone(snapshot);pending.pending_price_commits=[{epoch:1}]; assert.throws(()=>prepareMarketActivityUpgrade(pending),/resolve all prior-version/); diff --git a/intercom/tests/market-sim.test.js b/intercom/tests/market-sim.test.js index 72387642..05e2e525 100644 --- a/intercom/tests/market-sim.test.js +++ b/intercom/tests/market-sim.test.js @@ -2,7 +2,7 @@ import assert from 'node:assert/strict'; import test from 'node:test'; import {runMarketSimulation,validateMarketSimulation,formatMarketSimulationMarkdown,marketConstants} from '../scripts/market-sim.mjs'; -test('market simulation uses live activity constants and respects step/hard bands',()=>{ +test('market simulation uses live utilization constants and respects step/hard bands',()=>{ const report=runMarketSimulation();assert.deepEqual(report.constants,marketConstants()); const result=validateMarketSimulation(report);assert.equal(result.ok,true,result.failures.join('\n')); }); @@ -17,8 +17,8 @@ test('spend and phantom-provider changes do not alter market activity',()=>{ const r=runMarketSimulation(); for(const name of ['spend_spike','phantom_supply']) assert.ok(r.scenarios[name].rows.every(row=>row.price_au===r.seed_price_au)); }); -test('simulation describes activity without a dollar utilization target',()=>{ +test('simulation describes utilization without a dollar target or previous-hour comparison',()=>{ const markdown=formatMarketSimulationMarkdown(runMarketSimulation()); - assert.match(markdown,/Settled activity momentum/);assert.match(markdown,/max_step_bps/);assert.match(markdown,/Validation: PASS/); + assert.match(markdown,/Signed slot utilization/);assert.match(markdown,/price_step_bps/);assert.match(markdown,/Validation: PASS/); assert.doesNotMatch(markdown,/target_utilization_bps|provider_epoch_target_au/); }); diff --git a/intercom/tests/mayhem-feature-relay.test.js b/intercom/tests/mayhem-feature-relay.test.js index f072aecb..7f52f8b7 100644 --- a/intercom/tests/mayhem-feature-relay.test.js +++ b/intercom/tests/mayhem-feature-relay.test.js @@ -908,6 +908,9 @@ test('admin writer returns a bounded relay error when an accepted feature never ); assert.equal(result.ok, false); + assert.equal(result.accepted, true); + assert.equal(result.status, 'pending'); + assert.equal(result.phase, 'admin_ack'); assert.equal(result.relayed, true); assert.match(result.message, /no canonical result appeared before the relay result budget/); assert.equal(writer.appended.length, 1); @@ -969,6 +972,7 @@ test('participant does not broadcast when the canonical admin channel is unavail participant.peer.sidechannel = { started: true, connectDirectPeer: async () => false, + directConnectFailure: () => ({ phase: 'protocol_incompatible' }), broadcast() { broadcasts += 1; return true; @@ -984,6 +988,7 @@ test('participant does not broadcast when the canonical admin channel is unavail assert.equal(result.ok, false); assert.match(result.message, /direct channel to the canonical admin/); + assert.equal(result.phase, 'protocol_incompatible'); assert.equal(broadcasts, 0); }); diff --git a/intercom/tests/msb-reader-catchup.test.mjs b/intercom/tests/msb-reader-catchup.test.mjs new file mode 100644 index 00000000..32aeca7e --- /dev/null +++ b/intercom/tests/msb-reader-catchup.test.mjs @@ -0,0 +1,34 @@ +import assert from 'node:assert/strict'; +import test from 'node:test'; + +import { waitForMinimumSignedLength } from '../scripts/msb-reader-catchup.mjs'; + +test('waits for a stale nonzero reader to pass the durable cursor', async () => { + const lengths = [5, 5, 11]; + let reads = 0; + let sleeps = 0; + const signedLength = await waitForMinimumSignedLength({ + getSignedLength: () => lengths[Math.min(reads++, lengths.length - 1)], + }, { + minimumSignedLength: 11, + timeoutSec: 3, + sleepImpl: async () => { sleeps += 1; }, + }); + + assert.equal(signedLength, 11); + assert.equal(sleeps, 2); +}); + +test('returns the last observed length when catch-up times out', async () => { + let sleeps = 0; + const signedLength = await waitForMinimumSignedLength({ + getSignedLength: () => 5, + }, { + minimumSignedLength: 11, + timeoutSec: 2, + sleepImpl: async () => { sleeps += 1; }, + }); + + assert.equal(signedLength, 5); + assert.equal(sleeps, 2); +}); diff --git a/intercom/tests/msb-transfer-helper.test.js b/intercom/tests/msb-transfer-helper.test.js index e5611940..fb149711 100644 --- a/intercom/tests/msb-transfer-helper.test.js +++ b/intercom/tests/msb-transfer-helper.test.js @@ -1,4 +1,7 @@ import assert from 'node:assert/strict'; +import fs from 'node:fs'; +import os from 'node:os'; +import path from 'node:path'; import test from 'node:test'; import { parseRootMsbBalanceHelperArgs } from '../src/msb-balance-helper.js'; @@ -9,6 +12,7 @@ import { } from '../src/network-config.js'; import { executeTransfer, + readWalletPasswordFile, } from '../src/msb-settlement-transfer-helper.js'; import { runRootMsbTransferHelper } from '../src/msb-transfer-helper.js'; import { bigIntTo16ByteBuffer } from 'trac-msb/src/utils/amountSerialization.js'; @@ -28,6 +32,14 @@ test('root Intercom app resolves the bundled MSB transfer helper', async () => { ); }); +test('wallet password files ignore a terminal line break only', (t) => { + const directory = fs.mkdtempSync(path.join(os.tmpdir(), 'mayhem-wallet-password-')); + t.after(() => fs.rmSync(directory, { recursive: true, force: true })); + const passwordFile = path.join(directory, 'password'); + fs.writeFileSync(passwordFile, ' wallet password \r\n', { mode: 0o600 }); + assert.equal(readWalletPasswordFile(passwordFile), ' wallet password '); +}); + test('root Intercom app parses a read-only official-MSB balance helper', () => { assert.deepEqual( parseRootMsbBalanceHelperArgs([ diff --git a/intercom/tests/recompute-epoch-roots-v17.test.js b/intercom/tests/recompute-epoch-roots-v17.test.js index 869e697f..5144b190 100644 --- a/intercom/tests/recompute-epoch-roots-v17.test.js +++ b/intercom/tests/recompute-epoch-roots-v17.test.js @@ -26,7 +26,7 @@ async function canonicalBundle(count, { for (const ordinal of order) { const billingId = hex(ordinal); const body = { - schema_version: 11, + schema_version: 12, session_id: hex(10_000 + ordinal), billing_id: billingId, billing_attempt: 0, @@ -47,6 +47,8 @@ async function canonicalBundle(count, { locked_per_req_au: '0', locked_min_session_au: '0', served_ctx: 1024, + compute_ms: 1_800_000, + capacity_slots: 1, ctx_bracket: 'le32k', ctx_bracket_table_ver: 1, rules_ver: 1, @@ -150,6 +152,22 @@ test('v17 recompute preserves insertion-order snapshots and emits targeted field } }); +test('v27 recompute preserves a legacy receipt and marks utilization incomplete', async () => { + const bundle = await canonicalBundle(1, { + mutateBody: (body) => { + body.schema_version = 11; + delete body.compute_ms; + delete body.capacity_slots; + }, + }); + const result = await recomputeEpoch(bundle); + const usage = result.apply_pages.at(-1).market_usage[0]; + assert.equal(usage.session_count, 1); + assert.equal(usage.legacy_receipt_count, 1); + assert.equal(usage.compute_ms, '0'); + assert.equal(usage.capacity_slot_count, 0); +}); + test('v17 recompute pages more than 1000 exact receipt heads without peers', async () => { const receiptCount = Number(process.env.MAYHEM_RECOMPUTE_SCALE_RECEIPTS ?? 1_001); const bundle = await canonicalBundle(receiptCount); diff --git a/intercom/tests/release-identity.test.js b/intercom/tests/release-identity.test.js index 783d1840..a9166344 100644 --- a/intercom/tests/release-identity.test.js +++ b/intercom/tests/release-identity.test.js @@ -104,8 +104,8 @@ const clone = (value) => JSON.parse(JSON.stringify(value)); test('checked-in Intercom release identity verifies exact sorted contract code bytes', () => { const identity = verifyReleaseIdentity({ rootDir: INTERCOM_ROOT }); - assert.equal(identity.releaseVersion, '0.2.197'); - assert.equal(identity.contractVersion, 25); + assert.equal(identity.releaseVersion, '0.2.262'); + assert.equal(identity.contractVersion, 28); assert.match(identity.contractCodeSha256, /^[0-9a-f]{64}$/); assert.deepEqual( identity.files.map((file) => file.path), @@ -317,7 +317,7 @@ test('health exposes only a verified Intercom contract identity', async (t) => { assert.equal(response.status, 200); assert.deepEqual(await response.json(), { ok: true, - contract_version: 25, + contract_version: 28, contract_code_sha256: releaseIdentity.contractCodeSha256, }); }); @@ -335,7 +335,7 @@ test('health has no compatibility fallback when release identity is unavailable' }); test('release identity binds retained implementations and replay admission code', (t) => { - for (const name of ['contract/history/v23.js', 'contract/history/v24.js', 'trac/trac-peer/src/base/canonical-replay.js']) { + for (const name of ['contract/history/v23.js', 'contract/history/v24.js', 'contract/history/v25.js', 'contract/history/v26.js', 'contract/history/v27.js', 'trac/trac-peer/src/base/canonical-replay.js']) { const rootDir = fixtureRoot(t); fs.appendFileSync(path.join(rootDir, name), '\n// changed replay semantics\n'); assert.throws(() => verifyReleaseIdentity({ rootDir }), /mismatch/); diff --git a/intercom/tests/retail-crypto-payment-worker.test.mjs b/intercom/tests/retail-crypto-payment-worker.test.mjs new file mode 100644 index 00000000..aeafd550 --- /dev/null +++ b/intercom/tests/retail-crypto-payment-worker.test.mjs @@ -0,0 +1,16 @@ +import assert from 'node:assert/strict'; +import test from 'node:test'; + +import { tnkVerificationWindow } from '../scripts/retail-crypto-verification.mjs'; + +test('TNK verification bounds its scan from the caught-up frontier', () => { + const staleStartupLength = 338_219; + const caughtUpLength = 354_500; + const window = tnkVerificationWindow(caughtUpLength, 5_000); + + assert.deepEqual(window, { + fromSignedLength: 349_500, + minimumSignedLength: 354_500, + }); + assert.notEqual(window.fromSignedLength, staleStartupLength - 5_000); +}); diff --git a/intercom/tests/retail-crypto-verification.test.mjs b/intercom/tests/retail-crypto-verification.test.mjs new file mode 100644 index 00000000..9f678f8b --- /dev/null +++ b/intercom/tests/retail-crypto-verification.test.mjs @@ -0,0 +1,148 @@ +import assert from 'node:assert/strict'; +import test from 'node:test'; + +import { + RetryWork, + ReviewWork, + addressTopic, + isBridgeFundingShortfall, + normalizeTnkAddress, + summarizePayoutLiabilities, + uniqueIntentByAmount, + validateTapBridgePreflight, + verifyTapTransferReceipt, +} from '../scripts/retail-crypto-verification.mjs'; + +const HASH = `0x${'1'.repeat(64)}`; +const BLOCK_HASH = `0x${'2'.repeat(64)}`; +const TOKEN = `0x${'3'.repeat(40)}`; +const SENDER = `0x${'4'.repeat(40)}`; +const DESTINATION = `0x${'5'.repeat(40)}`; +const TRANSFER = '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef'; + +function receipt(overrides = {}) { + return { + transactionHash: HASH, + status: '0x1', + blockNumber: '0x64', + blockHash: BLOCK_HASH, + logs: [{ + address: TOKEN, + topics: [TRANSFER, addressTopic(SENDER), addressTopic(DESTINATION)], + data: '0x2a', + logIndex: '0x7', + }], + ...overrides, + }; +} + +const options = { + transactionHash: HASH, + token: TOKEN, + destination: DESTINATION, + amountBaseUnits: '42', + latestBlock: 120n, + finalizedBlock: 110n, +}; + +test('accepts exactly one finalized transfer to the quoted token and destination', () => { + const evidence = verifyTapTransferReceipt(receipt(), options); + assert.equal(evidence.transactionHash, HASH); + assert.equal(evidence.logIndex, 7); + assert.equal(evidence.fromAddress, SENDER); + assert.equal(evidence.tokenAmountBaseUnits, 42n); + assert.equal(evidence.confirmations, 21); + assert.equal(evidence.finalized, true); + assert.equal(evidence.externalRecordKey, `tap/${HASH}/7`); +}); + +test('keeps an unseen or unfinalized transfer pending', () => { + assert.throws(() => verifyTapTransferReceipt(null, options), (error) => + error instanceof RetryWork && error.code === 'transfer_pending' && !error.transferObserved); + assert.throws(() => verifyTapTransferReceipt(receipt(), { ...options, finalizedBlock: 99n }), (error) => + error instanceof RetryWork && error.code === 'awaiting_finality' && error.transferObserved); +}); + +test('sends a mismatched transfer to review instead of crediting it', () => { + assert.throws(() => verifyTapTransferReceipt(receipt(), { ...options, amountBaseUnits: '43' }), (error) => + error instanceof ReviewWork && error.reason === 'amount_mismatch' && + error.evidence?.tokenAmountBaseUnits === 42n && error.evidence?.toAddress === DESTINATION); + assert.throws(() => verifyTapTransferReceipt(receipt(), { ...options, destination: `0x${'6'.repeat(40)}` }), (error) => + error instanceof ReviewWork && error.reason === 'wrong_destination' && + error.evidence?.toAddress === DESTINATION); + assert.throws(() => verifyTapTransferReceipt(receipt(), { ...options, token: `0x${'7'.repeat(40)}` }), (error) => + error instanceof ReviewWork && error.reason === 'wrong_token'); +}); + +test('rejects reverted or identity-mismatched receipts', () => { + assert.throws(() => verifyTapTransferReceipt(receipt({ status: '0x0' }), options), (error) => + error instanceof ReviewWork && error.reason === 'malformed_transfer'); + assert.throws(() => verifyTapTransferReceipt(receipt({ transactionHash: `0x${'8'.repeat(64)}` }), options), (error) => + error instanceof ReviewWork && error.reason === 'malformed_transfer'); +}); + +test('requires the TAP collection account to be bound to the platform buyer', () => { + const collection = `0x${'7'.repeat(40)}`; + const buyer = '8'.repeat(64); + const report = { + from: collection, + tap_account_binding: { user: buyer }, + payment_config: { peer_rpc_url: 'http://127.0.0.1:49223/v1/' }, + }; + assert.deepEqual(validateTapBridgePreflight(report, { + platformBuyer: buyer, + collection, + coreRpc: 'http://127.0.0.1:49223/v1/', + }), { ethereumAccount: collection, boundUser: buyer }); + assert.throws(() => validateTapBridgePreflight({ + ...report, tap_account_binding: { user: '9'.repeat(64) }, + }, { + platformBuyer: buyer, + collection, + coreRpc: 'http://127.0.0.1:49223/v1/', + }), /configured buyer/); +}); + +test('validates TNK collection addresses against their configured network', () => { + assert.equal(normalizeTnkAddress(`trac1${'a'.repeat(40)}`, 'mainnet', 'collection'), `trac1${'a'.repeat(40)}`); + assert.equal(normalizeTnkAddress(`testtrac1${'b'.repeat(40)}`, 'testnet1', 'collection'), `testtrac1${'b'.repeat(40)}`); + assert.throws(() => normalizeTnkAddress(`trac1${'a'.repeat(40)}`, 'testnet1', 'collection'), /invalid/); +}); + +test('summarizes unsettled, held, and currently payable rail liabilities', () => { + assert.deepEqual(summarizePayoutLiabilities([ + { value: { type: 'provider_payout_liability', rail: 'tnk', total_au: '100', held_au: '20', paid_cum_au: '30' } }, + { value: { type: 'provider_payout_liability', rail: 'tnk', total_au: '40', held_au: '10', paid_cum_au: '5' } }, + ], 'TNK'), { + totalAu: 140n, + heldAu: 30n, + paidAu: 35n, + unsettledAu: 105n, + payableAu: 75n, + }); + assert.throws(() => summarizePayoutLiabilities([ + { value: { type: 'provider_payout_liability', rail: 'tap', total_au: '10', held_au: '8', paid_cum_au: '4' } }, + ], 'TAP'), /totals/); +}); + +test('attributes a transfer only when its exact amount identifies one quote', () => { + const intents = [ + { id: 'one', token_amount_base_units: '1001' }, + { id: 'two', token_amount_base_units: '1002' }, + ]; + assert.equal(uniqueIntentByAmount(intents, 1002n)?.id, 'two'); + assert.equal(uniqueIntentByAmount(intents, 999n), null); + assert.equal(uniqueIntentByAmount([...intents, { id: 'duplicate', token_amount_base_units: '1002' }], 1002n), null); +}); + +test('distinguishes collection funding shortfalls from RPC failures', () => { + const tap = Object.assign(new Error('command exited 1'), { + stderr: 'not enough TAP - send 0.2 TAP to the collection wallet', + }); + const tnk = Object.assign(new Error('command exited 1'), { + stdout: 'TNK balance is insufficient for the settlement transfer', + }); + assert.equal(isBridgeFundingShortfall(tap, 'TAP'), true); + assert.equal(isBridgeFundingShortfall(tnk, 'TNK'), true); + assert.equal(isBridgeFundingShortfall(new Error('Ethereum RPC failed'), 'TAP'), false); +}); diff --git a/intercom/tests/sidechannel-policy.test.js b/intercom/tests/sidechannel-policy.test.js index 02802fbc..0bb7d5cc 100644 --- a/intercom/tests/sidechannel-policy.test.js +++ b/intercom/tests/sidechannel-policy.test.js @@ -452,6 +452,139 @@ test('sidechannel bounds channel names/count and reclaims limiter state under co assert.equal(sidechannel.relaySourceLimits.has('aa'.repeat(32)), false); }); +const directRecoveryHarness = ({ healthy = false, unrelated = false, proofDelayMs = 0 } = {}) => { + const remote = 'dd'.repeat(32); + const unrelatedRemote = 'ee'.repeat(32); + const counts = { destroyed: 0, healthProofs: 0, leaves: 0, recoveryJoins: 0 }; + const makeConnection = (remoteKey, opened) => ({ + remotePublicKey: b4a.from(remoteKey, 'hex'), + userData: { + pair() {}, + createChannel: () => ({ + opened, + addMessage: () => ({ send: () => true }), + open() {}, + close() {}, + fullyOpened: async () => opened, + }), + }, + }); + const stale = makeConnection(remote, false); + const fresh = makeConnection(remote, true); + const base = makeConnection(unrelatedRemote, true); + const connections = new Set(unrelated ? [stale, base] : [stale]); + let sidechannel; + stale.destroy = () => { + counts.destroyed += 1; + stale.destroyed = true; + connections.delete(stale); + }; + const directPeer = { + wallet: peer.wallet, + directSession: { + async proveConnection(connection) { + counts.healthProofs += 1; + if (proofDelayMs > 0) { + await new Promise((resolve) => setTimeout(resolve, proofDelayMs)); + } + if (connection === stale && !healthy) throw new Error('bidirectional health timed out'); + return { remote: connection === base ? unrelatedRemote : remote, proven: true }; + }, + }, + swarm: { + connections, + joinPeer() { + if (!connections.has(stale) && !connections.has(fresh)) { + counts.recoveryJoins += 1; + connections.add(fresh); + sidechannel.connections.set(fresh, new Map()); + } + }, + leavePeer(key) { + assert.equal(b4a.toString(key, 'hex'), remote); + counts.leaves += 1; + }, + }, + }; + sidechannel = new Sidechannel(directPeer, { + channels: [MAYHEM_RELAY_CHANNEL], + channelOpenTimeoutMs: 2, + directConnectMaxWaitMs: 20, + directConnectPollMs: 1, + directRecoveryHealthTimeoutMs: 2, + directRecoveryBackoffMs: 1, + openRetryMax: 0, + }); + sidechannel.connections.set(stale, new Map()); + if (unrelated) sidechannel.connections.set(base, new Map()); + return { base, connections, counts, fresh, remote, sidechannel, stale }; +}; + +test('direct peer connect recovers an asymmetric transport only after failed health proof', async () => { + const harness = directRecoveryHarness(); + + assert.equal( + await harness.sidechannel.connectDirectPeer(harness.remote, MAYHEM_RELAY_CHANNEL, 20), + false + ); + assert.deepEqual(harness.counts, { + destroyed: 1, + healthProofs: 1, + leaves: 1, + recoveryJoins: 1, + }); + assert.equal( + await harness.sidechannel.connectDirectPeer(harness.remote, MAYHEM_RELAY_CHANNEL, 20), + true, + 'the bounded rejoin must make a fresh Mayhem channel available to the next relay attempt' + ); +}); + +test('direct peer connect never churns a healthy transport with an incompatible sidechannel', async () => { + const harness = directRecoveryHarness({ healthy: true }); + + assert.equal( + await harness.sidechannel.connectDirectPeer(harness.remote, MAYHEM_RELAY_CHANNEL, 20), + false + ); + assert.equal(harness.sidechannel.directConnectFailure( + harness.remote, + MAYHEM_RELAY_CHANNEL + )?.phase, 'protocol_incompatible'); + assert.equal(harness.counts.destroyed, 0); + assert.equal(harness.counts.leaves, 0); + assert.equal(harness.connections.has(harness.stale), true); +}); + +test('concurrent direct peer callers serialize one dead-transport recovery', async () => { + const harness = directRecoveryHarness({ proofDelayMs: 2 }); + + assert.deepEqual( + await Promise.all(Array.from({ length: 3 }, () => ( + harness.sidechannel.connectDirectPeer(harness.remote, MAYHEM_RELAY_CHANNEL, 20) + ))), + [false, false, false] + ); + assert.equal(harness.counts.healthProofs, 1); + assert.equal(harness.counts.destroyed, 1); + assert.equal(harness.counts.leaves, 1); + assert.equal(harness.counts.recoveryJoins, 1); +}); + +test('direct peer recovery leaves unrelated peer connections and channels untouched', async () => { + const harness = directRecoveryHarness({ unrelated: true }); + const unrelatedRecords = harness.sidechannel.connections.get(harness.base); + + assert.equal( + await harness.sidechannel.connectDirectPeer(harness.remote, MAYHEM_RELAY_CHANNEL, 20), + false + ); + assert.equal(harness.connections.has(harness.base), true); + assert.equal(harness.sidechannel.connections.get(harness.base), unrelatedRecords); + assert.equal(harness.base.destroyed, undefined); + assert.equal(harness.counts.healthProofs, 1); +}); + test('sidechannel does not send from a stale fully-opened callback after channel removal', async () => { const room = 'room-race'; let resolveOpened = null; diff --git a/intercom/tests/tnk-deposit-watcher.test.js b/intercom/tests/tnk-deposit-watcher.test.js index 3799fa2e..9921fd05 100644 --- a/intercom/tests/tnk-deposit-watcher.test.js +++ b/intercom/tests/tnk-deposit-watcher.test.js @@ -8,6 +8,7 @@ import { pendingEntriesFromState, pubKeyHexToMsbAddress, resolveActiveBillingEpoch, + resolveMinimumMsbSignedLength, transferFromTxDetails, waitForDepositState, } from '../scripts/tnk-deposit-watcher.mjs'; @@ -32,6 +33,18 @@ test('tnk deposit watcher derives the active billing epoch from ledger apply sta ); }); +test('tnk deposit watcher waits for the canonical peer MSB height', async () => { + const statusFetch = async () => ({ + ok: true, + json: async () => ({ msb: { signedLength: 356_400 } }), + }); + assert.equal( + await resolveMinimumMsbSignedLength('http://peer/v1', 339_540, { fetchImpl: statusFetch }), + 356_400, + ); + assert.equal(await resolveMinimumMsbSignedLength(null, 339_540), 339_540); +}); + test('tnk deposit watcher matches quoted pending intents by user-derived MSB sender, treasury, and amount', () => { const pendingEntries = pendingEntriesFromState({ values: [ diff --git a/intercom/tests/writer-checkpoint-transport.test.js b/intercom/tests/writer-checkpoint-transport.test.js index e01d9c6e..993ec950 100644 --- a/intercom/tests/writer-checkpoint-transport.test.js +++ b/intercom/tests/writer-checkpoint-transport.test.js @@ -145,7 +145,7 @@ const paidOperation = (paid, proof = { signed_length: 100, validator: 'ab'.repea ipk: paid.surrogate.address, wp: proof.validator, msbsl: proof.signed_length }, }); -test('v24 durable checkpoint survives v25 restart and exact paid replay only once', async () => { +test('v24 durable checkpoint survives v26 restart and exact paid replay only once', async () => { const ctx = await harness(); const paid = await historicalPreparation(ctx); await assert.rejects(ctx.transport.broadcast(paid), /response lost/); @@ -173,9 +173,9 @@ test('v24 durable checkpoint survives v25 restart and exact paid replay only onc test('fresh v24 dispatch cannot fabricate historical preparation', async () => { const ctx = await harness(); const paid = await historicalPreparation(ctx); - assert.throws(() => validateMayhemOperationContractVersion(paidOperation(paid)), /expected CONTRACT_VERSION 25, got 24/); + assert.throws(() => validateMayhemOperationContractVersion(paidOperation(paid)), /expected CONTRACT_VERSION 26, got 24/); await ctx.storage.del('checkpoint/prepared/100'); - await assert.rejects(ctx.contract.execute(paidOperation(paid), ctx.storage), /expected CONTRACT_VERSION 25, got 24/); + await assert.rejects(ctx.contract.execute(paidOperation(paid), ctx.storage), /expected CONTRACT_VERSION 26, got 24/); await assert.rejects(ctx.transport.broadcast(paid), /no matching historical canonical preparation/); assert.equal(ctx.broadcasts, 0); }); @@ -192,7 +192,7 @@ test('historical checkpoint recovery rejects tampered evidence and unrelated old const snapshot = (await ctx.storage.get('checkpoint/prepared/100')).value; mutate(snapshot); await ctx.storage.put('checkpoint/prepared/100', snapshot); - await assert.rejects(ctx.contract.execute(paidOperation(paid), ctx.storage), /expected CONTRACT_VERSION 25, got 24/); + await assert.rejects(ctx.contract.execute(paidOperation(paid), ctx.storage), /expected CONTRACT_VERSION 26, got 24/); await assert.rejects(ctx.transport.broadcast(paid), /no matching historical canonical preparation/); } const ctx = await harness(); @@ -200,7 +200,7 @@ test('historical checkpoint recovery rejects tampered evidence and unrelated old const unrelated = paidOperation(paid); unrelated.value.dispatch = { type: 'setRules', value: { op: 'set_rules', ver: 1, hash: 'aa'.repeat(32), contract_version: 24 } }; - await assert.rejects(ctx.contract.execute(unrelated, ctx.storage), /expected CONTRACT_VERSION 25, got 24/); + await assert.rejects(ctx.contract.execute(unrelated, ctx.storage), /expected CONTRACT_VERSION 26, got 24/); const mutated = structuredClone(paid); mutated.dispatch.value.snapshot_hash = '99'.repeat(32); await assert.rejects(ctx.transport.broadcast(mutated), /bytes or signature changed/); diff --git a/intercom/trac/msb/package-lock.json b/intercom/trac/msb/package-lock.json index 37be5871..efb8b7f5 100644 --- a/intercom/trac/msb/package-lock.json +++ b/intercom/trac/msb/package-lock.json @@ -26,7 +26,7 @@ "hyperbee": "2.26.5", "hypercore": "11.18.3", "hypercore-crypto": "3.6.1", - "hyperdht": "6.27.0", + "hyperdht": "6.29.6", "hyperschema": "1.17.1", "hyperswarm": "4.14.2", "lodash": "^4.18.1", @@ -5401,9 +5401,9 @@ } }, "node_modules/hyperdht": { - "version": "6.27.0", - "resolved": "https://registry.npmjs.org/hyperdht/-/hyperdht-6.27.0.tgz", - "integrity": "sha512-ZOXPSpsphn83hBxfFcZKNfXcgLLZH3HBWM2G0TAT0vl0hrBY287oEJ/2Nj0NimgxizpzA4J4t8wFsp/LSLs6Sw==", + "version": "6.29.6", + "resolved": "https://registry.npmjs.org/hyperdht/-/hyperdht-6.29.6.tgz", + "integrity": "sha512-bMMtw02fhiALdsTVLNZ/VFmMZuEY8kxb0/Rprl6oYF4/TCS653i3FDjtJeRbqtnAzwAj6dVZ/EwhoNOSCFGaYQ==", "license": "MIT", "dependencies": { "@hyperswarm/secret-stream": "^6.6.2", @@ -5412,7 +5412,6 @@ "blind-relay": "^1.3.0", "bogon": "^1.0.0", "compact-encoding": "^2.4.1", - "compact-encoding-net": "^1.0.1", "dht-rpc": "^6.15.1", "hypercore-crypto": "^3.3.0", "hypercore-id-encoding": "^1.2.0", diff --git a/intercom/trac/msb/package.json b/intercom/trac/msb/package.json index 3d63121d..d1a896e7 100644 --- a/intercom/trac/msb/package.json +++ b/intercom/trac/msb/package.json @@ -54,7 +54,7 @@ "hyperbee": "2.26.5", "hypercore": "11.18.3", "hypercore-crypto": "3.6.1", - "hyperdht": "6.27.0", + "hyperdht": "6.29.6", "hyperschema": "1.17.1", "hyperswarm": "4.14.2", "lodash": "^4.18.1", diff --git a/intercom/trac/trac-peer/package-lock.json b/intercom/trac/trac-peer/package-lock.json index bcaaf25d..1a78d714 100644 --- a/intercom/trac/trac-peer/package-lock.json +++ b/intercom/trac/trac-peer/package-lock.json @@ -53,7 +53,7 @@ "hyperbee": "^2.24.2", "hypercore": "11.8.3", "hypercore-crypto": "^3.4.0", - "hyperdht": "^6.20.5", + "hyperdht": "6.29.6", "hyperswarm": "^4.11.5", "inspector": "npm:bare-node-inspector", "is-options": "1.0.2", @@ -75,7 +75,7 @@ "timers": "npm:bare-node-timers", "tls": "npm:bare-node-tls", "trac-crypto-api": "^0.1.5", - "trac-msb": "^0.2.21", + "trac-msb": "file:../msb", "trac-wallet": "^1.0.4", "url": "npm:bare-node-url", "util": "npm:bare-node-util", @@ -224,15 +224,6 @@ "url": "https://paulmillr.com/funding/" } }, - "node_modules/@metamask/scure-bip39/node_modules/@scure/base": { - "version": "1.1.9", - "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.1.9.tgz", - "integrity": "sha512-8YKhl8GHiNI/pU2VMaofa2Tor7PJRAjwQLBBuilkJ9L5+13yVbC7JO/wS7piioAvPSwR3JKM1IJ/u4xQzbcXKg==", - "license": "MIT", - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, "node_modules/@metamask/superstruct": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/@metamask/superstruct/-/superstruct-3.2.1.tgz", @@ -349,9 +340,9 @@ "license": "BSD-3-Clause" }, "node_modules/@scure/base": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.2.6.tgz", - "integrity": "sha512-g/nm5FgUa//MCj1gV09zTJTaM6KBAHqLN907YVQqf7zC49+DcO4B1so4ZX07Ef10Twr6nuqYEH9GEggFXA4Fmg==", + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.1.9.tgz", + "integrity": "sha512-8YKhl8GHiNI/pU2VMaofa2Tor7PJRAjwQLBBuilkJ9L5+13yVbC7JO/wS7piioAvPSwR3JKM1IJ/u4xQzbcXKg==", "license": "MIT", "funding": { "url": "https://paulmillr.com/funding/" @@ -395,15 +386,6 @@ "url": "https://paulmillr.com/funding/" } }, - "node_modules/@scure/bip32/node_modules/@scure/base": { - "version": "1.1.9", - "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.1.9.tgz", - "integrity": "sha512-8YKhl8GHiNI/pU2VMaofa2Tor7PJRAjwQLBBuilkJ9L5+13yVbC7JO/wS7piioAvPSwR3JKM1IJ/u4xQzbcXKg==", - "license": "MIT", - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, "node_modules/@scure/bip39": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.3.0.tgz", @@ -429,15 +411,6 @@ "url": "https://paulmillr.com/funding/" } }, - "node_modules/@scure/bip39/node_modules/@scure/base": { - "version": "1.1.9", - "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.1.9.tgz", - "integrity": "sha512-8YKhl8GHiNI/pU2VMaofa2Tor7PJRAjwQLBBuilkJ9L5+13yVbC7JO/wS7piioAvPSwR3JKM1IJ/u4xQzbcXKg==", - "license": "MIT", - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, "node_modules/@tracsystems/blake3": { "version": "0.0.15", "resolved": "https://registry.npmjs.org/@tracsystems/blake3/-/blake3-0.0.15.tgz", @@ -472,12 +445,12 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "25.6.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-25.6.0.tgz", - "integrity": "sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ==", + "version": "22.20.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.20.3.tgz", + "integrity": "sha512-DZmzkmwHzXrLPAXPyKNDzlIwMMUZCVacoD25ywdy5YTKGbOx/2ld+Q38Im2zJ0vBuZP5Prd3VZutKZyXwkOS8A==", "license": "MIT", "dependencies": { - "undici-types": "~7.19.0" + "undici-types": "~6.21.0" } }, "node_modules/assert": { @@ -605,19 +578,39 @@ "bare": ">=1.7.0" } }, + "node_modules/bare-channel/node_modules/b4a": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.9.0.tgz", + "integrity": "sha512-dpfcF9fDNR6++cthXR67iyhgqWy9CBouAvIWhIntzBG6cvK/cnIPiZQjBwi/ZqjjBEDGfoNDtmB0kTjroOJ3pQ==", + "license": "Apache-2.0", + "peerDependencies": { + "react-native-b4a": "*" + }, + "peerDependenciesMeta": { + "react-native-b4a": { + "optional": true + } + } + }, "node_modules/bare-channel/node_modules/bare-stream": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.7.0.tgz", - "integrity": "sha512-oyXQNicV1y8nc2aKffH+BUHFRXmx6VrPzlnaEvMhram0nPBrKcEdcyBg5r08D0i8VxngHFAiVyn1QKXpSG0B8A==", + "version": "2.13.4", + "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.13.4.tgz", + "integrity": "sha512-PcrQ8lVLbiJscNm1Kez+Yp4Gy4AHGcN1lzwjvf5NybWen7VvEgUfyfnXYJ2zNqWnzOfCb1Abq6lH8ti0syQszA==", "license": "Apache-2.0", "dependencies": { - "streamx": "^2.21.0" + "b4a": "^1.8.1", + "streamx": "^2.25.0", + "teex": "^1.0.1" }, "peerDependencies": { + "bare-abort-controller": "*", "bare-buffer": "*", "bare-events": "*" }, "peerDependenciesMeta": { + "bare-abort-controller": { + "optional": true + }, "bare-buffer": { "optional": true }, @@ -746,9 +739,9 @@ } }, "node_modules/bare-fs": { - "version": "4.5.2", - "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-4.5.2.tgz", - "integrity": "sha512-veTnRzkb6aPHOvSKIOy60KzURfBdUflr5VReI+NSaPL6xf+XLdONQgZgpYvUuZLVQ8dCqxpBAudaOM1+KpAUxw==", + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-4.8.1.tgz", + "integrity": "sha512-N1nnXdHZAOSstz0XiHikGS4HGMH4CnSwhqWdGQQMqqdvp4Jybm9sE3R1WVnpWVd4SFkc8ryPDBLViNLwiEqECg==", "license": "Apache-2.0", "dependencies": { "bare-events": "^2.5.4", @@ -758,7 +751,7 @@ "fast-fifo": "^1.3.2" }, "engines": { - "bare": ">=1.16.0" + "bare": ">=1.28.0" }, "peerDependencies": { "bare-buffer": "*" @@ -770,9 +763,9 @@ } }, "node_modules/bare-fs/node_modules/bare-url": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/bare-url/-/bare-url-2.3.2.tgz", - "integrity": "sha512-ZMq4gd9ngV5aTMa5p9+UfY0b3skwhHELaDkhEHetMdX0LRkW9kzaym4oo/Eh+Ghm0CCDuMTsRIGM/ytUc1ZYmw==", + "version": "2.5.4", + "resolved": "https://registry.npmjs.org/bare-url/-/bare-url-2.5.4.tgz", + "integrity": "sha512-Gxa7UVWBr0/edU1b+TJhn/AZvMQUj9OGspvYsaTYQrAbZA4BOTZGL3LiZxvD+CeMlDH4juwD84+eTAp/bLYW5g==", "license": "Apache-2.0", "dependencies": { "bare-path": "^3.0.0" @@ -785,9 +778,9 @@ "license": "Apache-2.0" }, "node_modules/bare-http-parser": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/bare-http-parser/-/bare-http-parser-1.0.1.tgz", - "integrity": "sha512-A3LTDTcELcmNJ3g5liIaS038v/BQxOhA9cjhBESn7eoV7QCuMoIRBKLDadDe08flxyLbxI2f+1l2MZ/5+HnKPA==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/bare-http-parser/-/bare-http-parser-1.1.5.tgz", + "integrity": "sha512-sPaDetLbWRmth04JmuTCvw/hoNdWJvYUJN8n1tYdGW3HM0mMnCvK2f0oIxE6HK7iOq+WlsRzEMg1LT/b0wGbLQ==", "license": "Apache-2.0" }, "node_modules/bare-http1": { @@ -875,13 +868,17 @@ } }, "node_modules/bare-module-lexer": { - "version": "1.4.7", - "resolved": "https://registry.npmjs.org/bare-module-lexer/-/bare-module-lexer-1.4.7.tgz", - "integrity": "sha512-0klU4eMsjh/wcxi8FdHmNom2j2F4kmkXOhyJFL9qTaSFp2lE3m6BtbKgMHY8R5miqC9r8/IfA8wzXnC5Os14WA==", + "version": "1.6.7", + "resolved": "https://registry.npmjs.org/bare-module-lexer/-/bare-module-lexer-1.6.7.tgz", + "integrity": "sha512-ImbCteBVEc5JKNV2M6V6s23mAunzl+hDolPWfaexYqBMiN7JG4yDy6Dg5ue2N61U8l2mlvgZuefVl13Yd06lDA==", "license": "Apache-2.0", "dependencies": { "require-addon": "^1.0.2" }, + "engines": { + "bare": ">=1.0.0", + "node": "^22.21.0 || >=24.9.0" + }, "peerDependencies": { "bare-buffer": "*" }, @@ -927,16 +924,24 @@ } }, "node_modules/bare-pipe": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/bare-pipe/-/bare-pipe-4.1.2.tgz", - "integrity": "sha512-btXtZLlABEDRp50cfLj9iweISqAJSNMCjeq5v0v9tBY2a7zSSqmfa2ZoE1ki2qxAvubagLUqw6VDifpsuI/qmg==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/bare-pipe/-/bare-pipe-4.3.1.tgz", + "integrity": "sha512-3P4MYTgOys1Bbz5dgWzwfroNOOl8x1zWJHTHVlwsojThhV/uJPRf+qFrYYBOnI0vQiJJLJtHlw8a4Osj/kcsJQ==", "license": "Apache-2.0", "dependencies": { - "bare-events": "^2.0.0", - "bare-stream": "^2.0.0" + "bare-events": "^2.5.4", + "bare-stream": "^2.6.4" }, "engines": { "bare": ">=1.16.0" + }, + "peerDependencies": { + "bare-buffer": "*" + }, + "peerDependenciesMeta": { + "bare-buffer": { + "optional": true + } } }, "node_modules/bare-process": { @@ -1069,9 +1074,9 @@ } }, "node_modules/bare-tcp": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/bare-tcp/-/bare-tcp-2.2.2.tgz", - "integrity": "sha512-bYnw1AhzGlfLOD4nTceUXkhhgznZKvDuwjX1Au0VWaVitwqG40oaTvvhEQVCcK3FEwjRTiukUzHnAFsYXUI+3Q==", + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/bare-tcp/-/bare-tcp-2.6.1.tgz", + "integrity": "sha512-8f8UFP7O27gIGaVWJPysQ2am8LSD6YCuVSbnTbOrQjUD0RkWEZexvi2Q66P99MLxSephlyjoU0mLxWmozRJMnA==", "license": "Apache-2.0", "dependencies": { "bare-dns": "^2.0.4", @@ -1080,6 +1085,14 @@ }, "engines": { "bare": ">=1.16.0" + }, + "peerDependencies": { + "bare-pipe": "*" + }, + "peerDependenciesMeta": { + "bare-pipe": { + "optional": true + } } }, "node_modules/bare-timers": { @@ -1129,6 +1142,27 @@ "bare": ">=1.2.0" } }, + "node_modules/bare-type-stripper": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/bare-type-stripper/-/bare-type-stripper-0.1.7.tgz", + "integrity": "sha512-kiYTY1PqJnCQmaGT4Q23gskKCTtxqinPdA0SKMO80rjyLMdZhkT+mALf+/UryvovV6ysXtYXGSNPZR4wFhCI/g==", + "license": "Apache-2.0", + "dependencies": { + "require-addon": "^1.0.2" + }, + "engines": { + "bare": ">=1.0.0", + "node": "^22.21.0 || >=24.9.0" + }, + "peerDependencies": { + "bare-buffer": "*" + }, + "peerDependenciesMeta": { + "bare-buffer": { + "optional": true + } + } + }, "node_modules/bare-url": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/bare-url/-/bare-url-2.1.5.tgz", @@ -1170,9 +1204,9 @@ } }, "node_modules/bare-v8-to-istanbul/node_modules/bare-module": { - "version": "6.1.3", - "resolved": "https://registry.npmjs.org/bare-module/-/bare-module-6.1.3.tgz", - "integrity": "sha512-5XWsVHsvtWMH4tK4DQWgpNTV0t/sg3ZrAaQLIxrwjrS5+u8Q9vEgc/zQ4QaDPWDse/y/5h+d+YG1Q0JfSMt0zA==", + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/bare-module/-/bare-module-6.4.0.tgz", + "integrity": "sha512-Yn4V5g5EqGQL4LYUOmt7fjKzj2JPWyJOqE3lPoeZwfUH5rk4CKUfZj6JhDwbzhBYCqqmUgjgQ5aY8cihAPILLA==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -1180,10 +1214,11 @@ "bare-module-lexer": "^1.0.0", "bare-module-resolve": "^1.8.0", "bare-path": "^3.0.0", + "bare-type-stripper": "^0.1.2", "bare-url": "^2.0.1" }, "engines": { - "bare": ">=1.23.0" + "bare": ">=1.29.4" }, "peerDependencies": { "bare-buffer": "*" @@ -1385,9 +1420,9 @@ } }, "node_modules/brittle/node_modules/bare-url": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/bare-url/-/bare-url-2.3.2.tgz", - "integrity": "sha512-ZMq4gd9ngV5aTMa5p9+UfY0b3skwhHELaDkhEHetMdX0LRkW9kzaym4oo/Eh+Ghm0CCDuMTsRIGM/ytUc1ZYmw==", + "version": "2.5.4", + "resolved": "https://registry.npmjs.org/bare-url/-/bare-url-2.5.4.tgz", + "integrity": "sha512-Gxa7UVWBr0/edU1b+TJhn/AZvMQUj9OGspvYsaTYQrAbZA4BOTZGL3LiZxvD+CeMlDH4juwD84+eTAp/bLYW5g==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -1522,17 +1557,6 @@ "which-runtime": "^1.2.1" } }, - "node_modules/corestore/node_modules/hypercore-crypto": { - "version": "3.6.1", - "resolved": "https://registry.npmjs.org/hypercore-crypto/-/hypercore-crypto-3.6.1.tgz", - "integrity": "sha512-ltIz2uDwy9pO/ZGTvqcjzyBkvt6O4cVm4r/nNxh0GFs/RbQtqP/i4wCvLEdmU7ptgtnw7fI67WYD1aHPuv4OVA==", - "license": "MIT", - "dependencies": { - "b4a": "^1.6.6", - "compact-encoding": "^2.15.0", - "sodium-universal": "^5.0.0" - } - }, "node_modules/crc-32": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz", @@ -1701,9 +1725,9 @@ } }, "node_modules/events-universal/node_modules/bare-events": { - "version": "2.8.2", - "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.8.2.tgz", - "integrity": "sha512-riJjyv1/mHLIPX4RwiK+oW9/4c3TEUeORHKefKAKnZ5kyslbN+HXowtbaVEqt4IMUB7OXlfixcs6gsFeo/jhiQ==", + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.9.2.tgz", + "integrity": "sha512-AIPKioV7/Y/8KfZ3AAhjPJxLLbY49S64Ym5DakZlUg75qQiTgUq9hEJoEwa4eUezPUlXRy/i5NpsKvo9jgKmoA==", "license": "Apache-2.0", "peerDependencies": { "bare-abort-controller": "*" @@ -1832,9 +1856,9 @@ } }, "node_modules/hyperbee": { - "version": "2.24.2", - "resolved": "https://registry.npmjs.org/hyperbee/-/hyperbee-2.24.2.tgz", - "integrity": "sha512-RAzptsdDN4oDCQ/MjWavjt720D+jRbzHvVl+YW6OwdcaLJslGpbKjbdWV1yuDiGwBs7iRwTUaFA78GtcRHZFwA==", + "version": "2.27.3", + "resolved": "https://registry.npmjs.org/hyperbee/-/hyperbee-2.27.3.tgz", + "integrity": "sha512-PXURH2U4juUZyJRKHTrY5z1zX851pmI1Q0jfv5F/hCIErDt/ND8jOZuxc3hfOLM9f0W3qJEDTMlV5AJBkVPy8w==", "license": "MIT", "dependencies": { "b4a": "^1.6.0", @@ -1845,6 +1869,7 @@ "protocol-buffers-encodings": "^1.2.0", "rache": "^1.0.0", "ready-resource": "^1.0.0", + "resolve-reject-promise": "^1.1.0", "safety-catch": "^1.0.2", "streamx": "^2.12.4", "unslab": "^1.2.0" @@ -1880,40 +1905,23 @@ } }, "node_modules/hypercore-crypto": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/hypercore-crypto/-/hypercore-crypto-3.4.0.tgz", - "integrity": "sha512-0cZA1B58p1J84TDbTh8DMMIj7Qr7Rzz8NyGIo+ykUhdwD21gtjiiLWoME92QvN+lgbfu0Zfr9vwxT8sRmyg+AA==", - "license": "MIT", - "dependencies": { - "b4a": "^1.1.0", - "compact-encoding": "^2.5.1", - "sodium-universal": "^4.0.0" - } - }, - "node_modules/hypercore-crypto/node_modules/sodium-native": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/sodium-native/-/sodium-native-4.3.3.tgz", - "integrity": "sha512-OnxSlN3uyY8D0EsLHpmm2HOFmKddQVvEMmsakCrXUzSd8kjjbzL413t4ZNF3n0UxSwNgwTyUvkmZHTfuCeiYSw==", + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/hypercore-crypto/-/hypercore-crypto-3.7.0.tgz", + "integrity": "sha512-SWxobptQf2V/+ZLCCDRTXqFzGfTPIkNIuDyLKXpEMfcpJ1/ec94N9aWgylHcWOHmRt14ng/KR7z0BugebWHK9Q==", "license": "MIT", "dependencies": { - "require-addon": "^1.1.0" + "b4a": "^1.6.6", + "compact-encoding": "^3.0.0", + "sodium-universal": "^5.0.0" } }, - "node_modules/hypercore-crypto/node_modules/sodium-universal": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/sodium-universal/-/sodium-universal-4.0.1.tgz", - "integrity": "sha512-sNp13PrxYLaUFHTGoDKkSDFvoEu51bfzE12RwGlqU1fcrkpAOK0NvizaJzOWV0Omtk9me2+Pnbjcf/l0efxuGQ==", - "license": "MIT", + "node_modules/hypercore-crypto/node_modules/compact-encoding": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/compact-encoding/-/compact-encoding-3.5.0.tgz", + "integrity": "sha512-X7yaWh0NNMVB2YXAWSTWjYU3WXvDTniKxbtQlisbXOVutIE6awd+zCQdNqScTjIXv9m9orqdlri+oaWWsKqTuA==", + "license": "Apache-2.0", "dependencies": { - "sodium-native": "^4.0.0" - }, - "peerDependencies": { - "sodium-javascript": "~0.8.0" - }, - "peerDependenciesMeta": { - "sodium-javascript": { - "optional": true - } + "b4a": "^1.3.0" } }, "node_modules/hypercore-errors": { @@ -1956,21 +1964,10 @@ "streamx": "^2.21.1" } }, - "node_modules/hypercore-storage/node_modules/hypercore-crypto": { - "version": "3.6.1", - "resolved": "https://registry.npmjs.org/hypercore-crypto/-/hypercore-crypto-3.6.1.tgz", - "integrity": "sha512-ltIz2uDwy9pO/ZGTvqcjzyBkvt6O4cVm4r/nNxh0GFs/RbQtqP/i4wCvLEdmU7ptgtnw7fI67WYD1aHPuv4OVA==", - "license": "MIT", - "dependencies": { - "b4a": "^1.6.6", - "compact-encoding": "^2.15.0", - "sodium-universal": "^5.0.0" - } - }, "node_modules/hyperdht": { - "version": "6.20.5", - "resolved": "https://registry.npmjs.org/hyperdht/-/hyperdht-6.20.5.tgz", - "integrity": "sha512-eDAwTmAtE9rjMivgqYtqHalTdBVhhCMBVHlCWRVhEcWtchpDonsd2dmX26lJ0raoF+l9djkXvPcN1/kb9/kykw==", + "version": "6.29.6", + "resolved": "https://registry.npmjs.org/hyperdht/-/hyperdht-6.29.6.tgz", + "integrity": "sha512-bMMtw02fhiALdsTVLNZ/VFmMZuEY8kxb0/Rprl6oYF4/TCS653i3FDjtJeRbqtnAzwAj6dVZ/EwhoNOSCFGaYQ==", "license": "MIT", "dependencies": { "@hyperswarm/secret-stream": "^6.6.2", @@ -1979,7 +1976,6 @@ "blind-relay": "^1.3.0", "bogon": "^1.0.0", "compact-encoding": "^2.4.1", - "compact-encoding-net": "^1.0.1", "dht-rpc": "^6.15.1", "hypercore-crypto": "^3.3.0", "hypercore-id-encoding": "^1.2.0", @@ -1998,28 +1994,38 @@ } }, "node_modules/hyperschema": { - "version": "1.19.0", - "resolved": "https://registry.npmjs.org/hyperschema/-/hyperschema-1.19.0.tgz", - "integrity": "sha512-gHbLxLygsDUmX9MVs8G1W4xC9NglSyrw+t28sfFFzdU40gCUUmIo3n2MkIRHpUOT7Jj+8iuvq2wSkpaG+3k/Xg==", + "version": "1.26.2", + "resolved": "https://registry.npmjs.org/hyperschema/-/hyperschema-1.26.2.tgz", + "integrity": "sha512-5Y7C5zCfH28yiK7RhuMl7VkWt24Mr0E5sp1r2ntd3Ab3+yOBdkDWau0/d/F5beC799Vd5e4xqIAu5NJLM7i//w==", "license": "Apache-2.0", "dependencies": { "bare-fs": "^4.0.1", - "compact-encoding": "^2.15.0", + "compact-encoding": "^3.5.0", "generate-object-property": "^2.0.0", "generate-string": "^1.0.1" } }, + "node_modules/hyperschema/node_modules/compact-encoding": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/compact-encoding/-/compact-encoding-3.5.0.tgz", + "integrity": "sha512-X7yaWh0NNMVB2YXAWSTWjYU3WXvDTniKxbtQlisbXOVutIE6awd+zCQdNqScTjIXv9m9orqdlri+oaWWsKqTuA==", + "license": "Apache-2.0", + "dependencies": { + "b4a": "^1.3.0" + } + }, "node_modules/hyperswarm": { - "version": "4.11.5", - "resolved": "https://registry.npmjs.org/hyperswarm/-/hyperswarm-4.11.5.tgz", - "integrity": "sha512-KJ5qQrfFLYZvvgwxf43dcgp9R7CX3quwUEeycK9BWYHcZAqv7NggIQf6q3YqfHHH7ecks6WdRjD6s2dMsLdmzw==", + "version": "4.17.1", + "resolved": "https://registry.npmjs.org/hyperswarm/-/hyperswarm-4.17.1.tgz", + "integrity": "sha512-bFy89nulBSY1qQZMZMGWb6wwBHftthONZr5IpX4o7hcSmMQI5IHyfA7Id3KU03SDflWfPvEm1YJy0AL5BFFblA==", "license": "MIT", "dependencies": { "b4a": "^1.3.1", "bare-events": "^2.2.0", - "hyperdht": "^6.11.0", + "hyperdht": "^6.21.0", "safety-catch": "^1.0.2", "shuffled-priority-queue": "^2.1.0", + "streamx": "^2.22.1", "unslab": "^1.3.0" } }, @@ -2257,12 +2263,12 @@ } }, "node_modules/p-queue": { - "version": "9.1.2", - "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-9.1.2.tgz", - "integrity": "sha512-ktsDOALzTYTWWF1PbkNVg2rOt+HaOaMWJMUnt7T3qf5tvZ1L8dBW3tObzprBcXNMKkwj+yFSLqHso0x+UFcJXw==", + "version": "9.3.3", + "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-9.3.3.tgz", + "integrity": "sha512-NXAOdnEe5FsZJfT4oK84lE1Y5cFFdWlRuOo5tww8DyNMxyRXwn39fIkUtNLKppcPC+UYU/bXujNCUGDv01y7CA==", "license": "MIT", "dependencies": { - "eventemitter3": "^5.0.1", + "eventemitter3": "^5.0.4", "p-timeout": "^7.0.0" }, "engines": { @@ -2273,9 +2279,9 @@ } }, "node_modules/p-timeout": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-7.0.1.tgz", - "integrity": "sha512-AxTM2wDGORHGEkPCt8yqxOTMgpfbEHqF51f/5fJCmwFC3C/zNcGT63SymH2ttOAaiIws2zVg4+izQCjrakcwHg==", + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-7.0.2.tgz", + "integrity": "sha512-prbX4Z3YszrFNgH+MW5Zoeq3baXrMtP/MQnFeET90UB/GtGcGDQ5Usg9OCy6ETjTTntOw1SL2z9fMPUppN3Guw==", "license": "MIT", "engines": { "node": ">=20" @@ -2327,9 +2333,9 @@ } }, "node_modules/pear-runtime/node_modules/b4a": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.8.1.tgz", - "integrity": "sha512-aiqre1Nr0B/6DgE2N5vwTc+2/oQZ4Wh1t4NznYY4E00y8LCt6NqdRv81so00oo27D8MVKTpUa/MwUUtBLXCoDw==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.9.0.tgz", + "integrity": "sha512-dpfcF9fDNR6++cthXR67iyhgqWy9CBouAvIWhIntzBG6cvK/cnIPiZQjBwi/ZqjjBEDGfoNDtmB0kTjroOJ3pQ==", "license": "Apache-2.0", "peerDependencies": { "react-native-b4a": "*" @@ -2352,9 +2358,9 @@ } }, "node_modules/pear-runtime/node_modules/bare-events": { - "version": "2.9.1", - "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.9.1.tgz", - "integrity": "sha512-Z0oHEHAFDZkffN8Qc39zNZjQlMDkPJRyyyZieU1VH7u8c5S+qHZ2S8ixdKIAxEjfHO7FJxXmJWgteOghVanIsg==", + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.9.2.tgz", + "integrity": "sha512-AIPKioV7/Y/8KfZ3AAhjPJxLLbY49S64Ym5DakZlUg75qQiTgUq9hEJoEwa4eUezPUlXRy/i5NpsKvo9jgKmoA==", "license": "Apache-2.0", "peerDependencies": { "bare-abort-controller": "*" @@ -2365,30 +2371,6 @@ } } }, - "node_modules/pear-runtime/node_modules/bare-fs": { - "version": "4.7.4", - "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-4.7.4.tgz", - "integrity": "sha512-y1kC+ffIx/tPLdTE693uNjHfzTfr+ravR5tvWlMXe25nELbkqV400S71qHDwbkAQ1FVEZobB1NFRzFbCCcyBCQ==", - "license": "Apache-2.0", - "dependencies": { - "bare-events": "^2.5.4", - "bare-path": "^3.0.0", - "bare-stream": "^2.6.4", - "bare-url": "^2.2.2", - "fast-fifo": "^1.3.2" - }, - "engines": { - "bare": ">=1.16.0" - }, - "peerDependencies": { - "bare-buffer": "*" - }, - "peerDependenciesMeta": { - "bare-buffer": { - "optional": true - } - } - }, "node_modules/pear-runtime/node_modules/bare-mime": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/bare-mime/-/bare-mime-1.0.0.tgz", @@ -2420,23 +2402,6 @@ } } }, - "node_modules/pear-runtime/node_modules/bare-module-lexer": { - "version": "1.6.3", - "resolved": "https://registry.npmjs.org/bare-module-lexer/-/bare-module-lexer-1.6.3.tgz", - "integrity": "sha512-NQY7cnPV3GZlHJphX4nXmPdNPER/Tp17pVi9/he2ODw/GNZ7FXzrZlrS7WMF8zbtWigqW/NMc9aQc2BH8UJXqA==", - "license": "Apache-2.0", - "dependencies": { - "require-addon": "^1.0.2" - }, - "peerDependencies": { - "bare-buffer": "*" - }, - "peerDependenciesMeta": { - "bare-buffer": { - "optional": true - } - } - }, "node_modules/pear-runtime/node_modules/bare-module-traverse": { "version": "2.4.4", "resolved": "https://registry.npmjs.org/bare-module-traverse/-/bare-module-traverse-2.4.4.tgz", @@ -2462,24 +2427,11 @@ } }, "node_modules/pear-runtime/node_modules/bare-path": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/bare-path/-/bare-path-3.1.1.tgz", - "integrity": "sha512-JprUlveX3QjApC1cTpsUOiscADftCGVWkzitbHsRqv84hzYwYHw2mbluddsq5TvI8mH/8Ov1f4BiMAdcB0oYnQ==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bare-path/-/bare-path-3.1.2.tgz", + "integrity": "sha512-ZyKbsuuqK6Ag0K8pX6V5Txq6XeJRvY+wXucnFGRjiyVYP9YWDpIQugk/b+enRYrEYBJaqLzghRQpXPMR7341Nw==", "license": "Apache-2.0" }, - "node_modules/pear-runtime/node_modules/bare-pipe": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/bare-pipe/-/bare-pipe-4.2.3.tgz", - "integrity": "sha512-MJYji+Vy8cNMAB2fwg1WQAm1hFIOe5ojw5V9EhEa/wEEYpKT8c36WjHBDwgkgAZfUSX6wQcEFX+Q7wN09SgRow==", - "license": "Apache-2.0", - "dependencies": { - "bare-events": "^2.0.0", - "bare-stream": "^2.0.0" - }, - "engines": { - "bare": ">=1.16.0" - } - }, "node_modules/pear-runtime/node_modules/bare-sidecar": { "version": "0.5.3", "resolved": "https://registry.npmjs.org/bare-sidecar/-/bare-sidecar-0.5.3.tgz", @@ -2497,9 +2449,9 @@ } }, "node_modules/pear-runtime/node_modules/bare-stream": { - "version": "2.13.3", - "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.13.3.tgz", - "integrity": "sha512-Kc+brLqvEqGkjyfiwJmImAOqLZL7OsoLKuavx+hJjgVV3nLTOjloJyPMFxjUPerGGHrNH0fLU06jjykMLWrERQ==", + "version": "2.13.4", + "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.13.4.tgz", + "integrity": "sha512-PcrQ8lVLbiJscNm1Kez+Yp4Gy4AHGcN1lzwjvf5NybWen7VvEgUfyfnXYJ2zNqWnzOfCb1Abq6lH8ti0syQszA==", "license": "Apache-2.0", "dependencies": { "b4a": "^1.8.1", @@ -2549,28 +2501,6 @@ } } }, - "node_modules/pear-runtime/node_modules/bare-tcp": { - "version": "2.5.3", - "resolved": "https://registry.npmjs.org/bare-tcp/-/bare-tcp-2.5.3.tgz", - "integrity": "sha512-TsioekALDulWi0mglooIVG4ML7doxyANKfvuXUU6NS5tFMUnWVuvDht9wQplg07ZS6PQ3jfowzKxNeWdj2e02Q==", - "license": "Apache-2.0", - "dependencies": { - "bare-dns": "^2.0.4", - "bare-events": "^2.5.4", - "bare-stream": "^2.6.4" - }, - "engines": { - "bare": ">=1.16.0" - }, - "peerDependencies": { - "bare-pipe": "*" - }, - "peerDependenciesMeta": { - "bare-pipe": { - "optional": true - } - } - }, "node_modules/pear-runtime/node_modules/bare-thread": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/bare-thread/-/bare-thread-1.2.4.tgz", @@ -2583,27 +2513,10 @@ "bare-url": "^2.4.2" } }, - "node_modules/pear-runtime/node_modules/bare-type-stripper": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/bare-type-stripper/-/bare-type-stripper-0.1.4.tgz", - "integrity": "sha512-FdZhp9XEnQpj8AWFmIft/sVUyKS9XSmB6PhcxBHhuEDxxZM5Kkt8+kFS7eEpLXR7TkaRkNpSENoGH/8lpAmtkA==", - "license": "Apache-2.0", - "dependencies": { - "require-addon": "^1.0.2" - }, - "peerDependencies": { - "bare-buffer": "*" - }, - "peerDependenciesMeta": { - "bare-buffer": { - "optional": true - } - } - }, "node_modules/pear-runtime/node_modules/bare-url": { - "version": "2.4.6", - "resolved": "https://registry.npmjs.org/bare-url/-/bare-url-2.4.6.tgz", - "integrity": "sha512-iQxPClE07hETVpbRoX7JXX3v/ZQViCxe/SYCxylRLzdEx1xJAufPptfiOqR8tqiCtmbtMDANKWszzjLu1PMAZQ==", + "version": "2.5.4", + "resolved": "https://registry.npmjs.org/bare-url/-/bare-url-2.5.4.tgz", + "integrity": "sha512-Gxa7UVWBr0/edU1b+TJhn/AZvMQUj9OGspvYsaTYQrAbZA4BOTZGL3LiZxvD+CeMlDH4juwD84+eTAp/bLYW5g==", "license": "Apache-2.0", "dependencies": { "bare-path": "^3.0.0" @@ -2633,23 +2546,23 @@ } }, "node_modules/pear-runtime/node_modules/compact-encoding": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/compact-encoding/-/compact-encoding-3.3.0.tgz", - "integrity": "sha512-e64XyzlBvTRJ3iScuU/U2w25Dglkm7fhrRbrGaHIwuTlNnzjRKMaQBCVl7mJRvZg7wI5a9wX1IVTXCuaAANNkw==", + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/compact-encoding/-/compact-encoding-3.5.0.tgz", + "integrity": "sha512-X7yaWh0NNMVB2YXAWSTWjYU3WXvDTniKxbtQlisbXOVutIE6awd+zCQdNqScTjIXv9m9orqdlri+oaWWsKqTuA==", "license": "Apache-2.0", "dependencies": { "b4a": "^1.3.0" } }, "node_modules/pear-runtime/node_modules/corestore": { - "version": "7.12.0", - "resolved": "https://registry.npmjs.org/corestore/-/corestore-7.12.0.tgz", - "integrity": "sha512-yRWQ8VkjlAl7ITRo/xT8EK9NdiJ7xrsCCkRShD2TlSaoGa3B7bRVNPr1nWGE5NjiIi4HiLBCxtiI4gzmjAjJ0Q==", + "version": "7.12.5", + "resolved": "https://registry.npmjs.org/corestore/-/corestore-7.12.5.tgz", + "integrity": "sha512-jJxb0av/HgrNVXhNAyP6kVyCwldgzzosEvjSe6i5k96+Ys7fXX7pA2p6ntCisYjKynUhdGdH9UsjwLTYiJ6reQ==", "license": "MIT", "dependencies": { "b4a": "^1.6.7", "bare-events": "^2.8.3", - "hypercore": "^11.32.0", + "hypercore": "^11.35.4", "hypercore-crypto": "^3.4.2", "hypercore-errors": "^1.4.0", "hypercore-id-encoding": "^1.3.0", @@ -2676,9 +2589,9 @@ } }, "node_modules/pear-runtime/node_modules/hypercore": { - "version": "11.35.1", - "resolved": "https://registry.npmjs.org/hypercore/-/hypercore-11.35.1.tgz", - "integrity": "sha512-Oy3cfRfTBkiDSwewBicF8S97fNnon4RWYidthhQK+UB0z4SR85sfMqPtrNXQFIUt9e6i5W/vJHPfzd0lj0ZYtg==", + "version": "11.36.1", + "resolved": "https://registry.npmjs.org/hypercore/-/hypercore-11.36.1.tgz", + "integrity": "sha512-GtwsuF66ud4mQFvjb3VAW+smWYqqDj6c/nu4s/StLnUqsl0HMSK5bDPm9s/QsS25mkdsU3CUJlJlViwT75bvrg==", "license": "MIT", "dependencies": { "@hyperswarm/secret-stream": "^6.0.0", @@ -2704,21 +2617,10 @@ "z32": "^1.0.0" } }, - "node_modules/pear-runtime/node_modules/hypercore-crypto": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/hypercore-crypto/-/hypercore-crypto-3.7.0.tgz", - "integrity": "sha512-SWxobptQf2V/+ZLCCDRTXqFzGfTPIkNIuDyLKXpEMfcpJ1/ec94N9aWgylHcWOHmRt14ng/KR7z0BugebWHK9Q==", - "license": "MIT", - "dependencies": { - "b4a": "^1.6.6", - "compact-encoding": "^3.0.0", - "sodium-universal": "^5.0.0" - } - }, "node_modules/pear-runtime/node_modules/hypercore-storage": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/hypercore-storage/-/hypercore-storage-3.2.0.tgz", - "integrity": "sha512-i5O5ZMkdZaNAWGuNRXUZjuzv7B41OIhDHUwLCZPjucgVmywY0ZE5PDafu6e5oPuhtghjl6S8q6Jn+POVKN2BvQ==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/hypercore-storage/-/hypercore-storage-3.3.1.tgz", + "integrity": "sha512-JQsJVdoNiLI1dBK5ryUC/XfP32MAzQk87jM7fXyatp6hZHMQLE6pGLh5D7kSo/7QqBf+GFgBAnqKwndkvfd4mQ==", "license": "Apache-2.0", "dependencies": { "b4a": "^1.6.7", @@ -2730,52 +2632,12 @@ "hyperschema": "^1.21.0", "index-encoder": "^3.3.2", "resolve-reject-promise": "^1.0.0", - "rocksdb-native": "^3.11.0", + "rocksdb-native": "^3.18.0", "scope-lock": "^1.2.4", "streamx": "^2.21.1", "xache": "^1.2.1" } }, - "node_modules/pear-runtime/node_modules/hyperdht": { - "version": "6.33.0", - "resolved": "https://registry.npmjs.org/hyperdht/-/hyperdht-6.33.0.tgz", - "integrity": "sha512-veSvVKptjPTu2di3q5IWI62ZDFzEmTj1QSTAkiXW/cg0+lgMUlwfVWB4dX+WI14xNb54vogVIjA/Ph3KiVuRJQ==", - "license": "MIT", - "dependencies": { - "@hyperswarm/secret-stream": "^6.6.2", - "b4a": "^1.3.1", - "bare-events": "^2.2.0", - "blind-relay": "^1.3.0", - "bogon": "^1.0.0", - "compact-encoding": "^3.0.0", - "dht-rpc": "^6.15.1", - "hypercore-crypto": "^3.3.0", - "hypercore-id-encoding": "^1.2.0", - "hyperdht-address": "^1.0.1", - "noise-curve-ed": "^2.0.0", - "noise-handshake": "^4.0.0", - "record-cache": "^1.1.1", - "safety-catch": "^1.0.1", - "signal-promise": "^1.0.3", - "sodium-universal": "^5.0.1", - "streamx": "^2.16.1", - "unslab": "^1.3.0", - "xache": "^1.1.0" - }, - "bin": { - "hyperdht": "bin.js" - } - }, - "node_modules/pear-runtime/node_modules/hyperdht-address": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/hyperdht-address/-/hyperdht-address-1.1.1.tgz", - "integrity": "sha512-Mu/+7SW2cwvHxMXswa5mOrhrS5BJyntViAuB25k8d8wNtA8eAPs4LaIq6TwN1SS/KQY02h1r+gM8cQeN/k360w==", - "license": "Apache-2.0", - "dependencies": { - "compact-encoding": "^3.0.0", - "hyperschema": "^1.20.1" - } - }, "node_modules/pear-runtime/node_modules/hyperdrive": { "version": "13.3.3", "resolved": "https://registry.npmjs.org/hyperdrive/-/hyperdrive-13.3.3.tgz", @@ -2796,33 +2658,6 @@ "unix-path-resolve": "^1.0.2" } }, - "node_modules/pear-runtime/node_modules/hyperschema": { - "version": "1.21.0", - "resolved": "https://registry.npmjs.org/hyperschema/-/hyperschema-1.21.0.tgz", - "integrity": "sha512-lEnIbLTUQf7w6FU6X+R3yUJhBwCzF4ewRnAaYOrPdcVWe1rbOf94PzMiXb5pBKxroCXzlrPLxVujxAsTrrHc8g==", - "license": "Apache-2.0", - "dependencies": { - "bare-fs": "^4.0.1", - "compact-encoding": "^3.0.0", - "generate-object-property": "^2.0.0", - "generate-string": "^1.0.1" - } - }, - "node_modules/pear-runtime/node_modules/hyperswarm": { - "version": "4.17.0", - "resolved": "https://registry.npmjs.org/hyperswarm/-/hyperswarm-4.17.0.tgz", - "integrity": "sha512-oe86sK961Ueg7rvDN/veFwG8xH+Iv6vObPhGDkPJcDVxk/NduW41ZhAcVDnHzRbm7S0eLU7WaDUvehOYoKSpRQ==", - "license": "MIT", - "dependencies": { - "b4a": "^1.3.1", - "bare-events": "^2.2.0", - "hyperdht": "^6.21.0", - "safety-catch": "^1.0.2", - "shuffled-priority-queue": "^2.1.0", - "streamx": "^2.22.1", - "unslab": "^1.3.0" - } - }, "node_modules/pear-runtime/node_modules/localdrive": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/localdrive/-/localdrive-2.2.1.tgz", @@ -2954,26 +2789,6 @@ "integrity": "sha512-z/wAiTESw2XVPssY2XRcme4niTc4S5FkkJ4gknudtVoc33Zil8TdTxHy5torRcgqMqksJV2Yz8HQcvtbsnw0mQ==", "license": "MIT" }, - "node_modules/pear-runtime/node_modules/streamx": { - "version": "2.28.0", - "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.28.0.tgz", - "integrity": "sha512-1Yowhzjf0ivGMrTIkY9hav5TxobO9qIVqUE41fiCGMGgc3CLlf4MY+9AHmZqBWgDTue0fY9zWjYFVyf6Diuobw==", - "license": "MIT", - "dependencies": { - "events-universal": "^1.0.0", - "fast-fifo": "^1.3.2", - "text-decoder": "^1.1.0" - } - }, - "node_modules/pear-runtime/node_modules/teex": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/teex/-/teex-1.0.1.tgz", - "integrity": "sha512-eYE6iEI62Ni1H8oIa7KlDU6uQBtqr4Eajni3wX7rpfXD8ysFx8z0+dri+KWEPWpBsxXfxu58x/0jvTVT1ekOSg==", - "license": "MIT", - "dependencies": { - "streamx": "^2.12.5" - } - }, "node_modules/pear-runtime/node_modules/unix-path-resolve": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/unix-path-resolve/-/unix-path-resolve-1.0.2.tgz", @@ -3019,9 +2834,9 @@ } }, "node_modules/protobufjs": { - "version": "7.6.5", - "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.6.5.tgz", - "integrity": "sha512-/FPD0nUc9jH6rfFjji9IBqOz4pcSE3CsT1m7Ep6Mdb0LxSUMj8hgl6GomOvZzpNpAqqGaXA0P3VSrZLFzIhQrw==", + "version": "7.6.6", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.6.6.tgz", + "integrity": "sha512-dYDWdjSl5RNb7SgPxGQcRU+GtvP7s2fpkrY0r432PcOIaZ0/rBcxEZnQN67iJhFuQiVw754JDoPruPCNdGsbjg==", "hasInstallScript": true, "license": "BSD-3-Clause", "dependencies": { @@ -3077,17 +2892,6 @@ "protomux": "^3.10.1" } }, - "node_modules/protomux-wakeup/node_modules/hypercore-crypto": { - "version": "3.6.1", - "resolved": "https://registry.npmjs.org/hypercore-crypto/-/hypercore-crypto-3.6.1.tgz", - "integrity": "sha512-ltIz2uDwy9pO/ZGTvqcjzyBkvt6O4cVm4r/nNxh0GFs/RbQtqP/i4wCvLEdmU7ptgtnw7fI67WYD1aHPuv4OVA==", - "license": "MIT", - "dependencies": { - "b4a": "^1.6.6", - "compact-encoding": "^2.15.0", - "sodium-universal": "^5.0.0" - } - }, "node_modules/queue-tick": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/queue-tick/-/queue-tick-1.0.1.tgz", @@ -3198,12 +3002,12 @@ "license": "Apache-2.0" }, "node_modules/rocksdb-native": { - "version": "3.11.4", - "resolved": "https://registry.npmjs.org/rocksdb-native/-/rocksdb-native-3.11.4.tgz", - "integrity": "sha512-vG6NIkmipcAYV9QHIN1tALBHfIAcAfRlV5YWSwfn7yMmsXW0AJel0oZyeW8lRpTD7HTWq88GwzIMI+MV4iPSOg==", + "version": "3.18.0", + "resolved": "https://registry.npmjs.org/rocksdb-native/-/rocksdb-native-3.18.0.tgz", + "integrity": "sha512-Dzl1o3tb3Vit7M1Dn/80VvXMx2NhB4b36FboEr/WdhLta1TEDHPLAUheN3jAnYvyzZfIarGSDl609/bp1AKmtg==", "license": "Apache-2.0", "dependencies": { - "compact-encoding": "^2.15.0", + "compact-encoding": "^3.0.0", "ready-resource": "^1.0.0", "refcounter": "^1.0.0", "require-addon": "^1.0.2", @@ -3215,6 +3019,15 @@ "bare": ">=1.16.0" } }, + "node_modules/rocksdb-native/node_modules/compact-encoding": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/compact-encoding/-/compact-encoding-3.5.0.tgz", + "integrity": "sha512-X7yaWh0NNMVB2YXAWSTWjYU3WXvDTniKxbtQlisbXOVutIE6awd+zCQdNqScTjIXv9m9orqdlri+oaWWsKqTuA==", + "license": "Apache-2.0", + "dependencies": { + "b4a": "^1.3.0" + } + }, "node_modules/safety-catch": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/safety-catch/-/safety-catch-1.0.2.tgz", @@ -3425,9 +3238,9 @@ } }, "node_modules/streamx": { - "version": "2.23.0", - "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.23.0.tgz", - "integrity": "sha512-kn+e44esVfn2Fa/O0CPFcex27fjIL6MkVae0Mm6q+E6f0hWv578YCERbv+4m02cjxvDsPKLnmxral/rR6lBMAg==", + "version": "2.28.1", + "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.28.1.tgz", + "integrity": "sha512-zEzXb0s5Cds7tqMH6rhZ05lcJydCWiQPEwiNngVqzsxCc962vLY4Uw+mW7od8kDH258k2Uz/JrOkdIAAhSh9VA==", "license": "MIT", "dependencies": { "events-universal": "^1.0.0", @@ -3445,6 +3258,15 @@ "codecs": "^3.1.0" } }, + "node_modules/teex": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/teex/-/teex-1.0.1.tgz", + "integrity": "sha512-eYE6iEI62Ni1H8oIa7KlDU6uQBtqr4Eajni3wX7rpfXD8ysFx8z0+dri+KWEPWpBsxXfxu58x/0jvTVT1ekOSg==", + "license": "MIT", + "dependencies": { + "streamx": "^2.12.5" + } + }, "node_modules/test-tmp": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/test-tmp/-/test-tmp-1.4.0.tgz", @@ -3564,9 +3386,9 @@ } }, "node_modules/trac-crypto-api/node_modules/bare-url": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/bare-url/-/bare-url-2.3.2.tgz", - "integrity": "sha512-ZMq4gd9ngV5aTMa5p9+UfY0b3skwhHELaDkhEHetMdX0LRkW9kzaym4oo/Eh+Ghm0CCDuMTsRIGM/ytUc1ZYmw==", + "version": "2.5.4", + "resolved": "https://registry.npmjs.org/bare-url/-/bare-url-2.5.4.tgz", + "integrity": "sha512-Gxa7UVWBr0/edU1b+TJhn/AZvMQUj9OGspvYsaTYQrAbZA4BOTZGL3LiZxvD+CeMlDH4juwD84+eTAp/bLYW5g==", "license": "Apache-2.0", "dependencies": { "bare-path": "^3.0.0" @@ -3574,8 +3396,7 @@ }, "node_modules/trac-msb": { "version": "0.2.21", - "resolved": "https://registry.npmjs.org/trac-msb/-/trac-msb-0.2.21.tgz", - "integrity": "sha512-CYicrP7Qw3ERz3j81ZXqFhQop0IS2hcluFAyjSYAnZis0ohPBn+p4mvTaWLI8Zq7eWYE9qoLplWEc/ZyTvuPBA==", + "resolved": "file:../msb", "dependencies": { "autobase": "7.20.1", "b4a": "1.6.7", @@ -3595,7 +3416,7 @@ "hyperbee": "2.26.5", "hypercore": "11.18.3", "hypercore-crypto": "3.6.1", - "hyperdht": "6.27.0", + "hyperdht": "6.29.6", "hyperschema": "1.17.1", "hyperswarm": "4.14.2", "lodash": "^4.18.1", @@ -3606,6 +3427,7 @@ "protocol-buffers-encodings": "1.2.0", "protomux": "3.10.1", "protomux-wakeup": "2.4.0", + "rache": "1.0.0", "readline": "npm:bare-node-readline", "ready-resource": "1.1.2", "trac-crypto-api": "0.1.5", @@ -3644,9 +3466,9 @@ } }, "node_modules/trac-msb/node_modules/bare-events": { - "version": "2.8.2", - "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.8.2.tgz", - "integrity": "sha512-riJjyv1/mHLIPX4RwiK+oW9/4c3TEUeORHKefKAKnZ5kyslbN+HXowtbaVEqt4IMUB7OXlfixcs6gsFeo/jhiQ==", + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.9.2.tgz", + "integrity": "sha512-AIPKioV7/Y/8KfZ3AAhjPJxLLbY49S64Ym5DakZlUg75qQiTgUq9hEJoEwa4eUezPUlXRy/i5NpsKvo9jgKmoA==", "license": "Apache-2.0", "peerDependencies": { "bare-abort-controller": "*" @@ -3706,9 +3528,9 @@ } }, "node_modules/trac-msb/node_modules/bare-url": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/bare-url/-/bare-url-2.4.0.tgz", - "integrity": "sha512-NSTU5WN+fy/L0DDenfE8SXQna4voXuW0FHM7wH8i3/q9khUSchfPbPezO4zSFMnDGIf9YE+mt/RWhZgNRKRIXA==", + "version": "2.5.4", + "resolved": "https://registry.npmjs.org/bare-url/-/bare-url-2.5.4.tgz", + "integrity": "sha512-Gxa7UVWBr0/edU1b+TJhn/AZvMQUj9OGspvYsaTYQrAbZA4BOTZGL3LiZxvD+CeMlDH4juwD84+eTAp/bLYW5g==", "license": "Apache-2.0", "dependencies": { "bare-path": "^3.0.0" @@ -3799,36 +3621,6 @@ "sodium-universal": "^5.0.0" } }, - "node_modules/trac-msb/node_modules/hyperdht": { - "version": "6.27.0", - "resolved": "https://registry.npmjs.org/hyperdht/-/hyperdht-6.27.0.tgz", - "integrity": "sha512-ZOXPSpsphn83hBxfFcZKNfXcgLLZH3HBWM2G0TAT0vl0hrBY287oEJ/2Nj0NimgxizpzA4J4t8wFsp/LSLs6Sw==", - "license": "MIT", - "dependencies": { - "@hyperswarm/secret-stream": "^6.6.2", - "b4a": "^1.3.1", - "bare-events": "^2.2.0", - "blind-relay": "^1.3.0", - "bogon": "^1.0.0", - "compact-encoding": "^2.4.1", - "compact-encoding-net": "^1.0.1", - "dht-rpc": "^6.15.1", - "hypercore-crypto": "^3.3.0", - "hypercore-id-encoding": "^1.2.0", - "noise-curve-ed": "^2.0.0", - "noise-handshake": "^4.0.0", - "record-cache": "^1.1.1", - "safety-catch": "^1.0.1", - "signal-promise": "^1.0.3", - "sodium-universal": "^5.0.1", - "streamx": "^2.16.1", - "unslab": "^1.3.0", - "xache": "^1.1.0" - }, - "bin": { - "hyperdht": "bin.js" - } - }, "node_modules/trac-msb/node_modules/hyperschema": { "version": "1.17.1", "resolved": "https://registry.npmjs.org/hyperschema/-/hyperschema-1.17.1.tgz", @@ -3888,9 +3680,9 @@ } }, "node_modules/trac-msb/node_modules/uuid": { - "version": "13.0.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-13.0.0.tgz", - "integrity": "sha512-XQegIaBTVUjSHliKqcnFqYypAd4S+WCYt5NIeRs6w/UAry7z8Y9j5ZwRRL4kzq9U3sD6v+85er9FvkEaBpji2w==", + "version": "13.0.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-13.0.2.tgz", + "integrity": "sha512-vzi9uRZ926x4XV73S/4qQaTwPXM2JBj6/6lI/byHH1jOpCzb0zDbfytgA9LcN/hzb2l7WQSQnxITOVx5un/wGw==", "funding": [ "https://github.com/sponsors/broofa", "https://github.com/sponsors/ctavan" @@ -3943,9 +3735,9 @@ } }, "node_modules/trac-wallet/node_modules/bare-url": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/bare-url/-/bare-url-2.4.0.tgz", - "integrity": "sha512-NSTU5WN+fy/L0DDenfE8SXQna4voXuW0FHM7wH8i3/q9khUSchfPbPezO4zSFMnDGIf9YE+mt/RWhZgNRKRIXA==", + "version": "2.5.4", + "resolved": "https://registry.npmjs.org/bare-url/-/bare-url-2.5.4.tgz", + "integrity": "sha512-Gxa7UVWBr0/edU1b+TJhn/AZvMQUj9OGspvYsaTYQrAbZA4BOTZGL3LiZxvD+CeMlDH4juwD84+eTAp/bLYW5g==", "license": "Apache-2.0", "dependencies": { "bare-path": "^3.0.0" @@ -3993,9 +3785,9 @@ } }, "node_modules/undici-types": { - "version": "7.19.2", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.19.2.tgz", - "integrity": "sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", "license": "MIT" }, "node_modules/unordered-set": { diff --git a/intercom/trac/trac-peer/package.json b/intercom/trac/trac-peer/package.json index 685dbe5f..9ed721ee 100644 --- a/intercom/trac/trac-peer/package.json +++ b/intercom/trac/trac-peer/package.json @@ -78,7 +78,7 @@ "hyperbee": "^2.24.2", "hypercore": "11.8.3", "hypercore-crypto": "^3.4.0", - "hyperdht": "^6.20.5", + "hyperdht": "6.29.6", "hyperswarm": "^4.11.5", "inspector": "npm:bare-node-inspector", "is-options": "1.0.2", @@ -100,7 +100,7 @@ "timers": "npm:bare-node-timers", "tls": "npm:bare-node-tls", "trac-crypto-api": "^0.1.5", - "trac-msb": "^0.2.21", + "trac-msb": "file:../msb", "trac-wallet": "^1.0.4", "url": "npm:bare-node-url", "util": "npm:bare-node-util", diff --git a/ops/retail-crypto-payment-worker.env.example b/ops/retail-crypto-payment-worker.env.example new file mode 100644 index 00000000..0084e784 --- /dev/null +++ b/ops/retail-crypto-payment-worker.env.example @@ -0,0 +1,33 @@ +# Copy into the protected production secret store with mode 0600. Values are +# examples only; never commit wallet material or private endpoints. +OPENMAYHEM_CRYPTO_API_URL=https://api.openmayhem.ai/ +OPENMAYHEM_CRYPTO_WORKER_SECRET= +OPENMAYHEM_CRYPTO_WORKER_ID=retail-crypto-worker +OPENMAYHEM_CRYPTO_PLATFORM_BUYER= +OPENMAYHEM_CRYPTO_BUYER_HOME= +OPENMAYHEM_CRYPTO_WALLET_PASSWORD_FILE= +OPENMAYHEM_CRYPTO_MSB_STORES_DIRECTORY= +OPENMAYHEM_CRYPTO_MSB_STORE_NAME= +OPENMAYHEM_CRYPTO_WORKER_STATE_DIR= +OPENMAYHEM_CRYPTO_WORKER_INTERVAL_SECONDS=5 +OPENMAYHEM_CRYPTO_DISCOVERY_INTERVAL_SECONDS=5 +OPENMAYHEM_CRYPTO_STATUS_INTERVAL_SECONDS=60 +OPENMAYHEM_CRYPTO_READER_TIMEOUT_SECONDS=60 +OPENMAYHEM_CRYPTO_BRIDGE_TIMEOUT_SECONDS=900 +OPENMAYHEM_TNK_FINALITY_SIGNED_LENGTHS=2 +OPENMAYHEM_TNK_LOOKBACK_SIGNED_LENGTHS=5000 +OPENMAYHEM_TNK_DISCOVERY_READER_STORE=openmayhem-retail-crypto-discovery +OPENMAYHEM_TAP_BRIDGE_LOOKBACK_BLOCKS=7200 +MAYHEM_PEER_RPC=http://127.0.0.1:49223/v1/ +MAYHEM_MSB_NETWORK=mainnet +MAYHEM_TAP_ETH_CHAIN_ID=1 +MAYHEM_TAP_ETH_RPC= +OPENMAYHEM_TAP_TOKEN_ADDRESS= +OPENMAYHEM_TAP_COLLECTION_ADDRESS= +OPENMAYHEM_TAP_SETTLEMENT_GAS_ADDRESS= +OPENMAYHEM_TAP_MINIMUM_GAS_WEI=5000000000000000 +OPENMAYHEM_SETTLEMENT_MAX_EPOCH_LAG=2 +OPENMAYHEM_TNK_COLLECTION_ADDRESS= +# Ordered public fallbacks, separated by semicolons. The authenticated primary +# above is always attempted first and endpoints are chain-checked. +OPENMAYHEM_TAP_ETH_RPC_FALLBACKS= diff --git a/ops/systemd/mayhem-retail-crypto-payment-worker.service b/ops/systemd/mayhem-retail-crypto-payment-worker.service new file mode 100644 index 00000000..3460a6a5 --- /dev/null +++ b/ops/systemd/mayhem-retail-crypto-payment-worker.service @@ -0,0 +1,28 @@ +[Unit] +Description=OpenMayhem retail TNK/TAP verifier and Core funding bridge +Requires=mayhem-stack.service +After=network-online.target mayhem-stack.service mayhem-tap-deposit.service mayhem-tnk-deposit.service +Wants=network-online.target mayhem-tap-deposit.service mayhem-tnk-deposit.service +StartLimitIntervalSec=0 + +[Service] +Type=simple +User=@@SERVICE_USER@@ +Group=@@SERVICE_GROUP@@ +WorkingDirectory=@@REPO@@/intercom +EnvironmentFile=-@@SYSTEM_ENV@@ +EnvironmentFile=@@WORKER_ENV@@ +UMask=0077 +ExecStartPre=/usr/bin/node @@REPO@@/scripts/mainnet-proof.mjs --timeout-seconds 0 --json +ExecStart=/usr/bin/node @@REPO@@/intercom/scripts/retail-crypto-payment-worker.mjs +Restart=always +RestartSec=10 +TimeoutStopSec=60 +NoNewPrivileges=true +PrivateTmp=true +ProtectSystem=strict +ProtectHome=read-only +ReadWritePaths=@@STATE_DIR@@ @@BUYER_HOME@@ + +[Install] +WantedBy=multi-user.target diff --git a/scripts/i3-e15-sellable-surface-audit.mjs b/scripts/i3-e15-sellable-surface-audit.mjs index e2fb52f2..8b30658d 100644 --- a/scripts/i3-e15-sellable-surface-audit.mjs +++ b/scripts/i3-e15-sellable-surface-audit.mjs @@ -41,7 +41,7 @@ const productionRoots = [ const productionExtensions = new Set(['.rs', '.js', '.mjs', '.cjs', '.ts', '.toml']); const bannedPatterns = [ - ['deterministic', /deterministic_/], + ['deterministic-output', /deterministic_(?:completion|implementation|output|provider|response|result)/i], ['pending-marker', /\bPENDING_(?:IMPLEMENTATION|MODEL|PROOF|REPLACE_ME)\b/], ['canned', /\bcanned\b/i], ['placeholder', /\bplaceholder_(?:implementation|output|response|result)\b|\b(?:TODO|FIXME)\b[^\n]{0,40}\bplaceholder\b/i], @@ -112,6 +112,7 @@ function listFiles(dir) { } else if (stat.isFile()) { const rel = path.relative(repoRoot, current); if (rel.split(path.sep).includes('tests')) continue; + if (path.basename(rel) === 'tests.rs' || path.basename(rel).endsWith('_tests.rs')) continue; if (rel.endsWith('package-lock.json')) continue; if (!productionExtensions.has(path.extname(current))) continue; out.push(rel); @@ -137,7 +138,7 @@ function rustProductionText(text) { return ''; } - if (/^\s*#\[cfg\(test\)\]\s*$/.test(line)) { + if (/^\s*#\[cfg\([^\]]*\btest\b[^\]]*\)\]\s*$/.test(line)) { pendingTestItem = line.match(/^\s*/)[0]; return ''; } @@ -210,7 +211,12 @@ function checkCatalogAndReadme() { const section = launchSurfaceSection(readme); if (!section) return; - const rows = parseLaunchRows(section).filter(({ status }) => status === 'live'); + // Launch membership is a signed catalog state. Runtime availability can be + // offline or busy without removing the model from the active roster; only + // rows explicitly marked as planned, paused, or retired are outside it. + const rows = parseLaunchRows(section).filter( + ({ status }) => !/(?:planned|paused|retired)/.test(status), + ); for (const model of launchModels) { const matchingRows = launchRowsMatchingModel(rows, model); assertCheck( diff --git a/scripts/ops-settle-epoch.sh b/scripts/ops-settle-epoch.sh index 12f3691b..96d96c36 100755 --- a/scripts/ops-settle-epoch.sh +++ b/scripts/ops-settle-epoch.sh @@ -676,8 +676,8 @@ for identity in identities: canonical_au(head.get("incremental_au"), "canonical receipt head incremental_au", allow_zero=False) receipt = head.get("receipt") body = receipt.get("body") if isinstance(receipt, dict) else None - if not isinstance(body, dict) or body.get("schema_version") not in {10, 11}: - raise SystemExit("canonical receipt head must contain a signed receipt schema 10 or 11") + if not isinstance(body, dict) or body.get("schema_version") not in {10, 11, 12}: + raise SystemExit("canonical receipt head must contain a signed receipt schema 10, 11 or 12") if ( body.get("billing_id") != identity["billing_id"] or body.get("billing_attempt") != identity["billing_attempt"] diff --git a/scripts/ops/install-retail-crypto-payment-worker.sh b/scripts/ops/install-retail-crypto-payment-worker.sh new file mode 100644 index 00000000..1c3d8591 --- /dev/null +++ b/scripts/ops/install-retail-crypto-payment-worker.sh @@ -0,0 +1,60 @@ +#!/usr/bin/env bash +set -euo pipefail + +if [[ ${EUID:-$(id -u)} -ne 0 ]]; then + echo "Run as root: sudo scripts/ops/install-retail-crypto-payment-worker.sh" >&2 + exit 1 +fi + +repo="${MAYHEM_REPO:-/opt/mayhem/source}" +root="${MAYHEM_ROOT:-/opt/mayhem}" +service_user="${MAYHEM_SERVICE_USER:-mayhem}" +service_group="${MAYHEM_SERVICE_GROUP:-$service_user}" +buyer_home="${MAYHEM_BUYER_HOME:-$root/.mayhem-local/live-home}" +state_dir="${MAYHEM_WORKER_STATE_DIR:-$root/.mayhem-local/retail-crypto-worker}" +secret="${MAYHEM_WORKER_ENV:-$root/.mayhem-local/secrets/GO-LIVE/retail-crypto-payment-worker.env}" +system_env="${MAYHEM_SYSTEM_ENV:-$root/.mayhem-local/secrets/GO-LIVE/mayhem-systemd.env}" +template="$repo/ops/systemd/mayhem-retail-crypto-payment-worker.service" + +for value in "$repo" "$root" "$service_user" "$service_group" "$buyer_home" "$state_dir" "$secret" "$system_env"; do + [[ "$value" != *$'\n'* && "$value" != *$'\r'* ]] || { + echo "Service configuration values must not contain newlines." >&2 + exit 1 + } +done +id "$service_user" >/dev/null 2>&1 || { + echo "Unknown service user: $service_user" >&2 + exit 1 +} +getent group "$service_group" >/dev/null 2>&1 || { + echo "Unknown service group: $service_group" >&2 + exit 1 +} + +[[ -f "$secret" ]] || { + echo "Missing protected worker environment: $secret" >&2 + exit 1 +} +[[ "$(stat -c '%a' "$secret")" == "600" ]] || { + echo "Worker environment must have mode 0600: $secret" >&2 + exit 1 +} + +/usr/bin/node --check "$repo/intercom/scripts/retail-crypto-payment-worker.mjs" +install -d -m 0700 -o "$service_user" -g "$service_group" "$state_dir" + +unit="$(cat "$template")" +unit="${unit//@@SERVICE_USER@@/$service_user}" +unit="${unit//@@SERVICE_GROUP@@/$service_group}" +unit="${unit//@@REPO@@/$repo}" +unit="${unit//@@SYSTEM_ENV@@/$system_env}" +unit="${unit//@@WORKER_ENV@@/$secret}" +unit="${unit//@@STATE_DIR@@/$state_dir}" +unit="${unit//@@BUYER_HOME@@/$buyer_home}" +printf '%s\n' "$unit" > /etc/systemd/system/mayhem-retail-crypto-payment-worker.service +chmod 0644 /etc/systemd/system/mayhem-retail-crypto-payment-worker.service +systemd-analyze verify /etc/systemd/system/mayhem-retail-crypto-payment-worker.service +systemctl daemon-reload +systemctl enable --now mayhem-retail-crypto-payment-worker.service +systemctl is-active --quiet mayhem-retail-crypto-payment-worker.service +echo "Retail crypto payment worker is active." diff --git a/scripts/ops/run-tap-deposit-watcher.sh b/scripts/ops/run-tap-deposit-watcher.sh index 570db301..5d61f7e4 100755 --- a/scripts/ops/run-tap-deposit-watcher.sh +++ b/scripts/ops/run-tap-deposit-watcher.sh @@ -6,7 +6,12 @@ home="${MAYHEM_HOME:-/opt/mayhem/.mayhem-local/live-home}" peer_rpc="${MAYHEM_PEER_RPC:-http://127.0.0.1:49223/v1}" cursor="${MAYHEM_TAP_DEPOSIT_CURSOR:-/opt/mayhem/.mayhem-local/watchers/tap-deposit.json}" interval="${MAYHEM_TAP_DEPOSIT_INTERVAL_SECONDS:-30}" -mayhem_bin="$repo/target/release/mayhem" +mayhem_bin="${MAYHEM_BIN:-$repo/target/release/mayhem}" + +if [[ ! -x "$mayhem_bin" ]]; then + echo "Deposit watcher requires an executable MAYHEM_BIN: $mayhem_bin" >&2 + exit 1 +fi mkdir -p "$(dirname "$cursor")" diff --git a/scripts/ops/run-tnk-deposit-watcher.sh b/scripts/ops/run-tnk-deposit-watcher.sh index 0285f2c9..0684d987 100755 --- a/scripts/ops/run-tnk-deposit-watcher.sh +++ b/scripts/ops/run-tnk-deposit-watcher.sh @@ -8,6 +8,13 @@ cursor="${MAYHEM_TNK_DEPOSIT_CURSOR:-/opt/mayhem/.mayhem-local/watchers/tnk-depo state_dir="${MAYHEM_TNK_DEPOSIT_STATE_DIR:-/opt/mayhem/.mayhem-local/tnk-deposit-msb}" store_name="${MAYHEM_TNK_DEPOSIT_STORE_NAME:-mayhem-mainnet-deposit-watcher}" interval="${MAYHEM_TNK_DEPOSIT_INTERVAL_SECONDS:-30}" +reader_timeout="${MAYHEM_TNK_DEPOSIT_READER_TIMEOUT_SECONDS:-300}" +mayhem_bin="${MAYHEM_BIN:-$repo/target/release/mayhem}" + +if [[ ! -x "$mayhem_bin" ]]; then + echo "Deposit watcher requires an executable MAYHEM_BIN: $mayhem_bin" >&2 + exit 1 +fi mkdir -p "$(dirname "$cursor")" "$state_dir" @@ -18,8 +25,9 @@ while true; do --store-name "$store_name" \ --peer-rpc "$peer_rpc" \ --cursor "$cursor" \ + --timeout "$reader_timeout" \ --admin-home "$home" \ - --mayhem-bin "$repo/target/release/mayhem" \ + --mayhem-bin "$mayhem_bin" \ --submit \ --json; then echo "TNK deposit watcher tick failed; retrying after ${interval}s" >&2 diff --git a/scripts/tests/ops-settle-epoch-v17.test.mjs b/scripts/tests/ops-settle-epoch-v17.test.mjs index 51dba89d..90517636 100644 --- a/scripts/tests/ops-settle-epoch-v17.test.mjs +++ b/scripts/tests/ops-settle-epoch-v17.test.mjs @@ -22,9 +22,10 @@ function writeExecutable(target, source) { async function receiptHead(ordinal, { settlementEpoch = 1, billingEpoch = settlementEpoch, + schemaVersion = 11, } = {}) { const body = { - schema_version: 11, + schema_version: schemaVersion, session_id: hex(1_000 + ordinal), billing_id: hex(ordinal), billing_attempt: 0, @@ -45,6 +46,7 @@ async function receiptHead(ordinal, { locked_per_req_au: '0', locked_min_session_au: '0', served_ctx: 1_024, + ...(schemaVersion === 12 ? { compute_ms: 1_000, capacity_slots: 1 } : {}), ctx_bracket: 'le32k', ctx_bracket_table_ver: 1, rules_ver: 1, @@ -79,6 +81,7 @@ async function harness({ staleSnapshotAndCommit = false, settlementEpoch = 1, billingEpoch = settlementEpoch, + schemaVersion = 11, } = {}) { const root = fs.mkdtempSync(path.join(os.tmpdir(), 'mayhem-finalizer-')); const bin = path.join(root, 'bin'); @@ -94,9 +97,9 @@ async function harness({ fs.symlinkSync(process.execPath, path.join(bin, 'node')); const heads = [ - await receiptHead(3, { settlementEpoch, billingEpoch }), - await receiptHead(1, { settlementEpoch, billingEpoch }), - await receiptHead(2, { settlementEpoch, billingEpoch }), + await receiptHead(3, { settlementEpoch, billingEpoch, schemaVersion }), + await receiptHead(1, { settlementEpoch, billingEpoch, schemaVersion }), + await receiptHead(2, { settlementEpoch, billingEpoch, schemaVersion }), ]; const index = { type: 'canonical_receipt_epoch_index', @@ -503,6 +506,21 @@ test('finalizer settles a late billing receipt from the current receipt index', assert.equal(Object.hasOwn(snapshot, 'epoch'), false); }); +test('finalizer accepts current schema-12 utilization receipts', async (t) => { + const ctx = await harness({ schemaVersion: 12 }); + t.after(() => ctx.close()); + const result = ctx.run(); + assert.equal(result.status, 0, `${result.stdout}\n${result.stderr}`); + const state = ctx.state(); + assert.equal(state.apply.updated_epoch, 1); + assert.equal( + state.features.every((feature) => + feature.value.allocations.every((allocation) => allocation.billing_epoch === 1) + ), + true, + ); +}); + test('mid-page failure resumes the exact page and completed retry is idempotent', async (t) => { const ctx = await harness({ failPageOnce: 1 }); t.after(() => ctx.close()); diff --git a/scripts/tests/release-package-capabilities.test.sh b/scripts/tests/release-package-capabilities.test.sh index 907d72d0..b894459c 100755 --- a/scripts/tests/release-package-capabilities.test.sh +++ b/scripts/tests/release-package-capabilities.test.sh @@ -177,6 +177,9 @@ if (manifest.dependencies?.['trac-wallet'] !== '1.0.1' || Object.prototype.hasOwnProperty.call(manifest.overrides ?? {}, 'trac-wallet')) { throw new Error('Intercom root must pin trac-wallet 1.0.1 without overriding pinned MSB/peer wallets'); } +if (manifest.dependencies?.hyperdht !== '6.29.6') { + throw new Error('Intercom root must pin hyperdht 6.29.6'); +} for (const [name, source] of [ ['trac-msb', 'trac/msb'], ['trac-peer', 'trac/trac-peer'], @@ -206,6 +209,14 @@ if (wallets.length !== expectedWallets.size || wallets.some(([entry, locked]) => locked.version !== expectedWallets.get(entry))) { throw new Error('root lock does not contain the three pinned trac-wallet installs'); } +const hyperdhts = Object.entries(lock.packages) + .filter(([entry]) => entry === 'node_modules/hyperdht' || + entry.endsWith('/node_modules/hyperdht')); +if (hyperdhts.length !== 1 || + hyperdhts[0][0] !== 'node_modules/hyperdht' || + hyperdhts[0][1].version !== '6.29.6') { + throw new Error('root lock must contain exactly one hyperdht 6.29.6'); +} NODE release_hydration="$( @@ -238,6 +249,7 @@ mkdir -p \ "$topology/trac/msb" \ "$topology/trac/trac-peer" \ "$topology/scripts" \ + "$topology/node_modules/hyperdht" \ "$topology/node_modules/trac-msb" \ "$topology/node_modules/trac-peer" \ "$topology/node_modules/trac-wallet" \ @@ -251,6 +263,7 @@ cat >"$topology/package.json" <<'JSON' "name": "topology-root", "version": "1.0.0", "dependencies": { + "hyperdht": "6.29.6", "trac-msb": "file:trac/msb", "trac-peer": "file:trac/trac-peer", "trac-wallet": "1.0.1" @@ -265,12 +278,26 @@ cat >"$topology/package-lock.json" <<'JSON' "packages": { "": { "name": "topology-root", - "version": "1.0.0" + "version": "1.0.0", + "dependencies": { + "hyperdht": "6.29.6", + "trac-msb": "file:trac/msb", + "trac-peer": "file:trac/trac-peer", + "trac-wallet": "1.0.1" + } + }, + "node_modules/hyperdht": { + "name": "hyperdht", + "version": "6.29.6" }, "node_modules/trac-msb": { "name": "trac-msb", "version": "0.2.9", - "resolved": "file:trac/msb" + "resolved": "file:trac/msb", + "dependencies": { + "hyperdht": "6.29.6", + "trac-wallet": "2.1.0" + } }, "node_modules/trac-msb/node_modules/trac-wallet": { "name": "trac-wallet", @@ -279,7 +306,12 @@ cat >"$topology/package-lock.json" <<'JSON' "node_modules/trac-peer": { "name": "trac-peer", "version": "0.4.0", - "resolved": "file:trac/trac-peer" + "resolved": "file:trac/trac-peer", + "dependencies": { + "hyperdht": "6.29.6", + "trac-msb": "file:../msb", + "trac-wallet": "1.0.4" + } }, "node_modules/trac-peer/node_modules/trac-wallet": { "name": "trac-wallet", @@ -297,13 +329,22 @@ cat >"$topology/package-lock.json" <<'JSON' } JSON cat >"$topology/trac/msb/package.json" <<'JSON' -{"name":"trac-msb","version":"0.2.9","dependencies":{"trac-wallet":"2.1.0"}} +{"name":"trac-msb","version":"0.2.9","dependencies":{"hyperdht":"6.29.6","trac-wallet":"2.1.0"}} JSON cat >"$topology/trac/trac-peer/package.json" <<'JSON' -{"name":"trac-peer","version":"0.4.0","dependencies":{"trac-wallet":"1.0.4"}} +{"name":"trac-peer","version":"0.4.0","dependencies":{"hyperdht":"6.29.6","trac-msb":"file:../msb","trac-wallet":"1.0.4"}} +JSON +cat >"$topology/trac/msb/package-lock.json" <<'JSON' +{"name":"trac-msb","version":"0.2.9","lockfileVersion":3,"packages":{"":{"name":"trac-msb","version":"0.2.9","dependencies":{"hyperdht":"6.29.6"}},"node_modules/hyperdht":{"name":"hyperdht","version":"6.29.6"}}} +JSON +cat >"$topology/trac/trac-peer/package-lock.json" <<'JSON' +{"name":"trac-peer","version":"0.4.0","lockfileVersion":3,"packages":{"":{"name":"trac-peer","version":"0.4.0","dependencies":{"hyperdht":"6.29.6","trac-msb":"file:../msb"}},"node_modules/hyperdht":{"name":"hyperdht","version":"6.29.6"},"node_modules/trac-msb":{"name":"trac-msb","version":"0.2.9","resolved":"file:../msb"}}} JSON cp "$topology/trac/msb/package.json" "$topology/node_modules/trac-msb/package.json" cp "$topology/trac/trac-peer/package.json" "$topology/node_modules/trac-peer/package.json" +cat >"$topology/node_modules/hyperdht/package.json" <<'JSON' +{"name":"hyperdht","version":"6.29.6"} +JSON mkdir -p \ "$topology/node_modules/trac-msb/node_modules/trac-wallet" \ "$topology/node_modules/trac-peer/node_modules/trac-wallet" @@ -359,6 +400,14 @@ printf 'module.exports = class PeerWallet { static encodeBech32mSafe() {} };\n' >"$topology/node_modules/trac-wallet/index.js" node "$ROOT_DIR/scripts/verify-intercom-dependency-topology.mjs" "$topology" >/dev/null +mkdir -p "$topology/node_modules/trac-peer/node_modules/hyperdht" +cat >"$topology/node_modules/trac-peer/node_modules/hyperdht/package.json" <<'JSON' +{"name":"hyperdht","version":"6.27.0"} +JSON +expect_failure "topology verifier accepted a nested stale hyperdht" \ + node "$ROOT_DIR/scripts/verify-intercom-dependency-topology.mjs" "$topology" +rm -rf "$topology/node_modules/trac-peer/node_modules/hyperdht" + rm "$topology/node_modules/trac-msb/migration/initial_balances.csv" expect_failure "topology verifier accepted an npm-omitted pinned MSB runtime file" \ node "$ROOT_DIR/scripts/verify-intercom-dependency-topology.mjs" "$topology" diff --git a/scripts/verify-intercom-dependency-topology.mjs b/scripts/verify-intercom-dependency-topology.mjs index eee5366f..62f83d0c 100644 --- a/scripts/verify-intercom-dependency-topology.mjs +++ b/scripts/verify-intercom-dependency-topology.mjs @@ -30,6 +30,7 @@ const lock = readJson(lockfilePath); const npmrc = fs.readFileSync(npmrcPath, 'utf8'); const materializerPath = path.join(root, 'scripts', 'materialize-local-dependencies.mjs'); const { verifyLocalDependencies } = await import(pathToFileURL(materializerPath).href); +const requiredHyperdhtVersion = '6.29.6'; if (!/^\s*install-links\s*=\s*true\s*(?:[#;].*)?$/m.test(npmrc)) { fail(`${npmrcPath} must set install-links=true`); @@ -43,6 +44,56 @@ if (Object.prototype.hasOwnProperty.call(manifest.overrides ?? {}, 'trac-wallet' if (lock.lockfileVersion !== 3 || typeof lock.packages !== 'object') { fail('root package-lock.json must use lockfileVersion 3'); } +if (manifest.dependencies?.hyperdht !== requiredHyperdhtVersion) { + fail(`root dependencies must pin hyperdht to ${requiredHyperdhtVersion}`); +} + +const msbSourceManifest = readJson(path.join(root, 'trac', 'msb', 'package.json')); +const msbSourceLock = readJson(path.join(root, 'trac', 'msb', 'package-lock.json')); +const peerSourceManifest = readJson(path.join(root, 'trac', 'trac-peer', 'package.json')); +const peerSourceLock = readJson(path.join(root, 'trac', 'trac-peer', 'package-lock.json')); +if (msbSourceManifest.dependencies?.hyperdht !== requiredHyperdhtVersion) { + fail(`pinned trac-msb source must pin hyperdht to ${requiredHyperdhtVersion}`); +} +if (peerSourceManifest.dependencies?.hyperdht !== requiredHyperdhtVersion) { + fail(`pinned trac-peer source must pin hyperdht to ${requiredHyperdhtVersion}`); +} +if (peerSourceManifest.dependencies?.['trac-msb'] !== 'file:../msb') { + fail('pinned trac-peer source must follow the sibling pinned trac-msb source'); +} + +const assertSingleLockedHyperdht = (candidate, label) => { + if (candidate.lockfileVersion !== 3 || typeof candidate.packages !== 'object') { + fail(`${label} must use lockfileVersion 3`); + } + const paths = Object.keys(candidate.packages).filter( + (entry) => entry === 'node_modules/hyperdht' || entry.endsWith('/node_modules/hyperdht'), + ); + if (paths.length !== 1 || paths[0] !== 'node_modules/hyperdht') { + fail(`${label} must resolve exactly one root hyperdht, found: ${paths.join(', ')}`); + } + if (candidate.packages[paths[0]]?.version !== requiredHyperdhtVersion) { + fail(`${label} must resolve hyperdht ${requiredHyperdhtVersion}`); + } +}; + +assertSingleLockedHyperdht(lock, 'root package-lock.json'); +assertSingleLockedHyperdht(msbSourceLock, 'pinned trac-msb package-lock.json'); +assertSingleLockedHyperdht(peerSourceLock, 'pinned trac-peer package-lock.json'); + +const lockedBuses = Object.keys(lock.packages).filter( + (entry) => entry === 'node_modules/trac-msb' || entry.endsWith('/node_modules/trac-msb'), +); +if (lockedBuses.length !== 1 || lockedBuses[0] !== 'node_modules/trac-msb') { + fail(`root lock must resolve exactly one trac-msb, found: ${lockedBuses.join(', ')}`); +} +if (lock.packages['node_modules/trac-msb']?.dependencies?.hyperdht !== requiredHyperdhtVersion) { + fail(`installed trac-msb metadata must pin hyperdht ${requiredHyperdhtVersion}`); +} +if (lock.packages['node_modules/trac-peer']?.dependencies?.hyperdht !== requiredHyperdhtVersion || + lock.packages['node_modules/trac-peer']?.dependencies?.['trac-msb'] !== 'file:../msb') { + fail('installed trac-peer metadata must follow the pinned hyperdht and sibling trac-msb sources'); +} const localPackages = [ { name: 'trac-msb', source: 'trac/msb' }, @@ -141,6 +192,20 @@ const visitNodeModules = (directory) => { visitNodeModules(path.join(root, 'node_modules')); +const hyperdhtPackages = installedPackages.filter( + ({ manifest: packageManifest }) => packageManifest.name === 'hyperdht', +); +const expectedHyperdhtRoot = path.join(root, 'node_modules', 'hyperdht'); +if (hyperdhtPackages.length !== 1 || hyperdhtPackages[0].root !== expectedHyperdhtRoot) { + fail( + `runtime must contain exactly one root hyperdht, found: ` + + hyperdhtPackages.map(({ root: packageRoot }) => packageRoot).join(', '), + ); +} +if (hyperdhtPackages[0].manifest.version !== requiredHyperdhtVersion) { + fail(`runtime hyperdht must be ${requiredHyperdhtVersion}`); +} + const walletRoot = path.join(root, 'node_modules', 'trac-wallet'); const walletRoots = new Map([ [walletRoot, '1.0.1'],