xytara 3.3.0 → 3.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -135,8 +135,23 @@ POST /v1/credits/agents/:id/revoke owner key only; immediate; { reason } is stor
135
135
  GET /v1/credits/activity?limit=100&since=<iso>
136
136
  recent spends with agent id/label, units, task_ref, tool_name,
137
137
  credit_spend_id; owner sees the account, an agent sees its own
138
+ GET /v1/credits/agents/log the delegation log: every mint and revoke of the account's agent
139
+ keys (full events); an agent key sees only its own
140
+ GET /v1/credits/agents/:id/proof delegation proof for one key: its events, the hash-only chain to
141
+ the head, the signed checkpoint, how to verify
142
+ GET /v1/delegation-log/head public: event count, head hash, chain continuity
143
+ GET /v1/delegation-log/feed public: seq, type, time and hashes only (never an account or key)
144
+ GET /v1/delegation-log/checkpoint public: naxytra-attestation/1 over the head (+ ctp/1 binding
145
+ while anchoring); POST .../checkpoint/verify is a free check
138
146
  ```
139
147
 
148
+ Every mint and revoke is an append-only, hash-chained event (`naxytra-delegation-log/1`:
149
+ `event_sha256 = sha256(JCS(event))`, `chain_hash = sha256(previous chain_hash + event_sha256)`).
150
+ A relying party holding a key's delegation proof and a checkpoint can establish, without asking
151
+ this runtime, that the key was minted with these caps before T and not revoked before a head
152
+ anchored after T. Set `XYTARA_EXTERNAL_BINDING_JSON` while a chronology anchoring session is open
153
+ and every checkpoint carries the binding inside its signed bytes.
154
+
140
155
  An agent key spends exactly like the owner key (`/v1/economics/rails/credits/consume`, or
141
156
  `credits_first` execution), but every spend is checked against its per-call, per-UTC-day and
142
157
  lifetime caps, its tool allow-list, its expiry and revocation. Refusals are explicit and never
@@ -194,9 +209,14 @@ live RPC read, that you can hand to another party.
194
209
  `subject.kind` is `x402_settlement` (method `base-rpc-logs/1`) or `transaction_status` (method
195
210
  `base-rpc-receipt/1`, with `confirmations`). A negative answer (`claim.verified: false` — the
196
211
  receipt does not match the chain, the transaction is unknown) is attested too; a tool error (RPC
197
- unavailable) carries no attestation and is not charged. `bindings` is reserved for external
198
- bindings (for example `{ "scheme": "ctp/1", "q": "…" }`) and must be carried unchanged — it is
199
- inside the signed bytes. Signed bytes = JCS of the attestation with `sig.signatures` removed
212
+ unavailable) carries no attestation and is not charged. `bindings` holds external bindings and
213
+ must be carried unchanged — it is inside the signed bytes. The one known scheme is a
214
+ chronology-protocol binding, `{ "scheme": "ctp/1", "system_id": "NAXYTRA-ATTESTATION/1", "tag":
215
+ "<64 hex>" }`: the tag derives from a proof-of-work block that did not exist before the issuer's
216
+ anchoring session opened, so an attestation signed with it inside cannot predate that block; the
217
+ anchor then commits to `attestation_sha256` = SHA-256 of the JCS form of the whole attestation
218
+ (`attestationSha256` in the client; also returned by `GET /attestations/<id>` and
219
+ `POST /attestations/verify`). Signed bytes = JCS of the attestation with `sig.signatures` removed
200
220
  (`sig.suite` and `sig.kids` stay signed), with the same keys as the delivery receipts.
201
221
 
202
222
  ```js
package/RELEASE_NOTES.md CHANGED
@@ -1,6 +1,7 @@
1
- `xytara` 3.3.0 introduces the first paid products beyond pay-per-call tools: signed attestations and team spend authority.
1
+ `xytara` 3.5.0 makes delegated authority provable at a point in time: the delegation log.
2
2
 
3
- - **Signed attestations (verified answers)** — `x402.verify-receipt` and `tx.status` now return, in `_meta["naxytra.com/attestation"]`, a signed `naxytra-attestation/1` statement of the on-chain fact they verified (settlement matched or not; transaction status, block, confirmations), JCS-canonical and signed Ed25519 + ML-DSA-65 with the same keys as delivery receipts, with an `evidence_sha256` and a reserved `bindings` field. Negative findings are attested too; nothing is attested without a live chain read. Price $0.02 per call via x402 (20000 units on Base mainnet, 2000 on Base Sepolia) or 6 credits with a spend key; still within the free daily allowance. Free: `GET /attestations/<id>`, `POST /attestations/verify`, and offline verification with the client (`verifyAttestation`).
4
- - **Teams (beta)** — an owner key (the key a top-up returns) mints capped agent keys: `POST /v1/credits/agents` (per-call, per-day and total caps, expiry, tool allow-list, optional key-binding so every spend needs a fresh request-bound signature), `GET /v1/credits/agents`, `POST /v1/credits/agents/:id/revoke`, `GET /v1/credits/activity` (audit trail). Caps are enforced before any provider work runs and again at commit; an agent key cannot mint keys, see other agents, or borrow another agent's caps.
5
- - **Fixes** — an uncapped authority binding no longer refuses every delegated spend; the daily report lists the most common detail behind each frequent failure reason and names unidentified callers by their user-agent token.
6
- - Client package (thin): adds `verifyAttestation`, `attestationRecipe`, `attestationSignedBytes`, `ATTESTATION_VERSION`; tarball otherwise unchanged.
3
+ - **Delegation log (`naxytra-delegation-log/1`)** — every agent-key mint (delegatee, key fingerprint, caps, expiry) and revoke (stated time, reason) is an append-only, hash-chained event: `event_sha256 = sha256(JCS(event))`, `chain_hash = sha256(previous chain_hash + event_sha256)`. Mint and revoke responses return the event reference (`delegation_event`).
4
+ - **Public, key-less surface** — `GET /v1/delegation-log/head` (count, head hash, chain continuity), `GET /v1/delegation-log/feed?since_seq=&limit=` (seq, type, time and hashes only — never an account, a key or a label), `GET /v1/delegation-log/checkpoint` (a `naxytra-attestation/1` over the head, signed with the delivery-receipt keys; `POST /v1/delegation-log/checkpoint/verify` is a free check). The keys endpoint announces the log.
5
+ - **Owner and agent views** — `GET /v1/credits/agents/log` (owner key: the account's full events; agent key: its own) and `GET /v1/credits/agents/:id/proof`: the key's events, the hash-only chain from its first event to the head, the current checkpoint and its digest, plus the verification recipe. A relying party holding a proof and a checkpoint establishes, without asking the runtime, that a key was minted with these caps before T and not revoked before a head anchored after T.
6
+ - **Chronology binding on checkpoints** — `XYTARA_EXTERNAL_BINDING_JSON` (the `{scheme, system_id, tag}` object or the bare 64-hex tag, scheme `ctp/1`, system id `NAXYTRA-DELEGATION-LOG/1`) is carried inside every checkpoint's signed bytes while an anchoring session is open; `attestation_sha256` of the checkpoint is the digest an external anchor commits to.
7
+ - Client package (thin): unchanged.
package/client.js CHANGED
@@ -15,6 +15,9 @@ const {
15
15
  verificationRecipe,
16
16
  attestationSignedBytes,
17
17
  verifyAttestation,
18
+ attestationSha256,
19
+ normalizeExternalBinding,
20
+ EXTERNAL_BINDING_SCHEMES,
18
21
  attestationRecipe
19
22
  } = require("./lib/delivery_receipt_verify");
20
23
  const { version } = require("./package.json");
@@ -80,6 +83,11 @@ module.exports = {
80
83
  attestationSignedBytes,
81
84
  verifyAttestation,
82
85
  attestationRecipe,
86
+ // External bindings (chronology anchoring): the digest an anchor commits to, the binding shape
87
+ // check, and the known schemes.
88
+ attestationSha256,
89
+ normalizeExternalBinding,
90
+ EXTERNAL_BINDING_SCHEMES,
83
91
  // x402 header helpers for the paid command path.
84
92
  X402_PAYMENT_HEADER,
85
93
  X402_PAYMENT_REQUIRED_HEADER,
@@ -34,6 +34,15 @@ const ATTESTATION_VERSION = "naxytra-attestation/1";
34
34
  // `sig.kids` stay INSIDE the signed bytes, so a stripped algorithm or a swapped key id fails.
35
35
  const ATTESTATION_CANONICALIZATION = "JCS (RFC 8785) of the attestation with `sig.signatures` removed (`sig.suite` and `sig.kids` stay signed)";
36
36
  const ATTESTATION_ID_PATTERN = /^att_[0-9a-f]{12}$/;
37
+ // External bindings an attestation may carry INSIDE its signed bytes (attestation.bindings[]).
38
+ // "ctp/1": a chronology-protocol binding tag — a value derived from a proof-of-work block that did
39
+ // not exist when the attestation's session opened, so an attestation signed with it inside cannot
40
+ // predate that block (the lower time bound); the anchor then commits to attestation_sha256 (the
41
+ // upper bound). The tag is the derived binding tag, never the raw challenge, so records bound in
42
+ // different systems cannot be correlated. Shape: { scheme: "ctp/1", system_id, tag: <64 hex> }.
43
+ const EXTERNAL_BINDING_SCHEMES = ["ctp/1"];
44
+ const EXTERNAL_BINDING_TAG_PATTERN = /^[0-9a-f]{64}$/;
45
+ const EXTERNAL_BINDING_SYSTEM_ID_PATTERN = /^[\x21-\x7e]{1,64}$/;
37
46
 
38
47
  // JCS for the JSON values a receipt carries (strings, finite numbers, booleans, null, arrays,
39
48
  // objects). Object keys sorted by UTF-16 code units; `undefined` members dropped like JSON.
@@ -193,6 +202,27 @@ function verifyAttestation(attestation, trustedKeys) {
193
202
  };
194
203
  }
195
204
 
205
+ // The digest an external anchor (e.g. a chronology-protocol checkpoint) commits to: sha256 of the
206
+ // JCS form of the WHOLE attestation, signatures included. Stable across re-serialisation because
207
+ // it is computed over the canonical form, not the bytes a transport happened to send.
208
+ function attestationSha256(attestation) {
209
+ return hashCanonical(attestation);
210
+ }
211
+
212
+ // Normalizes one external binding ({ scheme, system_id, tag }) for issuing or inspection.
213
+ // Returns { binding } or { reason }; unknown schemes and malformed tags are refused so that an
214
+ // attestation never carries a binding a verifier could not interpret.
215
+ function normalizeExternalBinding(candidate) {
216
+ const c = isPlainObject(candidate) ? candidate : null;
217
+ if (!c) return { reason: "binding_must_be_an_object" };
218
+ if (!EXTERNAL_BINDING_SCHEMES.includes(c.scheme)) return { reason: "unsupported_binding_scheme", scheme: c.scheme === undefined ? null : c.scheme };
219
+ const systemId = typeof c.system_id === "string" ? c.system_id : "";
220
+ if (!EXTERNAL_BINDING_SYSTEM_ID_PATTERN.test(systemId)) return { reason: "binding_system_id_invalid" };
221
+ const tag = typeof c.tag === "string" ? c.tag.toLowerCase() : "";
222
+ if (!EXTERNAL_BINDING_TAG_PATTERN.test(tag)) return { reason: "binding_tag_must_be_64_hex" };
223
+ return { binding: { scheme: c.scheme, system_id: systemId, tag } };
224
+ }
225
+
196
226
  // A short, self-contained recipe for verifying an attestation without trusting the issuer's server.
197
227
  function attestationRecipe(keysUrl) {
198
228
  return {
@@ -203,8 +233,9 @@ function attestationRecipe(keysUrl) {
203
233
  "1. Fetch the issuer's public keys; check every kid in attestation.sig.kids is listed (and pin them).",
204
234
  "2. Remove attestation.sig.signatures (keep sig.suite and sig.kids) and JCS-canonicalize what is left: these are the signed bytes.",
205
235
  "3. For every alg in sig.suite, verify sig.signatures[alg] over the signed bytes with the key whose kid is sig.kids[alg] (node:crypto crypto.verify(null, bytes, spkiPublicKey, signature), or xoonya's verifyExternalProofSignature({ material, signature, algorithm, public_key, expected_key_ref: kid })).",
206
- "4. Read subject (what was checked), claim.verified / claim.status / claim.details (what was observed) and claim.observed_at_iso (when). evidence_sha256 = sha256(JCS({ tool, arguments, output })) of the tool call that produced it; recompute it when you hold that call. bindings is reserved for external bindings and must be carried unchanged."
236
+ "4. Read subject (what was checked), claim.verified / claim.status / claim.details (what was observed) and claim.observed_at_iso (when). evidence_sha256 = sha256(JCS({ tool, arguments, output })) of the tool call that produced it; recompute it when you hold that call. bindings[] (inside the signed bytes; carry unchanged) may hold external bindings: { scheme: \"ctp/1\", system_id, tag } is a chronology-protocol binding tag, proving the attestation was created after the proof-of-work block that minted the tag; the external anchor commits to attestation_sha256 = sha256(JCS(attestation)) for the upper bound."
207
237
  ],
238
+ external_bindings: { schemes: EXTERNAL_BINDING_SCHEMES.slice(), digest: "attestation_sha256 = sha256(JCS(attestation)), signatures included" },
208
239
  boundary: "Proves this issuer observed this claim about this subject from a live chain read at observed_at_iso, unaltered since signing — not that the chain cannot reorganize, and nothing beyond the stated claim."
209
240
  };
210
241
  }
@@ -216,6 +247,7 @@ module.exports = {
216
247
  ATTESTATION_VERSION,
217
248
  ATTESTATION_CANONICALIZATION,
218
249
  ATTESTATION_ID_PATTERN,
250
+ EXTERNAL_BINDING_SCHEMES,
219
251
  canonicalize,
220
252
  sha256Hex,
221
253
  hashCanonical,
@@ -226,5 +258,7 @@ module.exports = {
226
258
  verificationRecipe,
227
259
  attestationSignedBytes,
228
260
  verifyAttestation,
261
+ attestationSha256,
262
+ normalizeExternalBinding,
229
263
  attestationRecipe
230
264
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "xytara",
3
- "version": "3.3.0",
3
+ "version": "3.5.0",
4
4
  "description": "Client for the hosted naxytra runtime: discover a capability, pay per call with x402 (gasless USDC on Base) or credits, get the deliverable and a signed delivery receipt you can verify offline. The runtime is a hosted service; this package is the client.",
5
5
  "main": "client.js",
6
6
  "exports": {
@@ -95,7 +95,7 @@
95
95
  "verify:production-readiness": "node scripts/verify_production_readiness.js",
96
96
  "verify:final-activation": "node scripts/verify_final_activation.js",
97
97
  "verify:x402-facilitator": "node scripts/verify_x402_facilitator.js",
98
- "verify:all": "node scripts/verify_all.js && node scripts/verify_integrations.js && node scripts/verify_adapters.js && node scripts/verify_control_plane_contracts.js && node scripts/verify_control_plane_routes.js && node scripts/verify_framework_provider_promotion.js && node scripts/verify_operator_observability_boundary.js && node scripts/verify_pricing_experiment_plan.js && node scripts/verify_treasury_public_boundary.js && node scripts/verify_market_lanes.js && node scripts/verify_market_lane_depth.js && node scripts/verify_market_lane_traces.js && node scripts/verify_market_lane_promotion.js && node scripts/verify_provider_runtime_breadth.js && node scripts/verify_provider_runtime_journeys.js && node scripts/verify_lifecycle_continuity.js && node scripts/verify_first_run_conversions.js && node scripts/verify_delivery_summary.js && node scripts/verify_runtime_durability.js && node scripts/verify_revenue_launch_paths.js && node scripts/verify_full_catalog.js && node scripts/verify_tooling.js && node scripts/verify_examples.js && node scripts/verify_service.js && node scripts/verify_x402_facilitator.js && node scripts/verify_cdp_facilitator.js && node scripts/verify_a2a_rpc.js && node scripts/verify_checkout_processor.js && node scripts/verify_l402_adapter.js && node scripts/verify_bolt_adapter.js && node scripts/verify_mpp_adapter.js && node scripts/verify_rate_limiter.js && node scripts/verify_execution_surfacing.js && node scripts/verify_agent_identity.js && node scripts/verify_verify_execute_settle.js && node scripts/verify_execution_dedupe.js && node scripts/verify_refund_credit.js && node scripts/verify_commerce_bridge.js && node scripts/verify_settlement.js && node scripts/verify_provider_network.js && node scripts/verify_external_execution_retry.js && node scripts/verify_runtime_state_guard.js && node scripts/state_backup_offsite.js selftest && node scripts/verify_lifecycle_spend_binding.js && node scripts/verify_buyer_safety.js && node scripts/verify_buyer_event_log.js && node scripts/verify_x402_settlement_recovery.js && node scripts/verify_team_spend.js",
98
+ "verify:all": "node scripts/verify_all.js && node scripts/verify_integrations.js && node scripts/verify_adapters.js && node scripts/verify_control_plane_contracts.js && node scripts/verify_control_plane_routes.js && node scripts/verify_framework_provider_promotion.js && node scripts/verify_operator_observability_boundary.js && node scripts/verify_pricing_experiment_plan.js && node scripts/verify_treasury_public_boundary.js && node scripts/verify_market_lanes.js && node scripts/verify_market_lane_depth.js && node scripts/verify_market_lane_traces.js && node scripts/verify_market_lane_promotion.js && node scripts/verify_provider_runtime_breadth.js && node scripts/verify_provider_runtime_journeys.js && node scripts/verify_lifecycle_continuity.js && node scripts/verify_first_run_conversions.js && node scripts/verify_delivery_summary.js && node scripts/verify_runtime_durability.js && node scripts/verify_revenue_launch_paths.js && node scripts/verify_full_catalog.js && node scripts/verify_tooling.js && node scripts/verify_examples.js && node scripts/verify_service.js && node scripts/verify_x402_facilitator.js && node scripts/verify_cdp_facilitator.js && node scripts/verify_a2a_rpc.js && node scripts/verify_checkout_processor.js && node scripts/verify_l402_adapter.js && node scripts/verify_bolt_adapter.js && node scripts/verify_mpp_adapter.js && node scripts/verify_rate_limiter.js && node scripts/verify_execution_surfacing.js && node scripts/verify_agent_identity.js && node scripts/verify_verify_execute_settle.js && node scripts/verify_execution_dedupe.js && node scripts/verify_refund_credit.js && node scripts/verify_commerce_bridge.js && node scripts/verify_settlement.js && node scripts/verify_provider_network.js && node scripts/verify_external_execution_retry.js && node scripts/verify_runtime_state_guard.js && node scripts/state_backup_offsite.js selftest && node scripts/verify_lifecycle_spend_binding.js && node scripts/verify_buyer_safety.js && node scripts/verify_buyer_event_log.js && node scripts/verify_x402_settlement_recovery.js && node scripts/verify_team_spend.js && node scripts/verify_delegation_log.js",
99
99
  "prepublishOnly": "npm run verify:release-candidate",
100
100
  "verify:cdp-facilitator": "node scripts/verify_cdp_facilitator.js",
101
101
  "verify:a2a-rpc": "node scripts/verify_a2a_rpc.js",
@@ -120,6 +120,7 @@
120
120
  "verify:buyer-event-log": "node scripts/verify_buyer_event_log.js",
121
121
  "verify:x402-settlement-recovery": "node scripts/verify_x402_settlement_recovery.js",
122
122
  "verify:team-spend": "node scripts/verify_team_spend.js",
123
+ "verify:delegation-log": "node scripts/verify_delegation_log.js",
123
124
  "verify:client": "node scripts/verify_client.js"
124
125
  }
125
126
  }
@@ -252,7 +252,7 @@ function runCliAsync(args) {
252
252
  async function main() {
253
253
  check("package_surface", () => {
254
254
  assert.strictEqual(typeof CommerceClient, "function", "CommerceClient export missing");
255
- for (const name of ["verifyDeliveryReceipt", "canonicalize", "hashCanonical", "keyRef", "verificationRecipe", "verifyAttestation", "attestationRecipe", "attestationSignedBytes", "encodeX402PaymentHeader", "decodeX402Header", "summarizeCommandResultPayload", "summarizeCatalogPayload", "summarizeReceiptPayload"]) {
255
+ for (const name of ["verifyDeliveryReceipt", "canonicalize", "hashCanonical", "keyRef", "verificationRecipe", "verifyAttestation", "attestationRecipe", "attestationSignedBytes", "attestationSha256", "normalizeExternalBinding", "encodeX402PaymentHeader", "decodeX402Header", "summarizeCommandResultPayload", "summarizeCatalogPayload", "summarizeReceiptPayload"]) {
256
256
  assert.strictEqual(typeof client[name], "function", `${name} export missing`);
257
257
  }
258
258
  assert.strictEqual(RECEIPT_VERSION, "naxytra-delivery-receipt/1", "receipt version mismatch");
@@ -309,7 +309,9 @@ async function main() {
309
309
  check("attestation_offline_round_trip", () => {
310
310
  const issuer = makeSigner();
311
311
  const toolCall = { tool: "tx.status", arguments: { tx_hash: "0x" + "9b".repeat(32), network: "base" }, output: { ok: true, found: true, status: "success", block_number: 4080, confirmations: 17 } };
312
- const bindings = [{ scheme: "ctp/1", q: "cid:example" }];
312
+ const bindings = [{ scheme: "ctp/1", system_id: "NAXYTRA-ATTESTATION/1", tag: "7d".repeat(32) }];
313
+ assert.deepStrictEqual(client.normalizeExternalBinding(bindings[0]).binding, bindings[0], "a ctp/1 binding { scheme, system_id, tag } normalizes to itself");
314
+ assert.deepStrictEqual(client.EXTERNAL_BINDING_SCHEMES, ["ctp/1"], "known binding schemes");
313
315
  const attestation = signAttestation({
314
316
  attestation_id: "att_0123456789ab",
315
317
  issued_at_iso: "2026-01-01T00:00:02.000Z",
@@ -331,6 +333,8 @@ async function main() {
331
333
  const reordered = JSON.parse(JSON.stringify({ sig: attestation.sig, bindings: attestation.bindings, claim: attestation.claim, ...attestation }));
332
334
  assert.strictEqual(verifyAttestation(reordered, [issuer.trustedKey]).valid, true, "key order must not matter (JCS)");
333
335
  assert.deepStrictEqual(reordered.bindings, bindings, "bindings survive the round trip unchanged");
336
+ assert.strictEqual(client.attestationSha256(reordered), client.attestationSha256(attestation), "attestation_sha256 is the same digest whatever the key order (JCS, signatures included)");
337
+ assert.notStrictEqual(client.attestationSha256(attestation), result.signed_bytes_sha256, "attestation_sha256 (signatures included) differs from the signed-bytes hash");
334
338
  const negative = signAttestation({ ...attestation, claim: { ...attestation.claim, verified: false, status: "not_found" } }, issuer);
335
339
  assert.strictEqual(verifyAttestation(negative, [issuer.trustedKey]).claim_verified, false, "a negative claim (verified:false) is a valid signed attestation");
336
340
  // tamper -> reject
@@ -338,7 +342,7 @@ async function main() {
338
342
  tamperedClaim.claim.verified = false;
339
343
  assert.strictEqual(verifyAttestation(tamperedClaim, [issuer.trustedKey]).valid, false, "tampered claim must fail");
340
344
  const tamperedBindings = JSON.parse(JSON.stringify(attestation));
341
- tamperedBindings.bindings.push({ scheme: "ctp/1", q: "added-later" });
345
+ tamperedBindings.bindings.push({ scheme: "ctp/1", system_id: "LATER", tag: "00".repeat(32) });
342
346
  assert.strictEqual(verifyAttestation(tamperedBindings, [issuer.trustedKey]).valid, false, "bindings are signed: adding one later must fail");
343
347
  const removedBindings = JSON.parse(JSON.stringify(attestation));
344
348
  removedBindings.bindings = [];