@volter/world-core 2.0.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.
Files changed (180) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +29 -0
  3. package/app-route.cjs +154 -0
  4. package/app-route.d.cts +7 -0
  5. package/attach.cjs +80 -0
  6. package/dist/app-route.cjs +154 -0
  7. package/dist/app-route.d.cts +7 -0
  8. package/dist/attach.cjs +80 -0
  9. package/dist/generated/pack-facts.json +4306 -0
  10. package/dist/inject.cjs +1097 -0
  11. package/dist/network-policy.cjs +92 -0
  12. package/dist/network-policy.d.cts +10 -0
  13. package/dist/src/actions.d.ts +276 -0
  14. package/dist/src/actions.js +436 -0
  15. package/dist/src/ancestry.d.ts +22 -0
  16. package/dist/src/ancestry.js +238 -0
  17. package/dist/src/args.d.ts +3 -0
  18. package/dist/src/args.js +12 -0
  19. package/dist/src/blob-store.d.ts +55 -0
  20. package/dist/src/blob-store.js +186 -0
  21. package/dist/src/brand-tokens.d.ts +2 -0
  22. package/dist/src/brand-tokens.js +17 -0
  23. package/dist/src/changeset.d.ts +431 -0
  24. package/dist/src/changeset.js +0 -0
  25. package/dist/src/client-bundle.d.ts +1 -0
  26. package/dist/src/client-bundle.js +28 -0
  27. package/dist/src/credential.d.ts +38 -0
  28. package/dist/src/credential.js +114 -0
  29. package/dist/src/derived-core.d.ts +452 -0
  30. package/dist/src/derived-core.js +782 -0
  31. package/dist/src/derived.d.ts +84 -0
  32. package/dist/src/derived.js +122 -0
  33. package/dist/src/emit.d.ts +106 -0
  34. package/dist/src/emit.js +157 -0
  35. package/dist/src/executor.d.ts +120 -0
  36. package/dist/src/executor.js +387 -0
  37. package/dist/src/file-response.d.ts +3 -0
  38. package/dist/src/file-response.js +22 -0
  39. package/dist/src/fork.d.ts +26 -0
  40. package/dist/src/fork.js +68 -0
  41. package/dist/src/git/history.d.ts +36 -0
  42. package/dist/src/git/history.js +298 -0
  43. package/dist/src/git/index.d.ts +6 -0
  44. package/dist/src/git/index.js +6 -0
  45. package/dist/src/git/inflate.d.ts +11 -0
  46. package/dist/src/git/inflate.js +194 -0
  47. package/dist/src/git/objects.d.ts +64 -0
  48. package/dist/src/git/objects.js +161 -0
  49. package/dist/src/git/pack.d.ts +14 -0
  50. package/dist/src/git/pack.js +199 -0
  51. package/dist/src/git/refs.d.ts +19 -0
  52. package/dist/src/git/refs.js +35 -0
  53. package/dist/src/git/smart-http.d.ts +45 -0
  54. package/dist/src/git/smart-http.js +223 -0
  55. package/dist/src/hash.d.ts +38 -0
  56. package/dist/src/hash.js +48 -0
  57. package/dist/src/head.d.ts +140 -0
  58. package/dist/src/head.js +313 -0
  59. package/dist/src/history.d.ts +76 -0
  60. package/dist/src/history.js +322 -0
  61. package/dist/src/index.d.ts +73 -0
  62. package/dist/src/index.js +98 -0
  63. package/dist/src/lifecycle.d.ts +1 -0
  64. package/dist/src/lifecycle.js +8 -0
  65. package/dist/src/log.d.ts +254 -0
  66. package/dist/src/log.js +801 -0
  67. package/dist/src/mirror-shell.d.ts +2 -0
  68. package/dist/src/mirror-shell.js +13 -0
  69. package/dist/src/observe.d.ts +49 -0
  70. package/dist/src/observe.js +148 -0
  71. package/dist/src/pack-assets.d.ts +30 -0
  72. package/dist/src/pack-assets.js +88 -0
  73. package/dist/src/packRegistry.d.ts +374 -0
  74. package/dist/src/packRegistry.js +142 -0
  75. package/dist/src/placeholder-remote.d.ts +22 -0
  76. package/dist/src/placeholder-remote.js +86 -0
  77. package/dist/src/proxy.d.ts +25 -0
  78. package/dist/src/proxy.js +155 -0
  79. package/dist/src/rateBudget.d.ts +367 -0
  80. package/dist/src/rateBudget.js +925 -0
  81. package/dist/src/references.d.ts +18 -0
  82. package/dist/src/references.js +27 -0
  83. package/dist/src/remote-execute.d.ts +22 -0
  84. package/dist/src/remote-execute.js +1 -0
  85. package/dist/src/resource-blob.d.ts +10 -0
  86. package/dist/src/resource-blob.js +56 -0
  87. package/dist/src/scenario.d.ts +197 -0
  88. package/dist/src/scenario.js +425 -0
  89. package/dist/src/schemas.d.ts +78 -0
  90. package/dist/src/schemas.js +50 -0
  91. package/dist/src/serve-http.d.ts +48 -0
  92. package/dist/src/serve-http.js +340 -0
  93. package/dist/src/serve.d.ts +147 -0
  94. package/dist/src/serve.js +507 -0
  95. package/dist/src/shared-blob-index.d.ts +4 -0
  96. package/dist/src/shared-blob-index.js +126 -0
  97. package/dist/src/state-system.d.ts +70 -0
  98. package/dist/src/state-system.js +90 -0
  99. package/dist/src/storage.d.ts +101 -0
  100. package/dist/src/storage.js +337 -0
  101. package/dist/src/twin-fetch.d.ts +64 -0
  102. package/dist/src/twin-fetch.js +91 -0
  103. package/dist/src/types.d.ts +40 -0
  104. package/dist/src/types.js +1 -0
  105. package/dist/src/v1-removed.d.ts +159 -0
  106. package/dist/src/v1-removed.js +124 -0
  107. package/dist/src/volter-home.d.ts +5 -0
  108. package/dist/src/volter-home.js +10 -0
  109. package/dist/src/world-clock.d.ts +4 -0
  110. package/dist/src/world-clock.js +32 -0
  111. package/dist/src/world-env.d.ts +3 -0
  112. package/dist/src/world-env.js +22 -0
  113. package/dist/src/world-store-sql.d.ts +27 -0
  114. package/dist/src/world-store-sql.js +86 -0
  115. package/dist/src/world-store.d.ts +168 -0
  116. package/dist/src/world-store.js +475 -0
  117. package/dist/src/worldConfig.d.ts +9 -0
  118. package/dist/src/worldConfig.js +17 -0
  119. package/dist/stream-bridge.cjs +80 -0
  120. package/dist/vendor-hosts.cjs +200 -0
  121. package/generated/pack-facts.json +4306 -0
  122. package/inject.cjs +1097 -0
  123. package/network-policy.cjs +92 -0
  124. package/network-policy.d.cts +10 -0
  125. package/package.json +103 -0
  126. package/src/actions.ts +564 -0
  127. package/src/ancestry.ts +213 -0
  128. package/src/args.ts +14 -0
  129. package/src/blob-store.ts +185 -0
  130. package/src/brand-tokens.ts +17 -0
  131. package/src/changeset.ts +1032 -0
  132. package/src/client-bundle.ts +29 -0
  133. package/src/credential.ts +140 -0
  134. package/src/derived-core.ts +1004 -0
  135. package/src/derived.ts +176 -0
  136. package/src/emit.ts +242 -0
  137. package/src/executor.ts +431 -0
  138. package/src/file-response.ts +22 -0
  139. package/src/fork.ts +89 -0
  140. package/src/git/history.ts +177 -0
  141. package/src/git/index.ts +6 -0
  142. package/src/git/inflate.ts +125 -0
  143. package/src/git/objects.ts +110 -0
  144. package/src/git/pack.ts +105 -0
  145. package/src/git/refs.ts +25 -0
  146. package/src/git/smart-http.ts +149 -0
  147. package/src/hash.ts +66 -0
  148. package/src/head.ts +318 -0
  149. package/src/history.ts +246 -0
  150. package/src/index.ts +323 -0
  151. package/src/lifecycle.ts +8 -0
  152. package/src/log.ts +793 -0
  153. package/src/mirror-shell.ts +15 -0
  154. package/src/observe.ts +130 -0
  155. package/src/pack-assets.ts +81 -0
  156. package/src/packRegistry.ts +408 -0
  157. package/src/placeholder-remote.ts +81 -0
  158. package/src/proxy.ts +183 -0
  159. package/src/rateBudget.ts +1115 -0
  160. package/src/references.ts +46 -0
  161. package/src/remote-execute.ts +26 -0
  162. package/src/resource-blob.ts +57 -0
  163. package/src/scenario.ts +479 -0
  164. package/src/schemas.ts +56 -0
  165. package/src/serve-http.ts +299 -0
  166. package/src/serve.ts +618 -0
  167. package/src/shared-blob-index.ts +108 -0
  168. package/src/state-system.ts +115 -0
  169. package/src/storage.ts +407 -0
  170. package/src/twin-fetch.ts +147 -0
  171. package/src/types.ts +50 -0
  172. package/src/v1-removed.ts +172 -0
  173. package/src/volter-home.ts +11 -0
  174. package/src/world-clock.ts +33 -0
  175. package/src/world-env.ts +18 -0
  176. package/src/world-store-sql.ts +118 -0
  177. package/src/world-store.ts +572 -0
  178. package/src/worldConfig.ts +27 -0
  179. package/stream-bridge.cjs +80 -0
  180. package/vendor-hosts.cjs +200 -0
@@ -0,0 +1,431 @@
1
+ import { type HistoryReference } from './history.js';
2
+ import type { TwinActionRevertSpec } from './actions.js';
3
+ import type { TwinAction, TwinActionPrecondition } from './actions.js';
4
+ /** One twin's action ledger, addressed the way the control plane addresses state:
5
+ * `worldPaths(stateService, controlRoot)`. `service` is the caller's label for it (in a world:
6
+ * the world service id); `stateService` is the control-plane service whose ledger this is.
7
+ * They differ whenever a twin records under a different state name than its world service id. */
8
+ export type LedgerRef = {
9
+ /** the vendor/twin as the operator names it — what `diff` groups by and `replay` matches on */
10
+ service: string;
11
+ /** the control-plane state service whose `actions.jsonl` this is (often === service) */
12
+ stateService: string;
13
+ /** control root such that `worldPaths(stateService, controlRoot)` resolves the ledger */
14
+ controlRoot: string;
15
+ };
16
+ /** One ledger's recorded position inside a marker. `count` is the row count at capture;
17
+ * `lastActionId` is the verification anchor (see the marker note above). */
18
+ export type LedgerPosition = {
19
+ service: string;
20
+ stateService: string;
21
+ count: number;
22
+ lastActionId: string | null;
23
+ };
24
+ export declare const MARKER_KIND = "volter.world.marker.v1";
25
+ export declare const CHANGESET_KIND = "volter.world.changeset.v1";
26
+ /** The base position a diff or changeset is taken against. */
27
+ export type WorldMarker = {
28
+ kind: typeof MARKER_KIND;
29
+ id: string;
30
+ world: string;
31
+ createdAt: string;
32
+ /** Every ledger that existed at capture time. A ledger absent here is treated as position 0
33
+ * (a twin that recorded its first action AFTER the mark contributes its whole ledger). */
34
+ ledgers: LedgerPosition[];
35
+ };
36
+ /** The id reserved for the synthetic "everything this world has ever recorded" base. */
37
+ export declare const WORLD_BOOT_MARKER_ID = "world-boot";
38
+ /** An action inside a delta/changeset, bound to the twin whose ledger recorded it. The raw
39
+ * `TwinAction` is kept verbatim (provenance); `service`/`stateService` are the binding replay
40
+ * needs to route it into the right twin of another world. */
41
+ export type ChangesetAction = {
42
+ service: string;
43
+ stateService: string;
44
+ action: TwinAction;
45
+ };
46
+ /** Per-vendor rollup of a delta, in first-occurrence order — the shape `diff` renders. */
47
+ export type VendorSummary = {
48
+ service: string;
49
+ count: number;
50
+ operations: Array<{
51
+ operation: string;
52
+ count: number;
53
+ subjectIds: string[];
54
+ }>;
55
+ };
56
+ export type LedgerDelta = {
57
+ world: string;
58
+ base: WorldMarker;
59
+ /** every action after `base`, across every ledger, ordered by `occurredAt` */
60
+ actions: ChangesetAction[];
61
+ vendors: VendorSummary[];
62
+ total: number;
63
+ };
64
+ /** A verifier: one deterministic check against post-replay projected state — the CI assertion
65
+ * of the operational PR (docs/concepts/the-model.md v1). The `assert` is the kernel's ONE check
66
+ * expression (`TwinActionPrecondition`: subject/field/op/value, evaluated by
67
+ * `checkPrecondition`), the same shape write-time preconditions and plan conflicts already
68
+ * use — a verifier is that check pointed at a replay target instead of the authoring world. */
69
+ export type ChangesetVerifier = {
70
+ /** caller-chosen handle, unique within the changeset (results key on it) */
71
+ id: string;
72
+ /** the world service (twin) whose post-replay state the assert reads */
73
+ service: string;
74
+ assert: TwinActionPrecondition;
75
+ };
76
+ /** One verifier's outcome against a replay target. `assert` is repeated verbatim so the result
77
+ * is readable on its own; `actual` is what the projected state held (omitted when undefined —
78
+ * which is itself what `exists`/`not_exists` distinguish). */
79
+ export type ChangesetVerifierResult = {
80
+ id: string;
81
+ service: string;
82
+ assert: TwinActionPrecondition;
83
+ passed: boolean;
84
+ actual?: unknown;
85
+ };
86
+ /** The recorded outcome of `changeset verify` — ABOUT-the-body metadata, like approvals: it
87
+ * lives on the object but is outside `contentHash`, and each verify REPLACES the previous
88
+ * record (the provenance fields say exactly which run this is). */
89
+ export type ChangesetVerification = {
90
+ at: string;
91
+ /** where the replay landed — a world name, or 'ephemeral' for a throwaway target */
92
+ into: string;
93
+ /** the body hash at verification time; a later body edit makes this visibly stale */
94
+ contentHash: string;
95
+ /** sha256 over the post-replay projected state of every replay target — two verifies that
96
+ * produced the same world state produce the same digest (the determinism receipt) */
97
+ worldDigest: string;
98
+ passed: boolean;
99
+ results: ChangesetVerifierResult[];
100
+ /** the world's checks (state-system.ts) that refused an entry — CI on deployment, run at verify */
101
+ refusals?: Array<{
102
+ actionId: string;
103
+ service: string;
104
+ check: string;
105
+ reason: string;
106
+ }>;
107
+ };
108
+ /** One signature: who approved, when, against exactly which body hash. The hash-at-signing is
109
+ * the point — an approval is meaningless without the bytes it bound to. */
110
+ export type ChangesetApproval = {
111
+ principal: string;
112
+ at: string;
113
+ contentHash: string;
114
+ note?: string;
115
+ };
116
+ export type Changeset = {
117
+ kind: typeof CHANGESET_KIND;
118
+ id: string;
119
+ name: string;
120
+ /** the world it was authored in */
121
+ world: string;
122
+ /** the marker id it forked from */
123
+ base: string;
124
+ createdAt: string;
125
+ actions: ChangesetAction[];
126
+ /** deterministic checks that must pass on replay — part of the hashed body when present,
127
+ * so an approval also binds to WHAT was checked, not just what was done */
128
+ verifiers: ChangesetVerifier[];
129
+ /** latest verify run (replaced, never accumulated) — outside the hash, like approvals */
130
+ verification: ChangesetVerification | null;
131
+ /** who signed, against `contentHash` */
132
+ approvals: ChangesetApproval[];
133
+ /** the governed push's outcome (v2) — receipts and, on a partial apply, the compensation report; null until applied */
134
+ applied: ChangesetApplication | null;
135
+ /** v4 — the CHECKABLE NARRATION: the human summary rendered deterministically from `actions`
136
+ * at authoring (`narrateActions`). Outside the hash — it is DERIVED from the hashed actions, so
137
+ * an approval already binds to it — and re-rendered on every read: a hand-edited narration no
138
+ * longer equals its re-render, and the object is neither approvable nor applicable until
139
+ * re-authored. Absent on objects authored before v4. */
140
+ narration?: string;
141
+ /** The author's own words for the changeset (git's commit message) — what `volter changeset -m`
142
+ * records. Outside the hash, like the narration; a reviewer reads both. */
143
+ message?: string;
144
+ /** v3 — set by `rebaseChangeset`: the hash this object was rebased from, and when. Outside the hash. */
145
+ rebasedFrom?: {
146
+ contentHash: string;
147
+ at: string;
148
+ };
149
+ /** PROTOCOL 2 — where the parent log stood when this was cut, per state service (its entry count):
150
+ * a rebase reads the parent's entries since here to name what moved under the changeset. Outside
151
+ * the hash (metadata of the cut, not of the change). */
152
+ cut?: Record<string, HistoryReference>;
153
+ /** sha256 over the canonical JSON of `{id, world, base, actions}` (+ `verifiers` when any) */
154
+ contentHash: string;
155
+ };
156
+ export declare function assertSafeChangesetName(name: string, what?: string): string;
157
+ /** The "everything ever recorded in this world" base: zero ledgers, so every ledger diffs from
158
+ * position 0. Used when a world has no marks yet — `diff` must still answer, not refuse. */
159
+ export declare function worldBootMarker(world: string, createdAt: string): WorldMarker;
160
+ /** Capture the current position of every ledger — the cross-service base marker. */
161
+ export declare function captureMarker(opts: {
162
+ id: string;
163
+ world: string;
164
+ ledgers: LedgerRef[];
165
+ createdAt?: string;
166
+ }): WorldMarker;
167
+ /** A marker taken in world A says nothing about world B's ledgers — comparing them would
168
+ * produce a confident, wrong delta. Refuse. */
169
+ export declare function assertMarkerBelongsTo(marker: WorldMarker, world: string): void;
170
+ /**
171
+ * Every action appended after `base`, across `ledgers`, in `occurredAt` order.
172
+ *
173
+ * A ledger is verified against its recorded position before any of it is reported: fewer rows
174
+ * than the marker counted, or a different action id at that position, means the ledger was
175
+ * rewritten (scrub/purge/rebuild) and the marker no longer addresses anything real. That is a
176
+ * loud error — quietly re-basing on a rewritten ledger is how a diff lies.
177
+ */
178
+ export declare function diffLedgers(opts: {
179
+ world: string;
180
+ ledgers: LedgerRef[];
181
+ base: WorldMarker;
182
+ }): LedgerDelta;
183
+ /** Group a delta by vendor, then by operation — both in FIRST-OCCURRENCE order, so the rollup
184
+ * reads in the same causal order as the actions themselves. */
185
+ export declare function summarizeByVendor(actions: ChangesetAction[]): VendorSummary[];
186
+ /** The human rendering of a delta — one line per vendor plus a total. Shared by `diff` and
187
+ * `changeset show` so a frozen changeset reads exactly like the diff it was cut from. */
188
+ export declare function formatLedgerDelta(delta: LedgerDelta, opts?: {
189
+ title?: string;
190
+ }): string;
191
+ /** What `changesetContentHash` needs to see — the immutable fields, with `verifiers` optional
192
+ * so a v0 object (authored before verifiers existed) hashes exactly as it always did. */
193
+ export type ChangesetHashable = Pick<Changeset, 'id' | 'world' | 'base' | 'actions'> & Partial<Pick<Changeset, 'verifiers' | 'cut'>>;
194
+ export declare function changesetContentHash(changeset: ChangesetHashable): string;
195
+ /** A verifier that cannot be evaluated deterministically is refused at authoring time, not
196
+ * discovered at verify time. */
197
+ export declare function assertValidVerifiers(verifiers: ChangesetVerifier[]): ChangesetVerifier[];
198
+ /**
199
+ * Parse the CLI spelling of a verifier: `<service> <type>:<id> <field> <op> [<value>]`, e.g.
200
+ * `stripe price:price_annual unit_amount eq 47000`. The value is JSON when it parses as JSON
201
+ * (numbers, booleans, quoted strings) and a bare string otherwise, so `eq active` and
202
+ * `eq "active"` mean the same thing.
203
+ */
204
+ export declare function parseVerifierExpression(expression: string, id: string): ChangesetVerifier;
205
+ /** Freeze a delta into a named, content-addressed changeset. */
206
+ export declare function buildChangeset(opts: {
207
+ name: string;
208
+ world: string;
209
+ base: string;
210
+ actions: ChangesetAction[];
211
+ verifiers?: ChangesetVerifier[];
212
+ createdAt?: string;
213
+ message?: string;
214
+ cut?: Record<string, HistoryReference>;
215
+ }): Changeset;
216
+ /** Fill the lifecycle fields a changeset authored before v1 lacks on disk, without touching
217
+ * its hash (absent and empty `verifiers` hash identically by construction). */
218
+ export declare function normalizeChangeset(changeset: Changeset): Changeset;
219
+ /** Recompute and compare — a changeset whose bytes were edited after authoring is not the
220
+ * object anyone approved. Callers render this as a warning or a hard failure as suits. */
221
+ export declare function changesetHashMatches(changeset: Changeset): boolean;
222
+ /** Render a changeset exactly like the diff it was cut from. */
223
+ export declare function formatChangeset(changeset: Changeset): string;
224
+ export type ReplayTarget = {
225
+ /** the changeset service this target satisfies */
226
+ service: string;
227
+ /** the state service to write into (may differ from the source's) */
228
+ stateService: string;
229
+ controlRoot: string;
230
+ };
231
+ export type ReplayActionResult = {
232
+ service: string;
233
+ sourceActionId: string;
234
+ /** the action id in the TARGET ledger — identical to `sourceActionId` whenever the target
235
+ * state service matches the source's, which is what makes replay verifiable */
236
+ actionId: string;
237
+ /** 'write' = through `applyTwinWrite` (the kernel write path); 'append' = recorded verbatim */
238
+ via: 'write' | 'append';
239
+ status: 'performed' | 'replayed';
240
+ };
241
+ export type ReplayReport = {
242
+ changeset: string;
243
+ contentHash: string;
244
+ /** the authoring world */
245
+ world: string;
246
+ into: string;
247
+ total: number;
248
+ performed: number;
249
+ replayed: number;
250
+ services: string[];
251
+ actions: ReplayActionResult[];
252
+ };
253
+ /**
254
+ * Recover the `uniqueness` value `applyTwinWrite` folded into an action id, or report that the
255
+ * id is not twin-write-shaped at all (a hand-written or pack-appended action).
256
+ *
257
+ * The id format is `twin:<service>:<operation>:<subjectId>:<occurredAt>:<contentHash>[:<uniqueness>]`
258
+ * and both `operation` and `subjectId` may themselves contain ':', so the id cannot be split
259
+ * field-wise. A repeated write additionally carries an occurrence ordinal (`…#1`), stripped below. Instead the deterministic prefix is REBUILT from the action's own recorded parts —
260
+ * if it matches, whatever follows is exactly the uniqueness seam. Getting this right is what
261
+ * keeps billable occurrences (two identical AI completions in one millisecond) from collapsing
262
+ * into one action on replay.
263
+ */
264
+ export declare function twinWriteShape(action: TwinAction): {
265
+ uniqueness?: string;
266
+ } | null;
267
+ /**
268
+ * Replay a changeset's actions, in order, into `targets`.
269
+ *
270
+ * Two paths, and the split is about EXPRESSIVENESS, not preference:
271
+ * - `write` — the default and the point: back through `applyTwinWrite`, the same kernel entry
272
+ * an SDK call lands on. The target re-derives the action id from content, so it matches the
273
+ * source id and a second replay is a total no-op.
274
+ * - `append` — the honest fallback for actions the write path cannot express: multi-resource
275
+ * `projection` transactions (applyTwinWrite carries only flat `fields`, so routing them
276
+ * through it would silently DROP the creates/deletes/emits) and `revert`/`confirm`
277
+ * bookkeeping rows. These are appended verbatim under `appendActionIfAbsent`, which is
278
+ * id-keyed — so they are idempotent on re-replay too.
279
+ *
280
+ * `preconditions` are deliberately NOT replayed. They were evaluated against the authoring
281
+ * world's state at author time; re-evaluating them against a different base would fail replays
282
+ * that are perfectly valid recordings. Verifying a changeset against a target's state is v1's
283
+ * job (verifiers), not a silent side effect of replay. Dropping them cannot change any action
284
+ * id — preconditions are not part of the content hash.
285
+ */
286
+ export declare function replayChangeset(changeset: Changeset, opts: {
287
+ into: string;
288
+ targets: ReplayTarget[];
289
+ available?: string[];
290
+ }): Promise<ReplayReport>;
291
+ export declare function formatReplayReport(report: ReplayReport): string;
292
+ /**
293
+ * Run a changeset's verifiers against post-replay state. Deterministic on purpose: each assert
294
+ * reads the target's PROJECTED state (`projectResources` — the same projection the kernel's
295
+ * preconditions and plan conflicts read) and is evaluated by `checkPrecondition` — the same
296
+ * evaluator, so a verifier means exactly what a precondition means. The `worldDigest` is a
297
+ * receipt over everything the verifiers could have seen: two runs that produced the same
298
+ * post-replay state produce the same digest.
299
+ *
300
+ * `targets` are the same references replay wrote through — a verifier is only honest against
301
+ * the ledgers the replay actually landed in. A verifier naming a service with no target is a
302
+ * loud error (there is nothing real to check it against), never a silent pass.
303
+ */
304
+ export declare function runChangesetVerifiers(changeset: Changeset, targets: ReplayTarget[], opts: {
305
+ at: string;
306
+ into: string;
307
+ }): ChangesetVerification;
308
+ /** Record `verification` on the object — REPLACING any prior run (the provenance fields carry
309
+ * which run this is), never touching the hash the body is addressed by. */
310
+ export declare function withVerification(changeset: Changeset, verification: ChangesetVerification): Changeset;
311
+ /**
312
+ * Append an approval bound to the changeset's CURRENT body hash. Refuses when the stored
313
+ * `contentHash` no longer matches the body: an object whose bytes moved after authoring is not
314
+ * the object anyone reviewed, and signing it would launder the drift. Loud, never silent.
315
+ */
316
+ export declare function approveChangeset(changeset: Changeset, opts: {
317
+ principal: string;
318
+ at?: string;
319
+ note?: string;
320
+ }): {
321
+ changeset: Changeset;
322
+ approval: ChangesetApproval;
323
+ };
324
+ /** Everything `changeset status` reports — recomputed from the object, never trusted off
325
+ * stored booleans (the same rule `planRequiresApproval` follows). */
326
+ export type ChangesetReadiness = {
327
+ name: string;
328
+ world: string;
329
+ contentHash: string;
330
+ /** stored hash matches the body */
331
+ hashOk: boolean;
332
+ /** a verification exists, binds to the current hash, and every verifier passed */
333
+ verified: boolean;
334
+ verification: ChangesetVerification | null;
335
+ /** every approval on the object */
336
+ approvals: number;
337
+ /** approvals whose hash-at-signing is the current body hash — the only ones that count */
338
+ bindingApprovals: number;
339
+ ready: boolean;
340
+ /** empty exactly when ready */
341
+ reasons: string[];
342
+ };
343
+ export declare function changesetReadiness(changeset: Changeset): ChangesetReadiness;
344
+ /** The single honest line `changeset status` prints. No push here — v2's job; this line only
345
+ * tells the truth about the object. */
346
+ export declare function formatChangesetStatus(readiness: ChangesetReadiness): string;
347
+ /** The verify verb's report — the verification plus the replay that produced it. */
348
+ export declare function formatVerification(name: string, verification: ChangesetVerification, report: ReplayReport): string;
349
+ /** The object with its application recorded — `applied` is outside the hash. */
350
+ export declare function withApplication(changeset: Changeset, application: ChangesetApplication): Changeset;
351
+ export declare function formatApplication(name: string, application: ChangesetApplication): string;
352
+ /** One deterministic paragraph per twin, one sentence per operation, in first-occurrence order. */
353
+ export declare function narrateActions(actions: ChangesetAction[]): string;
354
+ /** The recorded narration against its re-render — null when they agree or when the object carries none. */
355
+ export declare function narrationDrift(changeset: Pick<Changeset, 'actions'> & Partial<Pick<Changeset, 'narration'>>): {
356
+ recorded: string;
357
+ expected: string;
358
+ } | null;
359
+ export type ApplyReceipt = {
360
+ actionId: string;
361
+ service: string;
362
+ stateService: string;
363
+ status: 'confirmed' | 'replayed' | 'failed' | 'skipped';
364
+ pushId?: string;
365
+ externalId?: string;
366
+ url?: string;
367
+ error?: string;
368
+ };
369
+ export type CompensationEntry = {
370
+ service: string;
371
+ actionId: string;
372
+ subject: {
373
+ type: string;
374
+ id: string;
375
+ };
376
+ externalId?: string;
377
+ /** the action's own revert spec when it carries one, else the inverse on the vendor id */
378
+ revert: TwinActionRevertSpec | {
379
+ strategy: 'inverse';
380
+ };
381
+ };
382
+ /** How a push went: the receipts origin answered, recorded on the changeset object. */
383
+ export type ChangesetApplication = {
384
+ at: string;
385
+ contentHash: string;
386
+ outcome: 'applied' | 'partial' | 'refused';
387
+ forced?: boolean;
388
+ refusal?: string[];
389
+ receipts: ApplyReceipt[];
390
+ compensation: CompensationEntry[];
391
+ };
392
+ export type RebaseTarget = {
393
+ service: string;
394
+ stateService: string;
395
+ controlRoot: string;
396
+ };
397
+ export type RebaseActionResult = {
398
+ actionId: string;
399
+ service: string;
400
+ status: 'unchanged' | 'rebased' | 'rebased-blind' | 'conflict';
401
+ moved?: Record<string, {
402
+ base: string | null;
403
+ current: string | null;
404
+ }>;
405
+ conflicts?: Array<{
406
+ subject: {
407
+ type: string;
408
+ id: string;
409
+ };
410
+ field: string;
411
+ op: string;
412
+ expected?: unknown;
413
+ actual: unknown;
414
+ }>;
415
+ };
416
+ export type RebaseReport = {
417
+ changeset: string;
418
+ from: string;
419
+ to: string | null;
420
+ at: string;
421
+ outcome: 'unchanged' | 'rebased' | 'conflict';
422
+ actions: RebaseActionResult[];
423
+ };
424
+ export declare function rebaseChangeset(changeset: Changeset, opts: {
425
+ targets: RebaseTarget[];
426
+ at?: string;
427
+ }): {
428
+ changeset: Changeset;
429
+ report: RebaseReport;
430
+ };
431
+ export declare function formatRebaseReport(report: RebaseReport): string;
Binary file
@@ -0,0 +1 @@
1
+ export declare function bundleClient(entry: string): Promise<string>;
@@ -0,0 +1,28 @@
1
+ // A mirror's browser client from its `client/<name>.tsx` entry, runtime-neutral (the serve seam's helpers,
2
+ // docs/contributing/architecture.md#the-serve-seam): under Bun the same `Bun.build` every mirror ran at
3
+ // serve time; under Node the PREBUILT `<name>.bundle.js` beside the entry, written at pack time
4
+ // by scripts/publish/build.mjs into dist/client/. Built once per process and kept; a failed build
5
+ // is retried on the next call. Nothing here runs at module scope.
6
+ import { existsSync, readFileSync } from 'node:fs';
7
+ const bundles = new Map();
8
+ export function bundleClient(entry) {
9
+ let pending = bundles.get(entry);
10
+ if (!pending) {
11
+ const bun = globalThis.Bun;
12
+ pending = (bun && typeof bun.build === 'function'
13
+ ? bun.build({ entrypoints: [entry], target: 'browser', minify: true }).then(async (result) => {
14
+ if (!result.success)
15
+ throw new Error(result.logs.map((l) => l.message).join('\n') || `client build failed: ${entry}`);
16
+ return result.outputs[0].text();
17
+ })
18
+ : Promise.resolve().then(() => {
19
+ const prebuilt = entry.replace(/\.tsx?$/, '.bundle.js');
20
+ if (!existsSync(prebuilt))
21
+ throw new Error(`client bundle missing at ${prebuilt} — the package's \`build\` writes it (scripts/publish/build.mjs); Node serves the prebuilt client`);
22
+ return readFileSync(prebuilt, 'utf8');
23
+ }))
24
+ .catch((error) => { bundles.delete(entry); throw error; });
25
+ bundles.set(entry, pending);
26
+ }
27
+ return pending;
28
+ }
@@ -0,0 +1,38 @@
1
+ export type CredentialPayload = {
2
+ headers: Record<string, string>;
3
+ secret?: string;
4
+ keyId?: string;
5
+ } & Record<string, unknown>;
6
+ /** The at-rest form. All byte fields base64. The fingerprint is a short SHA-256 prefix
7
+ * of the payload — enough for an operator to recognize "the key ending a1b2…", never
8
+ * enough to recover anything. */
9
+ export type SealedCredential = {
10
+ v: 1;
11
+ dekIv: string;
12
+ wrappedDek: string;
13
+ iv: string;
14
+ ciphertext: string;
15
+ placedAt: string;
16
+ fingerprint: string;
17
+ /** when a vendor last rotated a field of it (an OAuth refresh token): placedAt and fingerprint stay the credential's
18
+ * identity across rotations, the ledgers keyed by the fingerprint with them */
19
+ rotatedAt?: string;
20
+ };
21
+ export interface CredentialStorage {
22
+ getSealed(namespace: string, vendor: string): Promise<SealedCredential | null>;
23
+ putSealed(namespace: string, vendor: string, sealed: SealedCredential): Promise<void>;
24
+ deleteSealed(namespace: string, vendor: string): Promise<void>;
25
+ /** Purge every vendor's sealed record for the namespace — the lifecycle DELETE calls
26
+ * this: a deleted namespace's credentials must not survive to be inherited by
27
+ * whoever re-provisions the name (review B1: the resurrection theft chain). */
28
+ deleteNamespace(namespace: string): Promise<void>;
29
+ }
30
+ export declare class MemoryCredentialStorage implements CredentialStorage {
31
+ readonly records: Map<string, SealedCredential>;
32
+ getSealed(namespace: string, vendor: string): Promise<SealedCredential | null>;
33
+ putSealed(namespace: string, vendor: string, sealed: SealedCredential): Promise<void>;
34
+ deleteSealed(namespace: string, vendor: string): Promise<void>;
35
+ deleteNamespace(namespace: string): Promise<void>;
36
+ }
37
+ export declare function sealCredential(kekSecret: string, payload: CredentialPayload, placedAt: string, slot?: string): Promise<SealedCredential>;
38
+ export declare function openSealedCredential(kekSecret: string, sealed: SealedCredential, slot?: string): Promise<CredentialPayload>;
@@ -0,0 +1,114 @@
1
+ // THE SEALED CREDENTIAL STORE (docs/concepts/the-model.md, rule 5; custody ruled 2026-09-01:
2
+ // lives in core since v2 — a world that wraps reality seals its credential beside itself, and the
3
+ // kernel executor is the only reader). Originally the twins host's; the host re-exports it.
4
+ // cloud-side, idiomatic — like any web service). Real vendor credentials are:
5
+ // • WRITE-ONLY through the API: placement and deletion are the tenant's act; reads
6
+ // return metadata (placedAt, a fingerprint) and NEVER the secret — there is no
7
+ // read-back door, and none may ever be added.
8
+ // • ENVELOPE-ENCRYPTED: each credential gets its own random 256-bit DEK (AES-GCM);
9
+ // the DEK is wrapped by the deployment KEK, which lives in the platform's secret
10
+ // store (wrangler secret TWINS_CRED_KEK) and never in any bucket. KEK rotation is
11
+ // a re-wrap of DEKs, not a re-encryption of payloads.
12
+ // • OUTSIDE every namespace tree (beside keys and links, under '-/creds/'), so no
13
+ // flush, snapshot, or door can ever carry one.
14
+ // • CONSUMED only by the remote backing arm and the sync plane, through
15
+ // `openSealedCredential` — an in-process seam, never an HTTP surface.
16
+ //
17
+ // The payload is a JSON object; the convention the forward arm consumes is
18
+ // `{ headers: { authorization: "Bearer …", … } }` — credential APPLICATION is a value
19
+ // (which headers), never a per-vendor mechanism. WebCrypto only: workerd-clean.
20
+ //
21
+ // `secret` is the RAW material for the strategies that cannot be expressed as a header: a vendor
22
+ // that reads its key from a query parameter, or one that wants a signature computed per request
23
+ // (docs/concepts/the-model.md#the-rules, rule 5, "applies the credential by strategy"). It is optional and unread unless
24
+ // the pack declares such a strategy, so a header-authenticated vendor seals exactly what it sealed
25
+ // before. It is never logged and never enters pack code — the executor is the only reader.
26
+ export class MemoryCredentialStorage {
27
+ records = new Map();
28
+ async getSealed(namespace, vendor) {
29
+ return this.records.get(`${namespace}/${vendor}`) ?? null;
30
+ }
31
+ async putSealed(namespace, vendor, sealed) {
32
+ this.records.set(`${namespace}/${vendor}`, sealed);
33
+ }
34
+ async deleteSealed(namespace, vendor) {
35
+ this.records.delete(`${namespace}/${vendor}`);
36
+ }
37
+ async deleteNamespace(namespace) {
38
+ for (const key of [...this.records.keys()])
39
+ if (key.startsWith(`${namespace}/`))
40
+ this.records.delete(key);
41
+ }
42
+ }
43
+ const toB64 = (bytes) => {
44
+ // Chunked (review B3): a spread of the whole payload blows the arg-count stack well
45
+ // below the 4 MB body cap (verified ~1 MB on Bun, tens of KB on V8).
46
+ let bin = '';
47
+ for (let i = 0; i < bytes.length; i += 0x1000)
48
+ bin += String.fromCharCode(...bytes.subarray(i, i + 0x1000));
49
+ return btoa(bin);
50
+ };
51
+ const fromB64 = (b64) => {
52
+ const bin = atob(b64);
53
+ const bytes = new Uint8Array(new ArrayBuffer(bin.length));
54
+ for (let i = 0; i < bin.length; i += 1)
55
+ bytes[i] = bin.charCodeAt(i);
56
+ return bytes;
57
+ };
58
+ /** The KEK secret string (any length ≥ 16) normalizes to 32 key bytes via SHA-256 —
59
+ * operators paste one strong secret; the digest is the AES key material. */
60
+ async function importKek(secret) {
61
+ // ≥32 chars (review M3): SHA-256 with no stretching is fine for MACHINE secrets
62
+ // (`openssl rand -base64 32`) and hopeless against a short human passphrase — the
63
+ // floor forces the former.
64
+ if (secret.length < 32)
65
+ throw new Error('credential KEK must be at least 32 characters (generate with: openssl rand -base64 32)');
66
+ const material = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(secret));
67
+ return await crypto.subtle.importKey('raw', material, { name: 'AES-GCM' }, false, ['encrypt', 'decrypt']);
68
+ }
69
+ export async function sealCredential(kekSecret, payload, placedAt, slot = '') {
70
+ const kek = await importKek(kekSecret);
71
+ const plaintext = new TextEncoder().encode(JSON.stringify(payload));
72
+ const dekBytes = crypto.getRandomValues(new Uint8Array(32));
73
+ const dek = await crypto.subtle.importKey('raw', dekBytes, { name: 'AES-GCM' }, false, ['encrypt']);
74
+ const iv = crypto.getRandomValues(new Uint8Array(12));
75
+ const dekIv = crypto.getRandomValues(new Uint8Array(12));
76
+ // The SLOT ('{namespace}/{vendor}') rides as AAD (audit L16): a sealed record
77
+ // transplanted to another slot by a bucket-level writer refuses to open there.
78
+ const aad = new TextEncoder().encode(slot);
79
+ const ciphertext = new Uint8Array(await crypto.subtle.encrypt({ name: 'AES-GCM', iv, additionalData: aad }, dek, plaintext));
80
+ const wrappedDek = new Uint8Array(await crypto.subtle.encrypt({ name: 'AES-GCM', iv: dekIv }, kek, dekBytes));
81
+ // KEYED fingerprint (review m1): an unsalted hash prefix is an offline oracle for
82
+ // low-entropy payloads; HMAC under KEK-derived material keeps the operator UX
83
+ // (recognizable short id) with no oracle.
84
+ const macMaterial = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(`fingerprint:${kekSecret}`));
85
+ const macKey = await crypto.subtle.importKey('raw', macMaterial, { name: 'HMAC', hash: 'SHA-256' }, false, ['sign']);
86
+ const digest = new Uint8Array(await crypto.subtle.sign('HMAC', macKey, plaintext));
87
+ return {
88
+ v: 1,
89
+ dekIv: toB64(dekIv),
90
+ wrappedDek: toB64(wrappedDek),
91
+ iv: toB64(iv),
92
+ ciphertext: toB64(ciphertext),
93
+ placedAt,
94
+ fingerprint: [...digest.slice(0, 6)].map((b) => b.toString(16).padStart(2, '0')).join(''),
95
+ };
96
+ }
97
+ export async function openSealedCredential(kekSecret, sealed, slot = '') {
98
+ const kek = await importKek(kekSecret);
99
+ let dekBytes;
100
+ try {
101
+ dekBytes = await crypto.subtle.decrypt({ name: 'AES-GCM', iv: fromB64(sealed.dekIv) }, kek, fromB64(sealed.wrappedDek));
102
+ }
103
+ catch {
104
+ throw new Error('credential unseal failed: wrong KEK or corrupted record');
105
+ }
106
+ const dek = await crypto.subtle.importKey('raw', dekBytes, { name: 'AES-GCM' }, false, ['decrypt']);
107
+ try {
108
+ const plaintext = new Uint8Array(await crypto.subtle.decrypt({ name: 'AES-GCM', iv: fromB64(sealed.iv), additionalData: new TextEncoder().encode(slot) }, dek, fromB64(sealed.ciphertext)));
109
+ return JSON.parse(new TextDecoder().decode(plaintext));
110
+ }
111
+ catch {
112
+ throw new Error('credential unseal failed: wrong KEK or corrupted record');
113
+ }
114
+ }