@noy-db/hub 0.7.0-pre.11 → 0.7.0-pre.13

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 (98) hide show
  1. package/CHANGELOG.md +142 -0
  2. package/dist/as/index.js +3 -2
  3. package/dist/as/index.js.map +1 -1
  4. package/dist/{backup-VASRVLDZ.js → backup-R6A4VZRX.js} +4 -4
  5. package/dist/cargo/index.js +2 -2
  6. package/dist/chunk-66TQOPFM.js +37 -0
  7. package/dist/chunk-66TQOPFM.js.map +1 -0
  8. package/dist/{chunk-OEA5NVSU.js → chunk-AJQY44CE.js} +86 -6
  9. package/dist/chunk-AJQY44CE.js.map +1 -0
  10. package/dist/{chunk-S5AMPTNE.js → chunk-BMELYFHU.js} +2 -2
  11. package/dist/{chunk-T6PSWLAR.js → chunk-BUE2D7PX.js} +25 -2
  12. package/dist/chunk-BUE2D7PX.js.map +1 -0
  13. package/dist/chunk-BWXGSSFX.js +1589 -0
  14. package/dist/chunk-BWXGSSFX.js.map +1 -0
  15. package/dist/{chunk-HN4FNA4X.js → chunk-COTPDJR5.js} +8 -8
  16. package/dist/{chunk-E3EBMC4G.js → chunk-E2MYJGSG.js} +2 -2
  17. package/dist/{chunk-JJR2JIN4.js → chunk-FFBYRWDJ.js} +2 -2
  18. package/dist/{chunk-WQF3QABE.js → chunk-IHFXR2VQ.js} +9 -79
  19. package/dist/chunk-IHFXR2VQ.js.map +1 -0
  20. package/dist/chunk-MGOXJIBV.js +176 -0
  21. package/dist/chunk-MGOXJIBV.js.map +1 -0
  22. package/dist/chunk-PDD75NUZ.js +87 -0
  23. package/dist/chunk-PDD75NUZ.js.map +1 -0
  24. package/dist/chunk-RTBJ5IOO.js +87 -0
  25. package/dist/chunk-RTBJ5IOO.js.map +1 -0
  26. package/dist/chunk-RXLYKVTF.js +17 -0
  27. package/dist/chunk-RXLYKVTF.js.map +1 -0
  28. package/dist/{chunk-ESVREVWN.js → chunk-SXCYYMVS.js} +2 -2
  29. package/dist/chunk-TFMM5DBB.js +20 -0
  30. package/dist/chunk-TFMM5DBB.js.map +1 -0
  31. package/dist/chunk-TPHAGLUH.js +30 -0
  32. package/dist/chunk-TPHAGLUH.js.map +1 -0
  33. package/dist/{chunk-THNJ3IKU.js → chunk-ULUMAZ5S.js} +2 -2
  34. package/dist/{chunk-NO272T7Q.js → chunk-V4PXLYWM.js} +2 -31
  35. package/dist/chunk-V4PXLYWM.js.map +1 -0
  36. package/dist/{chunk-A5WZEPXJ.js → chunk-VGICZPVE.js} +5 -3
  37. package/dist/{chunk-A5WZEPXJ.js.map → chunk-VGICZPVE.js.map} +1 -1
  38. package/dist/{chunk-KHKKD62N.js → chunk-VYYM3MEN.js} +2 -16
  39. package/dist/chunk-VYYM3MEN.js.map +1 -0
  40. package/dist/{chunk-SWVNZQVR.js → chunk-YPXNAP72.js} +2 -169
  41. package/dist/chunk-YPXNAP72.js.map +1 -0
  42. package/dist/{chunk-I6DGKPTY.js → chunk-ZVCC36XQ.js} +101 -1273
  43. package/dist/chunk-ZVCC36XQ.js.map +1 -0
  44. package/dist/{collection-facade-MGGHQ3HU.js → collection-facade-4ACKIDDY.js} +4 -3
  45. package/dist/derivations/index.js +21 -6
  46. package/dist/dispatch-MSTR5VBI.js +255 -0
  47. package/dist/dispatch-MSTR5VBI.js.map +1 -0
  48. package/dist/{dispatch-44J4TL72.js → dispatch-PDP5TA73.js} +5 -5
  49. package/dist/{executor-KSPGNVDO.js → executor-YDDC5XGY.js} +4 -3
  50. package/dist/index.d.ts +2 -2
  51. package/dist/index.js +39 -35
  52. package/dist/index.js.map +1 -1
  53. package/dist/introspection/index.js +3 -2
  54. package/dist/kernel/collection.d.ts +11 -6
  55. package/dist/kernel/match-pairs.d.ts +30 -0
  56. package/dist/lazy/index.js +3 -2
  57. package/dist/materialized-views/index.js +4 -3
  58. package/dist/{noydb-QJFZAH34.js → noydb-2C6QQJ5S.js} +17 -12
  59. package/dist/pod/index.js +17 -12
  60. package/dist/registry-D2YXKRYC.js +33 -0
  61. package/dist/search/index.js +1 -1
  62. package/dist/{stale-KSPG6LFX.js → stale-2CHKZTMK.js} +2 -2
  63. package/dist/{walk-G3CZIL4L.js → walk-P3E6QSNC.js} +4 -3
  64. package/dist/with-formula/derivations/dispatch.d.ts +16 -1
  65. package/dist/with-formula/derivations/registry.d.ts +20 -6
  66. package/dist/with-formula/derivations/trigger-match.d.ts +52 -0
  67. package/dist/with-formula/derivations/types.d.ts +23 -0
  68. package/dist/with-shape/introspection/behaviors.d.ts +7 -0
  69. package/dist/with-shape/introspection/describe.d.ts +30 -0
  70. package/package.json +3 -3
  71. package/dist/chunk-I6DGKPTY.js.map +0 -1
  72. package/dist/chunk-KHKKD62N.js.map +0 -1
  73. package/dist/chunk-NO272T7Q.js.map +0 -1
  74. package/dist/chunk-OEA5NVSU.js.map +0 -1
  75. package/dist/chunk-SWVNZQVR.js.map +0 -1
  76. package/dist/chunk-T6PSWLAR.js.map +0 -1
  77. package/dist/chunk-UB5CRTFF.js +0 -344
  78. package/dist/chunk-UB5CRTFF.js.map +0 -1
  79. package/dist/chunk-WQF3QABE.js.map +0 -1
  80. package/dist/chunk-YPMW26SL.js +0 -81
  81. package/dist/chunk-YPMW26SL.js.map +0 -1
  82. package/dist/dispatch-IM52HDXT.js +0 -194
  83. package/dist/dispatch-IM52HDXT.js.map +0 -1
  84. package/dist/registry-FZXPQRAB.js +0 -18
  85. /package/dist/{backup-VASRVLDZ.js.map → backup-R6A4VZRX.js.map} +0 -0
  86. /package/dist/{chunk-S5AMPTNE.js.map → chunk-BMELYFHU.js.map} +0 -0
  87. /package/dist/{chunk-HN4FNA4X.js.map → chunk-COTPDJR5.js.map} +0 -0
  88. /package/dist/{chunk-E3EBMC4G.js.map → chunk-E2MYJGSG.js.map} +0 -0
  89. /package/dist/{chunk-JJR2JIN4.js.map → chunk-FFBYRWDJ.js.map} +0 -0
  90. /package/dist/{chunk-ESVREVWN.js.map → chunk-SXCYYMVS.js.map} +0 -0
  91. /package/dist/{chunk-THNJ3IKU.js.map → chunk-ULUMAZ5S.js.map} +0 -0
  92. /package/dist/{collection-facade-MGGHQ3HU.js.map → collection-facade-4ACKIDDY.js.map} +0 -0
  93. /package/dist/{dispatch-44J4TL72.js.map → dispatch-PDP5TA73.js.map} +0 -0
  94. /package/dist/{executor-KSPGNVDO.js.map → executor-YDDC5XGY.js.map} +0 -0
  95. /package/dist/{noydb-QJFZAH34.js.map → noydb-2C6QQJ5S.js.map} +0 -0
  96. /package/dist/{registry-FZXPQRAB.js.map → registry-D2YXKRYC.js.map} +0 -0
  97. /package/dist/{stale-KSPG6LFX.js.map → stale-2CHKZTMK.js.map} +0 -0
  98. /package/dist/{walk-G3CZIL4L.js.map → walk-P3E6QSNC.js.map} +0 -0
@@ -1,344 +0,0 @@
1
- import {
2
- DerivationCycleError,
3
- MaterializedViewCycleError
4
- } from "./chunk-7O3Y4PPD.js";
5
-
6
- // src/kernel/via/graph.ts
7
- var DEFAULT_POSTURE = { encryptedAtRest: "envelope", queryable: "full", exportable: true, forgettable: false };
8
- var QUERYABLE_RANK = {
9
- none: 0,
10
- "det-exact": 1,
11
- ordered: 2,
12
- full: 3
13
- };
14
- function foldPosture(a, b) {
15
- return {
16
- encryptedAtRest: a.encryptedAtRest === "sealed" || b.encryptedAtRest === "sealed" ? "sealed" : "envelope",
17
- queryable: QUERYABLE_RANK[a.queryable] <= QUERYABLE_RANK[b.queryable] ? a.queryable : b.queryable,
18
- exportable: a.exportable && b.exportable,
19
- forgettable: a.forgettable || b.forgettable
20
- };
21
- }
22
- function foldWildcardSecurity(base, contributor) {
23
- return {
24
- encryptedAtRest: base.encryptedAtRest === "sealed" || contributor.encryptedAtRest === "sealed" ? "sealed" : "envelope",
25
- queryable: base.queryable,
26
- exportable: base.exportable && contributor.exportable,
27
- forgettable: base.forgettable || contributor.forgettable
28
- };
29
- }
30
- var FOLDED_KINDS = /* @__PURE__ */ new Set(["derivation", "rollup", "mv", "overlay"]);
31
- function isDefaultPosture(p) {
32
- return p.encryptedAtRest === DEFAULT_POSTURE.encryptedAtRest && p.queryable === DEFAULT_POSTURE.queryable && p.exportable === DEFAULT_POSTURE.exportable && p.forgettable === DEFAULT_POSTURE.forgettable;
33
- }
34
- var SEP = "\0";
35
- var EMPTY_SET = /* @__PURE__ */ new Set();
36
- function nodeId(ref) {
37
- return `${ref.collection}${SEP}${ref.field}`;
38
- }
39
- function displayNodeId(id) {
40
- const sep = id.indexOf(SEP);
41
- return `${id.slice(0, sep)}.${id.slice(sep + 1)}`;
42
- }
43
- var ViaGraph = class {
44
- /** Declared source-field postures (registerField), keyed by node id. */
45
- _posture = /* @__PURE__ */ new Map();
46
- /** Every derived target's registration, keyed by the target's node id. */
47
- _in = /* @__PURE__ */ new Map();
48
- /** source node id -> derived edges that depend on it (dispatch/erasure), each carrying
49
- * its own registration's `kind` (#678 — see {@link OutEdge}). */
50
- _out = /* @__PURE__ */ new Map();
51
- /** Memoized effective posture per derived target node id. */
52
- _effectiveCache = /* @__PURE__ */ new Map();
53
- /** Memoized whole-collection security-axes fold for `'*'` wildcard LEAF nodes (#642),
54
- * keyed by collection name — see {@link _wildcardContribution}. Cleared alongside
55
- * `_effectiveCache` on every `registerField`/`registerDerived` (a later-registered
56
- * field must re-fold on the next read — registration ordering is free). */
57
- _wildcardCache = /* @__PURE__ */ new Map();
58
- /** Collections with at least one classified field (#638 Task 2 fix wave 2,
59
- * Finding I1 — the reconcile path's combined-state leak guard memory). */
60
- _classifiedCollections = /* @__PURE__ */ new Set();
61
- /** Computed field names declared with no `deps` entry, per collection
62
- * (Finding I1 — such a field registers no edge when no classified field is
63
- * present yet, so a LATER, separate reconcile call attaching classifiedFields
64
- * still needs to see it regardless of declaration order). */
65
- _depslessComputed = /* @__PURE__ */ new Map();
66
- /** Declare a source field's posture (money/i18n/classified/plain). Plain fields
67
- * default to `DEFAULT_POSTURE`; a later declaration for the same node wins-first (idempotent). */
68
- registerField(collection, field, posture) {
69
- const id = nodeId({ collection, field });
70
- if (this._posture.has(id)) return;
71
- this._posture.set(id, posture);
72
- this._effectiveCache.clear();
73
- this._wildcardCache.clear();
74
- }
75
- /** A derived target depends on `sources` (may be cross-collection). `kind`/`grain`
76
- * drive dispatch + erasure semantics; sources drive taint. Re-registering the same
77
- * target replaces `_in` but does not prune stale `_out` edges — callers must
78
- * register each target at most once per graph lifetime. `onDelete`/`keyField` (#650 Task 5)
79
- * are meaningful only for `kind === 'ref'` edges — {@link referencingEdgesOf} reads them back. */
80
- registerDerived(target, sources, kind, grain, onDelete, keyField) {
81
- const id = nodeId(target);
82
- this._in.set(id, {
83
- target,
84
- sources,
85
- kind,
86
- grain,
87
- ...onDelete !== void 0 ? { onDelete } : {},
88
- ...keyField !== void 0 ? { keyField } : {}
89
- });
90
- for (const source of sources) {
91
- const sourceId = nodeId(source);
92
- const outEdge = { target, kind };
93
- const dependents = this._out.get(sourceId);
94
- if (dependents) dependents.push(outEdge);
95
- else this._out.set(sourceId, [outEdge]);
96
- }
97
- this._effectiveCache.clear();
98
- this._wildcardCache.clear();
99
- }
100
- /** Whether `target` already has a registered in-edge — lets a caller skip
101
- * re-registering the same target (#638 Task 2 fix wave 2, Finding I2i:
102
- * `registerDerived`'s at-most-once contract must hold across repeated
103
- * identical `vault.collection()` calls, not just within a single one). */
104
- hasDerived(target) {
105
- return this._in.has(nodeId(target));
106
- }
107
- /** Mark/query a collection as having declared at least one classified field. */
108
- markClassified(collection) {
109
- this._classifiedCollections.add(collection);
110
- }
111
- isClassified(collection) {
112
- return this._classifiedCollections.has(collection);
113
- }
114
- /** Mark/query a collection's depsless (no declared `deps`) computed
115
- * field names — see `_depslessComputed`'s doc comment above. */
116
- markDepslessComputed(collection, field) {
117
- let set = this._depslessComputed.get(collection);
118
- if (!set) {
119
- set = /* @__PURE__ */ new Set();
120
- this._depslessComputed.set(collection, set);
121
- }
122
- set.add(field);
123
- }
124
- depslessComputedFields(collection) {
125
- return this._depslessComputed.get(collection) ?? EMPTY_SET;
126
- }
127
- /** Every field name the graph already has memory of for `collection` — from an
128
- * EARLIER, separate `vault.collection()` call (registered postures, derived
129
- * targets, depsless-computed names) — regardless of which via feature declared
130
- * it. #645 — `via/graph-wiring.ts`'s reconcile-path `knownFields` universe
131
- * (`collectKnownFieldNames`) is scoped to THIS call's own options only, so a
132
- * computed field attached in a LATER call whose `deps` correctly names a field
133
- * declared in an EARLIER call was spuriously refused as "unknown"; this method
134
- * lets that call site union in the graph's cross-call memory. Excludes the `'*'`
135
- * wildcard target (an overlay/derivation whole-record fold node, never a real
136
- * record field — see {@link registerDerived}'s doc comment). */
137
- knownFieldNames(collection) {
138
- const out = /* @__PURE__ */ new Set();
139
- const prefix = `${collection}${SEP}`;
140
- for (const id of this._posture.keys()) {
141
- if (id.startsWith(prefix)) out.add(id.slice(prefix.length));
142
- }
143
- for (const edge of this._in.values()) {
144
- if (edge.target.collection === collection && edge.target.field !== "*") out.add(edge.target.field);
145
- }
146
- const depsless = this._depslessComputed.get(collection);
147
- if (depsless) for (const field of depsless) out.add(field);
148
- return out;
149
- }
150
- /** Reject cycles at declare time (vault open). Throws `DerivationCycleError` for a
151
- * derivation/rollup/computed cycle, `MaterializedViewCycleError` for an MV cycle —
152
- * same classes + message shape the registries throw today (behavior lock). */
153
- assertAcyclic() {
154
- const visited = /* @__PURE__ */ new Set();
155
- const stack = [];
156
- const notRefEdge = (entry) => entry.kind !== "ref";
157
- const neighboursOf = (id) => {
158
- const own = (this._out.get(id) ?? []).filter(notRefEdge).map((entry) => entry.target);
159
- const sep = id.indexOf(SEP);
160
- if (id.slice(sep + 1) === "*") return own;
161
- const wildcard = (this._out.get(`${id.slice(0, sep)}${SEP}*`) ?? []).filter(notRefEdge).map((entry) => entry.target);
162
- if (wildcard.length === 0) return own;
163
- return [...own, ...wildcard];
164
- };
165
- const visit = (id) => {
166
- const idx = stack.indexOf(id);
167
- if (idx !== -1) {
168
- const cycle = stack.slice(idx).concat(id);
169
- const isMv = cycle.some((cycleId) => this._in.get(cycleId)?.kind === "mv");
170
- const path = cycle.map(displayNodeId);
171
- if (isMv) throw new MaterializedViewCycleError(path);
172
- throw new DerivationCycleError(path);
173
- }
174
- if (visited.has(id)) return;
175
- stack.push(id);
176
- for (const dependent of neighboursOf(id)) visit(nodeId(dependent));
177
- stack.pop();
178
- visited.add(id);
179
- };
180
- for (const id of this._out.keys()) visit(id);
181
- }
182
- /** A leaf (non-derived) node's own posture contribution: its declared posture,
183
- * or `DEFAULT_POSTURE` if never declared via `registerField`. */
184
- _declaredPosture(id) {
185
- return this._posture.get(id) ?? DEFAULT_POSTURE;
186
- }
187
- /** A node's posture contribution when folded into a dependent: recurse if it is itself
188
- * derived; else its declared/default posture — UNLESS it is a `'*'` LEAF node consumed
189
- * by a folded formula kind (#642, `derivation|rollup|mv|overlay`), in which case it
190
- * contributes its collection's whole-record security fold ({@link _wildcardContribution}).
191
- * `kind` is the CONSUMING edge's kind, threaded from {@link _computeEffective}. `'ref'`
192
- * edges (and the provenance-only untyped call, `kind` left `undefined`) keep `'*'` =
193
- * identity — the phase-D lookup reliance (`via/lookup/registry.ts:392-397`) that a
194
- * referencing field must not seal just because its dimension collection has a classified
195
- * column. */
196
- _contribution(id, kind) {
197
- const edge = this._in.get(id);
198
- if (edge) return this._computeEffective(id, edge);
199
- if (kind !== void 0 && FOLDED_KINDS.has(kind) && id.endsWith(`${SEP}*`)) {
200
- return this._wildcardContribution(id.slice(0, -2));
201
- }
202
- return this._declaredPosture(id);
203
- }
204
- /** Lazy fold (#642) of a collection's REGISTERED field postures on the security axes only
205
- * ({@link foldWildcardSecurity} — sealed-wins encryptedAtRest, AND exportable, OR
206
- * forgettable; queryable stays at DEFAULT_POSTURE's 'full', never folded), seeded at
207
- * DEFAULT_POSTURE. This is the collection-level posture a `'*'` LEAF node contributes to a
208
- * folded-kind formula edge (derivation/rollup/mv/overlay) — see {@link _contribution}. A
209
- * plain (unregistered) field contributes nothing (the identity), so the fold is strictly
210
- * more conservative than deps-based taint and never hits the KNOWN-LIMIT wall (it names no
211
- * fields — seam map §1d). Memoized per collection in `_wildcardCache`. */
212
- _wildcardContribution(collection) {
213
- const cached = this._wildcardCache.get(collection);
214
- if (cached) return cached;
215
- let result = DEFAULT_POSTURE;
216
- const prefix = `${collection}${SEP}`;
217
- for (const [id, posture] of this._posture) {
218
- if (!id.startsWith(prefix) || id === `${prefix}*`) continue;
219
- result = foldWildcardSecurity(result, posture);
220
- }
221
- this._wildcardCache.set(collection, result);
222
- return result;
223
- }
224
- _computeEffective(id, edge) {
225
- const cached = this._effectiveCache.get(id);
226
- if (cached) return cached;
227
- let result = DEFAULT_POSTURE;
228
- for (const source of edge.sources) {
229
- result = foldPosture(result, this._contribution(nodeId(source), edge.kind));
230
- }
231
- if (edge.grain === "virtual" && result.queryable !== "none") {
232
- result = { ...result, queryable: "none" };
233
- }
234
- this._effectiveCache.set(id, result);
235
- return result;
236
- }
237
- /** Strictest source posture on every axis, per §2. `undefined` when `target` has no
238
- * in-edges (not a derived field). Transitive: folds through chained derivations. */
239
- effectivePosture(target) {
240
- const id = nodeId(target);
241
- const edge = this._in.get(id);
242
- if (!edge) return void 0;
243
- return this._computeEffective(id, edge);
244
- }
245
- /** Per-collection { field → effectivePosture } overlay the pipeline consumes (Task 3). */
246
- taintedPostures(collection) {
247
- const out = /* @__PURE__ */ new Map();
248
- for (const edge of this._in.values()) {
249
- if (edge.target.collection !== collection) continue;
250
- out.set(edge.target.field, this._computeEffective(nodeId(edge.target), edge));
251
- }
252
- return out;
253
- }
254
- /** Materialized (`grain !== 'virtual'`) derived fields on `collection` whose effective
255
- * encryptedAtRest resolves to 'sealed' — the taint-seal set (Task 3). #638 Task 7
256
- * makes the `grain !== 'virtual'` filter meaningful: a `computed(fn, { mode: 'virtual' })`
257
- * field is never stored (rides the `present` phase, seam map Part 4's money-Formatted/
258
- * i18n-Label precedent), so it is EXCLUDED here even when its effective `encryptedAtRest`
259
- * folds to `'sealed'` — there is no envelope slot to seal. It still surfaces in
260
- * {@link taintedPostures} (query refusal via `postureFor`'s `queryable` clamp above, and
261
- * export refusal via `exportable`) — only the AT-REST SEALING ACTION is skipped. */
262
- taintSealedFields(collection) {
263
- const out = /* @__PURE__ */ new Set();
264
- for (const edge of this._in.values()) {
265
- if (edge.target.collection !== collection || edge.grain === "virtual") continue;
266
- const posture = this._computeEffective(nodeId(edge.target), edge);
267
- if (posture.encryptedAtRest === "sealed") out.add(edge.target.field);
268
- }
269
- return out;
270
- }
271
- /** Fields on `collection` whose registered edge is `grain === 'virtual'` (#638 Task 7) —
272
- * `via/graph-wiring.ts#applyTaintOverlay` intersects this with `exportable === false`
273
- * postures to know which virtual fields need PRESENT-TIME (not just export-time)
274
- * redaction, since a virtual field's value is only ever materialized inside `present()`. */
275
- virtualFields(collection) {
276
- const out = /* @__PURE__ */ new Set();
277
- for (const edge of this._in.values()) {
278
- if (edge.target.collection === collection && edge.grain === "virtual") out.add(edge.target.field);
279
- }
280
- return out;
281
- }
282
- /** Per-collection { field → immediate source field names that forced its
283
- * effective posture away from `DEFAULT_POSTURE` } — `describe()`'s
284
- * provenance (Task 3). Only the DIRECT declared sources are named (not the
285
- * ultimate origin several hops up a transitive chain); a source absent
286
- * from the result contributed nothing restrictive. */
287
- taintProvenance(collection) {
288
- const out = /* @__PURE__ */ new Map();
289
- for (const edge of this._in.values()) {
290
- if (edge.target.collection !== collection) continue;
291
- const forcedBy = edge.sources.filter((source) => !isDefaultPosture(this._contribution(nodeId(source)))).map((source) => source.field);
292
- if (forcedBy.length > 0) out.set(edge.target.field, forcedBy);
293
- }
294
- return out;
295
- }
296
- _targetsDependingOn(collection) {
297
- const out = [];
298
- for (const edge of this._in.values()) {
299
- if (edge.sources.some((source) => source.collection === collection)) {
300
- out.push({ target: edge.target, kind: edge.kind, grain: edge.grain });
301
- }
302
- }
303
- return out;
304
- }
305
- /** Dispatch (Task 4): every derived target triggered by a write to `collection`. Excludes
306
- * `'ref'` edges (#650 Task 5 review, folded Minor) — a `'ref'` edge is consulted only by the
307
- * delete/forget-time restrict/cascade/nullify path ({@link referencingEdgesOf}), never by the
308
- * sync/cutover/restore dispatch wave (`runGraphDispatchWave`); counting it here defeated that
309
- * wave's zero-cost early-continue, costing every write to a referenced backing collection an
310
- * unconditional decrypt + two no-op dispatch passes. {@link derivedArtifactsOf} (erasure
311
- * fanout) still includes `'ref'` edges — the forget path genuinely needs them. */
312
- dependentsOf(collection) {
313
- return this._targetsDependingOn(collection).filter((edge) => edge.kind !== "ref");
314
- }
315
- /** Erasure (Task 6): derived artifacts of a forgotten record whose source is `collection`. */
316
- derivedArtifactsOf(collection) {
317
- return this._targetsDependingOn(collection);
318
- }
319
- /** Referencing edges pointing AT a backing dimension (delete/forget-time restrict/cascade/
320
- * nullify, #650 Task 5) — an O(1) reverse lookup via the existing `_out` map (keyed at the
321
- * dimension's wildcard `field:'*'` node, the same convention every whole-collection source
322
- * already uses), NEVER a scan across collections or records. `backing` is the collection name
323
- * a lookup field's edge was registered against (a reserved dimension's `_dict_<name>`/
324
- * `_lookup_<name>` collection name, or a matrix dimension's own collection name). */
325
- referencingEdgesOf(backing) {
326
- const targets = this._out.get(nodeId({ collection: backing, field: "*" }));
327
- if (!targets) return [];
328
- const out = [];
329
- for (const entry of targets) {
330
- if (entry.kind !== "ref") continue;
331
- const edge = this._in.get(nodeId(entry.target));
332
- if (edge?.onDelete !== void 0) {
333
- out.push({ referencing: entry.target, onDelete: edge.onDelete, keyField: edge.keyField ?? "id" });
334
- }
335
- }
336
- return out;
337
- }
338
- };
339
-
340
- export {
341
- DEFAULT_POSTURE,
342
- ViaGraph
343
- };
344
- //# sourceMappingURL=chunk-UB5CRTFF.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/kernel/via/graph.ts"],"sourcesContent":["// kernel/via/graph.ts — the per-vault ViaGraph dependency model (Via port phase C, #638).\n//\n// Metadata-only dependency graph over (collection, field) nodes: source-field\n// postures, derived-field edges, and the taint (effective-posture) algebra.\n// NEVER stores record values or key material — see\n// design-history/2026-07-11-via-phase-c-design.md §1/§2.\n\nimport { DerivationCycleError, MaterializedViewCycleError } from '../errors.js'\nimport type { ViaPosture } from './index.js'\n\n/** A (collection, field) node. Artifact-grain targets (rollup field, MV row-class,\n * overlay output) are modelled as a field node whose `field` is the artifact key. */\nexport interface FieldRef { readonly collection: string; readonly field: string }\n\nexport type EdgeKind = 'computed' | 'derivation' | 'rollup' | 'mv' | 'overlay' | 'ref'\n/** `'virtual'` (#638 Task 7) marks a `computed(fn, { mode: 'virtual' })` field's edge —\n * never stored (rides the `present` phase), so it can never be sealed at rest\n * ({@link ViaGraph.taintSealedFields} excludes it) and is always non-queryable\n * regardless of source posture ({@link ViaGraph.effectivePosture} clamps it). */\nexport type Grain = 'record' | 'aggregate' | 'virtual'\n\n/** Delete/forget-time referential policy for a `'ref'` edge (#650 Task 5, spec §4) — duplicated\n * (not imported) from `via/lookup/descriptor.ts`'s `OnDelete`: the kernel spine may not\n * statically import `via/**` (Check 14, `via-layering`). Keep the two in sync by hand. */\nexport type OnDelete = 'restrict' | 'cascade' | 'nullify'\n\n/** Plain (non-via) field baseline — max-permissive; taint only ever tightens. */\nexport const DEFAULT_POSTURE: ViaPosture =\n { encryptedAtRest: 'envelope', queryable: 'full', exportable: true, forgettable: false }\n\nconst QUERYABLE_RANK: Record<ViaPosture['queryable'], number> = {\n none: 0,\n 'det-exact': 1,\n ordered: 2,\n full: 3,\n}\n\n/** The per-axis \"most restrictive\" fold — pure, exported for direct unit testing. */\nexport function foldPosture(a: ViaPosture, b: ViaPosture): ViaPosture {\n return {\n encryptedAtRest: a.encryptedAtRest === 'sealed' || b.encryptedAtRest === 'sealed' ? 'sealed' : 'envelope',\n queryable: QUERYABLE_RANK[a.queryable] <= QUERYABLE_RANK[b.queryable] ? a.queryable : b.queryable,\n exportable: a.exportable && b.exportable,\n forgettable: a.forgettable || b.forgettable,\n }\n}\n\n/** Security-axes-only fold for a wildcard collection node (#642): sealed-wins encryptedAtRest,\n * AND exportable, OR forgettable — queryable is NOT folded (stays base.queryable; the sealed\n * clamp to 'none' is buildTaintOverlay's job, so inheriting `sealed` never over-restricts a\n * blob-adjacent output to unqueryable). Pure; exported for unit testing. Distinct from\n * {@link foldPosture} (which folds all four axes) — do NOT reuse foldPosture here. */\nexport function foldWildcardSecurity(base: ViaPosture, contributor: ViaPosture): ViaPosture {\n return {\n encryptedAtRest: base.encryptedAtRest === 'sealed' || contributor.encryptedAtRest === 'sealed' ? 'sealed' : 'envelope',\n queryable: base.queryable,\n exportable: base.exportable && contributor.exportable,\n forgettable: base.forgettable || contributor.forgettable,\n }\n}\n\n// the folded formula kinds a '*' source contributes its collection-fold to (ref keeps identity):\ntype FoldedKind = 'derivation' | 'rollup' | 'mv' | 'overlay'\nconst FOLDED_KINDS: ReadonlySet<EdgeKind> = new Set<FoldedKind>(['derivation', 'rollup', 'mv', 'overlay'])\n\n/** Whether `p` is exactly the plain, non-taint baseline (Task 3 — `taintProvenance`). */\nfunction isDefaultPosture(p: ViaPosture): boolean {\n return p.encryptedAtRest === DEFAULT_POSTURE.encryptedAtRest && p.queryable === DEFAULT_POSTURE.queryable &&\n p.exportable === DEFAULT_POSTURE.exportable && p.forgettable === DEFAULT_POSTURE.forgettable\n}\n\n/** One `_out` entry — a `source -> target` derived edge, carrying its OWN registration's\n * `kind` (#678). `kind` must be edge-local, not re-derived from `_in`: `_in` is single-slot\n * per target and can be overwritten by a LATER `registerDerived` call on the SAME target\n * (a dual-role target, e.g. #631's exempt {computed, lookup} field composition) — asking\n * `_in` for \"this target's current kind\" answers the wrong question for a specific edge. */\ninterface OutEdge { readonly target: FieldRef; readonly kind: EdgeKind }\n\n/** One registered `registerDerived` call — a target's whole in-edge set. */\ninterface DerivedEdge {\n readonly target: FieldRef\n readonly sources: readonly FieldRef[]\n readonly kind: EdgeKind\n readonly grain: Grain\n /** `'ref'` edges only — the referencing field's delete/forget policy (#650 Task 5). */\n readonly onDelete?: OnDelete\n /** `'ref'` edges only — the backing dimension's canonical-key FIELD NAME on its own row (matrix\n * tier's `lookup(dim, {key})`; always `'id'` for reserved/static tiers). Defaults to `'id'`\n * when absent — a referencing field always stores this field's VALUE, never the backing row's\n * PUT-id when the two differ (#650 Task 5, mirrors Task 3's membership review fix). */\n readonly keyField?: string\n}\n\nconst SEP = '\\0'\nconst EMPTY_SET: ReadonlySet<string> = new Set()\n\nfunction nodeId(ref: FieldRef): string {\n return `${ref.collection}${SEP}${ref.field}`\n}\n\n/** Inverse of `nodeId`, for building a human-readable cycle path. */\nfunction displayNodeId(id: string): string {\n const sep = id.indexOf(SEP)\n return `${id.slice(0, sep)}.${id.slice(sep + 1)}`\n}\n\n/** Metadata-only dependency graph, ONE per vault. Never stores values or key material. */\nexport class ViaGraph {\n /** Declared source-field postures (registerField), keyed by node id. */\n private readonly _posture = new Map<string, ViaPosture>()\n /** Every derived target's registration, keyed by the target's node id. */\n private readonly _in = new Map<string, DerivedEdge>()\n /** source node id -> derived edges that depend on it (dispatch/erasure), each carrying\n * its own registration's `kind` (#678 — see {@link OutEdge}). */\n private readonly _out = new Map<string, OutEdge[]>()\n /** Memoized effective posture per derived target node id. */\n private readonly _effectiveCache = new Map<string, ViaPosture>()\n /** Memoized whole-collection security-axes fold for `'*'` wildcard LEAF nodes (#642),\n * keyed by collection name — see {@link _wildcardContribution}. Cleared alongside\n * `_effectiveCache` on every `registerField`/`registerDerived` (a later-registered\n * field must re-fold on the next read — registration ordering is free). */\n private readonly _wildcardCache = new Map<string, ViaPosture>()\n /** Collections with at least one classified field (#638 Task 2 fix wave 2,\n * Finding I1 — the reconcile path's combined-state leak guard memory). */\n private readonly _classifiedCollections = new Set<string>()\n /** Computed field names declared with no `deps` entry, per collection\n * (Finding I1 — such a field registers no edge when no classified field is\n * present yet, so a LATER, separate reconcile call attaching classifiedFields\n * still needs to see it regardless of declaration order). */\n private readonly _depslessComputed = new Map<string, Set<string>>()\n\n /** Declare a source field's posture (money/i18n/classified/plain). Plain fields\n * default to `DEFAULT_POSTURE`; a later declaration for the same node wins-first (idempotent). */\n registerField(collection: string, field: string, posture: ViaPosture): void {\n const id = nodeId({ collection, field })\n if (this._posture.has(id)) return\n this._posture.set(id, posture)\n this._effectiveCache.clear()\n this._wildcardCache.clear()\n }\n\n /** A derived target depends on `sources` (may be cross-collection). `kind`/`grain`\n * drive dispatch + erasure semantics; sources drive taint. Re-registering the same\n * target replaces `_in` but does not prune stale `_out` edges — callers must\n * register each target at most once per graph lifetime. `onDelete`/`keyField` (#650 Task 5)\n * are meaningful only for `kind === 'ref'` edges — {@link referencingEdgesOf} reads them back. */\n registerDerived(target: FieldRef, sources: readonly FieldRef[], kind: EdgeKind, grain: Grain, onDelete?: OnDelete, keyField?: string): void {\n const id = nodeId(target)\n this._in.set(id, {\n target, sources, kind, grain,\n ...(onDelete !== undefined ? { onDelete } : {}),\n ...(keyField !== undefined ? { keyField } : {}),\n })\n for (const source of sources) {\n const sourceId = nodeId(source)\n const outEdge: OutEdge = { target, kind }\n const dependents = this._out.get(sourceId)\n if (dependents) dependents.push(outEdge)\n else this._out.set(sourceId, [outEdge])\n }\n this._effectiveCache.clear()\n this._wildcardCache.clear()\n }\n\n /** Whether `target` already has a registered in-edge — lets a caller skip\n * re-registering the same target (#638 Task 2 fix wave 2, Finding I2i:\n * `registerDerived`'s at-most-once contract must hold across repeated\n * identical `vault.collection()` calls, not just within a single one). */\n hasDerived(target: FieldRef): boolean {\n return this._in.has(nodeId(target))\n }\n\n /** Mark/query a collection as having declared at least one classified field. */\n markClassified(collection: string): void {\n this._classifiedCollections.add(collection)\n }\n\n isClassified(collection: string): boolean {\n return this._classifiedCollections.has(collection)\n }\n\n /** Mark/query a collection's depsless (no declared `deps`) computed\n * field names — see `_depslessComputed`'s doc comment above. */\n markDepslessComputed(collection: string, field: string): void {\n let set = this._depslessComputed.get(collection)\n if (!set) { set = new Set(); this._depslessComputed.set(collection, set) }\n set.add(field)\n }\n\n depslessComputedFields(collection: string): ReadonlySet<string> {\n return this._depslessComputed.get(collection) ?? EMPTY_SET\n }\n\n /** Every field name the graph already has memory of for `collection` — from an\n * EARLIER, separate `vault.collection()` call (registered postures, derived\n * targets, depsless-computed names) — regardless of which via feature declared\n * it. #645 — `via/graph-wiring.ts`'s reconcile-path `knownFields` universe\n * (`collectKnownFieldNames`) is scoped to THIS call's own options only, so a\n * computed field attached in a LATER call whose `deps` correctly names a field\n * declared in an EARLIER call was spuriously refused as \"unknown\"; this method\n * lets that call site union in the graph's cross-call memory. Excludes the `'*'`\n * wildcard target (an overlay/derivation whole-record fold node, never a real\n * record field — see {@link registerDerived}'s doc comment). */\n knownFieldNames(collection: string): ReadonlySet<string> {\n const out = new Set<string>()\n const prefix = `${collection}${SEP}`\n for (const id of this._posture.keys()) {\n if (id.startsWith(prefix)) out.add(id.slice(prefix.length))\n }\n for (const edge of this._in.values()) {\n if (edge.target.collection === collection && edge.target.field !== '*') out.add(edge.target.field)\n }\n const depsless = this._depslessComputed.get(collection)\n if (depsless) for (const field of depsless) out.add(field)\n return out\n }\n\n /** Reject cycles at declare time (vault open). Throws `DerivationCycleError` for a\n * derivation/rollup/computed cycle, `MaterializedViewCycleError` for an MV cycle —\n * same classes + message shape the registries throw today (behavior lock). */\n assertAcyclic(): void {\n const visited = new Set<string>()\n const stack: string[] = []\n\n /** #639 — containment expansion: writing a real field `f` on collection `C` is\n * a write to `C`, so it must ALSO reach every whole-record (`'*'`) dependent of\n * `C` — the missing reachability step that made mutual/rotating rollup cycles\n * invisible (a rollup target is always a real field node and never itself a\n * graph SOURCE, so the old `_out.get(id)`-only walk dead-ended on it).\n *\n * TRAVERSAL-LOCAL ONLY (law, #642): this is a virtual adjacency computed\n * during the DFS walk, reading `_out` only. It must NEVER be materialized via\n * `registerDerived` (no new `(C,f)→(C,'*')` edge) and must NEVER touch `_in` —\n * putting `(C,'*')` into `_in` would flip `_contribution('C\\0*')` from the\n * `_wildcardContribution` fold (`#642`) onto the `_computeEffective` path,\n * bleeding cycle-reachability taint into `foldWildcardSecurity`'s posture\n * fold. Posture folding (`_contribution`/`_computeEffective`/\n * `_wildcardContribution`) never reads `_out`, so an expansion that reads\n * `_out` only is provably unable to perturb it. */\n // #671 item 5 — exclude `kind:'ref'` consuming edges: mutual FK lookups (two\n // collections each referencing the other) are legal and must not be treated as a\n // derivation cycle. Ref edges exist for cascade/rename machinery\n // (`referencingEdgesOf`/delete-time restrict/cascade/nullify), never derivation\n // ordering, so they carry no ordering constraint here. #678 — `kind` is read off\n // THIS `_out` entry (edge-local), NOT re-derefed from `_in`: a dual-role target\n // (e.g. #631's exempt {computed, lookup} composition) can have its `_in` entry's\n // `kind` overwritten by a LATER `registerDerived` call for the SAME target, so\n // asking `_in` \"what kind is `t` registered as NOW\" wrongly excluded a real\n // computed edge from the DFS as if it were a ref edge, hiding a genuine\n // derivation cycle. Asking \"what kind is THIS specific edge\" is correct\n // regardless of what else was later registered for the target.\n const notRefEdge = (entry: OutEdge): boolean => entry.kind !== 'ref'\n const neighboursOf = (id: string): readonly FieldRef[] => {\n const own = (this._out.get(id) ?? []).filter(notRefEdge).map((entry) => entry.target)\n const sep = id.indexOf(SEP)\n if (id.slice(sep + 1) === '*') return own\n const wildcard = (this._out.get(`${id.slice(0, sep)}${SEP}*`) ?? []).filter(notRefEdge).map((entry) => entry.target)\n if (wildcard.length === 0) return own\n return [...own, ...wildcard]\n }\n\n const visit = (id: string): void => {\n const idx = stack.indexOf(id)\n if (idx !== -1) {\n const cycle = stack.slice(idx).concat(id)\n const isMv = cycle.some(cycleId => this._in.get(cycleId)?.kind === 'mv')\n const path = cycle.map(displayNodeId)\n if (isMv) throw new MaterializedViewCycleError(path)\n throw new DerivationCycleError(path)\n }\n if (visited.has(id)) return\n stack.push(id)\n for (const dependent of neighboursOf(id)) visit(nodeId(dependent))\n stack.pop()\n visited.add(id)\n }\n\n for (const id of this._out.keys()) visit(id)\n }\n\n /** A leaf (non-derived) node's own posture contribution: its declared posture,\n * or `DEFAULT_POSTURE` if never declared via `registerField`. */\n private _declaredPosture(id: string): ViaPosture {\n return this._posture.get(id) ?? DEFAULT_POSTURE\n }\n\n /** A node's posture contribution when folded into a dependent: recurse if it is itself\n * derived; else its declared/default posture — UNLESS it is a `'*'` LEAF node consumed\n * by a folded formula kind (#642, `derivation|rollup|mv|overlay`), in which case it\n * contributes its collection's whole-record security fold ({@link _wildcardContribution}).\n * `kind` is the CONSUMING edge's kind, threaded from {@link _computeEffective}. `'ref'`\n * edges (and the provenance-only untyped call, `kind` left `undefined`) keep `'*'` =\n * identity — the phase-D lookup reliance (`via/lookup/registry.ts:392-397`) that a\n * referencing field must not seal just because its dimension collection has a classified\n * column. */\n private _contribution(id: string, kind?: EdgeKind): ViaPosture {\n const edge = this._in.get(id)\n if (edge) return this._computeEffective(id, edge)\n if (kind !== undefined && FOLDED_KINDS.has(kind) && id.endsWith(`${SEP}*`)) {\n return this._wildcardContribution(id.slice(0, -2))\n }\n return this._declaredPosture(id)\n }\n\n /** Lazy fold (#642) of a collection's REGISTERED field postures on the security axes only\n * ({@link foldWildcardSecurity} — sealed-wins encryptedAtRest, AND exportable, OR\n * forgettable; queryable stays at DEFAULT_POSTURE's 'full', never folded), seeded at\n * DEFAULT_POSTURE. This is the collection-level posture a `'*'` LEAF node contributes to a\n * folded-kind formula edge (derivation/rollup/mv/overlay) — see {@link _contribution}. A\n * plain (unregistered) field contributes nothing (the identity), so the fold is strictly\n * more conservative than deps-based taint and never hits the KNOWN-LIMIT wall (it names no\n * fields — seam map §1d). Memoized per collection in `_wildcardCache`. */\n private _wildcardContribution(collection: string): ViaPosture {\n const cached = this._wildcardCache.get(collection)\n if (cached) return cached\n let result = DEFAULT_POSTURE\n const prefix = `${collection}${SEP}`\n for (const [id, posture] of this._posture) {\n if (!id.startsWith(prefix) || id === `${prefix}*`) continue\n result = foldWildcardSecurity(result, posture)\n }\n this._wildcardCache.set(collection, result)\n return result\n }\n\n private _computeEffective(id: string, edge: DerivedEdge): ViaPosture {\n const cached = this._effectiveCache.get(id)\n if (cached) return cached\n let result = DEFAULT_POSTURE\n for (const source of edge.sources) {\n result = foldPosture(result, this._contribution(nodeId(source), edge.kind))\n }\n // #638 Task 7 — a virtual field is computed fresh on every read and never\n // stored, so it is structurally unqueryable regardless of what its\n // sources' fold would otherwise permit (e.g. a money-sourced virtual\n // field must NOT inherit money's 'ordered' queryability — there is no\n // stored/indexed form to query against). Every OTHER axis (exportable/\n // forgettable/encryptedAtRest) still folds normally — the taint rule is\n // identical to a materialized field; only queryability is a fixed,\n // grain-level property, not a source-derived one.\n if (edge.grain === 'virtual' && result.queryable !== 'none') {\n result = { ...result, queryable: 'none' }\n }\n this._effectiveCache.set(id, result)\n return result\n }\n\n /** Strictest source posture on every axis, per §2. `undefined` when `target` has no\n * in-edges (not a derived field). Transitive: folds through chained derivations. */\n effectivePosture(target: FieldRef): ViaPosture | undefined {\n const id = nodeId(target)\n const edge = this._in.get(id)\n if (!edge) return undefined\n return this._computeEffective(id, edge)\n }\n\n /** Per-collection { field → effectivePosture } overlay the pipeline consumes (Task 3). */\n taintedPostures(collection: string): ReadonlyMap<string, ViaPosture> {\n const out = new Map<string, ViaPosture>()\n for (const edge of this._in.values()) {\n if (edge.target.collection !== collection) continue\n out.set(edge.target.field, this._computeEffective(nodeId(edge.target), edge))\n }\n return out\n }\n\n /** Materialized (`grain !== 'virtual'`) derived fields on `collection` whose effective\n * encryptedAtRest resolves to 'sealed' — the taint-seal set (Task 3). #638 Task 7\n * makes the `grain !== 'virtual'` filter meaningful: a `computed(fn, { mode: 'virtual' })`\n * field is never stored (rides the `present` phase, seam map Part 4's money-Formatted/\n * i18n-Label precedent), so it is EXCLUDED here even when its effective `encryptedAtRest`\n * folds to `'sealed'` — there is no envelope slot to seal. It still surfaces in\n * {@link taintedPostures} (query refusal via `postureFor`'s `queryable` clamp above, and\n * export refusal via `exportable`) — only the AT-REST SEALING ACTION is skipped. */\n taintSealedFields(collection: string): ReadonlySet<string> {\n const out = new Set<string>()\n for (const edge of this._in.values()) {\n if (edge.target.collection !== collection || edge.grain === 'virtual') continue\n const posture = this._computeEffective(nodeId(edge.target), edge)\n if (posture.encryptedAtRest === 'sealed') out.add(edge.target.field)\n }\n return out\n }\n\n /** Fields on `collection` whose registered edge is `grain === 'virtual'` (#638 Task 7) —\n * `via/graph-wiring.ts#applyTaintOverlay` intersects this with `exportable === false`\n * postures to know which virtual fields need PRESENT-TIME (not just export-time)\n * redaction, since a virtual field's value is only ever materialized inside `present()`. */\n virtualFields(collection: string): ReadonlySet<string> {\n const out = new Set<string>()\n for (const edge of this._in.values()) {\n if (edge.target.collection === collection && edge.grain === 'virtual') out.add(edge.target.field)\n }\n return out\n }\n\n /** Per-collection { field → immediate source field names that forced its\n * effective posture away from `DEFAULT_POSTURE` } — `describe()`'s\n * provenance (Task 3). Only the DIRECT declared sources are named (not the\n * ultimate origin several hops up a transitive chain); a source absent\n * from the result contributed nothing restrictive. */\n taintProvenance(collection: string): ReadonlyMap<string, readonly string[]> {\n const out = new Map<string, readonly string[]>()\n for (const edge of this._in.values()) {\n if (edge.target.collection !== collection) continue\n const forcedBy = edge.sources\n .filter((source) => !isDefaultPosture(this._contribution(nodeId(source))))\n .map((source) => source.field)\n if (forcedBy.length > 0) out.set(edge.target.field, forcedBy)\n }\n return out\n }\n\n private _targetsDependingOn(collection: string): ReadonlyArray<{ readonly target: FieldRef; readonly kind: EdgeKind; readonly grain: Grain }> {\n const out: Array<{ target: FieldRef; kind: EdgeKind; grain: Grain }> = []\n for (const edge of this._in.values()) {\n if (edge.sources.some(source => source.collection === collection)) {\n out.push({ target: edge.target, kind: edge.kind, grain: edge.grain })\n }\n }\n return out\n }\n\n /** Dispatch (Task 4): every derived target triggered by a write to `collection`. Excludes\n * `'ref'` edges (#650 Task 5 review, folded Minor) — a `'ref'` edge is consulted only by the\n * delete/forget-time restrict/cascade/nullify path ({@link referencingEdgesOf}), never by the\n * sync/cutover/restore dispatch wave (`runGraphDispatchWave`); counting it here defeated that\n * wave's zero-cost early-continue, costing every write to a referenced backing collection an\n * unconditional decrypt + two no-op dispatch passes. {@link derivedArtifactsOf} (erasure\n * fanout) still includes `'ref'` edges — the forget path genuinely needs them. */\n dependentsOf(collection: string): ReadonlyArray<{ readonly target: FieldRef; readonly kind: EdgeKind; readonly grain: Grain }> {\n return this._targetsDependingOn(collection).filter((edge) => edge.kind !== 'ref')\n }\n\n /** Erasure (Task 6): derived artifacts of a forgotten record whose source is `collection`. */\n derivedArtifactsOf(collection: string): ReadonlyArray<{ readonly target: FieldRef; readonly kind: EdgeKind; readonly grain: Grain }> {\n return this._targetsDependingOn(collection)\n }\n\n /** Referencing edges pointing AT a backing dimension (delete/forget-time restrict/cascade/\n * nullify, #650 Task 5) — an O(1) reverse lookup via the existing `_out` map (keyed at the\n * dimension's wildcard `field:'*'` node, the same convention every whole-collection source\n * already uses), NEVER a scan across collections or records. `backing` is the collection name\n * a lookup field's edge was registered against (a reserved dimension's `_dict_<name>`/\n * `_lookup_<name>` collection name, or a matrix dimension's own collection name). */\n referencingEdgesOf(backing: string): ReadonlyArray<{ readonly referencing: FieldRef; readonly onDelete: OnDelete; readonly keyField: string }> {\n const targets = this._out.get(nodeId({ collection: backing, field: '*' }))\n if (!targets) return []\n const out: Array<{ referencing: FieldRef; onDelete: OnDelete; keyField: string }> = []\n for (const entry of targets) {\n // #678 — `kind` is read directly off THIS `_out` entry (edge-local) instead of\n // re-derefing `_in`. This also fixes the symmetric ref-vanishes hazard: under a\n // registration order where a LATER `registerDerived` call for the same target\n // overwrites `_in`'s kind away from `'ref'`, the old `_in`-derefing check would\n // silently drop a genuine referencing edge from cascade/nullify delete machinery\n // even though this specific edge was truly registered as `'ref'`.\n if (entry.kind !== 'ref') continue\n const edge = this._in.get(nodeId(entry.target))\n if (edge?.onDelete !== undefined) {\n out.push({ referencing: entry.target, onDelete: edge.onDelete, keyField: edge.keyField ?? 'id' })\n }\n }\n return out\n }\n}\n"],"mappings":"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","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/with-lookup/embeddings/cosine.ts","../src/with-lookup/embeddings/descriptor.ts","../src/with-lookup/embeddings/vector-set.ts","../src/with-lookup/embeddings/vec-id.ts","../src/with-lookup/search/collection-facade.ts"],"sourcesContent":["/** Cosine similarity in [-1,1]; 0 on zero-norm or length mismatch (no NaN). (L2) */\nexport function cosine(a: Float32Array | number[], b: Float32Array | number[]): number {\n if (a.length !== b.length) return 0\n let dot = 0, na = 0, nb = 0\n for (let i = 0; i < a.length; i++) {\n const x = a[i]!, y = b[i]!\n dot += x * y; na += x * x; nb += y * y\n }\n if (na === 0 || nb === 0) return 0\n return dot / (Math.sqrt(na) * Math.sqrt(nb))\n}\n","/** Per-collection embedding config (L2). The encode hook is host/remote — no bundled model. */\nimport { getAtPath } from '../../via/i18n/core.js'\n\nexport interface EmbeddingDescriptor {\n readonly source: string | readonly string[]\n readonly encode: (text: string) => Promise<Float32Array>\n readonly dim: number\n readonly model: string\n}\n\n/** Concatenate the record's source-field text (skips empties; supports nested/[]-wildcard paths). */\nexport function embeddingSourceText(record: Record<string, unknown>, source: string | readonly string[]): string {\n const fields = typeof source === 'string' ? [source] : source\n const parts: string[] = []\n for (const f of fields) {\n for (const leaf of getAtPath(record, f)) {\n if (typeof leaf === 'string' && leaf !== '') parts.push(leaf)\n }\n }\n return parts.join(' ')\n}\n","/** In-memory vector set for L2 semantic retrieval. Loaded once per session from\n * decrypted _vec sidecars (injected loader), brute-force cosine kNN, model-guarded. */\nimport { cosine } from './cosine.js'\nimport { EmbeddingModelMismatchError } from '../../kernel/errors.js'\n\nexport interface StoredVector { readonly id: string; readonly vec: Float32Array; readonly model: string }\nexport interface VectorHit { readonly id: string; readonly score: number }\n\nexport class VectorSet {\n private vectors: StoredVector[] | undefined\n get loaded(): boolean { return this.vectors !== undefined }\n\n async ensureLoaded(load: () => Promise<StoredVector[]>): Promise<void> {\n if (this.vectors === undefined) this.vectors = await load()\n }\n\n markDirty(): void { this.vectors = undefined }\n\n cosineTopK(query: Float32Array, k: number, opts: { minScore?: number; expectModel?: string } = {}): VectorHit[] {\n const all = this.vectors ?? []\n const minScore = opts.minScore ?? -Infinity\n const hits: VectorHit[] = []\n for (const v of all) {\n if (opts.expectModel !== undefined && v.model !== opts.expectModel) {\n throw new EmbeddingModelMismatchError(opts.expectModel, v.model)\n }\n const score = cosine(query, v.vec)\n if (score >= minScore) hits.push({ id: v.id, score })\n }\n hits.sort((a, b) => b.score - a.score)\n return hits.slice(0, k)\n }\n}\n","/**\n * Collection-namespaced `_vec` side-car ids (#726).\n *\n * `_vec` embedding side-cars used to be keyed by the bare record id,\n * vault-wide — two collections sharing a record id shared one `_vec` row.\n * This was verified to be a clobber/crash bug, NOT a confidentiality leak:\n * every collection has its own DEK, and AES-GCM auth-tag verification means\n * decrypting a foreign collection's `_vec` row under the wrong DEK throws\n * `TamperedError` rather than returning wrong plaintext — no disclosure\n * mechanism exists. The real bug was id collision: a same-id write/forget in\n * one collection could clobber or delete the other collection's row, and a\n * collection whose `similarTo()` / cold semantic `retrieve()` encountered a\n * foreign same-id row crashed with an uncaught `TamperedError` (a DoS). The\n * store-collection bucket stays the literal `'_vec'`; the id becomes\n * composite: `${collection}/${recordId}`. Mirrors `encodeIdxId`\n * (`../indexing/persisted-indexes.js`).\n *\n * Decode is deliberately NOT split-on-`/`: the collection is always known\n * at the decode site (`ctx.name`), so `decodeVecId` strips that known\n * prefix — correct even when `recordId` itself contains `/`.\n *\n * Precondition: `collection` itself must not contain `/` — otherwise\n * `${collection}/${recordId}` is ambiguous (e.g. collection `'a'` would\n * misattribute collection `'a/b'`'s rows via prefix match in `isVecIdFor`).\n * Collection names are NOT currently restricted to a `/`-free charset by\n * `vault.collection()`, so `encodeVecId` enforces the precondition itself,\n * failing loud at the write boundary rather than silently misencoding.\n */\n\n/**\n * Encode the `_vec` side-car id for a (collection, recordId) pair.\n * Format: `<collection>/<recordId>` — no escaping; `recordId` may contain `/`.\n *\n * Throws if `collection` contains `/`: that would make the composite id\n * ambiguous against `isVecIdFor`'s prefix match (collection `'a'` would\n * appear to own collection `'a/b'`'s rows), reintroducing the clobber/crash\n * risk the namespacing fix exists to close.\n */\nexport function encodeVecId(collection: string, recordId: string): string {\n if (collection.includes('/')) {\n throw new Error(\n `encodeVecId: collection name \"${collection}\" must not contain \"/\" — ` +\n `it would make the \"${collection}/${recordId}\" _vec id ambiguous against another collection's rows.`,\n )\n }\n return `${collection}/${recordId}`\n}\n\n/**\n * Decode a `_vec` id back to the bare record id, given the KNOWN owning\n * collection — strips the `<collection>/` prefix rather than splitting on\n * `/`, so a `recordId` containing `/` round-trips correctly. Returns `null`\n * if `vecId` does not belong to `collection`.\n */\nexport function decodeVecId(collection: string, vecId: string): string | null {\n const prefix = `${collection}/`\n if (!vecId.startsWith(prefix)) return null\n return vecId.slice(prefix.length)\n}\n\n/** Predicate form of {@link decodeVecId} — used to filter `list()` results. */\nexport function isVecIdFor(collection: string, vecId: string): boolean {\n return vecId.startsWith(`${collection}/`)\n}\n","/**\n * Collection-level search / retrieval (`search` / `retrieve` / `similarTo` +\n * the lexical-index build/flush/warm machinery).\n *\n * This is the retrieval surface: a pure client-side lexical scan\n * (`search`), the persisted-or-in-memory inverted-index lifecycle\n * (`flushIndex` / `warmIndex` / `buildRetrievalDocs` + the dict-label / blob-\n * filename resolvers that feed it), and the lexical / semantic / hybrid\n * `retrieve` fan-out plus raw-vector kNN (`similarTo`). None of it touches the\n * write path — it reads the live eager cache and the encrypted `_vec` / `_ftindex`\n * side-cars.\n *\n * Every function takes a small {@link SearchContext} instead of `this`,\n * mirroring the `record-keys/` siblings.\n *\n * The `cache` is the SAME `Map` reference `Collection` owns (passed by\n * reference, never copied) so the index always builds over the live working\n * set. {@link buildPersistedIndexCallbacks} is special: it is invoked from the\n * `Collection` constructor *before* `this.codec` exists, so it takes a context\n * THUNK and resolves the context lazily at each callback invocation.\n *\n * Internal service — not exported as a `@noy-db/hub/*` subpath.\n */\nimport type { NoydbStore } from '../../kernel/types.js'\nimport type { RecordCodec } from '../../kernel/enclave/index.js'\nimport { stripI18nFilled, type I18nTextDescriptor } from '../../via/i18n/core.js'\nimport { isStaticDictDescriptor, type DictKeyDescriptor, type StaticDictDescriptor, type DictionaryHandle } from '../../via/i18n/dictionary.js'\nimport type { BlobSet } from '../../with-shape/blobs/blob-set.js'\nimport type { BlobFieldsConfig } from '../../with-shape/blobs/blob-compaction.js'\nimport type { Query } from '../../kernel/query/index.js'\nimport { embeddingSourceText, type VectorSet, type EmbeddingDescriptor, type StoredVector } from '../embeddings/index.js'\nimport { encodeVecId, decodeVecId, isVecIdFor } from '../embeddings/vec-id.js'\nimport { EmbeddingDimMismatchError } from '../../kernel/errors.js'\nimport { liveRecordIsElevated } from '../../kernel/tier-visibility.js'\nimport { searchScan, fuseRetrieval, type SearchOptions, type SearchResult } from './index.js'\nimport type { IndexStore } from './index-store.js'\nimport type { PersistedIndexCallbacks } from './persisted-index-store.js'\nimport { extractSnippet } from './snippet.js'\nimport { buildStringFieldEntries, buildI18nFieldEntries, buildDictKeyFieldEntries, buildBlobFieldEntries } from './build-docs.js'\nimport type { IndexDoc, IndexHit } from './inverted-index.js'\nimport type { RetrieveOptions, RetrieveHit } from './retrieve-types.js'\n\n/** Everything the moving search/retrieval methods touched on `this.*`. */\nexport interface SearchContext<T> {\n /** Collection name — error context + `_ftindex` row key. */\n readonly name: string\n /** Vault namespace the side-cars (`_vec`, `_ftindex`) live under. */\n readonly vault: string\n /** The ciphertext store. */\n readonly adapter: NoydbStore\n /** The record codec — decrypts the `_vec` / `_ftindex` side-car bodies. */\n readonly codec: RecordCodec<T>\n /** The eager working-set cache (SHARED `Map` reference, never copied). */\n readonly cache: Map<string, { record: T; version: number }>\n /** True in lazy mode — search/retrieve/warm require eager mode. */\n readonly lazy: boolean\n /** Declared text-index fields, or undefined. */\n readonly textIndexes: readonly string[] | undefined\n /** Declared i18n fields, or undefined. */\n readonly i18nFields: Record<string, I18nTextDescriptor> | undefined\n /** Declared dictionary-key fields, or undefined. */\n readonly dictKeyFields: Record<string, DictKeyDescriptor | StaticDictDescriptor> | undefined\n /** Declared blob fields, or undefined. */\n readonly blobFields: BlobFieldsConfig | undefined\n /** Dictionary handle resolver (dynamic dict-key labels), or undefined. */\n readonly getDictionary: ((name: string) => Promise<DictionaryHandle>) | undefined\n /** The lexical inverted-index store (in-memory or persisted), or undefined. */\n readonly searchIndexStore: IndexStore | undefined\n /** The vector set for semantic retrieval, or undefined. */\n readonly vectorSet: VectorSet | undefined\n /** The embeddings descriptor, or undefined. */\n readonly embeddings: EmbeddingDescriptor | undefined\n /** Hydrate the eager cache before reading it. */\n ensureHydrated(): Promise<void>\n /** Open the blob facade for a record (used to list indexed blob filenames). */\n blob(id: string): BlobSet\n}\n\n/**\n * Client-side lexical scan over the live eager cache. Eager mode only;\n * nothing searchable is written to the store.\n */\nexport async function search<T>(ctx: SearchContext<T>, field: string, query: string, opts: SearchOptions = {}): Promise<SearchResult<T>[]> {\n if (ctx.lazy) {\n throw new Error(\n `Collection \"${ctx.name}\": search() (scan mode) requires eager mode (prefetch: true). ` +\n `A store-usable blind index for lazy / at-scale search is a separate gated opt-in (#308).`,\n )\n }\n await ctx.ensureHydrated()\n const entries: { id: string; record: T }[] = []\n // Strip the internal densify marker from the user-facing records.\n // Non-mutating: never touches the cached record object. The search index\n // is built over the same (marker-free) record, which is fine — the marker\n // is never a searchable field.\n for (const [id, e] of ctx.cache) entries.push({ id, record: stripI18nFilled(e.record as Record<string, unknown>) as T })\n return searchScan(entries, field, query, opts)\n}\n\n/** L1 — build IndexDoc[] for the configured text fields over the live cache. */\nexport function buildRetrievalDocs<T>(\n ctx: SearchContext<T>,\n labelMaps: Map<string, Map<string, Record<string, string>>>,\n blobFilenames: Map<string, Map<string, string[]>>,\n only?: readonly string[],\n): IndexDoc[] {\n const docs: IndexDoc[] = []\n for (const [id, e] of ctx.cache) {\n const rec = stripI18nFilled(e.record as Record<string, unknown>)\n const fields = buildStringFieldEntries(rec, ctx.textIndexes ?? [], only)\n if (ctx.i18nFields) fields.push(...buildI18nFieldEntries(rec, ctx.i18nFields, ctx.textIndexes ?? [], only))\n if (ctx.dictKeyFields) fields.push(...buildDictKeyFieldEntries(rec, ctx.dictKeyFields, labelMaps, ctx.textIndexes ?? [], only))\n const blobNames = blobFilenames.get(id)\n if (blobNames) fields.push(...buildBlobFieldEntries(blobNames))\n if (fields.length > 0) docs.push({ id, fields })\n }\n return docs\n}\n\n/** L1 — true iff any configured text index is also a blob field (gates ALL slot I/O). */\nexport function hasIndexedBlobFields<T>(ctx: SearchContext<T>, only?: readonly string[]): boolean {\n if (!ctx.blobFields || !ctx.textIndexes) return false\n const fields = only ? ctx.textIndexes.filter((f) => only.includes(f)) : ctx.textIndexes\n return fields.some((f) => f in ctx.blobFields!)\n}\n\n/**\n * L1 — resolve `recordId -> (blobField -> filenames[])` by listing slots\n * for the configured blob fields of each cached record. Blob slot metadata is\n * NOT inline on the record: it lives in a separate `_blob_slots_*` collection,\n * so this costs ONE `blob(id).list()` (a `listSlots`) per record at build time\n * — the heaviest indexing source. Fully gated by {@link hasIndexedBlobFields};\n * non-blob (and blob-but-not-indexed) collections do ZERO slot I/O.\n */\nasync function resolveBlobFilenames<T>(ctx: SearchContext<T>, only?: readonly string[]): Promise<Map<string, Map<string, string[]>>> {\n const out = new Map<string, Map<string, string[]>>()\n if (!hasIndexedBlobFields(ctx, only)) return out\n const indexed = (only ? ctx.textIndexes!.filter((f) => only.includes(f)) : ctx.textIndexes!)\n .filter((f) => f in ctx.blobFields!)\n const indexedSet = new Set(indexed)\n for (const id of ctx.cache.keys()) {\n let slots\n try {\n slots = await ctx.blob(id).list()\n } catch {\n continue\n }\n let byField: Map<string, string[]> | undefined\n for (const slot of slots) {\n if (!indexedSet.has(slot.name) || !slot.filename) continue\n if (!byField) { byField = new Map(); out.set(id, byField) }\n const names = byField.get(slot.name)\n if (names) names.push(slot.filename)\n else byField.set(slot.name, [slot.filename])\n }\n }\n return out\n}\n\n/** L1 — field -> (key -> {locale->label}) for dictKey fields; static from table, dynamic via getDictionary().list(). */\nasync function resolveDictLabelMaps<T>(ctx: SearchContext<T>): Promise<Map<string, Map<string, Record<string, string>>>> {\n const maps = new Map<string, Map<string, Record<string, string>>>()\n if (!ctx.dictKeyFields || !ctx.textIndexes) return maps\n for (const field of ctx.textIndexes) {\n const desc = ctx.dictKeyFields[field]\n if (!desc) continue\n const m = new Map<string, Record<string, string>>()\n if (isStaticDictDescriptor(desc)) {\n for (const [key, labels] of Object.entries(desc.table)) m.set(key, labels as Record<string, string>)\n } else {\n if (ctx.getDictionary) {\n const handle = await ctx.getDictionary(desc.name)\n for (const e of await handle.list()) m.set(e.key, e.labels)\n }\n }\n maps.set(field, m)\n }\n return maps\n}\n\n/** L1.5 — force-persist the lexical index now (e.g. on save/idle). Persists only when textIndexPersist is enabled; a no-op otherwise. */\nexport async function flushIndex<T>(ctx: SearchContext<T>): Promise<void> {\n if (!ctx.searchIndexStore) return\n await ctx.ensureHydrated()\n const labelMaps = await resolveDictLabelMaps(ctx)\n const blobFilenames = await resolveBlobFilenames(ctx)\n await ctx.searchIndexStore.ensureBuilt(() => buildRetrievalDocs(ctx, labelMaps, blobFilenames))\n await ctx.searchIndexStore.flush?.()\n}\n\n/** L2 — load + decrypt all _vec sidecars into StoredVector[] for the VectorSet. */\nfunction buildVectorLoad<T>(ctx: SearchContext<T>): () => Promise<StoredVector[]> {\n return async () => {\n const ids = await ctx.adapter.list(ctx.vault, '_vec')\n const out: StoredVector[] = []\n for (const vecId of ids) {\n // #726: _vec is a vault-wide bucket namespaced by collection prefix —\n // skip rows belonging to other collections before touching this one's.\n if (!isVecIdFor(ctx.name, vecId)) continue\n const id = decodeVecId(ctx.name, vecId)!\n // #721 defense-in-depth: a _vec row carries no _tier of its own; the purge\n // on elevate is best-effort and cannot reach a legacy sidecar, so gate on\n // the owning record's live tier. Envelope peek, no decryption.\n if (await liveRecordIsElevated(ctx.adapter, ctx.vault, ctx.name, id)) continue\n const env = await ctx.adapter.get(ctx.vault, '_vec', vecId)\n if (!env) continue\n // #726 fails-safe: a row can pass isVecIdFor's prefix filter yet still\n // be undecryptable under THIS collection's DEK — a surviving legacy\n // bare-id row whose record id happens to start with `<thisCollection>/`,\n // or plain corruption. _vec sidecars are derived, re-derivable\n // artifacts (embedOnWrite regenerates them on the record's next put()),\n // so best-effort skip is correct: never let one poison row crash\n // similarTo()/retrieve() for the whole collection.\n let body: string | null\n try {\n body = await ctx.codec.decryptJsonString({ collection: '_vec', id: vecId }, env)\n } catch {\n continue\n }\n if (body === null) continue\n const parsed = JSON.parse(body) as { vec: number[]; model: string }\n out.push({ id, vec: new Float32Array(parsed.vec), model: parsed.model })\n }\n return out\n }\n}\n\n/**\n * L1.5 — build the PersistedIndexCallbacks bridge: crypto lives here\n * (collection has getDEK / encryptJsonString / decryptJsonString / adapter),\n * the index store itself is crypto-free.\n *\n * Fingerprint encoding: body-wrap approach — save(json, fp) stores\n * JSON.stringify({ fp, idx: json }) as the encrypted body so the standard\n * EncryptedEnvelope shape is never extended. load() decrypts and JSON.parses\n * the wrapper back out.\n *\n * Cache shape: ctx.cache stores { record, version } — currentFingerprint()\n * iterates over e.version.\n *\n * NOTE: this factory is invoked from the `Collection` constructor *before*\n * `this.codec` is assigned, so it takes a context THUNK and resolves the\n * context lazily inside each callback — by the time a callback actually runs,\n * the codec and cache are fully wired.\n */\nexport function buildPersistedIndexCallbacks<T>(provideCtx: () => SearchContext<T>): PersistedIndexCallbacks {\n const FT = '_ftindex'\n return {\n load: async () => {\n const ctx = provideCtx()\n const env = await ctx.adapter.get(ctx.vault, FT, ctx.name)\n if (!env) return null\n const body = await ctx.codec.decryptJsonString({ collection: FT, id: ctx.name }, env)\n if (body === null) return null\n try {\n const wrapped = JSON.parse(body) as { fp: { count: number; maxVersion: number }; idx: string }\n return { json: wrapped.idx, fingerprint: wrapped.fp }\n } catch {\n return null\n }\n },\n save: async (json, fp, isStale) => {\n const ctx = provideCtx()\n const body = JSON.stringify({ fp, idx: json })\n const env = await ctx.codec.encryptJsonString({ collection: FT, id: ctx.name }, body, fp.count)\n // #725 review: a purge (removePersisted) can land while the encrypt above was\n // in flight — check right before the write and skip it rather than resurrect a\n // purged/forgotten record's blob. PersistedIndexStore's own post-save epoch\n // check is the backstop for every other ordering (incl. a purge landing during\n // this very put).\n if (isStale()) return\n await ctx.adapter.put(ctx.vault, FT, ctx.name, env)\n },\n remove: async () => { const ctx = provideCtx(); await ctx.adapter.delete(ctx.vault, FT, ctx.name) },\n currentFingerprint: () => {\n const ctx = provideCtx()\n let maxVersion = 0\n for (const e of ctx.cache.values()) if (e.version > maxVersion) maxVersion = e.version\n return { count: ctx.cache.size, maxVersion }\n },\n }\n}\n\n/** L1 — pre-build the lexical index (e.g. on open) so the first retrieve() pays no build scan. */\nexport async function warmIndex<T>(ctx: SearchContext<T>): Promise<void> {\n if (!ctx.searchIndexStore) return\n if (ctx.lazy) {\n throw new Error(\n `Collection \"${ctx.name}\": warmIndex() requires eager mode (prefetch: true).`,\n )\n }\n await ctx.ensureHydrated()\n const built = ctx.searchIndexStore.built\n const labelMaps = built ? new Map() : await resolveDictLabelMaps(ctx)\n const blobFilenames = built ? new Map() : await resolveBlobFilenames(ctx)\n await ctx.searchIndexStore.ensureBuilt(() => buildRetrievalDocs(ctx, labelMaps, blobFilenames))\n}\n\n/** Retrieval. mode: 'lexical' (default) | 'semantic' (L2) | 'hybrid' (L3). */\nexport async function retrieve<T>(ctx: SearchContext<T>, query: string, opts: RetrieveOptions<T> = {}): Promise<RetrieveHit<T>[]> {\n const hits =\n opts.mode === 'semantic' ? await retrieveSemantic(ctx, query, opts)\n : opts.mode === 'hybrid' ? await retrieveHybrid(ctx, query, opts)\n : await retrieveLexical(ctx, query, opts)\n return opts.within ? applyWithin(hits, opts.within) : hits\n}\n\n/** L3 — keep only hits whose id matches the structured query, re-rank 1-based. */\nfunction applyWithin<T>(hits: RetrieveHit<T>[], within: Query<T>): RetrieveHit<T>[] {\n const ids = new Set(within._idArray())\n return hits.filter(h => ids.has(h.id)).map((h, i) => ({ ...h, rank: i + 1 }))\n}\n\n/** L1 — client-side lexical retrieval; ranked { id, score, field, snippet, locale? }. */\nasync function retrieveLexical<T>(ctx: SearchContext<T>, query: string, opts: RetrieveOptions<T>): Promise<RetrieveHit<T>[]> {\n if (!ctx.searchIndexStore) {\n throw new Error(`Collection \"${ctx.name}\": retrieve() requires a textIndexes config.`)\n }\n if (ctx.lazy) {\n throw new Error(\n `Collection \"${ctx.name}\": retrieve() requires eager mode (prefetch: true).`,\n )\n }\n await ctx.ensureHydrated()\n const built = ctx.searchIndexStore.built\n const labelMaps = built ? new Map() : await resolveDictLabelMaps(ctx)\n const blobFilenames = built ? new Map() : await resolveBlobFilenames(ctx)\n const index = await ctx.searchIndexStore.ensureBuilt(() => buildRetrievalDocs(ctx, labelMaps, blobFilenames))\n const hits = index.query(query, {\n ...(opts.limit !== undefined ? { limit: opts.limit } : {}),\n ...(opts.match ? { match: opts.match } : {}),\n ...(opts.prefix ? { prefix: opts.prefix } : {}),\n ...(opts.fields ? { fields: opts.fields } : {}),\n })\n const window = opts.snippetWindow ?? 80\n return hits.map((h: IndexHit, i: number) => {\n const base: RetrieveHit<T> = {\n id: h.id,\n score: h.score,\n rank: i + 1,\n field: h.field,\n snippet: extractSnippet(h.text, h.offset, window),\n ...(h.locale !== undefined ? { locale: h.locale } : {}),\n ...(opts.includeRecord\n ? (() => {\n const e = ctx.cache.get(h.id)\n return e ? { record: stripI18nFilled(e.record as Record<string, unknown>) as T } : {}\n })()\n : {}),\n }\n return base\n })\n}\n\n/** L3 — hybrid: fuse lexical (L1) + semantic (L2) by RRF. Requires embeddings. */\nasync function retrieveHybrid<T>(ctx: SearchContext<T>, query: string, opts: RetrieveOptions<T>): Promise<RetrieveHit<T>[]> {\n if (!ctx.embeddings) {\n throw new Error(`Collection \"${ctx.name}\": retrieve({mode:'hybrid'}) requires an embeddings config.`)\n }\n const [lex, sem] = await Promise.all([\n retrieveLexical(ctx, query, opts),\n retrieveSemantic(ctx, query, opts),\n ])\n return fuseRetrieval([lex, sem], opts.limit !== undefined ? { limit: opts.limit } : {})\n}\n\n/** L2 — semantic branch of retrieve(): encode query → similarTo(). */\nasync function retrieveSemantic<T>(ctx: SearchContext<T>, query: string, opts: RetrieveOptions<T>): Promise<RetrieveHit<T>[]> {\n if (!ctx.embeddings) throw new Error(`Collection \"${ctx.name}\": retrieve({mode:'semantic'}) requires an embeddings config.`)\n if (ctx.lazy) throw new Error(`Collection \"${ctx.name}\": retrieve() requires eager mode (prefetch: true).`)\n const qVec = await ctx.embeddings.encode(query)\n return similarTo(ctx, qVec, {\n ...(opts.limit !== undefined ? { k: opts.limit } : {}),\n ...(opts.minScore !== undefined ? { minScore: opts.minScore } : {}),\n ...(opts.includeRecord ? { includeRecord: true } : {}),\n })\n}\n\n/** L2 — raw-vector kNN over the encrypted vector set (decrypted in the trusted tier).\n * Snippet is '' for vector hits in v1 (semantic match isn't span-located). */\nexport async function similarTo<T>(ctx: SearchContext<T>, vector: Float32Array, opts: { k?: number; minScore?: number; includeRecord?: boolean } = {}): Promise<RetrieveHit<T>[]> {\n if (!ctx.embeddings || !ctx.vectorSet) throw new Error(`Collection \"${ctx.name}\": similarTo() requires an embeddings config.`)\n if (ctx.lazy) throw new Error(`Collection \"${ctx.name}\": similarTo() requires eager mode (prefetch: true).`)\n await ctx.ensureHydrated()\n await ctx.vectorSet.ensureLoaded(buildVectorLoad(ctx))\n const hits = ctx.vectorSet.cosineTopK(vector, opts.k ?? 10, {\n ...(opts.minScore !== undefined ? { minScore: opts.minScore } : {}),\n expectModel: ctx.embeddings.model,\n })\n return hits.map((h, i) => {\n const base: RetrieveHit<T> = { id: h.id, score: h.score, rank: i + 1, field: '(vector)', snippet: '' }\n if (opts.includeRecord) { const e = ctx.cache.get(h.id); if (e) (base as { record?: T }).record = stripI18nFilled(e.record as Record<string, unknown>) as T }\n return base\n })\n}\n\n/**\n * L2 — the embedding write-hook: derive a record's embedding vector at\n * write and persist it as the encrypted `_vec` side-car. Routed through the\n * search strategy so it runs only when `withSearch()` is opted in — computing\n * vectors that no gated `similarTo` / semantic `retrieve` could query would be\n * dead weight, so the compute is paired with the search capability. The caller\n * (`Collection.put`) only invokes this when `embeddings` is declared.\n */\nexport async function embedOnWrite<T>(ctx: SearchContext<T>, id: string, record: T, version: number): Promise<void> {\n if (!ctx.embeddings) return\n const text = embeddingSourceText(record as Record<string, unknown>, ctx.embeddings.source)\n const vec = await ctx.embeddings.encode(text)\n if (vec.length !== ctx.embeddings.dim) throw new EmbeddingDimMismatchError('embeddings', ctx.embeddings.dim, vec.length)\n const body = JSON.stringify({ vec: Array.from(vec), model: ctx.embeddings.model, dim: ctx.embeddings.dim })\n const vecEnv = await ctx.codec.encryptJsonString({ collection: '_vec', id: encodeVecId(ctx.name, id) }, body, version)\n await ctx.adapter.put(ctx.vault, '_vec', encodeVecId(ctx.name, id), vecEnv)\n ctx.vectorSet?.markDirty()\n}\n\n/**\n * Force-re-derive every eligible tier-0 record's `_vec` sidecar once (#788).\n * Opt-in bulk repair for the #726 re-namespace: legacy bare-id rows are\n * unreachable to `buildVectorLoad` (it only recognises `<collection>/<id>`\n * keys) and otherwise self-heal only when a record is next `put()` — this\n * lets an adopter recover recall immediately instead of waiting on writes.\n *\n * **Skips elevated records** (`liveRecordIsElevated`) rather than refusing\n * the whole walk — the OPPOSITE of `_applyCutoverTransform`'s\n * `assertCutoverTierSafe` refuse-whole-batch precedent. An elevated record is\n * SUPPOSED to have no `_vec` (`syncTierSearch` purges it on elevate);\n * re-embedding one here would write searchable plaintext-derived data above\n * tier 0. Load-bearing: never write a `_vec` row for an elevated record.\n *\n * Tombstones, delete markers, and a raw `get` racing a delete between `list`\n * and `get` all decrypt to `null` (`decryptRecord` already folds\n * tombstone/delete-marker into that null) and are skipped the same way.\n * Unconditional re-derive, no already-migrated check — safe to re-run after\n * a partial failure (each id is independently idempotent).\n */\nexport async function rebuildEmbeddings<T>(ctx: SearchContext<T>): Promise<{ rebuilt: number; skipped: number }> {\n if (!ctx.embeddings) return { rebuilt: 0, skipped: 0 }\n const ids = await ctx.adapter.list(ctx.vault, ctx.name)\n let rebuilt = 0\n let skipped = 0\n for (const id of ids) {\n if (await liveRecordIsElevated(ctx.adapter, ctx.vault, ctx.name, id)) { skipped++; continue }\n const env = await ctx.adapter.get(ctx.vault, ctx.name, id)\n if (!env) { skipped++; continue }\n const decoded = await ctx.codec.decryptRecord({ collection: ctx.name, id }, env)\n if (decoded === null) { skipped++; continue }\n await embedOnWrite(ctx, id, decoded, env._v ?? 1)\n rebuilt++\n }\n return { rebuilt, skipped }\n}\n\n/**\n * Sync the collection's SEARCH artifacts after a tier move (#721). Both the\n * lexical `_ftindex` blob and the `_vec/<id>` embedding are encrypted under\n * the tier-0 DEK and hold the record's derived plaintext (full field text /\n * a text-invertible vector), so leaving them means elevation never hid what\n * the record was searchable by — the `forget()` precedent, unapplied to\n * elevate. `null` → the record left tier 0: purge its `_vec` sidecar (mirrors\n * `Collection._purgeVector`), and invalidate the `_ftindex` blob (mirrors\n * `Collection._purgeSearchIndex`: deletes the persisted blob when persisted,\n * else drops the in-memory index) so the next `retrieve()` rebuilds from the\n * elevated-free `ctx.cache`. A record → it is tier-0 again: re-embed it via\n * {@link embedOnWrite}, then invalidate `_ftindex` so the rebuild includes it\n * again. No-op fast when the collection has neither a lexical index nor a\n * vector set.\n */\nexport async function syncTierSearch<T>(\n ctx: SearchContext<T>,\n id: string,\n record: T | null,\n version?: number,\n): Promise<void> {\n if (!ctx.searchIndexStore && !ctx.vectorSet) return\n if (record === null) {\n await ctx.adapter.delete(ctx.vault, '_vec', encodeVecId(ctx.name, id))\n ctx.vectorSet?.markDirty()\n } else {\n await embedOnWrite(ctx, id, record, version ?? 1)\n }\n const store = ctx.searchIndexStore\n if (store && 'removePersisted' in store) await (store as { removePersisted(): Promise<void> }).removePersisted()\n else store?.markDirty()\n}\n"],"mappings":"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","names":[]}
@@ -1,81 +0,0 @@
1
- import {
2
- lazy
3
- } from "./chunk-5D2ALSM5.js";
4
-
5
- // src/with-formula/derivations/stale.ts
6
- var loadDerivationExecutor = lazy(() => import("./executor-TOLPBHOC.js"));
7
- var _staleByRegistry = /* @__PURE__ */ new WeakMap();
8
- var keyFor = (source, sourceId) => `${source}/${sourceId}`;
9
- async function markStale(registry, strategy, sourceId) {
10
- let map = _staleByRegistry.get(registry);
11
- if (!map) {
12
- map = /* @__PURE__ */ new Map();
13
- _staleByRegistry.set(registry, map);
14
- }
15
- const k = keyFor(strategy.source, sourceId);
16
- let set = map.get(k);
17
- if (!set) {
18
- set = /* @__PURE__ */ new Set();
19
- map.set(k, set);
20
- }
21
- set.add(strategy);
22
- }
23
- async function resolveStaleOnRead(accessor, outputCollection, id) {
24
- const registry = accessor.registry();
25
- const producers = registry.strategiesProducingOutput(outputCollection);
26
- if (producers.length === 0) return;
27
- const map = _staleByRegistry.get(registry);
28
- if (!map) return;
29
- let DerivationExecutor = null;
30
- for (const { spec, strategyHash } of producers) {
31
- const k = keyFor(spec.source, id);
32
- const pending = map.get(k);
33
- if (!pending || !pending.has(spec)) continue;
34
- const sourceColl = accessor.getCollection(spec.source);
35
- const source = await sourceColl.get(id);
36
- if (!source) {
37
- pending.delete(spec);
38
- continue;
39
- }
40
- const sourceWithId = { ...source, id };
41
- DerivationExecutor ??= (await loadDerivationExecutor()).DerivationExecutor;
42
- const ctx = { vault: accessor.getReadOnlyFacade() };
43
- const result = await DerivationExecutor.run(spec, sourceWithId, 0, strategyHash, ctx);
44
- for (const key of Object.keys(spec.outputs)) {
45
- const out = result.outputs[key];
46
- if (!out) continue;
47
- if (out.kind === "failed") {
48
- const err = out.error;
49
- if (spec.strict) {
50
- throw err;
51
- }
52
- console.warn(
53
- `[derivation] lazy output "${key}" for source "${spec.source}" id="${id}" failed:`,
54
- err
55
- );
56
- continue;
57
- }
58
- if (out.kind === "array") {
59
- console.warn(
60
- `[derivation] unexpected array-shape output "${key}" in lazy resolve path; array-shape derivations require lifecycle: "eager".`
61
- );
62
- continue;
63
- }
64
- const outSpec = spec.outputs[key];
65
- if (!outSpec) continue;
66
- const outputColl = accessor.getCollection(outSpec.collection);
67
- if (out.skipped === true) {
68
- await outputColl._internalDelete(id, accessor.getActiveTxContext());
69
- continue;
70
- }
71
- await outputColl.put(id, out.value);
72
- }
73
- pending.delete(spec);
74
- }
75
- }
76
-
77
- export {
78
- markStale,
79
- resolveStaleOnRead
80
- };
81
- //# sourceMappingURL=chunk-YPMW26SL.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/with-formula/derivations/stale.ts"],"sourcesContent":["import type { Collection } from '../../kernel/collection.js'\nimport type { DerivationExecutor as DerivationExecutorType } from './executor.js'\nimport type { ReadOnlyVaultFacade } from '../../with-audit/guards/types.js'\nimport type { TxContext } from '../../with-commit/tx/transaction.js'\nimport type { DerivationRegistry } from './registry.js'\n// Type-only — runtime class loaded via dynamic import in\n// `resolveStaleOnRead` only when a stale flag actually fires. Keeps\n// the executor chunk out of the floor bundle.\nimport type { DerivationSpec } from './types.js'\n\nimport { lazy } from '../../kernel/lazy.js'\n\n/** #846c — one memoized binding instead of a cast at the call site. */\nconst loadDerivationExecutor = lazy(() => import('./executor.js'))\n\n/**\n * Accessor shape passed through from the owning Vault. Provides the\n * registry (used to look up strategies and as the WeakMap key) and a\n * resolver from collection name to the live `Collection` instance.\n * Same shape as `Collection.derivationSource` so callers can pass it\n * through directly.\n */\nexport interface DerivationStaleAccessor {\n registry(): DerivationRegistry\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n getCollection(name: string): Collection<any>\n /**\n * Read-only vault facade handed to `derive(source, ctx)` on the lazy\n * resolve-on-read path. Same instance/shape as the eager path uses.\n */\n getReadOnlyFacade(): ReadOnlyVaultFacade\n /**\n * Active multi-record TxContext or `null`. The lazy resolve-on-read\n * path uses this to register tombstone deletes on `_executed` so a\n * later rollback restores the prior emission. Mirrors the eager\n * path's rollback tracking; the lazy `put` was historically\n * unregistered but the tombstone delete (a NEW write path)\n * matches the eager registration for symmetry.\n */\n getActiveTxContext(): TxContext | null\n}\n\n/**\n * In-memory stale map keyed by `DerivationRegistry` instance (stable\n * per vault). Maps `${source}/${sourceId}` → set of pending strategies.\n *\n * Persistence across vault close is NOT implemented in v1 (concern\n * flagged in the dim14 spec). On vault re-open, derived records'\n * `_derivedFrom.strategyHash` will still match the registered\n * strategies' hash, so an unset stale flag is interpreted as \"fresh.\"\n * `vault.deriveAll()` (Task D13) is the explicit recompute escape\n * hatch.\n *\n * @internal\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nconst _staleByRegistry = new WeakMap<DerivationRegistry, Map<string, Set<DerivationSpec<any, any>>>>()\n\nconst keyFor = (source: string, sourceId: string): string => `${source}/${sourceId}`\n\n/** Mark every output of (strategy, sourceId) as stale. */\nexport async function markStale(\n registry: DerivationRegistry,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n strategy: DerivationSpec<any, any>,\n sourceId: string,\n): Promise<void> {\n let map = _staleByRegistry.get(registry)\n if (!map) {\n map = new Map()\n _staleByRegistry.set(registry, map)\n }\n const k = keyFor(strategy.source, sourceId)\n let set = map.get(k)\n if (!set) {\n set = new Set()\n map.set(k, set)\n }\n set.add(strategy)\n}\n\n/**\n * Called from `Collection.get` on lazy-mode output collections. If the\n * id has a pending stale flag for any strategy producing this output\n * collection, re-derive before returning the record. No-op when there\n * is no pending work — keeps the read fast path negligible.\n */\nexport async function resolveStaleOnRead(\n accessor: DerivationStaleAccessor,\n outputCollection: string,\n id: string,\n): Promise<void> {\n const registry = accessor.registry()\n const producers = registry.strategiesProducingOutput(outputCollection)\n if (producers.length === 0) return\n\n const map = _staleByRegistry.get(registry)\n if (!map) return\n\n // Dynamic-import the executor only when at least one stale flag\n // actually fires. Vaults with no derivation strategies never call\n // this function (gated on `derivationSource` in `Collection.get`);\n // vaults with strategies but no pending stale ids reach the\n // `pending.has(spec)` short-circuit below without ever touching\n // the executor chunk.\n let DerivationExecutor: typeof DerivationExecutorType | null = null\n\n for (const { spec, strategyHash } of producers) {\n const k = keyFor(spec.source, id)\n const pending = map.get(k)\n if (!pending || !pending.has(spec)) continue\n\n // Read the source record from the source collection and re-derive.\n // We use the same getCollection accessor that eager dispatch uses\n // — it returns the live `Collection<any>` instance with full\n // crypto / keyring wiring.\n const sourceColl = accessor.getCollection(spec.source)\n const source = await sourceColl.get(id)\n if (!source) {\n pending.delete(spec)\n continue\n }\n const sourceWithId = { ...(source as Record<string, unknown>), id } as Record<string, unknown> & { id: string }\n // sourceVersion: not tracked in v1 stale map; pass 0 — matches the\n // forthcoming v0 semantics, `_derivedFrom.sourceVersion` is\n // informational, not load-bearing for correctness.\n DerivationExecutor ??= (await loadDerivationExecutor()).DerivationExecutor\n const ctx = { vault: accessor.getReadOnlyFacade() }\n const result = await DerivationExecutor.run(spec, sourceWithId, 0, strategyHash, ctx)\n for (const key of Object.keys(spec.outputs)) {\n const out = result.outputs[key]\n if (!out) continue\n if (out.kind === 'failed') {\n const err = out.error\n if (spec.strict) {\n // Leave the stale flag set so a future read retries.\n throw err\n }\n console.warn(\n `[derivation] lazy output \"${key}\" for source \"${spec.source}\" id=\"${id}\" failed:`,\n err,\n )\n continue\n }\n if (out.kind === 'array') {\n // Defensive — array-shape requires `lifecycle: 'eager'`\n // (validated at withDerivation registration).\n // Reaching the lazy-resolve path for array-shape would mean\n // a registration check was bypassed. Log and skip.\n console.warn(\n `[derivation] unexpected array-shape output \"${key}\" in lazy resolve path; `\n + 'array-shape derivations require lifecycle: \"eager\".',\n )\n continue\n }\n const outSpec = spec.outputs[key]\n if (!outSpec) continue\n const outputColl = accessor.getCollection(outSpec.collection)\n if (out.skipped === true) {\n // Optional output skipped on lazy resolve — delete any\n // prior emission so the read returns null (matches eager-path\n // tombstone semantics). Routed through `_internalDelete` so a\n // user-registered `onDelete` on the output collection\n // does NOT fire. The active TxContext (if any) is forwarded:\n // `resolveStaleOnRead` is reachable from `Collection.get()`\n // which can be called from inside a transaction, so the\n // tombstone must be observable to `revertExecuted` on\n // rollback.\n await outputColl._internalDelete(id, accessor.getActiveTxContext())\n continue\n }\n await outputColl.put(id, out.value)\n }\n pending.delete(spec)\n }\n}\n"],"mappings":";;;;;AAaA,IAAM,yBAAyB,KAAK,MAAM,OAAO,wBAAe,CAAC;AA2CjE,IAAM,mBAAmB,oBAAI,QAAwE;AAErG,IAAM,SAAS,CAAC,QAAgB,aAA6B,GAAG,MAAM,IAAI,QAAQ;AAGlF,eAAsB,UACpB,UAEA,UACA,UACe;AACf,MAAI,MAAM,iBAAiB,IAAI,QAAQ;AACvC,MAAI,CAAC,KAAK;AACR,UAAM,oBAAI,IAAI;AACd,qBAAiB,IAAI,UAAU,GAAG;AAAA,EACpC;AACA,QAAM,IAAI,OAAO,SAAS,QAAQ,QAAQ;AAC1C,MAAI,MAAM,IAAI,IAAI,CAAC;AACnB,MAAI,CAAC,KAAK;AACR,UAAM,oBAAI,IAAI;AACd,QAAI,IAAI,GAAG,GAAG;AAAA,EAChB;AACA,MAAI,IAAI,QAAQ;AAClB;AAQA,eAAsB,mBACpB,UACA,kBACA,IACe;AACf,QAAM,WAAW,SAAS,SAAS;AACnC,QAAM,YAAY,SAAS,0BAA0B,gBAAgB;AACrE,MAAI,UAAU,WAAW,EAAG;AAE5B,QAAM,MAAM,iBAAiB,IAAI,QAAQ;AACzC,MAAI,CAAC,IAAK;AAQV,MAAI,qBAA2D;AAE/D,aAAW,EAAE,MAAM,aAAa,KAAK,WAAW;AAC9C,UAAM,IAAI,OAAO,KAAK,QAAQ,EAAE;AAChC,UAAM,UAAU,IAAI,IAAI,CAAC;AACzB,QAAI,CAAC,WAAW,CAAC,QAAQ,IAAI,IAAI,EAAG;AAMpC,UAAM,aAAa,SAAS,cAAc,KAAK,MAAM;AACrD,UAAM,SAAS,MAAM,WAAW,IAAI,EAAE;AACtC,QAAI,CAAC,QAAQ;AACX,cAAQ,OAAO,IAAI;AACnB;AAAA,IACF;AACA,UAAM,eAAe,EAAE,GAAI,QAAoC,GAAG;AAIlE,4BAAwB,MAAM,uBAAuB,GAAG;AACxD,UAAM,MAAM,EAAE,OAAO,SAAS,kBAAkB,EAAE;AAClD,UAAM,SAAS,MAAM,mBAAmB,IAAI,MAAM,cAAc,GAAG,cAAc,GAAG;AACpF,eAAW,OAAO,OAAO,KAAK,KAAK,OAAO,GAAG;AAC3C,YAAM,MAAM,OAAO,QAAQ,GAAG;AAC9B,UAAI,CAAC,IAAK;AACV,UAAI,IAAI,SAAS,UAAU;AACzB,cAAM,MAAM,IAAI;AAChB,YAAI,KAAK,QAAQ;AAEf,gBAAM;AAAA,QACR;AACA,gBAAQ;AAAA,UACN,6BAA6B,GAAG,iBAAiB,KAAK,MAAM,SAAS,EAAE;AAAA,UACvE;AAAA,QACF;AACA;AAAA,MACF;AACA,UAAI,IAAI,SAAS,SAAS;AAKxB,gBAAQ;AAAA,UACN,+CAA+C,GAAG;AAAA,QAEpD;AACA;AAAA,MACF;AACA,YAAM,UAAU,KAAK,QAAQ,GAAG;AAChC,UAAI,CAAC,QAAS;AACd,YAAM,aAAa,SAAS,cAAc,QAAQ,UAAU;AAC5D,UAAI,IAAI,YAAY,MAAM;AAUxB,cAAM,WAAW,gBAAgB,IAAI,SAAS,mBAAmB,CAAC;AAClE;AAAA,MACF;AACA,YAAM,WAAW,IAAI,IAAI,IAAI,KAAK;AAAA,IACpC;AACA,YAAQ,OAAO,IAAI;AAAA,EACrB;AACF;","names":[]}