@agenticprimitives/fabric 0.0.0-alpha.4 → 0.0.0-alpha.41

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 (198) hide show
  1. package/CLAUDE.md +28 -35
  2. package/README.md +18 -16
  3. package/dist/adoption.d.ts +79 -0
  4. package/dist/adoption.d.ts.map +1 -0
  5. package/dist/adoption.js +95 -0
  6. package/dist/adoption.js.map +1 -0
  7. package/dist/case-emissions.d.ts +95 -0
  8. package/dist/case-emissions.d.ts.map +1 -0
  9. package/dist/case-emissions.js +208 -0
  10. package/dist/case-emissions.js.map +1 -0
  11. package/dist/case-machine.d.ts +28 -2
  12. package/dist/case-machine.d.ts.map +1 -1
  13. package/dist/case-machine.js +34 -7
  14. package/dist/case-machine.js.map +1 -1
  15. package/dist/cloudflare/authority-verdict-cache.d.ts +28 -1
  16. package/dist/cloudflare/authority-verdict-cache.d.ts.map +1 -1
  17. package/dist/cloudflare/authority-verdict-cache.js +32 -1
  18. package/dist/cloudflare/authority-verdict-cache.js.map +1 -1
  19. package/dist/cloudflare/exchange-store.d.ts +20 -1
  20. package/dist/cloudflare/exchange-store.d.ts.map +1 -1
  21. package/dist/cloudflare/exchange-store.js +26 -4
  22. package/dist/cloudflare/exchange-store.js.map +1 -1
  23. package/dist/cloudflare/index.d.ts +7 -4
  24. package/dist/cloudflare/index.d.ts.map +1 -1
  25. package/dist/cloudflare/index.js +8 -2
  26. package/dist/cloudflare/index.js.map +1 -1
  27. package/dist/cloudflare/interaction-store.d.ts +81 -0
  28. package/dist/cloudflare/interaction-store.d.ts.map +1 -0
  29. package/dist/cloudflare/interaction-store.js +281 -0
  30. package/dist/cloudflare/interaction-store.js.map +1 -0
  31. package/dist/cloudflare/orchestration-runtime.d.ts +19 -5
  32. package/dist/cloudflare/orchestration-runtime.d.ts.map +1 -1
  33. package/dist/cloudflare/orchestration-runtime.js +11 -6
  34. package/dist/cloudflare/orchestration-runtime.js.map +1 -1
  35. package/dist/cloudflare/principal-gateway-do.d.ts +360 -30
  36. package/dist/cloudflare/principal-gateway-do.d.ts.map +1 -1
  37. package/dist/cloudflare/principal-gateway-do.js +580 -58
  38. package/dist/cloudflare/principal-gateway-do.js.map +1 -1
  39. package/dist/cloudflare/receipt-signer.d.ts.map +1 -1
  40. package/dist/cloudflare/receipt-signer.js +6 -3
  41. package/dist/cloudflare/receipt-signer.js.map +1 -1
  42. package/dist/cloudflare/sql-vault.d.ts.map +1 -1
  43. package/dist/cloudflare/sql-vault.js +56 -0
  44. package/dist/cloudflare/sql-vault.js.map +1 -1
  45. package/dist/cloudflare/vault-exchange-store.d.ts +47 -0
  46. package/dist/cloudflare/vault-exchange-store.d.ts.map +1 -0
  47. package/dist/cloudflare/vault-exchange-store.js +135 -0
  48. package/dist/cloudflare/vault-exchange-store.js.map +1 -0
  49. package/dist/cloudflare/vault-interaction-store.d.ts +44 -0
  50. package/dist/cloudflare/vault-interaction-store.d.ts.map +1 -0
  51. package/dist/cloudflare/vault-interaction-store.js +259 -0
  52. package/dist/cloudflare/vault-interaction-store.js.map +1 -0
  53. package/dist/communicative-act.d.ts +24 -1
  54. package/dist/communicative-act.d.ts.map +1 -1
  55. package/dist/communicative-act.js +28 -0
  56. package/dist/communicative-act.js.map +1 -1
  57. package/dist/delivery-envelope.d.ts.map +1 -1
  58. package/dist/delivery-envelope.js +0 -1
  59. package/dist/delivery-envelope.js.map +1 -1
  60. package/dist/email/index.d.ts +79 -0
  61. package/dist/email/index.d.ts.map +1 -0
  62. package/dist/email/index.js +91 -0
  63. package/dist/email/index.js.map +1 -0
  64. package/dist/enum-retirement.d.ts +43 -0
  65. package/dist/enum-retirement.d.ts.map +1 -0
  66. package/dist/enum-retirement.js +93 -0
  67. package/dist/enum-retirement.js.map +1 -0
  68. package/dist/exchange-record.d.ts +35 -4
  69. package/dist/exchange-record.d.ts.map +1 -1
  70. package/dist/exchange-record.js.map +1 -1
  71. package/dist/exchange-reducer.d.ts +32 -4
  72. package/dist/exchange-reducer.d.ts.map +1 -1
  73. package/dist/exchange-reducer.js +75 -1
  74. package/dist/exchange-reducer.js.map +1 -1
  75. package/dist/exchange-state.d.ts +30 -8
  76. package/dist/exchange-state.d.ts.map +1 -1
  77. package/dist/exchange-state.js +12 -10
  78. package/dist/exchange-state.js.map +1 -1
  79. package/dist/inbox-doc.d.ts +2 -2
  80. package/dist/inbox-doc.d.ts.map +1 -1
  81. package/dist/inbox-doc.js.map +1 -1
  82. package/dist/inbox-projection.d.ts +2 -2
  83. package/dist/inbox-projection.d.ts.map +1 -1
  84. package/dist/inbox-projection.js.map +1 -1
  85. package/dist/inbox-revision.d.ts +42 -0
  86. package/dist/inbox-revision.d.ts.map +1 -0
  87. package/dist/inbox-revision.js +57 -0
  88. package/dist/inbox-revision.js.map +1 -0
  89. package/dist/index.d.ts +26 -3
  90. package/dist/index.d.ts.map +1 -1
  91. package/dist/index.js +40 -2
  92. package/dist/index.js.map +1 -1
  93. package/dist/interaction-machines.d.ts +136 -0
  94. package/dist/interaction-machines.d.ts.map +1 -0
  95. package/dist/interaction-machines.js +174 -0
  96. package/dist/interaction-machines.js.map +1 -0
  97. package/dist/interaction-profile.d.ts +136 -0
  98. package/dist/interaction-profile.d.ts.map +1 -0
  99. package/dist/interaction-profile.js +212 -0
  100. package/dist/interaction-profile.js.map +1 -0
  101. package/dist/interaction-record.d.ts +168 -0
  102. package/dist/interaction-record.d.ts.map +1 -0
  103. package/dist/interaction-record.js +108 -0
  104. package/dist/interaction-record.js.map +1 -0
  105. package/dist/interactions/grant-scopes.d.ts +21 -0
  106. package/dist/interactions/grant-scopes.d.ts.map +1 -0
  107. package/dist/interactions/grant-scopes.js +88 -0
  108. package/dist/interactions/grant-scopes.js.map +1 -0
  109. package/dist/interactions/index.d.ts +1 -0
  110. package/dist/interactions/index.d.ts.map +1 -1
  111. package/dist/interactions/index.js +3 -0
  112. package/dist/interactions/index.js.map +1 -1
  113. package/dist/interactions/machine.d.ts +2 -2
  114. package/dist/interactions/machine.d.ts.map +1 -1
  115. package/dist/interactions/machine.js +2 -2
  116. package/dist/interactions/machine.js.map +1 -1
  117. package/dist/interactions/open-case.d.ts +60 -0
  118. package/dist/interactions/open-case.d.ts.map +1 -0
  119. package/dist/interactions/open-case.js +83 -0
  120. package/dist/interactions/open-case.js.map +1 -0
  121. package/dist/interactions/store.d.ts +9 -0
  122. package/dist/interactions/store.d.ts.map +1 -1
  123. package/dist/interactions/store.js +57 -2
  124. package/dist/interactions/store.js.map +1 -1
  125. package/dist/messaging/board.d.ts +4 -4
  126. package/dist/messaging/board.d.ts.map +1 -1
  127. package/dist/messaging/board.js +2 -2
  128. package/dist/messaging/board.js.map +1 -1
  129. package/dist/messaging/consult-routing.d.ts +103 -23
  130. package/dist/messaging/consult-routing.d.ts.map +1 -1
  131. package/dist/messaging/consult-routing.js +157 -47
  132. package/dist/messaging/consult-routing.js.map +1 -1
  133. package/dist/messaging/consult.d.ts +4 -0
  134. package/dist/messaging/consult.d.ts.map +1 -1
  135. package/dist/messaging/consult.js +3 -1
  136. package/dist/messaging/consult.js.map +1 -1
  137. package/dist/messaging/conversation-interaction.d.ts +66 -0
  138. package/dist/messaging/conversation-interaction.d.ts.map +1 -0
  139. package/dist/messaging/conversation-interaction.js +139 -0
  140. package/dist/messaging/conversation-interaction.js.map +1 -0
  141. package/dist/messaging/conversation.d.ts +69 -1
  142. package/dist/messaging/conversation.d.ts.map +1 -1
  143. package/dist/messaging/conversation.js +131 -0
  144. package/dist/messaging/conversation.js.map +1 -1
  145. package/dist/messaging/delivery.d.ts +2 -2
  146. package/dist/messaging/delivery.d.ts.map +1 -1
  147. package/dist/messaging/delivery.js +8 -1
  148. package/dist/messaging/delivery.js.map +1 -1
  149. package/dist/messaging/envelope.d.ts +7 -3
  150. package/dist/messaging/envelope.d.ts.map +1 -1
  151. package/dist/messaging/envelope.js +18 -1
  152. package/dist/messaging/envelope.js.map +1 -1
  153. package/dist/messaging/hash.d.ts +8 -2
  154. package/dist/messaging/hash.d.ts.map +1 -1
  155. package/dist/messaging/hash.js +6 -0
  156. package/dist/messaging/hash.js.map +1 -1
  157. package/dist/messaging/inbox-assistant.d.ts +16 -4
  158. package/dist/messaging/inbox-assistant.d.ts.map +1 -1
  159. package/dist/messaging/inbox-assistant.js +27 -4
  160. package/dist/messaging/inbox-assistant.js.map +1 -1
  161. package/dist/messaging/index.d.ts +7 -3
  162. package/dist/messaging/index.d.ts.map +1 -1
  163. package/dist/messaging/index.js +11 -2
  164. package/dist/messaging/index.js.map +1 -1
  165. package/dist/messaging/outbound-message.d.ts +42 -0
  166. package/dist/messaging/outbound-message.d.ts.map +1 -0
  167. package/dist/messaging/outbound-message.js +64 -0
  168. package/dist/messaging/outbound-message.js.map +1 -0
  169. package/dist/messaging/projector.d.ts +3 -3
  170. package/dist/messaging/projector.d.ts.map +1 -1
  171. package/dist/messaging/projector.js.map +1 -1
  172. package/dist/messaging/receipts.d.ts +3 -3
  173. package/dist/messaging/receipts.d.ts.map +1 -1
  174. package/dist/messaging/receipts.js.map +1 -1
  175. package/dist/messaging/rules.d.ts +10 -3
  176. package/dist/messaging/rules.d.ts.map +1 -1
  177. package/dist/messaging/rules.js +8 -1
  178. package/dist/messaging/rules.js.map +1 -1
  179. package/dist/messaging/store.d.ts +4 -4
  180. package/dist/messaging/store.d.ts.map +1 -1
  181. package/dist/messaging/store.js +5 -3
  182. package/dist/messaging/store.js.map +1 -1
  183. package/dist/messaging/types.d.ts +30 -0
  184. package/dist/messaging/types.d.ts.map +1 -1
  185. package/dist/messaging/types.js +3 -0
  186. package/dist/messaging/types.js.map +1 -1
  187. package/dist/messaging/vault-store.d.ts +2 -2
  188. package/dist/messaging/vault-store.d.ts.map +1 -1
  189. package/dist/messaging/vault-store.js +8 -0
  190. package/dist/messaging/vault-store.js.map +1 -1
  191. package/dist/protocol-binding.d.ts +71 -0
  192. package/dist/protocol-binding.d.ts.map +1 -0
  193. package/dist/protocol-binding.js +0 -0
  194. package/dist/protocol-binding.js.map +1 -0
  195. package/dist/record-catalog.d.ts.map +1 -1
  196. package/dist/record-catalog.js +12 -0
  197. package/dist/record-catalog.js.map +1 -1
  198. package/package.json +28 -15
package/CLAUDE.md CHANGED
@@ -6,22 +6,9 @@ exec/case FSM. Decentralized (per-principal gateway + vault), tiered (T0/T1/T2),
6
6
  re-verified against the principal SA. Transport-agnostic core; the Cloudflare `PrincipalGatewayDO`
7
7
  adapter ships from `./cloudflare`.
8
8
 
9
- ## Status (wave-by-wave build)
10
- - **W1 DONE:** exec/case FSM spine + absorbed messaging/interactions reducers (`./messaging`,
11
- `./interactions` subpaths) + unified reducer + inbox projection. Golden-vectored.
12
- - **W2 DONE (`./cloudflare` adapter):** `PrincipalGatewayDO`, co-resident on one `state.storage.sql`
13
- trust domain — `createDurableObjectSqlVault` (per-owner DEK, L2 envelope crypto); durable
14
- single-writer `createDurableObjectExchangeStore` (fail-closed `applyExchangeEvent`); T0
15
- `deliver`/`applyEvent`/`inbox`; T1 `AuthorityVerdictCache` + `RequestIdLedger` (§3a: cached verdict
16
- A + single-use requestId); **co-resident MCP** (`invokeFast` runs the tool IN-PROCESS over the DO
17
- vault after the A→B→requestId gate; heavy verify + KEK wrapper INJECTED via `GatewayDeps`); **T2
18
- orchestration loop** (co-resident tools AS the `orchestration` ToolInvoker; only the LLM planner
19
- injected); **signed `DeliveryReceiptV1`** (DO-local secp256k1). The ONLY subtree importing
20
- `@cloudflare/workers-types`.
21
- - **W4 DONE:** `GatewaySurfaceCommitmentV1` (gatewayRoot preimage) + peer `DeliveryEnvelopeV1` +
22
- `receiveDelivery` + `OutboxStore` store-and-forward.
23
- - **Next:** on-chain `atl:gatewayRoot` record (contracts, separate); `A2aTaskDO`/`SessionStoreDO`
24
- absorption + `alarm()` drain; `fetch`/`alarm` e2e via vitest-pool-workers.
9
+ ## Status
10
+ **W1–W10b DONE** — history: [`docs/architecture.md` §7a](docs/architecture.md). Next: `MessageEnvelopeV1.kind`
11
+ (a re-signing cutover, spec 340 §R.1/§R.3); on-chain `atl:gatewayRoot`; `A2aTaskDO`/`SessionStoreDO` absorption.
25
12
 
26
13
  ## What this package owns
27
14
  - `ExchangeRecordV1` — the ONE canonical object (spine id `xch_`); two-level FSM: `ExecState`
@@ -33,12 +20,29 @@ adapter ships from `./cloudflare`.
33
20
  case reducer + typed payloads + runs (from `interactions`), delivery/admission/discovery.
34
21
 
35
22
  ## Hard rules
23
+ - **DO-local is the serving plane; the vault is the RECORD** ([ADR-0055](../../docs/architecture/decisions/0055-vaults-are-the-canonical-system-of-record-for-content.md)
24
+ + amendment). `state.storage.sql` holds ordering, cursors, receipts, request-id ledgers, projections,
25
+ caches. Test for any new table: *if this DO were wiped, is the loss a rebuild or a bereavement?* A
26
+ projection must be rebuildable AND the rebuild tested (vault-first write · `rebuild()` in code · a test
27
+ that wipes and rebuilds) — `createVaultExchangeStore` / `createVaultInteractionStore` satisfy all three;
28
+ residency (**each party holds their own copy**) is [spec 316 §0b](../../specs/316-agentic-interaction-fabric.md).
29
+ `PrincipalGatewayDO` builds a DO-SQL vault INTERNALLY, so an adopting app MUST inject.
30
+ - **Two serving planes; the transition is a LADDER** ([ADR-0060](../../docs/architecture/decisions/0060-incremental-gateway-adoption.md)).
31
+ `InteractionsDO` serves; this is the target. Ops climb `off → shadow → serving` one at a time. A clean
32
+ shadow proves the planes AGREE, never that either is right.
33
+ - **The gateway authenticates its own ingress** (spec 445 §3.4): `principal`/`tier`/`actor` come only from the
34
+ injected `GatewayDeps.authenticate` via `admitCaller` → `AuthenticatedCaller`; no authenticator ⇒ refused.
35
+ `orchestrate` = verdict + per-step caveat pass + request id. Only an `active` interaction takes exchanges;
36
+ `openInteraction` never overwrites. Consult eligibility = current member ∩ live grant (two inputs).
36
37
  - **Authority is never satisfied by the exchange.** A request carries an `authorityRef`; approving a
37
38
  case advances the lifecycle, it NEVER unlocks a resource (spec 316 §2, ADR-0041).
38
39
  - **`ExecState` is `fulfillment`'s `TaskState`, unmodified** — `fulfillment` stays a pure leaf.
39
40
  - **Fail-closed coupling.** An `(exec, case)` pair with no mapping is a typed rejection.
40
41
  - **Transport-agnostic core.** No Cloudflare/MCP/A2A-SDK imports in `src/` — `./cloudflare` is the
41
42
  only adapter and the only `@cloudflare/workers-types` consumer.
43
+ - **The adoption ladder's mechanics are here** (`src/adoption.ts`, spec 399 §4): `adoptionStage(ledger, op, env)`
44
+ (`GATEWAY_SHADOW` gates a shadow; `""` is unset), `compareServed` / `classifyDivergence`, `shouldShadow`,
45
+ `recordDivergence`. The ledger VALUES — which op is on which rung, and why — stay with the product.
42
46
 
43
47
  ## Boundary
44
48
  `types`, `fulfillment`, `vault`, `audit`, `delegation`, `chain-state`, `tool-policy`, `agent-naming`
@@ -46,22 +50,11 @@ adapter ships from `./cloudflare`.
46
50
  provenance` (the PROV-O projector). No back-edges; nothing imports `fabric` except `./cloudflare` + apps.
47
51
 
48
52
  ## Validate
49
- ```bash
50
- pnpm --filter @agenticprimitives/fabric typecheck
51
- pnpm --filter @agenticprimitives/fabric test
52
- ```
53
-
54
- ## Capabilities this package participates in
55
- - **Messaging + Interactions** ([`specs/309`](../../specs/309-messaging-and-interactions.md)) — fabric IS
56
- the substrate the messaging (envelope/inbox) + interactions (case/run) capability was absorbed into
57
- (spec 316 W1); demo guide: [`apps/demo-sso-next/docs/messaging-interactions/guide.md`](../../apps/demo-sso-next/docs/messaging-interactions/guide.md).
58
- - **Home / agentic control plane + inbox** ([`specs/310`](../../specs/310-agentic-trust-home-control-plane-and-inbox.md)) —
59
- the vault-resident inbox + control-plane projections read fabric wire types; demo guide:
60
- [`apps/demo-sso-next/docs/home-inbox/guide.md`](../../apps/demo-sso-next/docs/home-inbox/guide.md).
61
- - Index: [`docs/architecture/cross-cutting-capabilities.md`](../../docs/architecture/cross-cutting-capabilities.md).
62
-
63
- ## Spec + demo guides
64
- `../../specs/316-agentic-interaction-fabric.md` (architect-of-record) +
65
- `../../docs/architecture/agentic-interaction-fabric-target.md` + ADR-0047. Demo guides: spec 309
66
- `../../apps/demo-sso-next/docs/messaging-interactions/guide.md` · spec 310
67
- `../../apps/demo-sso-next/docs/home-inbox/guide.md`.
53
+ `pnpm --filter @agenticprimitives/fabric typecheck && pnpm --filter @agenticprimitives/fabric test`
54
+
55
+ ## Capabilities, spec + guides
56
+ Messaging + Interactions ([spec 309](../../specs/309-messaging-and-interactions.md)) · Home control plane +
57
+ inbox ([spec 310](../../specs/310-agentic-trust-home-control-plane-and-inbox.md)) ·
58
+ [index](../../docs/architecture/cross-cutting-capabilities.md) · [spec 316](../../specs/316-agentic-interaction-fabric.md) (architect-of-record) ·
59
+ [target](../../docs/architecture/agentic-interaction-fabric-target.md) · ADR-0047 ·
60
+ [fabric architecture](docs/architecture.md) (live wiring + build history).
package/README.md CHANGED
@@ -1,21 +1,23 @@
1
1
  # @agenticprimitives/fabric
2
2
 
3
- The **Agentic Interaction Fabric** (spec 316): the A2A-native substrate that absorbs `messaging`
4
- (envelope/inbox) and `interactions` (case/run) into ONE canonical exchange object over a two-level
5
- exec/case state machine. Transport-agnostic core; the Cloudflare gateway ships as a `./cloudflare` adapter.
3
+ > Part of **[Agentic Primitives](https://agenticprimitives.dev)** — the open-source trust substrate for agentic applications: identity that can sign, authority checked at act time, evidence the owner carries. [Developer kit](https://github.com/agentictrustlabs/agentic-primitives) · [All packages](https://agenticprimitives.dev/developers)
6
4
 
7
- - **One object:** `ExchangeRecordV1` — an `ExecState` transport envelope carrying an optional 11-state
8
- `CaseState` business substate, coupled by an exhaustive fail-closed table.
9
- - **One reducer:** `applyExchangeEvent` over a single append-only event stream (message | case | artifact |
10
- transport). The inbox is a projection (`projectInboxItem`).
11
- - **Subpaths:** `@agenticprimitives/fabric/messaging` + `/interactions` re-export the absorbed surfaces.
5
+ Ring-0 **operational** realization of Agentic Interaction. Semantics live under `apix:`
6
+ ([ADR-0058](../../docs/architecture/decisions/0058-agentic-interaction-canonical-bounded-context.md) ·
7
+ [spec 340](../../specs/340-agentic-interaction.md)); this package owns wire types, reducers, and the
8
+ Cloudflare gateway adapter — not the ontology namespace (`apfab:` retired).
12
9
 
13
- Authority is never satisfied by the exchange — a request carries an `authorityRef`; approving a case advances
14
- the lifecycle, it never unlocks a resource (ADR-0041).
10
+ Runtime doctrine still [spec 316](../../specs/316-agentic-interaction-fabric.md) /
11
+ [ADR-0047](../../docs/architecture/decisions/0047-agent-exchange-fabric-a2a-native.md): A2A-native
12
+ substrate absorbing messaging + interactions into one exchange object over a two-level exec/case FSM.
15
13
 
16
- See `CLAUDE.md` for the implementation guide and `../../specs/316-agentic-interaction-fabric.md` (architect
17
- of record) + `../../docs/architecture/agentic-interaction-fabric-target.md` + ADR-0047.
18
- Interaction-flow diagrams (person↔person, org channels, cases) + XMTP/Diode alignment:
19
- [`docs/architecture.md`](docs/architecture.md) · commercial comparison:
20
- [`docs/fabric-capabilities-vs-commercial.md`](docs/fabric-capabilities-vs-commercial.md) · gaps:
21
- [`docs/fabric-capability-gaps.md`](docs/fabric-capability-gaps.md).
14
+ - **Exchange:** `ExchangeRecordV1` — `ExecState` + optional `CaseState`, fail-closed coupling.
15
+ - **Interaction (spec 340):** `InteractionRecordV1` + profiles + context snapshots on
16
+ `PrincipalGatewayDO` — **in package; apps still use demo-a2a `InteractionsDO` + vault board**.
17
+ - **Subpaths:** `@agenticprimitives/fabric/messaging` + `/interactions`.
18
+
19
+ Authority is never satisfied by the exchange (ADR-0041).
20
+
21
+ **Start here for app wiring + adoption matrix:** [`docs/architecture.md`](docs/architecture.md).
22
+ Also: `CLAUDE.md` · [target architecture](../../docs/architecture/agentic-interaction-fabric-target.md) ·
23
+ [commercial comparison](docs/fabric-capabilities-vs-commercial.md) · [gaps](docs/fabric-capability-gaps.md).
@@ -0,0 +1,79 @@
1
+ /** Which of the five candidate causes an op's move actually exercises. Naming it per op is the point of
2
+ * moving one at a time: if an op diverges, its concerns say where to look, and where not to. */
3
+ export type AdoptionConcern = 'auth' | 'projection' | 'ordering' | 'data-shape' | 'assistant';
4
+ export type AdoptionStage = 'off' | 'shadow' | 'serving';
5
+ export interface AdoptionEntry {
6
+ stage: AdoptionStage;
7
+ /** What moving this op puts at risk — read this first when its shadow diverges. */
8
+ concerns: readonly AdoptionConcern[];
9
+ /** Why this op is at this rung. Prose, because "it's next" and "it's blocked" look identical in a stage
10
+ * field, and the difference is the whole state of the migration. */
11
+ note: string;
12
+ /**
13
+ * Fields that legitimately differ between the two planes because each computes them at its own instant.
14
+ * Declared PER OP and BEFORE evidence arrives — classifying noise after seeing a report is how a real
15
+ * divergence gets explained away as expected. An EMPTY list is a claim, not an omission: it asserts this
16
+ * op has no per-read computation, so any difference is either a race or a defect. Say why in `note`.
17
+ */
18
+ volatileFields: readonly string[];
19
+ }
20
+ /** The ledger: one place that says which plane serves what, so adoption is data rather than conditionals
21
+ * spread across a serving object. An op absent from it is `off`: adoption is opt-in, because the failure
22
+ * mode of opt-out is an op that moved because nobody remembered to stop it. */
23
+ export type AdoptionLedger = Readonly<Record<string, AdoptionEntry>>;
24
+ /**
25
+ * The stage an op runs at IN THIS DEPLOYMENT. The ledger says how far an op has climbed in the code; the
26
+ * deployment's `GATEWAY_SHADOW` says whether it is running the comparison at all. Both are required, and the
27
+ * env one defaults to OFF — constructing the gateway creates its tables, and an ungated shadow on a hot op
28
+ * would make that storage decision for every principal at once. Empty string is unset, not enabled
29
+ * (`wrangler`'s `VAR = ""` binds an empty string that `??` sails straight past).
30
+ */
31
+ export declare function adoptionStage(ledger: AdoptionLedger, op: string, env?: {
32
+ GATEWAY_SHADOW?: string;
33
+ }): AdoptionStage;
34
+ export interface Divergence {
35
+ op: string;
36
+ at: string;
37
+ /** `equal` is recorded too. A shadow that only reports differences cannot distinguish "the planes agree"
38
+ * from "the shadow never ran", and those justify opposite decisions about promoting. */
39
+ kind: 'equal' | 'value' | 'raced' | 'gateway-error' | 'shadow-skipped';
40
+ detail?: string;
41
+ }
42
+ /**
43
+ * Compare the two planes' answers. Canonical JSON with sorted keys: key ORDER is not a difference worth
44
+ * blocking a promotion on. Everything else counts, including `undefined` vs absent — a field that vanished
45
+ * is exactly the data-shape drift being watched for.
46
+ */
47
+ export declare function compareServed(op: string, served: unknown, shadow: unknown, at: string): Divergence;
48
+ /**
49
+ * Was an apparent divergence just the document moving between the two reads? After the shadow read, take
50
+ * the serving read AGAIN: `servedAgain` matches the shadow ⇒ the document moved — RACED, benign, evidence of
51
+ * nothing; `servedAgain` still matches the original and differs from the shadow ⇒ the planes really
52
+ * disagree. DELIBERATELY CONSERVATIVE: anything not provably a race stays `value`.
53
+ */
54
+ export declare function classifyDivergence(input: {
55
+ op: string;
56
+ served: unknown;
57
+ shadow: unknown;
58
+ servedAgain: unknown;
59
+ at: string;
60
+ }): Divergence;
61
+ /**
62
+ * Should this call run its shadow? Shadowing doubles the delegated reads on the path being shadowed, so it
63
+ * is SAMPLED: at most one shadow per principal per `intervalMs`. Time-based rather than 1-in-N because it
64
+ * bounds the ADDED LOAD directly; a ratio bounds a proportion of a rate that varies with how many clients a
65
+ * principal has open. The first call always samples — a low-traffic principal must still contribute.
66
+ */
67
+ export declare function shouldShadow(input: {
68
+ lastShadowAt: number | undefined;
69
+ now: number;
70
+ intervalMs: number;
71
+ }): boolean;
72
+ /** One shadow per principal per minute: enough to catch a systematic difference within a session, far below
73
+ * a per-principal read limiter's budget even with several clients open. */
74
+ export declare const SHADOW_INTERVAL_MS = 60000;
75
+ /** How many divergences to keep — in MEMORY, not durable storage: diagnostics with a shelf life of one
76
+ * investigation. Losing them on isolate recycle is the correct trade. */
77
+ export declare const DIVERGENCE_BUFFER = 20;
78
+ export declare function recordDivergence(buf: Divergence[], d: Divergence): Divergence[];
79
+ //# sourceMappingURL=adoption.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"adoption.d.ts","sourceRoot":"","sources":["../src/adoption.ts"],"names":[],"mappings":"AAiBA;iGACiG;AACjG,MAAM,MAAM,eAAe,GAAG,MAAM,GAAG,YAAY,GAAG,UAAU,GAAG,YAAY,GAAG,WAAW,CAAC;AAE9F,MAAM,MAAM,aAAa,GAAG,KAAK,GAAG,QAAQ,GAAG,SAAS,CAAC;AAEzD,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,aAAa,CAAC;IACrB,mFAAmF;IACnF,QAAQ,EAAE,SAAS,eAAe,EAAE,CAAC;IACrC;yEACqE;IACrE,IAAI,EAAE,MAAM,CAAC;IACb;;;;;OAKG;IACH,cAAc,EAAE,SAAS,MAAM,EAAE,CAAC;CACnC;AAED;;gFAEgF;AAChF,MAAM,MAAM,cAAc,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC,CAAC;AAErE;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,cAAc,EAAE,EAAE,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE;IAAE,cAAc,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,aAAa,CAIlH;AAID,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,EAAE,EAAE,MAAM,CAAC;IACX;6FACyF;IACzF,IAAI,EAAE,OAAO,GAAG,OAAO,GAAG,OAAO,GAAG,eAAe,GAAG,gBAAgB,CAAC;IACvE,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,EAAE,EAAE,MAAM,GAAG,UAAU,CAKlG;AAoBD;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,OAAO,CAAC;IAAC,WAAW,EAAE,OAAO,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE,GAAG,UAAU,CAQxI;AAID;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE;IAAE,YAAY,EAAE,MAAM,GAAG,SAAS,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,GAAG,OAAO,CAGlH;AAED;4EAC4E;AAC5E,eAAO,MAAM,kBAAkB,QAAS,CAAC;AAEzC;0EAC0E;AAC1E,eAAO,MAAM,iBAAiB,KAAK,CAAC;AAEpC,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,UAAU,EAAE,EAAE,CAAC,EAAE,UAAU,GAAG,UAAU,EAAE,CAG/E"}
@@ -0,0 +1,95 @@
1
+ // INCREMENTAL SERVING-PLANE ADOPTION — the LADDER (ADR-0060; ADR-0055 amendment). Promoted from the runtime
2
+ // app in spec 399 W0 (§4): the ledger TYPES, the rung rule and the divergence/sampling mechanics are the
3
+ // contract every product adopting `PrincipalGatewayDO` needs; the ledger VALUES (which op sits on which rung,
4
+ // and why) stay with the product that owns those ops.
5
+ //
6
+ // THE LADDER. Every op climbs the same three rungs, and never skips one:
7
+ // `off` — the incumbent plane serves. The gateway is not consulted.
8
+ // `shadow` — the incumbent serves AND ITS ANSWER IS THE RESPONSE. The gateway runs alongside and the
9
+ // two are compared. Divergence is recorded; it never reaches the caller and never fails the
10
+ // request. This is the rung that converts "we think it matches" into evidence.
11
+ // `serving` — the gateway serves. Promotion is earned by a shadow run with no unexplained divergence,
12
+ // not by the code being finished.
13
+ //
14
+ // WHAT A RUNG DOES NOT DO. Shadowing proves the two planes AGREE; it does not prove either is right. An op
15
+ // whose incumbent implementation is wrong will shadow perfectly clean while the gateway faithfully
16
+ // reproduces the bug. Promotion is evidence of equivalence, not of correctness.
17
+ /**
18
+ * The stage an op runs at IN THIS DEPLOYMENT. The ledger says how far an op has climbed in the code; the
19
+ * deployment's `GATEWAY_SHADOW` says whether it is running the comparison at all. Both are required, and the
20
+ * env one defaults to OFF — constructing the gateway creates its tables, and an ungated shadow on a hot op
21
+ * would make that storage decision for every principal at once. Empty string is unset, not enabled
22
+ * (`wrangler`'s `VAR = ""` binds an empty string that `??` sails straight past).
23
+ */
24
+ export function adoptionStage(ledger, op, env) {
25
+ const stage = ledger[op]?.stage ?? 'off';
26
+ if (stage === 'shadow' && (env?.GATEWAY_SHADOW ?? '').trim().toLowerCase() !== 'on')
27
+ return 'off';
28
+ return stage;
29
+ }
30
+ /**
31
+ * Compare the two planes' answers. Canonical JSON with sorted keys: key ORDER is not a difference worth
32
+ * blocking a promotion on. Everything else counts, including `undefined` vs absent — a field that vanished
33
+ * is exactly the data-shape drift being watched for.
34
+ */
35
+ export function compareServed(op, served, shadow, at) {
36
+ const a = canonical(served);
37
+ const b = canonical(shadow);
38
+ if (a === b)
39
+ return { op, at, kind: 'equal' };
40
+ return { op, at, kind: 'value', detail: firstDifference(a, b) };
41
+ }
42
+ function canonical(v) {
43
+ return JSON.stringify(v, (_k, val) => val && typeof val === 'object' && !Array.isArray(val)
44
+ ? Object.fromEntries(Object.entries(val).sort(([x], [y]) => x.localeCompare(y)))
45
+ : val) ?? 'undefined';
46
+ }
47
+ /** A bounded excerpt around the first difference. Bounded because a divergence report that embeds two whole
48
+ * documents is a copy of the person's records in a diagnostic buffer — the thing the vault doctrine exists
49
+ * to prevent, arrived at through the back door. */
50
+ function firstDifference(a, b) {
51
+ let i = 0;
52
+ while (i < a.length && i < b.length && a[i] === b[i])
53
+ i++;
54
+ const w = 40;
55
+ return `@${i}: served=${JSON.stringify(a.slice(Math.max(0, i - 8), i + w))} shadow=${JSON.stringify(b.slice(Math.max(0, i - 8), i + w))}`;
56
+ }
57
+ /**
58
+ * Was an apparent divergence just the document moving between the two reads? After the shadow read, take
59
+ * the serving read AGAIN: `servedAgain` matches the shadow ⇒ the document moved — RACED, benign, evidence of
60
+ * nothing; `servedAgain` still matches the original and differs from the shadow ⇒ the planes really
61
+ * disagree. DELIBERATELY CONSERVATIVE: anything not provably a race stays `value`.
62
+ */
63
+ export function classifyDivergence(input) {
64
+ const first = compareServed(input.op, input.served, input.shadow, input.at);
65
+ if (first.kind === 'equal')
66
+ return first;
67
+ const settled = compareServed(input.op, input.servedAgain, input.shadow, input.at);
68
+ if (settled.kind === 'equal') {
69
+ return { op: input.op, at: input.at, kind: 'raced', detail: 'document changed between the serving read and the shadow read' };
70
+ }
71
+ return first;
72
+ }
73
+ // ── Sampling ──────────────────────────────────────────────────────────────────────────────────────────
74
+ /**
75
+ * Should this call run its shadow? Shadowing doubles the delegated reads on the path being shadowed, so it
76
+ * is SAMPLED: at most one shadow per principal per `intervalMs`. Time-based rather than 1-in-N because it
77
+ * bounds the ADDED LOAD directly; a ratio bounds a proportion of a rate that varies with how many clients a
78
+ * principal has open. The first call always samples — a low-traffic principal must still contribute.
79
+ */
80
+ export function shouldShadow(input) {
81
+ if (input.lastShadowAt === undefined)
82
+ return true;
83
+ return input.now - input.lastShadowAt >= input.intervalMs;
84
+ }
85
+ /** One shadow per principal per minute: enough to catch a systematic difference within a session, far below
86
+ * a per-principal read limiter's budget even with several clients open. */
87
+ export const SHADOW_INTERVAL_MS = 60_000;
88
+ /** How many divergences to keep — in MEMORY, not durable storage: diagnostics with a shelf life of one
89
+ * investigation. Losing them on isolate recycle is the correct trade. */
90
+ export const DIVERGENCE_BUFFER = 20;
91
+ export function recordDivergence(buf, d) {
92
+ buf.push(d);
93
+ return buf.length > DIVERGENCE_BUFFER ? buf.slice(-DIVERGENCE_BUFFER) : buf;
94
+ }
95
+ //# sourceMappingURL=adoption.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"adoption.js","sourceRoot":"","sources":["../src/adoption.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,95 @@
1
+ import type { CanonicalAgentId } from '@agenticprimitives/types';
2
+ import type { CommitmentRecordV1, CommitmentTransition, DecisionRecordV1, DecisionTransition, MachineRejection } from './interaction-machines.js';
3
+ import type { CaseFsmFields, CaseTransition, CaseTransitionEventV1 } from './case-machine.js';
4
+ /**
5
+ * A record this emission needs to exist before its transition can land, given in its INITIAL state.
6
+ *
7
+ * Both machines refuse a transition out of a state the record is not in — `grant` is `pending →
8
+ * granted`, `void` is `proposed|active → void`. So an emission that only carried a transition would be
9
+ * unapplicable on the first move of a case that never had a decision record. The applier creates the
10
+ * record if absent, in exactly this state, and then applies the transition through the machine. It
11
+ * does NOT create it in the resulting state; that would be the state-assignment bypass this module
12
+ * exists to avoid.
13
+ */
14
+ export interface DecisionSeedV1 {
15
+ decisionId: string;
16
+ decider: CanonicalAgentId;
17
+ subject: string;
18
+ expiresAt?: string;
19
+ }
20
+ export interface CommitmentSeedV1 {
21
+ commitmentId: string;
22
+ debtor: CanonicalAgentId;
23
+ creditor: CanonicalAgentId;
24
+ }
25
+ export interface CaseEmissionsV1 {
26
+ decision?: {
27
+ seed: DecisionSeedV1;
28
+ transition: DecisionTransition;
29
+ createIfAbsent: boolean;
30
+ };
31
+ commitment?: {
32
+ seed: CommitmentSeedV1;
33
+ transition: CommitmentTransition;
34
+ createIfAbsent: boolean;
35
+ };
36
+ }
37
+ /**
38
+ * Deterministic ids, derived from the case id.
39
+ *
40
+ * Deliberately not random and not clock-derived: a case transition may be replayed (the interaction
41
+ * store is idempotent on `idempotencyKey`, and `processDue` re-runs sweeps), and a fresh id per replay
42
+ * would fork one determination into several. Same case ⇒ same decision ⇒ the second application is a
43
+ * no-op against a terminal state rather than a duplicate record.
44
+ */
45
+ export declare function decisionIdForCase(caseId: string): string;
46
+ export declare function commitmentIdForCase(caseId: string): string;
47
+ /**
48
+ * Derive what a case transition emits. Pure, and total over `CaseTransition` — a transition with no
49
+ * emission returns `{}` rather than throwing, because most transitions genuinely emit nothing.
50
+ *
51
+ * Call this only for a transition the case machine ACCEPTED. It does not re-check legality: duplicating
52
+ * the case machine's role and ordering rules here would create a second, quietly diverging copy of them.
53
+ */
54
+ export declare function caseTransitionEmissions(current: CaseFsmFields, event: CaseTransitionEventV1): CaseEmissionsV1;
55
+ /** The case transitions that emit something. Exported so a test can assert the set rather than
56
+ * restate it, and so a newly-added emitting transition shows up in a diff of this constant. */
57
+ export declare const EMITTING_CASE_TRANSITIONS: readonly CaseTransition[];
58
+ /** Seed a decision record in its INITIAL state. Never the resulting state — see `DecisionSeedV1`. */
59
+ export declare function seedDecision(seed: DecisionSeedV1, at: string): DecisionRecordV1;
60
+ /** Seed a commitment record in its INITIAL state. */
61
+ export declare function seedCommitment(seed: CommitmentSeedV1, at: string): CommitmentRecordV1;
62
+ /** Why an emission produced no record. `absent` is the deliberate `void`-without-a-commitment case;
63
+ * the others are the owning machine's own typed rejection, surfaced rather than swallowed. */
64
+ export type EmissionOutcome<T> = {
65
+ applied: true;
66
+ record: T;
67
+ } | {
68
+ applied: false;
69
+ reason: MachineRejection | 'absent';
70
+ };
71
+ export interface CaseEmissionApplyResult {
72
+ decision?: EmissionOutcome<DecisionRecordV1>;
73
+ commitment?: EmissionOutcome<CommitmentRecordV1>;
74
+ }
75
+ /**
76
+ * Apply emissions through the OWNING machines.
77
+ *
78
+ * The point of this function is what it does not do: it never assigns a state. A record is created
79
+ * only in its initial state and every move goes through `applyDecisionTransition` /
80
+ * `applyCommitmentTransition`, so their role and ordering checks still hold — the creditor still
81
+ * voids, the decider still decides.
82
+ *
83
+ * Rejections are RETURNED, not thrown and not swallowed. A caller that persists these must treat a
84
+ * rejected emission as a fact about the record it failed to write, because a silent no-op here is
85
+ * indistinguishable from success and would leave a case whose determination was never recorded
86
+ * looking exactly like one whose determination was.
87
+ */
88
+ export declare function applyCaseEmissions(emissions: CaseEmissionsV1, existing: {
89
+ decision: DecisionRecordV1 | null;
90
+ commitment: CommitmentRecordV1 | null;
91
+ }, event: {
92
+ actor: CanonicalAgentId;
93
+ at: string;
94
+ }): CaseEmissionApplyResult;
95
+ //# sourceMappingURL=case-emissions.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"case-emissions.d.ts","sourceRoot":"","sources":["../src/case-emissions.ts"],"names":[],"mappings":"AAqBA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAEjE,OAAO,KAAK,EACV,kBAAkB,EAClB,oBAAoB,EACpB,gBAAgB,EAChB,kBAAkB,EAClB,gBAAgB,EAEjB,MAAM,2BAA2B,CAAC;AACnC,OAAO,KAAK,EAAE,aAAa,EAAE,cAAc,EAAE,qBAAqB,EAAE,MAAM,mBAAmB,CAAC;AAE9F;;;;;;;;;GASG;AACH,MAAM,WAAW,cAAc;IAC7B,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,gBAAgB,CAAC;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,gBAAgB;IAC/B,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,gBAAgB,CAAC;IACzB,QAAQ,EAAE,gBAAgB,CAAC;CAC5B;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,EAAE;QAAE,IAAI,EAAE,cAAc,CAAC;QAAC,UAAU,EAAE,kBAAkB,CAAC;QAAC,cAAc,EAAE,OAAO,CAAA;KAAE,CAAC;IAC7F,UAAU,CAAC,EAAE;QAAE,IAAI,EAAE,gBAAgB,CAAC;QAAC,UAAU,EAAE,oBAAoB,CAAC;QAAC,cAAc,EAAE,OAAO,CAAA;KAAE,CAAC;CACpG;AAED;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAExD;AAED,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAE1D;AAqGD;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CACrC,OAAO,EAAE,aAAa,EACtB,KAAK,EAAE,qBAAqB,GAC3B,eAAe,CAmBjB;AAED;gGACgG;AAChG,eAAO,MAAM,yBAAyB,EAAE,SAAS,cAAc,EAE1C,CAAC;AAEtB,qGAAqG;AACrG,wBAAgB,YAAY,CAAC,IAAI,EAAE,cAAc,EAAE,EAAE,EAAE,MAAM,GAAG,gBAAgB,CAS/E;AAED,qDAAqD;AACrD,wBAAgB,cAAc,CAAC,IAAI,EAAE,gBAAgB,EAAE,EAAE,EAAE,MAAM,GAAG,kBAAkB,CAQrF;AAED;+FAC+F;AAC/F,MAAM,MAAM,eAAe,CAAC,CAAC,IACzB;IAAE,OAAO,EAAE,IAAI,CAAC;IAAC,MAAM,EAAE,CAAC,CAAA;CAAE,GAC5B;IAAE,OAAO,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,gBAAgB,GAAG,QAAQ,CAAA;CAAE,CAAC;AAE5D,MAAM,WAAW,uBAAuB;IACtC,QAAQ,CAAC,EAAE,eAAe,CAAC,gBAAgB,CAAC,CAAC;IAC7C,UAAU,CAAC,EAAE,eAAe,CAAC,kBAAkB,CAAC,CAAC;CAClD;AAeD;;;;;;;;;;;;GAYG;AACH,wBAAgB,kBAAkB,CAChC,SAAS,EAAE,eAAe,EAC1B,QAAQ,EAAE;IAAE,QAAQ,EAAE,gBAAgB,GAAG,IAAI,CAAC;IAAC,UAAU,EAAE,kBAAkB,GAAG,IAAI,CAAA;CAAE,EACtF,KAAK,EAAE;IAAE,KAAK,EAAE,gBAAgB,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE,GAC7C,uBAAuB,CAsBzB"}
@@ -0,0 +1,208 @@
1
+ // spec 340 W10 — what a case transition EMITS into the decision and commitment machines.
2
+ //
3
+ // §R.2 established that the eleven `CaseState`s were never one machine: `approved`/`denied`/`expired`
4
+ // are **decisions**, `revoked` is what happens to a **commitment**, and the rest are **execution**. One
5
+ // field was answering three questions with three different signers, which is why a case could be
6
+ // `denied` while a commitment stayed outstanding with nowhere to record it.
7
+ //
8
+ // §R.3 names the order this has to happen in: the case flow EMITS decision and commitment records
9
+ // FIRST, and only then can `CaseState` shrink to execution. Deleting the enum before this module
10
+ // existed would have replaced working behaviour with machines nothing writes to — a regression wearing
11
+ // the costume of a cleanup.
12
+ //
13
+ // WHAT THIS MODULE IS NOT. It derives records; it does not move state. Every emission is expressed as a
14
+ // TRANSITION to be applied by the owning machine, never as a state assignment, so the machines keep
15
+ // their role checks — the creditor still discharges, the decider still decides, and persistence does
16
+ // not become a bypass (ADR-0058 §15, and the same rule `createDurableObjectInteractionStore` follows).
17
+ //
18
+ // NONE OF THIS IS AUTHORITY. A `grant` here records that a determination was made. It permits nothing:
19
+ // the authority re-check happens at the moment of acting regardless of what any of these records say
20
+ // (ADR-0041, ADR-0043).
21
+ import { applyCommitmentTransition, applyDecisionTransition } from './interaction-machines.js';
22
+ /**
23
+ * Deterministic ids, derived from the case id.
24
+ *
25
+ * Deliberately not random and not clock-derived: a case transition may be replayed (the interaction
26
+ * store is idempotent on `idempotencyKey`, and `processDue` re-runs sweeps), and a fresh id per replay
27
+ * would fork one determination into several. Same case ⇒ same decision ⇒ the second application is a
28
+ * no-op against a terminal state rather than a duplicate record.
29
+ */
30
+ export function decisionIdForCase(caseId) {
31
+ return `dec_${caseId}`;
32
+ }
33
+ export function commitmentIdForCase(caseId) {
34
+ return `cmt_${caseId}`;
35
+ }
36
+ /**
37
+ * WHO IS THE CREDITOR. The approving side — `responder` / `resource-owner`.
38
+ *
39
+ * This is a derivation, not a preference. Case `revoke` is signed by `responder`/`resource-owner`, and
40
+ * the only creditor-only commitment transition is `void`. Under the opposite assignment (debtor =
41
+ * responder, creditor = requester) an issuer revoking what it granted has no legal transition at all,
42
+ * which contradicts a case machine that plainly permits it. One reading is self-consistent; the other
43
+ * is not. `issue-credential` corroborates it: its signers are `resource-owner`/`responder` and
44
+ * `activate` is likewise creditor-only.
45
+ */
46
+ function creditorOf(c) {
47
+ return c.resourceOwner ?? c.responder;
48
+ }
49
+ /**
50
+ * WHO IS THE DECIDER — and why it is not one rule.
51
+ *
52
+ * For `approve`/`deny` it is `event.actor`: the party that actually made the determination. It cannot
53
+ * be the deciding ROLE, because the case machine accepts EITHER `responder` or `resource-owner` for
54
+ * those moves while the decision machine requires exactly `decision.decider`. Pinning the decider to
55
+ * the role would make a responder-signed approval on a case that also has a resource-owner emit a
56
+ * decision the decision machine then rejects as `unauthorized-actor` — a case that succeeded and an
57
+ * emission that silently could not land.
58
+ *
59
+ * For `expire` it is the creditor. `expire` is signed by role `any` (time is not an actor, so a
60
+ * deterministic sweep may record it), and attributing an expiry to whichever party happened to run the
61
+ * sweep would put a determination in someone's name they never made. The decision machine skips the
62
+ * actor check for `expire` precisely so this is possible.
63
+ */
64
+ function decisionSeed(c, event, transition) {
65
+ return {
66
+ decisionId: decisionIdForCase(c.id),
67
+ decider: transition === 'expire' ? creditorOf(c) : event.actor,
68
+ subject: c.id,
69
+ expiresAt: c.expiresAt,
70
+ };
71
+ }
72
+ function commitmentSeed(c) {
73
+ return {
74
+ commitmentId: commitmentIdForCase(c.id),
75
+ debtor: c.requester,
76
+ creditor: creditorOf(c),
77
+ };
78
+ }
79
+ /**
80
+ * The mapping, stated once.
81
+ *
82
+ * `deliver-credential` is deliberately ABSENT. It completes the case, and completing the work does not
83
+ * discharge the obligation — that is exactly the collapse ADR-0058 §15 forbids, and `fabric` already
84
+ * refuses to draw that edge implicitly (`mayDischargeOnExecution` exists so a profile has to choose it
85
+ * in a diff). A creditor who wants to discharge does so as its own act, against the commitment
86
+ * machine, with its own signature.
87
+ *
88
+ * `fail` is absent for the same reason in the other direction: a failed execution is not a refusal by
89
+ * anyone, and recording it as one would attribute a determination nobody made.
90
+ */
91
+ const EMITTING_TRANSITIONS = {
92
+ approve: { decision: 'grant' },
93
+ deny: { decision: 'refuse' },
94
+ expire: { decision: 'expire' },
95
+ // Issuing is what creates the obligation, so this emission may bring the commitment into being.
96
+ 'issue-credential': { commitment: 'activate' },
97
+ revoke: { commitment: 'void' },
98
+ };
99
+ /**
100
+ * Whether an emission may bring its record into existence.
101
+ *
102
+ * Deciding CREATES a determination — there was no prior record and the act of approving or denying is
103
+ * what makes one. Issuing likewise creates the obligation.
104
+ *
105
+ * `void` must not. `revoke` is legal from any non-terminal case, including `submitted`, where nothing
106
+ * was ever approved and no obligation ever arose. Seeding a commitment there so it could immediately
107
+ * be voided would write a record asserting an obligation existed and was released, when neither
108
+ * happened — inventing history to satisfy a type. Absent a commitment, this emission is a no-op, and
109
+ * the case's own `revoked` state remains the honest record of what occurred.
110
+ */
111
+ const CREATES_ITS_RECORD = {
112
+ grant: true,
113
+ refuse: true,
114
+ expire: true,
115
+ activate: true,
116
+ withdraw: false,
117
+ supersede: false,
118
+ 'claim-fulfilment': false,
119
+ 'dispute-claim': false,
120
+ discharge: false,
121
+ 'assert-breach': false,
122
+ void: false,
123
+ };
124
+ /**
125
+ * Derive what a case transition emits. Pure, and total over `CaseTransition` — a transition with no
126
+ * emission returns `{}` rather than throwing, because most transitions genuinely emit nothing.
127
+ *
128
+ * Call this only for a transition the case machine ACCEPTED. It does not re-check legality: duplicating
129
+ * the case machine's role and ordering rules here would create a second, quietly diverging copy of them.
130
+ */
131
+ export function caseTransitionEmissions(current, event) {
132
+ const spec = EMITTING_TRANSITIONS[event.transition];
133
+ if (!spec)
134
+ return {};
135
+ const out = {};
136
+ if (spec.decision) {
137
+ out.decision = {
138
+ seed: decisionSeed(current, event, spec.decision),
139
+ transition: spec.decision,
140
+ createIfAbsent: CREATES_ITS_RECORD[spec.decision],
141
+ };
142
+ }
143
+ if (spec.commitment) {
144
+ out.commitment = {
145
+ seed: commitmentSeed(current),
146
+ transition: spec.commitment,
147
+ createIfAbsent: CREATES_ITS_RECORD[spec.commitment],
148
+ };
149
+ }
150
+ return out;
151
+ }
152
+ /** The case transitions that emit something. Exported so a test can assert the set rather than
153
+ * restate it, and so a newly-added emitting transition shows up in a diff of this constant. */
154
+ export const EMITTING_CASE_TRANSITIONS = Object.keys(EMITTING_TRANSITIONS);
155
+ /** Seed a decision record in its INITIAL state. Never the resulting state — see `DecisionSeedV1`. */
156
+ export function seedDecision(seed, at) {
157
+ return {
158
+ decisionId: seed.decisionId,
159
+ state: 'pending',
160
+ decider: seed.decider,
161
+ subject: seed.subject,
162
+ expiresAt: seed.expiresAt,
163
+ updatedAt: at,
164
+ };
165
+ }
166
+ /** Seed a commitment record in its INITIAL state. */
167
+ export function seedCommitment(seed, at) {
168
+ return {
169
+ commitmentId: seed.commitmentId,
170
+ state: 'proposed',
171
+ debtor: seed.debtor,
172
+ creditor: seed.creditor,
173
+ updatedAt: at,
174
+ };
175
+ }
176
+ function applyOne(emission, existing, create, apply) {
177
+ if (!emission)
178
+ return undefined;
179
+ const record = existing ?? (emission.createIfAbsent ? create() : null);
180
+ if (record === null)
181
+ return { applied: false, reason: 'absent' };
182
+ const res = apply(record, emission.transition);
183
+ return res.ok ? { applied: true, record: res.next } : { applied: false, reason: res.reason };
184
+ }
185
+ /**
186
+ * Apply emissions through the OWNING machines.
187
+ *
188
+ * The point of this function is what it does not do: it never assigns a state. A record is created
189
+ * only in its initial state and every move goes through `applyDecisionTransition` /
190
+ * `applyCommitmentTransition`, so their role and ordering checks still hold — the creditor still
191
+ * voids, the decider still decides.
192
+ *
193
+ * Rejections are RETURNED, not thrown and not swallowed. A caller that persists these must treat a
194
+ * rejected emission as a fact about the record it failed to write, because a silent no-op here is
195
+ * indistinguishable from success and would leave a case whose determination was never recorded
196
+ * looking exactly like one whose determination was.
197
+ */
198
+ export function applyCaseEmissions(emissions, existing, event) {
199
+ const out = {};
200
+ const decision = applyOne(emissions.decision, existing.decision, () => seedDecision(emissions.decision.seed, event.at), (record, transition) => applyDecisionTransition(record, { transition, actor: event.actor, at: event.at }));
201
+ if (decision)
202
+ out.decision = decision;
203
+ const commitment = applyOne(emissions.commitment, existing.commitment, () => seedCommitment(emissions.commitment.seed, event.at), (record, transition) => applyCommitmentTransition(record, { transition, actor: event.actor, at: event.at }));
204
+ if (commitment)
205
+ out.commitment = commitment;
206
+ return out;
207
+ }
208
+ //# sourceMappingURL=case-emissions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"case-emissions.js","sourceRoot":"","sources":["../src/case-emissions.ts"],"names":[],"mappings":"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"}