@passioncode-ai/passioncode 0.1.44 → 0.1.45

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.
Files changed (20) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/family.json +1 -1
  3. package/package.json +1 -1
  4. package/payload/.claude-plugin/marketplace.json +2 -2
  5. package/payload/manifest.json +8 -8
  6. package/payload/plugins/fabric-agent-adapter/.claude-plugin/plugin.json +1 -1
  7. package/payload/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/SKILL.md +3 -3
  8. package/payload/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/scripts/adapt_project.py +2 -2
  9. package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/SKILL.md +5 -3
  10. package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/references/delegation.md +14 -0
  11. package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/references/estate-record.md +90 -0
  12. package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/references/protocol.md +1 -1
  13. package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-delegation.mjs +26 -4
  14. package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-estate.mjs +443 -0
  15. package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-service.mjs +8 -2
  16. package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_delegation.py +32 -6
  17. package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_estate.py +826 -0
  18. package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_service.py +7 -1
  19. package/payload/plugins/fabric-agent-adapter/skills/creating-fabric-agents/SKILL.md +3 -3
  20. package/payload/plugins/passioncode/.claude-plugin/plugin.json +1 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.45 - 2026-10-12
4
+
5
+ ### Changed
6
+
7
+ - **Fabric Agent Adapter is pinned at `v0.21.0`** (it was `v0.20.0`). Fabric Agent Adapter 0.21.0 (FAA-31): both service kits verify the operator's signed estate record (estate-record/1, contract DEC-0055) — the chain of revisions, the passkey's WebAuthn assertion checked offline, the 30-day window and the record's rules — and a delegation gate given the current record trusts a token's key only while that record certifies it. The adapter now pins contract b291bf0.
8
+
3
9
  ## 0.1.44 - 2026-10-12
4
10
 
5
11
  ### Changed
package/family.json CHANGED
@@ -8,7 +8,7 @@
8
8
  "name": "fabric-agent-adapter",
9
9
  "displayName": "Fabric Agent Adapter",
10
10
  "repo": "passioncode-ai/fabric-agent-adapter",
11
- "ref": "v0.20.0",
11
+ "ref": "v0.21.0",
12
12
  "kind": "plugin",
13
13
  "path": "plugins/fabric-agent-adapter",
14
14
  "legacyPluginIds": [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@passioncode-ai/passioncode",
3
- "version": "0.1.44",
3
+ "version": "0.1.45",
4
4
  "description": "The PassionCode.ai agent skill set \u2014 Fabric Agent Adapter, Observatory Log and the organisation's working rules \u2014 for Claude Code and every other agent on the machine, updated as one set.",
5
5
  "bin": {
6
6
  "passioncode": "bin/passioncode.js"
@@ -11,7 +11,7 @@
11
11
  "name": "fabric-agent-adapter",
12
12
  "displayName": "Fabric Agent Adapter",
13
13
  "source": "./plugins/fabric-agent-adapter",
14
- "version": "0.20.0",
14
+ "version": "0.21.0",
15
15
  "description": "Create new Fabric-compatible agents, build them as always-alive local services with dashboards (fabric-service/0.1), and adapt existing projects to the Fabric Agent Contract through evidence-backed MCP, A2A, or local-runner provider bundles.",
16
16
  "author": {
17
17
  "name": "PassionCode.ai",
@@ -35,7 +35,7 @@
35
35
  "name": "passioncode",
36
36
  "displayName": "PassionCode.ai",
37
37
  "source": "./plugins/passioncode",
38
- "version": "0.1.44",
38
+ "version": "0.1.45",
39
39
  "description": "The organisation's working rules for every contributor's agent (working-in-passioncode), and a once-a-day check that keeps the PassionCode.ai set current.",
40
40
  "author": {
41
41
  "name": "PassionCode.ai",
@@ -1,15 +1,15 @@
1
1
  {
2
2
  "family": "passioncode",
3
- "version": "0.1.44",
3
+ "version": "0.1.45",
4
4
  "release": true,
5
5
  "members": [
6
6
  {
7
7
  "name": "fabric-agent-adapter",
8
8
  "displayName": "Fabric Agent Adapter",
9
9
  "repo": "passioncode-ai/fabric-agent-adapter",
10
- "ref": "v0.20.0",
11
- "commit": "925d052d682572dc4ba60a7e1dea3da5a77a4de7",
12
- "version": "0.20.0",
10
+ "ref": "v0.21.0",
11
+ "commit": "7dfbb0a7af36dc1a28e266c0ef248168dabc904f",
12
+ "version": "0.21.0",
13
13
  "via": "clone of https://***@github.com/passioncode-ai/fabric-agent-adapter.git",
14
14
  "skills": [
15
15
  "adapting-projects-to-fabric",
@@ -22,7 +22,7 @@
22
22
  "legacyMarketplaces": [
23
23
  "fabric-agent-adapter"
24
24
  ],
25
- "contentHash": "sha256:9ae4b03e68e22f96174fff64430c47a7d575471efc5d0303560c4eed9adec038",
25
+ "contentHash": "sha256:c849515947a8e3ccb28b9bcc570e5cb7a3b417eb5c61e5ab9e1d2a7e656c78ef",
26
26
  "description": "Create new Fabric-compatible agents, build them as always-alive local services with dashboards (fabric-service/0.1), and adapt existing projects to the Fabric Agent Contract through evidence-backed MCP, A2A, or local-runner provider bundles.",
27
27
  "author": {
28
28
  "name": "PassionCode.ai",
@@ -61,16 +61,16 @@
61
61
  "name": "passioncode",
62
62
  "displayName": "PassionCode.ai",
63
63
  "repo": "passioncode-ai/passioncode",
64
- "ref": "v0.1.44",
64
+ "ref": "v0.1.45",
65
65
  "commit": null,
66
- "version": "0.1.44",
66
+ "version": "0.1.45",
67
67
  "via": "this repository",
68
68
  "skills": [
69
69
  "working-in-passioncode"
70
70
  ],
71
71
  "legacyPluginIds": [],
72
72
  "legacyMarketplaces": [],
73
- "contentHash": "sha256:209f32cdc3656684b3622173cfc630add724f38d50bb9d9c3d80d57f926f6981",
73
+ "contentHash": "sha256:9cca65f79d5971661e6593ae604ab184e1aef8de06c60fbc05b00240cabd84ac",
74
74
  "description": "The organisation's working rules for every contributor's agent (working-in-passioncode), and a once-a-day check that keeps the PassionCode.ai set current.",
75
75
  "author": {
76
76
  "name": "PassionCode.ai",
@@ -3,7 +3,7 @@
3
3
  "name": "fabric-agent-adapter",
4
4
  "displayName": "Fabric Agent Adapter",
5
5
  "description": "Create new Fabric-compatible agents, build them as always-alive local services with dashboards (fabric-service/0.1), and adapt existing projects to the Fabric Agent Contract through evidence-backed MCP, A2A, or local-runner provider bundles.",
6
- "version": "0.20.0",
6
+ "version": "0.21.0",
7
7
  "author": {
8
8
  "name": "PassionCode.ai",
9
9
  "url": "https://passioncode.ai/"
@@ -5,9 +5,9 @@ license: AGPL-3.0-only OR LicenseRef-PassionCode-Commercial
5
5
  compatibility: Requires filesystem access and Python 3.9+. Exact schema checks additionally need git, Node.js, pnpm, and the pinned fabric-agent-contract checkout (a public repository). Works without those tools in an explicitly degraded structural-check mode.
6
6
  metadata:
7
7
  author: PassionCode.ai
8
- version: "0.20.0"
8
+ version: "0.21.0"
9
9
  contract-version: "0.1.0"
10
- contract-commit: "0cacd08b0e95b6abf131eae1206b60792138bc3c"
10
+ contract-commit: "b291bf09e0a9f9fd3c16cf540832cd021203b7d1"
11
11
  ---
12
12
 
13
13
  # Adapting projects to Fabric
@@ -70,7 +70,7 @@ Use exactly:
70
70
 
71
71
  - contract version `0.1.0`;
72
72
  - repository `https://github.com/passioncode-ai/fabric-agent-contract`;
73
- - commit `0cacd08b0e95b6abf131eae1206b60792138bc3c`.
73
+ - commit `b291bf09e0a9f9fd3c16cf540832cd021203b7d1`.
74
74
 
75
75
  This is the default for new bundles. When checking an issued legacy bundle, preserve its
76
76
  explicit supported lock and use that exact clean checkout. Never silently upgrade or
@@ -21,10 +21,10 @@ import fabric_secrets as _secrets # noqa: E402 - the contract's credential dete
21
21
 
22
22
  CONTRACT_VERSION = "0.1.0"
23
23
  CONTRACT_REPOSITORY = "https://github.com/passioncode-ai/fabric-agent-contract"
24
- CONTRACT_COMMIT = "0cacd08b0e95b6abf131eae1206b60792138bc3c"
24
+ CONTRACT_COMMIT = "b291bf09e0a9f9fd3c16cf540832cd021203b7d1"
25
25
  # #region supported-contract-revisions — docs: README.md#contract-pin
26
26
  # Only this declaration may name a legacy revision in live files.
27
- SUPPORTED_CONTRACT_COMMITS = (CONTRACT_COMMIT, "2eb91c00e852d9c31d804310d38fad91589fcbac", "8e49fbd5fbbf230e32c1969cd24ac3c22422f0a5", "a83bec6b47776af688402c18efec507143061f68", "52da526d7cc1e063d74c5b520dbc03d0ace15246", "78b2da018d2c4bd9ac1bde1dc3b7f1a110a175f5", "623bf61358c339cb10297807b3f024b5d9f1f327", "23f9fda4c05f8a3852246ee98d4f2adf74ed0875", "9091d3d6606b0c4b5591a38c3674356a01fd7f55", "df55c8c54a23251342a7ee57ba95642b7eb39e61", "2ce392291c6668598d12cd38327e24696b5ca15c")
27
+ SUPPORTED_CONTRACT_COMMITS = (CONTRACT_COMMIT, "0cacd08b0e95b6abf131eae1206b60792138bc3c", "2eb91c00e852d9c31d804310d38fad91589fcbac", "8e49fbd5fbbf230e32c1969cd24ac3c22422f0a5", "a83bec6b47776af688402c18efec507143061f68", "52da526d7cc1e063d74c5b520dbc03d0ace15246", "78b2da018d2c4bd9ac1bde1dc3b7f1a110a175f5", "623bf61358c339cb10297807b3f024b5d9f1f327", "23f9fda4c05f8a3852246ee98d4f2adf74ed0875", "9091d3d6606b0c4b5591a38c3674356a01fd7f55", "df55c8c54a23251342a7ee57ba95642b7eb39e61", "2ce392291c6668598d12cd38327e24696b5ca15c")
28
28
  # #endregion supported-contract-revisions
29
29
  INTEROP_KEY = "https://fabric.passioncode.ai/agent-contract/extensions/interop/0.1"
30
30
  MCP_REVISION = "2026-07-28"
@@ -16,10 +16,10 @@ license: AGPL-3.0-only OR LicenseRef-PassionCode-Commercial
16
16
  compatibility: Python 3.9+ or Node.js 20+ for the kits; the probe needs Python 3.9+. launchd steps are macOS-only; Linux services install a systemd user unit and Windows ones a per-user Scheduled Task with the kits' writers. Dashboard handoff optionally uses Fabric Dashboards MCP link/host_status/open; without it, report unresolved host capability. The contract checkout is optional.
17
17
  metadata:
18
18
  author: PassionCode.ai
19
- version: "0.20.0"
19
+ version: "0.21.0"
20
20
  contract-version: "0.1.0"
21
21
  extension: "fabric-service/0.1"
22
- extension-commit: "0cacd08b0e95b6abf131eae1206b60792138bc3c"
22
+ extension-commit: "b291bf09e0a9f9fd3c16cf540832cd021203b7d1"
23
23
  ---
24
24
 
25
25
  # Building Fabric services
@@ -83,7 +83,9 @@ are in [`scripts/fabric_interop.py`](scripts/fabric_interop.py) and
83
83
  [`scripts/fabric-interop.mjs`](scripts/fabric-interop.mjs). When the callers are agents of the estate, the
84
84
  service checks each call's delegation token (DEC-0039): read [the delegation reference](references/delegation.md);
85
85
  the verifier is [`scripts/fabric_delegation.py`](scripts/fabric_delegation.py) /
86
- [`scripts/fabric-delegation.mjs`](scripts/fabric-delegation.mjs). When Fabric chooses the service's runner and models
86
+ [`scripts/fabric-delegation.mjs`](scripts/fabric-delegation.mjs). A service on the operator's machines that
87
+ applies the estate record (DEC-0055) verifies each revision first and trusts only the keys it certifies: read
88
+ [the estate record reference](references/estate-record.md). When Fabric chooses the service's runner and models
87
89
  (DEC-0046), or a project routes its work through an ordered list of runners (DEC-0026: the doctor's closed probe
88
90
  results, the route checks FAC-SEM-028…034), read [the profiles reference](references/profiles.md).
89
91
 
@@ -16,6 +16,7 @@ contract's own verifier does.
16
16
  - Approvals
17
17
  - What a refusal looks like
18
18
  - Keys and the cache
19
+ - Keys the estate record certifies
19
20
  - What the kit does not do
20
21
 
21
22
  ## Declaring it
@@ -92,6 +93,19 @@ Python kit verifies Ed25519 itself (RFC 8032, standard library only, strict as O
92
93
  canonical points, no small-order key); it follows no redirect when it reads the key set; the Node kit uses `node:crypto` and refuses small-order keys
93
94
  itself (`isSmallOrderKey`), because Node before 26 lets OpenSSL accept a signature under one.
94
95
 
96
+ ## Keys the estate record certifies
97
+
98
+ A verifier that also reads the estate's record (DEC-0055, [the estate record reference](estate-record.md)) trusts a
99
+ `kid` only while a current revision certifies it for a machine and does not revoke it (DL-14). Pass the current
100
+ verified revision as `estate=` / `{ estate }`, as a value or as a function returning it, to `delegation_gate`,
101
+ `verify_delegation` or `verify_approval`.
102
+ - An uncertified `kid` is refused `unauthenticated` (`approval-invalid` for an approval), with detail
103
+ `kid-not-certified`.
104
+ - An unreadable record is refused `issuer-unavailable`, with detail `estate-unreadable`.
105
+ - Without `estate`, nothing changes.
106
+ - Pass only a revision `verify_estate_record` accepted, and the highest one the machine has applied. The rule trusts
107
+ what it is given, so an unverified or older revision would certify a key the current one revoked.
108
+
95
109
  ## What the kit does not do
96
110
 
97
111
  It does not mint tokens, check attenuation against a parent (the issuer's rule, DL-5), check a grant or a spend
@@ -0,0 +1,90 @@
1
+ # The estate record — what the operator's machines are, signed by their passkey (DEC-0055)
2
+
3
+ Fabric Agent Contract DEC-0055, `docs/specification/estate-record.md`, schema `schemas/estate-record.schema.json`,
4
+ rules `FAC-SEM-072…076`. One revision of one person's estate lists:
5
+ - the passkeys that sign it;
6
+ - the machines, with their device keys and a certificate of each machine's hub key;
7
+ - the packages placed on each machine;
8
+ - where each secret slot is filled from (by reference only);
9
+ - share grants and revocations.
10
+
11
+ A revision names its parent's digest and is signed by a passkey assertion (WebAuthn Level 3) over a challenge
12
+ derived from its own digest. Python: [`scripts/fabric_estate.py`](../scripts/fabric_estate.py). Node:
13
+ [`scripts/fabric-estate.mjs`](../scripts/fabric-estate.mjs). Both answer the contract's own vectors.
14
+
15
+ ## Contents
16
+
17
+ - Applying a revision
18
+ - Trusting a delegation key
19
+ - Checking a record you produce
20
+ - What the kit does not do
21
+
22
+ ## Applying a revision
23
+
24
+ A machine applies a revision only when it is valid against `estate-record.schema.json` **and** the verifier accepts
25
+ it. The verifier does not check the schema.
26
+
27
+ ```python
28
+ verdict = fe.verify_estate_record(record, at=datetime.now(timezone.utc), parent=current) # revision 2 and later
29
+ verdict = fe.verify_estate_record(record, at=now, authorities=pinned_passkeys) # revision 1
30
+ if verdict["ok"]:
31
+ current = record # verdict: digest, revision, signedBy, signCount
32
+ ```
33
+
34
+ ```js
35
+ const verdict = verifyEstateRecord(record, { at: new Date(), parent: current });
36
+ ```
37
+
38
+ It checks, in the order of the ER table:
39
+ 1. **The clock.** An unreadable one is `no-clock`, and it comes first.
40
+ 2. **The shape.**
41
+ 3. **The digest.** SHA-256 over the RFC 8785 form, without `digest` and `signature`.
42
+ 4. **The chain to the parent.** Same estate, the next revision number, the parent's digest, never issued before the
43
+ parent, and no revocation dropped.
44
+ 5. **The signer.** The signing passkey must be among the trusted ones and not revoked by the parent. A revision may
45
+ revoke its own signer: that is rotation.
46
+ 6. **The assertion.**
47
+ - Client data: type, challenge, origin, no cross-origin use.
48
+ - The RP ID hash, and the user-presence and user-verification flags.
49
+ - The signature: ES256 or EdDSA, under a key validated first (a valid P-256 point; an Ed25519 key of full order).
50
+ 7. **The window.** At most 30 days.
51
+ 8. **The record's own rules.** A violation is `rule-violation` with its `codes`.
52
+
53
+ Dates are strict RFC 3339: a `T`, an offset, and a day that exists. `:60` and a space are refused. Passkey JWK `x`
54
+ and `y` must be canonical base64url. The Python kit verifies ES256 in pure Python (P-256, strict DER), and the Node
55
+ kit through `node:crypto`.
56
+
57
+ ## Trusting a delegation key
58
+
59
+ A delegation verifier that reads the estate record trusts a token's `kid` only while a current revision certifies it
60
+ for a machine and does not revoke it (DL-14, contract CT-20). Give the gate the current verified revision, or a
61
+ function that returns it:
62
+
63
+ ```python
64
+ gate = fd.delegation_gate(audience=..., jwks=keys, issuer=..., effects=..., estate=lambda: current)
65
+ ```
66
+
67
+ - A `kid` the record does not certify is refused like an unknown key: `unauthenticated` (for an approval,
68
+ `approval-invalid`) with detail `kid-not-certified`. The same holds when the record names another key under that
69
+ kid, revokes it, or is outside its window.
70
+ - A record that cannot be read certifies nothing: `issuer-unavailable` with detail `estate-unreadable`.
71
+ - **What you pass in.** Pass only a revision that `verify_estate_record` accepted, and the highest revision the
72
+ machine has applied. The rule trusts the record it is given. A record nobody verified, or an older revision still
73
+ inside its window, would certify a key the current revision revoked (a rollback).
74
+ - `hub_key_certified(record, kid, x, at)` / `hubKeyCertified(...)` answers the question on its own.
75
+
76
+ ## Checking a record you produce
77
+
78
+ `estate_record_rules(kind, value)` / `estateRecordRules(kind, value)` are the contract's rules, finding for finding:
79
+ - `estate-record` (FAC-SEM-072…076);
80
+ - `estate-record-revision` `{parent, next}` (072);
81
+ - `estate-placement` `{record, machine, package}` (074, 075).
82
+
83
+ `placement_trust(record, machine, agentRef)` names the publisher keys a package verifier trusts for a placed package.
84
+
85
+ ## What the kit does not do
86
+
87
+ - It does not sign: it holds no passkey and no private key.
88
+ - It does not check the schema or verify packages.
89
+ - It does not attest the authenticator (an assertion carries no attestation).
90
+ - It does not track the signature counter: passkeys sync between devices.
@@ -1,6 +1,6 @@
1
1
  # fabric-service/0.1 — wire reference
2
2
 
3
- Pinned to `fabric-agent-contract` commit `0cacd08b0e95b6abf131eae1206b60792138bc3c`
3
+ Pinned to `fabric-agent-contract` commit `b291bf09e0a9f9fd3c16cf540832cd021203b7d1`
4
4
  (`docs/specification/service.md`, DEC-0015; the usage report DEC-0021; Windows and Linux supervision
5
5
  DEC-0032 and DEC-0033; the operator channel DEC-0034 and DEC-0035; the embedded dashboard DEC-0036;
6
6
  delegation and the operator session's lifetime DEC-0039; the meter's call window DEC-0041; session handoff DEC-0042;
@@ -14,6 +14,7 @@ import crypto from 'node:crypto';
14
14
  import fs from 'node:fs';
15
15
  import net from 'node:net';
16
16
  import path from 'node:path';
17
+ import { hubKeyCertified } from './fabric-estate.mjs';
17
18
 
18
19
  export const REFUSAL_CODES = Object.freeze([
19
20
  'unauthenticated', 'token-expired', 'wrong-audience', 'scope-not-granted', 'effect-above-ceiling', 'no-grant',
@@ -252,11 +253,30 @@ function approvalShape(payload) {
252
253
  * DL-13, in its fixed order: nothing about a token is believed before its signature is. `now` is seconds since
253
254
  * the epoch. Answers {ok: true, claims, principal, caller, chain, kid} or {ok: false, code, detail}.
254
255
  */
255
- export function verifyDelegation(token, { audience, jwks, now, capability, effect, issuer, readOnly } = {}) {
256
+ /**
257
+ * DL-14 with DEC-0055: a verifier that reads the estate record trusts a kid only while a current revision certifies it
258
+ * for a machine and does not revoke it. `estate` is a revision the caller verified (fabric-estate.mjs
259
+ * verifyEstateRecord) or a function returning the current one; undefined skips the rule. `[kind, detail]` or null.
260
+ */
261
+ function estateProblem(estate, jwks, kid, now) {
262
+ if (estate === undefined || estate === null) return null;
263
+ let record;
264
+ try {
265
+ record = typeof estate === 'function' ? estate() : estate;
266
+ } catch {
267
+ return ['issuer', 'estate-unreadable']; // an unreadable record certifies nothing; it is never a pass
268
+ }
269
+ const jwk = jwks.keys.find((candidate) => isObject(candidate) && candidate.kid === kid) ?? {};
270
+ return hubKeyCertified(record, kid, jwk.x, now) ? null : ['token', 'kid-not-certified'];
271
+ }
272
+
273
+ export function verifyDelegation(token, { audience, jwks, now, capability, effect, issuer, readOnly, estate } = {}) {
256
274
  if (!isString(issuer)) return refuse('issuer-unavailable', 'no-trusted-issuer');
257
275
  if (!hasKeys(jwks)) return refuse('issuer-unavailable', 'no-published-keys');
258
276
  const checked = authentic(token, jwks, DELEGATION_TYP);
259
277
  if ('problem' in checked) return refuse(checked.issuer ? 'issuer-unavailable' : 'unauthenticated', checked.problem);
278
+ const uncertified = estateProblem(estate, jwks, checked.kid, now);
279
+ if (uncertified) return refuse(uncertified[0] === 'issuer' ? 'issuer-unavailable' : 'unauthenticated', uncertified[1]);
260
280
  const shaped = delegationShape(checked.compact.payload);
261
281
  if ('problem' in shaped) return refuse('unauthenticated', shaped.problem);
262
282
  const { claims, depth } = shaped;
@@ -275,13 +295,15 @@ export function verifyDelegation(token, { audience, jwks, now, capability, effec
275
295
  }
276
296
 
277
297
  /** DL-18: approval-required carries the digest; any fault is approval-invalid; the jti is consumed last. */
278
- export function verifyApproval(approval, { audience, jwks, now, actionDigest: expected, issuer, jtiStore } = {}) {
298
+ export function verifyApproval(approval, { audience, jwks, now, actionDigest: expected, issuer, jtiStore, estate } = {}) {
279
299
  if (approval === undefined || approval === null || approval === '') return refuse('approval-required', 'no-approval', { actionDigest: expected });
280
300
  if (!isObject(jtiStore) || typeof jtiStore.consume !== 'function') return refuse('unknown-state', 'no-jti-store');
281
301
  if (!isString(issuer)) return refuse('issuer-unavailable', 'no-trusted-issuer');
282
302
  if (!hasKeys(jwks)) return refuse('issuer-unavailable', 'no-published-keys');
283
303
  const checked = authentic(approval, jwks, APPROVAL_TYP);
284
304
  if ('problem' in checked) return refuse(checked.issuer ? 'issuer-unavailable' : 'approval-invalid', checked.problem);
305
+ const uncertified = estateProblem(estate, jwks, checked.kid, now);
306
+ if (uncertified) return refuse(uncertified[0] === 'issuer' ? 'issuer-unavailable' : 'approval-invalid', uncertified[1]);
285
307
  const claims = approvalShape(checked.compact.payload);
286
308
  if (!claims) return refuse('approval-invalid', 'malformed-claims');
287
309
  if (claims.iss !== issuer) return refuse('approval-invalid', 'wrong-issuer');
@@ -447,7 +469,7 @@ export const TIERS = Object.freeze(['T0', 'T1', 'T2']);
447
469
  * relayed from off the machine). A T0 or T1 caller is never required to present a token: with none, the call passes
448
470
  * with no delegation. A present token is verified at every tier. `issuer` (auth.delegation.issuer) is required.
449
471
  */
450
- export function delegationGate({ audience, jwks, effects, issuer, readOnly = false, clock = () => Date.now() / 1000 }) {
472
+ export function delegationGate({ audience, jwks, effects, issuer, readOnly = false, clock = () => Date.now() / 1000, estate }) {
451
473
  if (!isString(issuer)) throw new Error('the gate needs the issuer it trusts (auth.delegation.issuer)');
452
474
  return async (tool, headers, tier = 'T2') => {
453
475
  const effect = effects?.[tool];
@@ -458,7 +480,7 @@ export function delegationGate({ audience, jwks, effects, issuer, readOnly = fal
458
480
  return refuse('unauthenticated', 'no-token');
459
481
  }
460
482
  const keys = jwks instanceof JwksCache ? await jwks.get(kidOf(carried.token)) : jwks;
461
- const verdict = verifyDelegation(carried.token, { audience, jwks: keys, now: clock(), capability: tool, effect, readOnly, issuer });
483
+ const verdict = verifyDelegation(carried.token, { audience, jwks: keys, now: clock(), capability: tool, effect, readOnly, issuer, estate });
462
484
  if (verdict.ok) verdict.tier = TIERS.includes(tier) ? tier : 'T2';
463
485
  return verdict;
464
486
  };