@superbee/core 0.1.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 (145) hide show
  1. package/dist/backend.d.ts +77 -0
  2. package/dist/backend.d.ts.map +1 -0
  3. package/dist/backend.js +380 -0
  4. package/dist/backend.js.map +1 -0
  5. package/dist/bundle.d.ts +38 -0
  6. package/dist/bundle.d.ts.map +1 -0
  7. package/dist/bundle.js +107 -0
  8. package/dist/bundle.js.map +1 -0
  9. package/dist/content-type.d.ts +72 -0
  10. package/dist/content-type.d.ts.map +1 -0
  11. package/dist/content-type.js +149 -0
  12. package/dist/content-type.js.map +1 -0
  13. package/dist/document-mutation.d.ts +83 -0
  14. package/dist/document-mutation.d.ts.map +1 -0
  15. package/dist/document-mutation.js +319 -0
  16. package/dist/document-mutation.js.map +1 -0
  17. package/dist/document-precondition.d.ts +62 -0
  18. package/dist/document-precondition.d.ts.map +1 -0
  19. package/dist/document-precondition.js +134 -0
  20. package/dist/document-precondition.js.map +1 -0
  21. package/dist/document-write-policy.d.ts +42 -0
  22. package/dist/document-write-policy.d.ts.map +1 -0
  23. package/dist/document-write-policy.js +134 -0
  24. package/dist/document-write-policy.js.map +1 -0
  25. package/dist/engine.d.ts +48 -0
  26. package/dist/engine.d.ts.map +1 -0
  27. package/dist/engine.js +209 -0
  28. package/dist/engine.js.map +1 -0
  29. package/dist/errors.d.ts +36 -0
  30. package/dist/errors.d.ts.map +1 -0
  31. package/dist/errors.js +50 -0
  32. package/dist/errors.js.map +1 -0
  33. package/dist/filesystem-identity.d.ts +208 -0
  34. package/dist/filesystem-identity.d.ts.map +1 -0
  35. package/dist/filesystem-identity.js +675 -0
  36. package/dist/filesystem-identity.js.map +1 -0
  37. package/dist/filesystem-lock.d.ts +65 -0
  38. package/dist/filesystem-lock.d.ts.map +1 -0
  39. package/dist/filesystem-lock.js +460 -0
  40. package/dist/filesystem-lock.js.map +1 -0
  41. package/dist/freshness.d.ts +31 -0
  42. package/dist/freshness.d.ts.map +1 -0
  43. package/dist/freshness.js +98 -0
  44. package/dist/freshness.js.map +1 -0
  45. package/dist/frontmatter-contract.d.ts +17 -0
  46. package/dist/frontmatter-contract.d.ts.map +1 -0
  47. package/dist/frontmatter-contract.js +29 -0
  48. package/dist/frontmatter-contract.js.map +1 -0
  49. package/dist/frontmatter.d.ts +30 -0
  50. package/dist/frontmatter.d.ts.map +1 -0
  51. package/dist/frontmatter.js +115 -0
  52. package/dist/frontmatter.js.map +1 -0
  53. package/dist/index-marker.d.ts +3 -0
  54. package/dist/index-marker.d.ts.map +1 -0
  55. package/dist/index-marker.js +3 -0
  56. package/dist/index-marker.js.map +1 -0
  57. package/dist/index-projection.d.ts +79 -0
  58. package/dist/index-projection.d.ts.map +1 -0
  59. package/dist/index-projection.js +245 -0
  60. package/dist/index-projection.js.map +1 -0
  61. package/dist/index.d.ts +68 -0
  62. package/dist/index.d.ts.map +1 -0
  63. package/dist/index.js +110 -0
  64. package/dist/index.js.map +1 -0
  65. package/dist/kinds-load.d.ts +13 -0
  66. package/dist/kinds-load.d.ts.map +1 -0
  67. package/dist/kinds-load.js +34 -0
  68. package/dist/kinds-load.js.map +1 -0
  69. package/dist/kinds.d.ts +241 -0
  70. package/dist/kinds.d.ts.map +1 -0
  71. package/dist/kinds.js +1001 -0
  72. package/dist/kinds.js.map +1 -0
  73. package/dist/links.d.ts +62 -0
  74. package/dist/links.d.ts.map +1 -0
  75. package/dist/links.js +203 -0
  76. package/dist/links.js.map +1 -0
  77. package/dist/meaningful-change-time.d.ts +11 -0
  78. package/dist/meaningful-change-time.d.ts.map +1 -0
  79. package/dist/meaningful-change-time.js +16 -0
  80. package/dist/meaningful-change-time.js.map +1 -0
  81. package/dist/memory-backend.d.ts +55 -0
  82. package/dist/memory-backend.d.ts.map +1 -0
  83. package/dist/memory-backend.js +214 -0
  84. package/dist/memory-backend.js.map +1 -0
  85. package/dist/mutation-attribution.d.ts +19 -0
  86. package/dist/mutation-attribution.d.ts.map +1 -0
  87. package/dist/mutation-attribution.js +34 -0
  88. package/dist/mutation-attribution.js.map +1 -0
  89. package/dist/mutation.d.ts +89 -0
  90. package/dist/mutation.d.ts.map +1 -0
  91. package/dist/mutation.js +94 -0
  92. package/dist/mutation.js.map +1 -0
  93. package/dist/page.d.ts +87 -0
  94. package/dist/page.d.ts.map +1 -0
  95. package/dist/page.js +151 -0
  96. package/dist/page.js.map +1 -0
  97. package/dist/paths.d.ts +78 -0
  98. package/dist/paths.d.ts.map +1 -0
  99. package/dist/paths.js +148 -0
  100. package/dist/paths.js.map +1 -0
  101. package/dist/portable-frontmatter.d.ts +8 -0
  102. package/dist/portable-frontmatter.d.ts.map +1 -0
  103. package/dist/portable-frontmatter.js +35 -0
  104. package/dist/portable-frontmatter.js.map +1 -0
  105. package/dist/publication-filesystem.d.ts +6 -0
  106. package/dist/publication-filesystem.d.ts.map +1 -0
  107. package/dist/publication-filesystem.js +6 -0
  108. package/dist/publication-filesystem.js.map +1 -0
  109. package/dist/query-filter.d.ts +12 -0
  110. package/dist/query-filter.d.ts.map +1 -0
  111. package/dist/query-filter.js +29 -0
  112. package/dist/query-filter.js.map +1 -0
  113. package/dist/query-selection.d.ts +25 -0
  114. package/dist/query-selection.d.ts.map +1 -0
  115. package/dist/query-selection.js +48 -0
  116. package/dist/query-selection.js.map +1 -0
  117. package/dist/remote-backend.d.ts +152 -0
  118. package/dist/remote-backend.d.ts.map +1 -0
  119. package/dist/remote-backend.js +505 -0
  120. package/dist/remote-backend.js.map +1 -0
  121. package/dist/remote.d.ts +4 -0
  122. package/dist/remote.d.ts.map +1 -0
  123. package/dist/remote.js +3 -0
  124. package/dist/remote.js.map +1 -0
  125. package/dist/storage.d.ts +7 -0
  126. package/dist/storage.d.ts.map +1 -0
  127. package/dist/storage.js +6 -0
  128. package/dist/storage.js.map +1 -0
  129. package/dist/types.d.ts +438 -0
  130. package/dist/types.d.ts.map +1 -0
  131. package/dist/types.js +12 -0
  132. package/dist/types.js.map +1 -0
  133. package/dist/validation.d.ts +22 -0
  134. package/dist/validation.d.ts.map +1 -0
  135. package/dist/validation.js +12 -0
  136. package/dist/validation.js.map +1 -0
  137. package/dist/version-transport.d.ts +13 -0
  138. package/dist/version-transport.d.ts.map +1 -0
  139. package/dist/version-transport.js +26 -0
  140. package/dist/version-transport.js.map +1 -0
  141. package/dist/versioning.d.ts +48 -0
  142. package/dist/versioning.d.ts.map +1 -0
  143. package/dist/versioning.js +62 -0
  144. package/dist/versioning.js.map +1 -0
  145. package/package.json +84 -0
@@ -0,0 +1,214 @@
1
+ /**
2
+ * In-memory {@link StorageBackend} — the PROOF that the seam's hard contract is
3
+ * implementable, and that the engine leaks no filesystem assumptions.
4
+ *
5
+ * Where {@link FilesystemBackend} is the degenerate adapter (single-version history,
6
+ * cross-process filesystem compare-and-swap), this adapter implements the historical hard case:
7
+ * - a genuine per-document VERSION CHAIN (`versions()` returns the full history,
8
+ * newest-first);
9
+ * - ENFORCED compare-and-swap: a `write` whose `expectedVersion` no longer matches
10
+ * the current head throws {@link VersionConflict}, atomically against the in-process
11
+ * map (no check-then-write window);
12
+ * - per-write ACTOR attribution recorded on every revision;
13
+ * - a real batch {@link MemoryBackend.readMany}.
14
+ *
15
+ * It touches NO filesystem and NO markdown serialization for storage — documents are
16
+ * held as parsed {@link OkfDocument} objects, keyed by concept id — so running the
17
+ * core operations over it exercises exactly the parts of the engine that must be
18
+ * backend-neutral. Version tokens are still content-addressed via {@link contentVersion},
19
+ * so an engine-written document carries the SAME token here as on disk.
20
+ *
21
+ * Id/dir safety mirrors {@link FilesystemBackend} exactly: an unsafe concept id (a `..`
22
+ * segment, an absolute id) or an unsafe reserved-file `dir` is harmless against a plain
23
+ * `Map` key, but this adapter rejects it via the SAME `assertSafeConceptId` /
24
+ * `assertSafeReservedDir` guards anyway — the two adapters must agree on rejecting it, or
25
+ * the dual-backend / wire-protocol contract tests diverge.
26
+ */
27
+ import { resolveContentType } from "./content-type.js";
28
+ import { assertSafeBlobKey, assertSafeConceptId, assertSafeReservedDir, toPosix } from "./paths.js";
29
+ import { blobVersion, contentVersion, defaultActor, VersionConflict, versionOfBytes } from "./versioning.js";
30
+ /** Deep copy so stored state never aliases a caller's object (and vice-versa). */
31
+ function snapshot(value) {
32
+ return structuredClone(value);
33
+ }
34
+ /** An ENOENT-shaped rejection so missing-document handling matches {@link FilesystemBackend}. */
35
+ function notFound(id) {
36
+ const err = new Error(`no concept document '${id}'`);
37
+ err.code = "ENOENT";
38
+ return err;
39
+ }
40
+ /** Bundle-relative key for a reserved file (`""` = bundle root), mirroring the fs adapter's layout. */
41
+ function reservedKey(dir, name) {
42
+ const d = toPosix(dir).replace(/^\.?\//, "").replace(/\/$/, "");
43
+ return d === "" ? name : `${d}/${name}`;
44
+ }
45
+ /**
46
+ * A fully in-memory OKF store implementing the {@link StorageBackend} contract for
47
+ * the hard, multi-writer case. Not persistent — its purpose is to prove the seam and
48
+ * to back tests, not to be a shipping store.
49
+ */
50
+ export class MemoryBackend {
51
+ /** Concept id → revision chain, newest-first (index 0 is the head). */
52
+ chains = new Map();
53
+ /** Reserved-file path → raw content. */
54
+ reserved = new Map();
55
+ /** Blob key → current state. Separate from `chains` — a different namespace, no history. */
56
+ blobs = new Map();
57
+ async read(id) {
58
+ // An unsafe id is harmless against a plain Map key, but the two adapters must AGREE
59
+ // on rejecting it — otherwise the dual-backend/wire contract tests diverge.
60
+ assertSafeConceptId(id);
61
+ const head = this.chains.get(id)?.[0];
62
+ if (!head)
63
+ throw notFound(id);
64
+ return { doc: snapshot(head.doc), version: head.version };
65
+ }
66
+ async readMany(ids) {
67
+ // Validate every id up front (same contract as FilesystemBackend.readMany), then a
68
+ // real multi-get: one pass over the in-memory index, results in input order.
69
+ for (const id of ids)
70
+ assertSafeConceptId(id);
71
+ return Promise.all(ids.map((id) => this.read(id)));
72
+ }
73
+ async write(id, doc, options = {}) {
74
+ assertSafeConceptId(id);
75
+ const chain = this.chains.get(id);
76
+ const current = chain?.[0]?.version ?? null;
77
+ // Enforced compare-and-swap, atomic against the in-process map.
78
+ if (options.expectedVersion !== undefined && options.expectedVersion !== current) {
79
+ throw new VersionConflict(id, options.expectedVersion, current);
80
+ }
81
+ // The seam's `id` argument owns identity. Filesystem and wire adapters reconstruct/attach
82
+ // that route id on reads because document bytes do not serialize `doc.id`; mirror them here
83
+ // instead of retaining a mismatched caller-supplied `doc.id` in the in-memory snapshot.
84
+ const storedDoc = { ...doc, id };
85
+ const version = contentVersion(storedDoc);
86
+ // Idempotent: re-writing byte-identical content is a no-op that does not grow the
87
+ // chain (the content address is unchanged). A genuine content change appends a revision.
88
+ if (current === version)
89
+ return version;
90
+ const revision = {
91
+ version,
92
+ actor: options.actor?.trim() || defaultActor(),
93
+ timestamp: new Date().toISOString(),
94
+ // Unlike `actor`, an unattested agent is simply absent — no default is applied.
95
+ agent: options.agent?.trim() || undefined,
96
+ doc: snapshot(storedDoc),
97
+ };
98
+ this.chains.set(id, chain ? [revision, ...chain] : [revision]);
99
+ return version;
100
+ }
101
+ async delete(id, options = {}) {
102
+ assertSafeConceptId(id);
103
+ const chain = this.chains.get(id);
104
+ const current = chain?.[0]?.version ?? null;
105
+ if (current === null)
106
+ return false; // absent ⇒ idempotent no-op, EVEN under CAS
107
+ // Enforced compare-and-swap, atomic against the in-process map (no check-then-write
108
+ // window — synchronous with no `await` between the check above and the mutation below).
109
+ if (options.expectedVersion !== undefined && current !== options.expectedVersion) {
110
+ throw new VersionConflict(id, options.expectedVersion, current);
111
+ }
112
+ // D5: the whole chain is purged, not just the head — `versions(id)` reports `[]`
113
+ // afterward, matching a never-written concept (a delete's history is unobservable).
114
+ this.chains.delete(id);
115
+ return true;
116
+ }
117
+ async exists(id) {
118
+ assertSafeConceptId(id);
119
+ return this.chains.has(id);
120
+ }
121
+ async list(prefix) {
122
+ const ids = [...this.chains.keys()].filter((id) => !prefix || id.startsWith(prefix));
123
+ ids.sort((a, b) => a.localeCompare(b));
124
+ return ids;
125
+ }
126
+ async versions(id) {
127
+ assertSafeConceptId(id);
128
+ const chain = this.chains.get(id) ?? [];
129
+ // Strip the stored doc snapshot; expose only the history metadata (newest-first).
130
+ // `agent` is omitted (not merely `undefined`-valued) when the revision was never
131
+ // agent-attested, so a never-agent'd revision serializes clean over the wire.
132
+ return chain.map(({ version, actor, timestamp, agent }) => agent === undefined ? { version, actor, timestamp } : { version, actor, timestamp, agent });
133
+ }
134
+ async readReserved(dir, name) {
135
+ assertSafeReservedDir(dir);
136
+ const content = this.reserved.get(reservedKey(dir, name));
137
+ if (content === undefined)
138
+ return null;
139
+ // Content-addressed, so the token matches the filesystem adapter's for identical bytes.
140
+ return { content, version: versionOfBytes(content) };
141
+ }
142
+ async writeReserved(dir, name, content, options = {}) {
143
+ assertSafeReservedDir(dir);
144
+ const key = reservedKey(dir, name);
145
+ const existing = this.reserved.get(key);
146
+ const current = existing === undefined ? null : versionOfBytes(existing);
147
+ // Enforced compare-and-swap, atomic against the in-process map (no check-then-write window).
148
+ if (options.expectedVersion !== undefined && options.expectedVersion !== current) {
149
+ throw new VersionConflict(key, options.expectedVersion, current);
150
+ }
151
+ this.reserved.set(key, content);
152
+ return versionOfBytes(content);
153
+ }
154
+ // ── blobs: opaque bytes + a content-type ──────────────────────────────────
155
+ async readBlob(key) {
156
+ assertSafeBlobKey(key);
157
+ const entry = this.blobs.get(key);
158
+ if (!entry)
159
+ return null;
160
+ // `new Uint8Array(entry.bytes)`, NOT `entry.bytes.slice()`: a `Buffer` (the shape
161
+ // every real caller's bytes actually are — `fs.readFile`, the CLI's `promote`)
162
+ // OVERRIDES `.slice()` to return a VIEW onto the SAME underlying ArrayBuffer, not a
163
+ // copy — a caller mutating its own buffer after the fact would silently mutate the
164
+ // "stored" blob too. The constructor form always copies element-by-element into a
165
+ // fresh buffer, regardless of the input's concrete TypedArray subclass.
166
+ return { bytes: new Uint8Array(entry.bytes), contentType: entry.contentType, version: entry.version };
167
+ }
168
+ async writeBlob(key, bytes, contentType, options = {}) {
169
+ assertSafeBlobKey(key);
170
+ const existing = this.blobs.get(key);
171
+ const current = existing?.version ?? null;
172
+ // Enforced compare-and-swap, atomic against the in-process map (no check-then-write window).
173
+ if (options.expectedVersion !== undefined && options.expectedVersion !== current) {
174
+ throw new VersionConflict(key, options.expectedVersion, current);
175
+ }
176
+ const version = blobVersion(bytes);
177
+ // Unlike FilesystemBackend, this adapter KEEPS STATE, so an explicit content-type
178
+ // override is persisted (not merely inferred-on-read) — the documented divergence
179
+ // the tri-adapter contentType parity test pins (B5).
180
+ const resolvedType = resolveContentType(key, contentType);
181
+ // Idempotent: a byte-identical AND content-type-identical re-write is a true
182
+ // same-version no-op (I7) — mirrors write()'s content-address idempotency.
183
+ if (existing && existing.version === version && existing.contentType === resolvedType) {
184
+ return version;
185
+ }
186
+ // See readBlob's comment: `new Uint8Array(bytes)`, never `bytes.slice()` (a Buffer's
187
+ // `.slice()` returns a shared VIEW, not a copy) — the caller's own buffer must not
188
+ // alias what gets stored.
189
+ this.blobs.set(key, { bytes: new Uint8Array(bytes), contentType: resolvedType, version });
190
+ return version;
191
+ }
192
+ async deleteBlob(key, options = {}) {
193
+ assertSafeBlobKey(key);
194
+ const existing = this.blobs.get(key);
195
+ const current = existing?.version ?? null;
196
+ if (current === null)
197
+ return false; // absent ⇒ idempotent no-op, EVEN under CAS
198
+ if (options.expectedVersion !== undefined && current !== options.expectedVersion) {
199
+ throw new VersionConflict(key, options.expectedVersion, current);
200
+ }
201
+ this.blobs.delete(key);
202
+ return true;
203
+ }
204
+ async existsBlob(key) {
205
+ assertSafeBlobKey(key);
206
+ return this.blobs.has(key);
207
+ }
208
+ async listBlobs(prefix) {
209
+ const keys = [...this.blobs.keys()].filter((k) => !prefix || k.startsWith(prefix));
210
+ keys.sort((a, b) => a.localeCompare(b));
211
+ return keys;
212
+ }
213
+ }
214
+ //# sourceMappingURL=memory-backend.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"memory-backend.js","sourceRoot":"","sources":["../src/memory-backend.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH,OAAO,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AACvD,OAAO,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,qBAAqB,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AACpG,OAAO,EAAE,WAAW,EAAE,cAAc,EAAE,YAAY,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AA2B7G,kFAAkF;AAClF,SAAS,QAAQ,CAAI,KAAQ;IAC3B,OAAO,eAAe,CAAC,KAAK,CAAC,CAAC;AAChC,CAAC;AAeD,iGAAiG;AACjG,SAAS,QAAQ,CAAC,EAAa;IAC7B,MAAM,GAAG,GAAG,IAAI,KAAK,CAAC,wBAAwB,EAAE,GAAG,CAA0B,CAAC;IAC9E,GAAG,CAAC,IAAI,GAAG,QAAQ,CAAC;IACpB,OAAO,GAAG,CAAC;AACb,CAAC;AAED,uGAAuG;AACvG,SAAS,WAAW,CAAC,GAAW,EAAE,IAAsB;IACtD,MAAM,CAAC,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;IAChE,OAAO,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,IAAI,EAAE,CAAC;AAC1C,CAAC;AAED;;;;GAIG;AACH,MAAM,OAAO,aAAa;IACxB,uEAAuE;IACtD,MAAM,GAAG,IAAI,GAAG,EAAyB,CAAC;IAC3D,wCAAwC;IACvB,QAAQ,GAAG,IAAI,GAAG,EAAkB,CAAC;IACtD,4FAA4F;IAC3E,KAAK,GAAG,IAAI,GAAG,EAAsB,CAAC;IAEvD,KAAK,CAAC,IAAI,CAAC,EAAa;QACtB,oFAAoF;QACpF,4EAA4E;QAC5E,mBAAmB,CAAC,EAAE,CAAC,CAAC;QACxB,MAAM,IAAI,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;QACtC,IAAI,CAAC,IAAI;YAAE,MAAM,QAAQ,CAAC,EAAE,CAAC,CAAC;QAC9B,OAAO,EAAE,GAAG,EAAE,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,CAAC;IAC5D,CAAC;IAED,KAAK,CAAC,QAAQ,CAAC,GAAgB;QAC7B,mFAAmF;QACnF,6EAA6E;QAC7E,KAAK,MAAM,EAAE,IAAI,GAAG;YAAE,mBAAmB,CAAC,EAAE,CAAC,CAAC;QAC9C,OAAO,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;IACrD,CAAC;IAED,KAAK,CAAC,KAAK,CAAC,EAAa,EAAE,GAAgB,EAAE,UAAwB,EAAE;QACrE,mBAAmB,CAAC,EAAE,CAAC,CAAC;QACxB,MAAM,KAAK,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QAClC,MAAM,OAAO,GAAG,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,OAAO,IAAI,IAAI,CAAC;QAC5C,gEAAgE;QAChE,IAAI,OAAO,CAAC,eAAe,KAAK,SAAS,IAAI,OAAO,CAAC,eAAe,KAAK,OAAO,EAAE,CAAC;YACjF,MAAM,IAAI,eAAe,CAAC,EAAE,EAAE,OAAO,CAAC,eAAe,EAAE,OAAO,CAAC,CAAC;QAClE,CAAC;QACD,0FAA0F;QAC1F,4FAA4F;QAC5F,wFAAwF;QACxF,MAAM,SAAS,GAAG,EAAE,GAAG,GAAG,EAAE,EAAE,EAAE,CAAC;QACjC,MAAM,OAAO,GAAG,cAAc,CAAC,SAAS,CAAC,CAAC;QAC1C,kFAAkF;QAClF,yFAAyF;QACzF,IAAI,OAAO,KAAK,OAAO;YAAE,OAAO,OAAO,CAAC;QACxC,MAAM,QAAQ,GAAa;YACzB,OAAO;YACP,KAAK,EAAE,OAAO,CAAC,KAAK,EAAE,IAAI,EAAE,IAAI,YAAY,EAAE;YAC9C,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;YACnC,gFAAgF;YAChF,KAAK,EAAE,OAAO,CAAC,KAAK,EAAE,IAAI,EAAE,IAAI,SAAS;YACzC,GAAG,EAAE,QAAQ,CAAC,SAAS,CAAC;SACzB,CAAC;QACF,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,QAAQ,EAAE,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC;QAC/D,OAAO,OAAO,CAAC;IACjB,CAAC;IAED,KAAK,CAAC,MAAM,CAAC,EAAa,EAAE,UAAyB,EAAE;QACrD,mBAAmB,CAAC,EAAE,CAAC,CAAC;QACxB,MAAM,KAAK,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QAClC,MAAM,OAAO,GAAG,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,OAAO,IAAI,IAAI,CAAC;QAC5C,IAAI,OAAO,KAAK,IAAI;YAAE,OAAO,KAAK,CAAC,CAAC,4CAA4C;QAChF,oFAAoF;QACpF,wFAAwF;QACxF,IAAI,OAAO,CAAC,eAAe,KAAK,SAAS,IAAI,OAAO,KAAK,OAAO,CAAC,eAAe,EAAE,CAAC;YACjF,MAAM,IAAI,eAAe,CAAC,EAAE,EAAE,OAAO,CAAC,eAAe,EAAE,OAAO,CAAC,CAAC;QAClE,CAAC;QACD,iFAAiF;QACjF,oFAAoF;QACpF,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;QACvB,OAAO,IAAI,CAAC;IACd,CAAC;IAED,KAAK,CAAC,MAAM,CAAC,EAAa;QACxB,mBAAmB,CAAC,EAAE,CAAC,CAAC;QACxB,OAAO,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAC7B,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,MAAe;QACxB,MAAM,GAAG,GAAG,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,MAAM,IAAI,EAAE,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC;QACrF,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,CAAC;QACvC,OAAO,GAAG,CAAC;IACb,CAAC;IAED,KAAK,CAAC,QAAQ,CAAC,EAAa;QAC1B,mBAAmB,CAAC,EAAE,CAAC,CAAC;QACxB,MAAM,KAAK,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC;QACxC,kFAAkF;QAClF,iFAAiF;QACjF,8EAA8E;QAC9E,OAAO,KAAK,CAAC,GAAG,CAAC,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,KAAK,EAAE,EAAE,EAAE,CACxD,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,KAAK,EAAE,CAC3F,CAAC;IACJ,CAAC;IAED,KAAK,CAAC,YAAY,CAAC,GAAW,EAAE,IAAsB;QACpD,qBAAqB,CAAC,GAAG,CAAC,CAAC;QAC3B,MAAM,OAAO,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,WAAW,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC,CAAC;QAC1D,IAAI,OAAO,KAAK,SAAS;YAAE,OAAO,IAAI,CAAC;QACvC,wFAAwF;QACxF,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,cAAc,CAAC,OAAO,CAAC,EAAE,CAAC;IACvD,CAAC;IAED,KAAK,CAAC,aAAa,CACjB,GAAW,EACX,IAAsB,EACtB,OAAe,EACf,UAAwB,EAAE;QAE1B,qBAAqB,CAAC,GAAG,CAAC,CAAC;QAC3B,MAAM,GAAG,GAAG,WAAW,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;QACnC,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACxC,MAAM,OAAO,GAAG,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,cAAc,CAAC,QAAQ,CAAC,CAAC;QACzE,6FAA6F;QAC7F,IAAI,OAAO,CAAC,eAAe,KAAK,SAAS,IAAI,OAAO,CAAC,eAAe,KAAK,OAAO,EAAE,CAAC;YACjF,MAAM,IAAI,eAAe,CAAC,GAAG,EAAE,OAAO,CAAC,eAAe,EAAE,OAAO,CAAC,CAAC;QACnE,CAAC;QACD,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;QAChC,OAAO,cAAc,CAAC,OAAO,CAAC,CAAC;IACjC,CAAC;IAED,6EAA6E;IAE7E,KAAK,CAAC,QAAQ,CAAC,GAAY;QACzB,iBAAiB,CAAC,GAAG,CAAC,CAAC;QACvB,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QAClC,IAAI,CAAC,KAAK;YAAE,OAAO,IAAI,CAAC;QACxB,kFAAkF;QAClF,+EAA+E;QAC/E,oFAAoF;QACpF,mFAAmF;QACnF,kFAAkF;QAClF,wEAAwE;QACxE,OAAO,EAAE,KAAK,EAAE,IAAI,UAAU,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE,WAAW,EAAE,KAAK,CAAC,WAAW,EAAE,OAAO,EAAE,KAAK,CAAC,OAAO,EAAE,CAAC;IACxG,CAAC;IAED,KAAK,CAAC,SAAS,CACb,GAAY,EACZ,KAAiB,EACjB,WAAoB,EACpB,UAAwB,EAAE;QAE1B,iBAAiB,CAAC,GAAG,CAAC,CAAC;QACvB,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACrC,MAAM,OAAO,GAAG,QAAQ,EAAE,OAAO,IAAI,IAAI,CAAC;QAC1C,6FAA6F;QAC7F,IAAI,OAAO,CAAC,eAAe,KAAK,SAAS,IAAI,OAAO,CAAC,eAAe,KAAK,OAAO,EAAE,CAAC;YACjF,MAAM,IAAI,eAAe,CAAC,GAAG,EAAE,OAAO,CAAC,eAAe,EAAE,OAAO,CAAC,CAAC;QACnE,CAAC;QACD,MAAM,OAAO,GAAG,WAAW,CAAC,KAAK,CAAC,CAAC;QACnC,kFAAkF;QAClF,kFAAkF;QAClF,qDAAqD;QACrD,MAAM,YAAY,GAAG,kBAAkB,CAAC,GAAG,EAAE,WAAW,CAAC,CAAC;QAC1D,6EAA6E;QAC7E,2EAA2E;QAC3E,IAAI,QAAQ,IAAI,QAAQ,CAAC,OAAO,KAAK,OAAO,IAAI,QAAQ,CAAC,WAAW,KAAK,YAAY,EAAE,CAAC;YACtF,OAAO,OAAO,CAAC;QACjB,CAAC;QACD,qFAAqF;QACrF,mFAAmF;QACnF,0BAA0B;QAC1B,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE,EAAE,KAAK,EAAE,IAAI,UAAU,CAAC,KAAK,CAAC,EAAE,WAAW,EAAE,YAAY,EAAE,OAAO,EAAE,CAAC,CAAC;QAC1F,OAAO,OAAO,CAAC;IACjB,CAAC;IAED,KAAK,CAAC,UAAU,CAAC,GAAY,EAAE,UAAyB,EAAE;QACxD,iBAAiB,CAAC,GAAG,CAAC,CAAC;QACvB,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACrC,MAAM,OAAO,GAAG,QAAQ,EAAE,OAAO,IAAI,IAAI,CAAC;QAC1C,IAAI,OAAO,KAAK,IAAI;YAAE,OAAO,KAAK,CAAC,CAAC,4CAA4C;QAChF,IAAI,OAAO,CAAC,eAAe,KAAK,SAAS,IAAI,OAAO,KAAK,OAAO,CAAC,eAAe,EAAE,CAAC;YACjF,MAAM,IAAI,eAAe,CAAC,GAAG,EAAE,OAAO,CAAC,eAAe,EAAE,OAAO,CAAC,CAAC;QACnE,CAAC;QACD,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QACvB,OAAO,IAAI,CAAC;IACd,CAAC;IAED,KAAK,CAAC,UAAU,CAAC,GAAY;QAC3B,iBAAiB,CAAC,GAAG,CAAC,CAAC;QACvB,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IAC7B,CAAC;IAED,KAAK,CAAC,SAAS,CAAC,MAAe;QAC7B,MAAM,IAAI,GAAG,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,MAAM,IAAI,CAAC,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC;QACnF,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,CAAC;QACxC,OAAO,IAAI,CAAC;IACd,CAAC;CACF"}
@@ -0,0 +1,19 @@
1
+ import type { Frontmatter } from "./types.js";
2
+ /** Producer-qualified portable projection of the actor behind the latest Superbee mutation. */
3
+ export declare const SUPERBEE_UPDATED_BY_FIELD = "superbee_updated_by";
4
+ /**
5
+ * Resolve advisory mutation attribution from document bytes.
6
+ *
7
+ * The producer-qualified v0.2 extension wins. The remaining spellings preserve permissive
8
+ * consumption of documents written before the extension existed.
9
+ */
10
+ export declare function mutationActorFromFrontmatter(frontmatter: Readonly<Record<string, unknown>>): string | undefined;
11
+ export interface PersistMutationActorOptions {
12
+ actor: string | undefined;
13
+ okfVersion: "0.1" | "0.2";
14
+ /** A v0.2 Kind may independently require the legacy-named `actor` field. */
15
+ kindRequiresActor: boolean;
16
+ }
17
+ /** Apply the edition-appropriate portable mutation-attribution coordinate. */
18
+ export declare function persistMutationActor(frontmatter: Frontmatter, options: PersistMutationActorOptions): Frontmatter;
19
+ //# sourceMappingURL=mutation-attribution.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mutation-attribution.d.ts","sourceRoot":"","sources":["../src/mutation-attribution.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAE9C,+FAA+F;AAC/F,eAAO,MAAM,yBAAyB,wBAAwB,CAAC;AAM/D;;;;;GAKG;AACH,wBAAgB,4BAA4B,CAC1C,WAAW,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,GAC7C,MAAM,GAAG,SAAS,CAIpB;AAED,MAAM,WAAW,2BAA2B;IAC1C,KAAK,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1B,UAAU,EAAE,KAAK,GAAG,KAAK,CAAC;IAC1B,4EAA4E;IAC5E,iBAAiB,EAAE,OAAO,CAAC;CAC5B;AAED,8EAA8E;AAC9E,wBAAgB,oBAAoB,CAClC,WAAW,EAAE,WAAW,EACxB,OAAO,EAAE,2BAA2B,GACnC,WAAW,CAcb"}
@@ -0,0 +1,34 @@
1
+ /** Producer-qualified portable projection of the actor behind the latest Superbee mutation. */
2
+ export const SUPERBEE_UPDATED_BY_FIELD = "superbee_updated_by";
3
+ function nonEmptyString(value) {
4
+ return typeof value === "string" && value.trim() !== "" ? value.trim() : undefined;
5
+ }
6
+ /**
7
+ * Resolve advisory mutation attribution from document bytes.
8
+ *
9
+ * The producer-qualified v0.2 extension wins. The remaining spellings preserve permissive
10
+ * consumption of documents written before the extension existed.
11
+ */
12
+ export function mutationActorFromFrontmatter(frontmatter) {
13
+ return nonEmptyString(frontmatter[SUPERBEE_UPDATED_BY_FIELD])
14
+ ?? nonEmptyString(frontmatter.updated_by)
15
+ ?? nonEmptyString(frontmatter.actor);
16
+ }
17
+ /** Apply the edition-appropriate portable mutation-attribution coordinate. */
18
+ export function persistMutationActor(frontmatter, options) {
19
+ if (options.actor === undefined) {
20
+ if (options.okfVersion === "0.1" || !(SUPERBEE_UPDATED_BY_FIELD in frontmatter)) {
21
+ return frontmatter;
22
+ }
23
+ const { [SUPERBEE_UPDATED_BY_FIELD]: _previousActor, ...rest } = frontmatter;
24
+ return rest;
25
+ }
26
+ if (options.okfVersion === "0.1")
27
+ return { ...frontmatter, actor: options.actor };
28
+ return {
29
+ ...frontmatter,
30
+ [SUPERBEE_UPDATED_BY_FIELD]: options.actor,
31
+ ...(options.kindRequiresActor ? { actor: options.actor } : {}),
32
+ };
33
+ }
34
+ //# sourceMappingURL=mutation-attribution.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mutation-attribution.js","sourceRoot":"","sources":["../src/mutation-attribution.ts"],"names":[],"mappings":"AAEA,+FAA+F;AAC/F,MAAM,CAAC,MAAM,yBAAyB,GAAG,qBAAqB,CAAC;AAE/D,SAAS,cAAc,CAAC,KAAc;IACpC,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC;AACrF,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,4BAA4B,CAC1C,WAA8C;IAE9C,OAAO,cAAc,CAAC,WAAW,CAAC,yBAAyB,CAAC,CAAC;WACxD,cAAc,CAAC,WAAW,CAAC,UAAU,CAAC;WACtC,cAAc,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC;AACzC,CAAC;AASD,8EAA8E;AAC9E,MAAM,UAAU,oBAAoB,CAClC,WAAwB,EACxB,OAAoC;IAEpC,IAAI,OAAO,CAAC,KAAK,KAAK,SAAS,EAAE,CAAC;QAChC,IAAI,OAAO,CAAC,UAAU,KAAK,KAAK,IAAI,CAAC,CAAC,yBAAyB,IAAI,WAAW,CAAC,EAAE,CAAC;YAChF,OAAO,WAAW,CAAC;QACrB,CAAC;QACD,MAAM,EAAE,CAAC,yBAAyB,CAAC,EAAE,cAAc,EAAE,GAAG,IAAI,EAAE,GAAG,WAAW,CAAC;QAC7E,OAAO,IAAmB,CAAC;IAC7B,CAAC;IACD,IAAI,OAAO,CAAC,UAAU,KAAK,KAAK;QAAE,OAAO,EAAE,GAAG,WAAW,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,EAAE,CAAC;IAClF,OAAO;QACL,GAAG,WAAW;QACd,CAAC,yBAAyB,CAAC,EAAE,OAAO,CAAC,KAAK;QAC1C,GAAG,CAAC,OAAO,CAAC,iBAAiB,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KAC/D,CAAC;AACJ,CAAC"}
@@ -0,0 +1,89 @@
1
+ /**
2
+ * The ONE versioned-mutation boundary: every read-modify-write over this engine's
3
+ * compare-and-swap seam for one independently retryable document/link mutation reduces to
4
+ * the SAME shape — a fresh versioned
5
+ * read, a domain decision over exactly that read, and a CAS write bounded-retried on
6
+ * conflict. This module is that shape, extracted once so the retry loop cannot drift
7
+ * between the prior hand-rolled document overwrite/patch and `link add` copies — see
8
+ * the single-authority contract: ONE
9
+ * implementation, consumed everywhere, not a schema fork.
10
+ *
11
+ * The structural property that kills the "stale decision paired with a fresher CAS token"
12
+ * bug class: {@link VersionedMutationOptions.decide} is handed ONLY the state a fresh
13
+ * {@link VersionedMutationOptions.read} just produced — never a version token, never a
14
+ * previous attempt's state — and {@link VersionedMutationOptions.write} CASes with THAT
15
+ * SAME read's version. A caller cannot compute a decision once and merely retry the WRITE
16
+ * with a newer token; each attempt re-derives its decision from its own fresh read.
17
+ *
18
+ * What this guarantees is VERSION-safety (a decision is always paired with the read that
19
+ * produced it), NOT domain-invariant-safety. `decide` receiving a fresh `state` every attempt
20
+ * means stale BYTES are structurally impossible to act on — it does NOT mean a consumer's
21
+ * decision logic automatically re-checks whatever assumption it made about that state BEFORE
22
+ * the retry loop started. A `decide` that only re-derives PART of its decision from `state`
23
+ * (e.g. re-splicing a field list while silently trusting an invariant — "this doc still
24
+ * governs kind X" — that was true when the caller looked the doc up, but is not re-verified
25
+ * against THIS attempt's fresh read) can still commit a change that is well-formed CAS-wise
26
+ * and wrong domain-wise. Every consumer's `decide` MUST derive its ENTIRE decision — including
27
+ * re-checking any mutable invariant it depends on — from the `state` it was just handed, not
28
+ * from anything captured before the loop began; the primitive cannot enforce this for a
29
+ * caller. For example, `kind.ts`'s `buildCandidate` re-verifies `governs` on every attempt; merely
30
+ * re-splicing field lists would not prove that the convention still governs the kind being edited.
31
+ */
32
+ import type { Version } from "./types.js";
33
+ /**
34
+ * What {@link VersionedMutationOptions.decide} resolves a single attempt to: either commit
35
+ * `next` (CAS write against this attempt's read version) or converge with no write at all
36
+ * (`done` — e.g. an idempotent no-op, or a retry that discovers a competing writer already
37
+ * made the same change).
38
+ */
39
+ export type MutationDecision<S, R> = {
40
+ action: "write";
41
+ next: S;
42
+ result: R;
43
+ } | {
44
+ action: "done";
45
+ result: R;
46
+ };
47
+ /** Inputs to a {@link versionedMutation} call, parameterized over the mutated state `S` and the caller's result shape `R`. */
48
+ export interface VersionedMutationOptions<S, R> {
49
+ /** A FRESH versioned read, called once per attempt. `version: null` means the state is absent (an expect-absent CAS basis). */
50
+ read: () => Promise<{
51
+ state: S | undefined;
52
+ version: Version | null;
53
+ }>;
54
+ /**
55
+ * Re-run against EVERY attempt's fresh `state` — never a previous attempt's decision, never a
56
+ * version token (decisions are made over domain state only; CAS pairing is this module's job,
57
+ * not the caller's). Safe to call more than once; may throw a terminal domain error, which
58
+ * propagates out of {@link versionedMutation} unchanged (not retried).
59
+ */
60
+ decide: (state: S | undefined, attempt: number) => MutationDecision<S, R> | Promise<MutationDecision<S, R>>;
61
+ /** CAS-write `next`, conditional on THIS attempt's read version (`null` = expect-absent create). Returns the new version. */
62
+ write: (next: S, expectedVersion: Version | null) => Promise<Version>;
63
+ /** Bounded retry budget. Default 5. `1` makes a conflict terminal (hard single-shot CAS, no retry). */
64
+ maxAttempts?: number;
65
+ }
66
+ /** The result of a {@link versionedMutation} call. */
67
+ export interface VersionedMutationOutcome<R> {
68
+ /** The winning attempt's decision result. */
69
+ result: R;
70
+ /** The version current after this call: the write's new version, or (a `done` convergence) the read's version. */
71
+ version: Version | null;
72
+ /** `true` if a write actually happened; `false` for a `done` convergence. */
73
+ wrote: boolean;
74
+ }
75
+ /**
76
+ * Run one versioned read-decide-write cycle, retrying on a {@link VersionConflict} up to
77
+ * `maxAttempts` (default 5). Each attempt: (1) one fresh `read`; (2) `decide` against THAT
78
+ * state only; (3) `done` returns immediately with no write; (4) `write` CASes with THAT
79
+ * attempt's own version; (5) a `VersionConflict` retries (fresh read, fresh decide) while
80
+ * budget remains, else the final `VersionConflict` is RETHROWN unchanged — callers map it
81
+ * to their own conflict shape (e.g. `STALE_HEAD`, exit 5); (6) a `ConcurrentReplacementError`
82
+ * from `read` or `write` (the filesystem adapter could not bind the attempt to one safe entry) is
83
+ * the same contention in filesystem clothing and retries the COMPLETE fresh read/decide/CAS cycle
84
+ * on the SAME attempt budget, so
85
+ * `maxAttempts: 1` makes it terminal too; (7) any other thrown error is terminal and propagates
86
+ * unchanged.
87
+ */
88
+ export declare function versionedMutation<S, R>(opts: VersionedMutationOptions<S, R>): Promise<VersionedMutationOutcome<R>>;
89
+ //# sourceMappingURL=mutation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mutation.d.ts","sourceRoot":"","sources":["../src/mutation.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAMH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAe1C;;;;;GAKG;AACH,MAAM,MAAM,gBAAgB,CAAC,CAAC,EAAE,CAAC,IAC7B;IAAE,MAAM,EAAE,OAAO,CAAC;IAAC,IAAI,EAAE,CAAC,CAAC;IAAC,MAAM,EAAE,CAAC,CAAA;CAAE,GACvC;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,CAAC,CAAA;CAAE,CAAC;AAElC,8HAA8H;AAC9H,MAAM,WAAW,wBAAwB,CAAC,CAAC,EAAE,CAAC;IAC5C,+HAA+H;IAC/H,IAAI,EAAE,MAAM,OAAO,CAAC;QAAE,KAAK,EAAE,CAAC,GAAG,SAAS,CAAC;QAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAAA;KAAE,CAAC,CAAC;IACvE;;;;;OAKG;IACH,MAAM,EAAE,CAAC,KAAK,EAAE,CAAC,GAAG,SAAS,EAAE,OAAO,EAAE,MAAM,KAAK,gBAAgB,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;IAC5G,6HAA6H;IAC7H,KAAK,EAAE,CAAC,IAAI,EAAE,CAAC,EAAE,eAAe,EAAE,OAAO,GAAG,IAAI,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IACtE,uGAAuG;IACvG,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,sDAAsD;AACtD,MAAM,WAAW,wBAAwB,CAAC,CAAC;IACzC,6CAA6C;IAC7C,MAAM,EAAE,CAAC,CAAC;IACV,kHAAkH;IAClH,OAAO,EAAE,OAAO,GAAG,IAAI,CAAC;IACxB,6EAA6E;IAC7E,KAAK,EAAE,OAAO,CAAC;CAChB;AAED;;;;;;;;;;;;GAYG;AACH,wBAAsB,iBAAiB,CAAC,CAAC,EAAE,CAAC,EAC1C,IAAI,EAAE,wBAAwB,CAAC,CAAC,EAAE,CAAC,CAAC,GACnC,OAAO,CAAC,wBAAwB,CAAC,CAAC,CAAC,CAAC,CA8BtC"}
@@ -0,0 +1,94 @@
1
+ /**
2
+ * The ONE versioned-mutation boundary: every read-modify-write over this engine's
3
+ * compare-and-swap seam for one independently retryable document/link mutation reduces to
4
+ * the SAME shape — a fresh versioned
5
+ * read, a domain decision over exactly that read, and a CAS write bounded-retried on
6
+ * conflict. This module is that shape, extracted once so the retry loop cannot drift
7
+ * between the prior hand-rolled document overwrite/patch and `link add` copies — see
8
+ * the single-authority contract: ONE
9
+ * implementation, consumed everywhere, not a schema fork.
10
+ *
11
+ * The structural property that kills the "stale decision paired with a fresher CAS token"
12
+ * bug class: {@link VersionedMutationOptions.decide} is handed ONLY the state a fresh
13
+ * {@link VersionedMutationOptions.read} just produced — never a version token, never a
14
+ * previous attempt's state — and {@link VersionedMutationOptions.write} CASes with THAT
15
+ * SAME read's version. A caller cannot compute a decision once and merely retry the WRITE
16
+ * with a newer token; each attempt re-derives its decision from its own fresh read.
17
+ *
18
+ * What this guarantees is VERSION-safety (a decision is always paired with the read that
19
+ * produced it), NOT domain-invariant-safety. `decide` receiving a fresh `state` every attempt
20
+ * means stale BYTES are structurally impossible to act on — it does NOT mean a consumer's
21
+ * decision logic automatically re-checks whatever assumption it made about that state BEFORE
22
+ * the retry loop started. A `decide` that only re-derives PART of its decision from `state`
23
+ * (e.g. re-splicing a field list while silently trusting an invariant — "this doc still
24
+ * governs kind X" — that was true when the caller looked the doc up, but is not re-verified
25
+ * against THIS attempt's fresh read) can still commit a change that is well-formed CAS-wise
26
+ * and wrong domain-wise. Every consumer's `decide` MUST derive its ENTIRE decision — including
27
+ * re-checking any mutable invariant it depends on — from the `state` it was just handed, not
28
+ * from anything captured before the loop began; the primitive cannot enforce this for a
29
+ * caller. For example, `kind.ts`'s `buildCandidate` re-verifies `governs` on every attempt; merely
30
+ * re-splicing field lists would not prove that the convention still governs the kind being edited.
31
+ */
32
+ import { setTimeout as delay } from "node:timers/promises";
33
+ import { ConcurrentReplacementError } from "./errors.js";
34
+ import { VersionConflict } from "./versioning.js";
35
+ /** Bounded compare-and-swap retry budget for a {@link versionedMutation} call, unless overridden. */
36
+ const CAS_MAX_ATTEMPTS = 5;
37
+ /**
38
+ * Honor a host-provided transient-contention hint between complete attempts. The attempt-indexed
39
+ * growth restores useful Windows sharing-violation tolerance without ever retrying the unsafe
40
+ * rename itself; ordinary observation replacement has no delay hint and retries immediately.
41
+ */
42
+ async function waitForFreshAttempt(error, attempt) {
43
+ if (error.retryAfterMs <= 0)
44
+ return;
45
+ await delay(error.retryAfterMs * (4 ** attempt));
46
+ }
47
+ /**
48
+ * Run one versioned read-decide-write cycle, retrying on a {@link VersionConflict} up to
49
+ * `maxAttempts` (default 5). Each attempt: (1) one fresh `read`; (2) `decide` against THAT
50
+ * state only; (3) `done` returns immediately with no write; (4) `write` CASes with THAT
51
+ * attempt's own version; (5) a `VersionConflict` retries (fresh read, fresh decide) while
52
+ * budget remains, else the final `VersionConflict` is RETHROWN unchanged — callers map it
53
+ * to their own conflict shape (e.g. `STALE_HEAD`, exit 5); (6) a `ConcurrentReplacementError`
54
+ * from `read` or `write` (the filesystem adapter could not bind the attempt to one safe entry) is
55
+ * the same contention in filesystem clothing and retries the COMPLETE fresh read/decide/CAS cycle
56
+ * on the SAME attempt budget, so
57
+ * `maxAttempts: 1` makes it terminal too; (7) any other thrown error is terminal and propagates
58
+ * unchanged.
59
+ */
60
+ export async function versionedMutation(opts) {
61
+ const maxAttempts = opts.maxAttempts ?? CAS_MAX_ATTEMPTS;
62
+ for (let attempt = 0;; attempt++) {
63
+ let fresh;
64
+ try {
65
+ fresh = await opts.read();
66
+ }
67
+ catch (err) {
68
+ if (err instanceof ConcurrentReplacementError && attempt < maxAttempts - 1) {
69
+ await waitForFreshAttempt(err, attempt);
70
+ continue;
71
+ }
72
+ throw err;
73
+ }
74
+ const { state, version } = fresh;
75
+ const decision = await opts.decide(state, attempt);
76
+ if (decision.action === "done") {
77
+ return { result: decision.result, version, wrote: false };
78
+ }
79
+ try {
80
+ const newVersion = await opts.write(decision.next, version);
81
+ return { result: decision.result, version: newVersion, wrote: true };
82
+ }
83
+ catch (err) {
84
+ if (err instanceof VersionConflict && attempt < maxAttempts - 1)
85
+ continue;
86
+ if (err instanceof ConcurrentReplacementError && attempt < maxAttempts - 1) {
87
+ await waitForFreshAttempt(err, attempt);
88
+ continue;
89
+ }
90
+ throw err;
91
+ }
92
+ }
93
+ }
94
+ //# sourceMappingURL=mutation.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mutation.js","sourceRoot":"","sources":["../src/mutation.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAEH,OAAO,EAAE,UAAU,IAAI,KAAK,EAAE,MAAM,sBAAsB,CAAC;AAE3D,OAAO,EAAE,0BAA0B,EAAE,MAAM,aAAa,CAAC;AACzD,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAGlD,qGAAqG;AACrG,MAAM,gBAAgB,GAAG,CAAC,CAAC;AAE3B;;;;GAIG;AACH,KAAK,UAAU,mBAAmB,CAAC,KAAiC,EAAE,OAAe;IACnF,IAAI,KAAK,CAAC,YAAY,IAAI,CAAC;QAAE,OAAO;IACpC,MAAM,KAAK,CAAC,KAAK,CAAC,YAAY,GAAG,CAAC,CAAC,IAAI,OAAO,CAAC,CAAC,CAAC;AACnD,CAAC;AAuCD;;;;;;;;;;;;GAYG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,IAAoC;IAEpC,MAAM,WAAW,GAAG,IAAI,CAAC,WAAW,IAAI,gBAAgB,CAAC;IACzD,KAAK,IAAI,OAAO,GAAG,CAAC,GAAI,OAAO,EAAE,EAAE,CAAC;QAClC,IAAI,KAAwD,CAAC;QAC7D,IAAI,CAAC;YACH,KAAK,GAAG,MAAM,IAAI,CAAC,IAAI,EAAE,CAAC;QAC5B,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,IAAI,GAAG,YAAY,0BAA0B,IAAI,OAAO,GAAG,WAAW,GAAG,CAAC,EAAE,CAAC;gBAC3E,MAAM,mBAAmB,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;gBACxC,SAAS;YACX,CAAC;YACD,MAAM,GAAG,CAAC;QACZ,CAAC;QACD,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,GAAG,KAAK,CAAC;QACjC,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;QACnD,IAAI,QAAQ,CAAC,MAAM,KAAK,MAAM,EAAE,CAAC;YAC/B,OAAO,EAAE,MAAM,EAAE,QAAQ,CAAC,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC;QAC5D,CAAC;QACD,IAAI,CAAC;YACH,MAAM,UAAU,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;YAC5D,OAAO,EAAE,MAAM,EAAE,QAAQ,CAAC,MAAM,EAAE,OAAO,EAAE,UAAU,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;QACvE,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,IAAI,GAAG,YAAY,eAAe,IAAI,OAAO,GAAG,WAAW,GAAG,CAAC;gBAAE,SAAS;YAC1E,IAAI,GAAG,YAAY,0BAA0B,IAAI,OAAO,GAAG,WAAW,GAAG,CAAC,EAAE,CAAC;gBAC3E,MAAM,mBAAmB,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;gBACxC,SAAS;YACX,CAAC;YACD,MAAM,GAAG,CAAC;QACZ,CAAC;IACH,CAAC;AACH,CAAC"}
package/dist/page.d.ts ADDED
@@ -0,0 +1,87 @@
1
+ /**
2
+ * Pure View registry/entry path grammar shared by every producer and consumer.
3
+ *
4
+ * `View` is the ONLY accepted kind name: the legacy `Page` spelling is no longer read
5
+ * (the repo's `migrate-legacy-view-names` script renames legacy content in place). Registry ids
6
+ * are concept ids under `views-registry/` (legacy `pages-registry/` LOCATIONS stay recognized;
7
+ * relocation is a separate open decision); entries are opaque blob keys under
8
+ * `views/` (legacy `pages/`). Both retain exact, case-preserving nested paths while
9
+ * rejecting spellings that discovery or storage cannot safely round-trip. ONE segment
10
+ * grammar, parameterized by prefix — never a parallel module per name.
11
+ */
12
+ /** Legacy registry-id prefix — the LOCATION stays recognized (only the legacy kind NAME is retired). */
13
+ export declare const PAGE_REGISTRY_PREFIX = "pages-registry/";
14
+ /** Legacy blob-key prefix for View entries — the LOCATION stays recognized. */
15
+ export declare const PAGE_ENTRY_PREFIX = "pages/";
16
+ /** Current registry-id prefix for `type: View` docs. */
17
+ export declare const VIEW_REGISTRY_PREFIX = "views-registry/";
18
+ /** Current blob-key prefix for View entries. */
19
+ export declare const VIEW_ENTRY_PREFIX = "views/";
20
+ /**
21
+ * The kind names the launcher/registry surfaces accept: exactly `View`. Exact, case-sensitive
22
+ * match. The legacy `Page` name is deliberately NOT here — a Page-typed doc no longer registers
23
+ * anywhere (the CLI's `status` legacy_naming finding is the loud diagnostic for leftover stock,
24
+ * and `migrate-legacy-view-names` is the remedy).
25
+ */
26
+ export declare const PAGE_TYPE_NAMES: readonly ["View"];
27
+ export type PageTypeName = (typeof PAGE_TYPE_NAMES)[number];
28
+ /**
29
+ * The shell-enforced capability requested by a registered View. `bundle-propose` includes the
30
+ * read surface but grants no direct write authority: it may only propose a narrow action for the
31
+ * trusted shell and human to confirm. Unknown values fail closed to `none`.
32
+ */
33
+ export type BridgeCapability = "none" | "bundle-read" | "bundle-propose";
34
+ /** One capability resolver shared by registry parsing and the loopback server's security gates. */
35
+ export declare function resolveBridgeCapability(value: unknown): BridgeCapability;
36
+ /**
37
+ * THE one reader of the registry-doc capability FIELD: `access`, and ONLY `access`. The legacy
38
+ * `bridge` spelling is no longer read — a doc declaring only `bridge` resolves to `undefined`
39
+ * here and fails closed to `none` downstream (the `migrate-legacy-view-names` script renames
40
+ * existing stock in place; the CLI's `status` legacy_naming finding flags leftover `bridge`
41
+ * fields loudly).
42
+ */
43
+ export declare function declaredAccessValue(frontmatter: Record<string, unknown>): unknown;
44
+ /** Resolve a registry doc's declared capability: {@link declaredAccessValue} through the fail-closed {@link resolveBridgeCapability} — an absent or unrecognized `access` yields `none`. */
45
+ export declare function resolveDeclaredAccess(frontmatter: Record<string, unknown>): BridgeCapability;
46
+ /** True iff `value` is exactly the accepted kind name (`View`). The legacy `Page` name is rejected. */
47
+ export declare function isPageTypeName(value: unknown): value is PageTypeName;
48
+ /** Strict concept-id grammar for registry documents at the legacy LOCATION (`pages-registry/…`). Nested ids and ordinary dots are valid. */
49
+ export declare function isPageRegistryId(id: unknown): id is string;
50
+ /** Strict concept-id grammar for View registry documents (`views-registry/…`) — the SAME segment rules as {@link isPageRegistryId}. */
51
+ export declare function isViewRegistryId(id: unknown): id is string;
52
+ /** True iff `id` is a valid registry id under EITHER accepted prefix (`views-registry/` or legacy `pages-registry/`). */
53
+ export declare function isAnyRegistryId(id: unknown): id is string;
54
+ /** Strict blob-key grammar for executable View entries at the legacy LOCATION (`pages/…`). */
55
+ export declare function isPageEntryKey(entry: unknown): entry is string;
56
+ /** Strict blob-key grammar for executable View entries (`views/…`) — the SAME segment rules as {@link isPageEntryKey}. */
57
+ export declare function isViewEntryKey(entry: unknown): entry is string;
58
+ /** True iff `entry` is a valid entry key under EITHER accepted prefix (`views/` or legacy `pages/`). */
59
+ export declare function isAnyEntryKey(entry: unknown): entry is string;
60
+ /** True only for the content-addressed version tokens allowed to pin executable View bytes. */
61
+ export declare function isViewEntryVersion(value: unknown): value is string;
62
+ /** A complete, valid View registration — one identity, entry, and optional exact-byte pin. */
63
+ export interface PageRegistration {
64
+ /** The registry doc's concept id (under an accepted registry prefix). */
65
+ id: string;
66
+ /** The accepted kind name the doc declares — exactly `View`. */
67
+ type: PageTypeName;
68
+ /** The declared executable entry blob key (under an accepted entry prefix). */
69
+ entry: string;
70
+ /** Optional exact blob-version pin. When present, every host must refuse other bytes. */
71
+ entryVersion?: string;
72
+ }
73
+ /**
74
+ * THE one registration predicate: a doc is a usable View registration iff its id satisfies
75
+ * an accepted registry-id grammar ({@link isAnyRegistryId}), its `type` is exactly an accepted
76
+ * kind name ({@link isPageTypeName}), AND its `entry` satisfies an accepted entry-key grammar
77
+ * ({@link isAnyEntryKey}). An explicitly declared `entry_version` must be a canonical
78
+ * content-addressed token. Returns the validated registration, or `null`.
79
+ *
80
+ * This is a SECURITY boundary shared by every surface that decides what counts as a registered
81
+ * page — the launcher/`open-page` parse (ui `parseRegisteredPage`), the `ui` command's
82
+ * nonce-mint allowlist, and its serve-time re-verification. All of them MUST consume this one
83
+ * function: a doc any surface rejects must be un-mintable and un-servable everywhere, never
84
+ * "rejected by the launcher but still served by the nonce route".
85
+ */
86
+ export declare function parseRegistration(id: unknown, frontmatter: Record<string, unknown>): PageRegistration | null;
87
+ //# sourceMappingURL=page.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"page.d.ts","sourceRoot":"","sources":["../src/page.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAIH,wGAAwG;AACxG,eAAO,MAAM,oBAAoB,oBAAoB,CAAC;AACtD,+EAA+E;AAC/E,eAAO,MAAM,iBAAiB,WAAW,CAAC;AAC1C,wDAAwD;AACxD,eAAO,MAAM,oBAAoB,oBAAoB,CAAC;AACtD,gDAAgD;AAChD,eAAO,MAAM,iBAAiB,WAAW,CAAC;AAE1C;;;;;GAKG;AACH,eAAO,MAAM,eAAe,mBAAoB,CAAC;AACjD,MAAM,MAAM,YAAY,GAAG,CAAC,OAAO,eAAe,CAAC,CAAC,MAAM,CAAC,CAAC;AAE5D;;;;GAIG;AACH,MAAM,MAAM,gBAAgB,GAAG,MAAM,GAAG,aAAa,GAAG,gBAAgB,CAAC;AAEzE,mGAAmG;AACnG,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,OAAO,GAAG,gBAAgB,CAExE;AAED;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CAAC,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAIjF;AAED,4LAA4L;AAC5L,wBAAgB,qBAAqB,CAAC,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,gBAAgB,CAE5F;AAED,uGAAuG;AACvG,wBAAgB,cAAc,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,YAAY,CAEpE;AA4CD,4IAA4I;AAC5I,wBAAgB,gBAAgB,CAAC,EAAE,EAAE,OAAO,GAAG,EAAE,IAAI,MAAM,CAE1D;AAED,uIAAuI;AACvI,wBAAgB,gBAAgB,CAAC,EAAE,EAAE,OAAO,GAAG,EAAE,IAAI,MAAM,CAE1D;AAED,yHAAyH;AACzH,wBAAgB,eAAe,CAAC,EAAE,EAAE,OAAO,GAAG,EAAE,IAAI,MAAM,CAEzD;AAED,8FAA8F;AAC9F,wBAAgB,cAAc,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,MAAM,CAE9D;AAED,0HAA0H;AAC1H,wBAAgB,cAAc,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,MAAM,CAE9D;AAED,wGAAwG;AACxG,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,MAAM,CAE7D;AAED,+FAA+F;AAC/F,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,MAAM,CAElE;AAED,8FAA8F;AAC9F,MAAM,WAAW,gBAAgB;IAC/B,yEAAyE;IACzE,EAAE,EAAE,MAAM,CAAC;IACX,gEAAgE;IAChE,IAAI,EAAE,YAAY,CAAC;IACnB,+EAA+E;IAC/E,KAAK,EAAE,MAAM,CAAC;IACd,yFAAyF;IACzF,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,iBAAiB,CAAC,EAAE,EAAE,OAAO,EAAE,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,gBAAgB,GAAG,IAAI,CAiB5G"}