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

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 (62) hide show
  1. package/CHANGELOG.md +40 -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-UB5CRTFF.js → chunk-7BE7ZDVQ.js} +379 -1
  7. package/dist/chunk-7BE7ZDVQ.js.map +1 -0
  8. package/dist/{chunk-ESVREVWN.js → chunk-7NTBVFGD.js} +2 -2
  9. package/dist/{chunk-T6PSWLAR.js → chunk-BUE2D7PX.js} +25 -2
  10. package/dist/chunk-BUE2D7PX.js.map +1 -0
  11. package/dist/{chunk-S5AMPTNE.js → chunk-E5OHKSBD.js} +2 -2
  12. package/dist/{chunk-OEA5NVSU.js → chunk-H7XCIM3Z.js} +59 -6
  13. package/dist/chunk-H7XCIM3Z.js.map +1 -0
  14. package/dist/chunk-KL7AS3HV.js +84 -0
  15. package/dist/chunk-KL7AS3HV.js.map +1 -0
  16. package/dist/chunk-RXLYKVTF.js +17 -0
  17. package/dist/chunk-RXLYKVTF.js.map +1 -0
  18. package/dist/chunk-TFMM5DBB.js +20 -0
  19. package/dist/chunk-TFMM5DBB.js.map +1 -0
  20. package/dist/chunk-TPHAGLUH.js +30 -0
  21. package/dist/chunk-TPHAGLUH.js.map +1 -0
  22. package/dist/{chunk-A5WZEPXJ.js → chunk-VGICZPVE.js} +5 -3
  23. package/dist/{chunk-A5WZEPXJ.js.map → chunk-VGICZPVE.js.map} +1 -1
  24. package/dist/{chunk-KHKKD62N.js → chunk-VYYM3MEN.js} +2 -16
  25. package/dist/chunk-VYYM3MEN.js.map +1 -0
  26. package/dist/{chunk-I6DGKPTY.js → chunk-XMF3GKUR.js} +60 -412
  27. package/dist/chunk-XMF3GKUR.js.map +1 -0
  28. package/dist/derivations/index.js +17 -6
  29. package/dist/dispatch-XEBHUGBV.js +250 -0
  30. package/dist/dispatch-XEBHUGBV.js.map +1 -0
  31. package/dist/index.d.ts +2 -2
  32. package/dist/index.js +19 -19
  33. package/dist/introspection/index.js +3 -2
  34. package/dist/kernel/collection.d.ts +11 -6
  35. package/dist/kernel/match-pairs.d.ts +30 -0
  36. package/dist/{noydb-QJFZAH34.js → noydb-JJXPICIC.js} +9 -7
  37. package/dist/pod/index.js +9 -7
  38. package/dist/registry-BZQIVNNO.js +29 -0
  39. package/dist/{walk-G3CZIL4L.js → walk-P3E6QSNC.js} +4 -3
  40. package/dist/with-formula/derivations/dispatch.d.ts +16 -1
  41. package/dist/with-formula/derivations/registry.d.ts +15 -0
  42. package/dist/with-formula/derivations/trigger-match.d.ts +52 -0
  43. package/dist/with-formula/derivations/types.d.ts +23 -0
  44. package/dist/with-shape/introspection/behaviors.d.ts +7 -0
  45. package/dist/with-shape/introspection/describe.d.ts +10 -0
  46. package/package.json +3 -3
  47. package/dist/chunk-I6DGKPTY.js.map +0 -1
  48. package/dist/chunk-KHKKD62N.js.map +0 -1
  49. package/dist/chunk-OEA5NVSU.js.map +0 -1
  50. package/dist/chunk-T6PSWLAR.js.map +0 -1
  51. package/dist/chunk-UB5CRTFF.js.map +0 -1
  52. package/dist/chunk-YPMW26SL.js +0 -81
  53. package/dist/chunk-YPMW26SL.js.map +0 -1
  54. package/dist/dispatch-IM52HDXT.js +0 -194
  55. package/dist/dispatch-IM52HDXT.js.map +0 -1
  56. package/dist/registry-FZXPQRAB.js +0 -18
  57. /package/dist/{backup-VASRVLDZ.js.map → backup-R6A4VZRX.js.map} +0 -0
  58. /package/dist/{chunk-ESVREVWN.js.map → chunk-7NTBVFGD.js.map} +0 -0
  59. /package/dist/{chunk-S5AMPTNE.js.map → chunk-E5OHKSBD.js.map} +0 -0
  60. /package/dist/{noydb-QJFZAH34.js.map → noydb-JJXPICIC.js.map} +0 -0
  61. /package/dist/{registry-FZXPQRAB.js.map → registry-BZQIVNNO.js.map} +0 -0
  62. /package/dist/{walk-G3CZIL4L.js.map → walk-P3E6QSNC.js.map} +0 -0
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/with-shape/introspection/field-meta.ts","../src/with-shape/introspection/describe.ts","../src/kernel/via/graph.ts"],"sourcesContent":["/**\n * Consumer-neutral, data-relatable field descriptors — the canonical,\n * validator-agnostic authoring channel. Merged by `collection.describe()`.\n *\n * Descriptive, never prescriptive: label/semanticType/unit/sensitivity/\n * aggregate/aliases/displayFor only. Layout, styling, and active-locale\n * selection stay app-side.\n *\n * @module\n */\n\n/** Known semantic types; the union is open — unknown strings pass through. */\nexport type SemanticType =\n | 'date' | 'datetime' | 'email' | 'url' | 'currency' | 'percent'\n | 'country' | 'vat' | 'iban' | 'entity'\n | (string & {})\n\nexport interface FieldMeta {\n /** Human label for any displayable field. Required. */\n label: string\n description?: string\n semanticType?: SemanticType\n /** Display unit, e.g. '€', '%', 'kg'. */\n unit?: string\n /** Data classification driving redaction/inspector masking. */\n sensitivity?: 'public' | 'pii' | 'secret'\n /** Default aggregation for this field. */\n aggregate?: 'sum' | 'count' | 'distinct' | 'none'\n /** Canonical search synonyms (data vocabulary, not UI). */\n aliases?: readonly string[]\n /** Entity pairing: the field holding the human label for this id (buyerId → buyerName). */\n displayFor?: string\n /**\n * Override the widget hint derived from semanticType/type.\n * e.g. 'textarea', 'date', 'money', 'select', 'checkbox', 'number', 'text'.\n * When absent, the widget is derived automatically by buildDescription.\n */\n widget?: string\n /**\n * Card/section grouping hint for detail & form layouts (e.g. 'Identity',\n * 'Amounts'). Purely descriptive metadata — consumers group; describe()\n * keeps emitting fields alphabetically.\n */\n group?: string\n /** Relative ordering hint within (and across) groups. Lower renders first. */\n order?: number\n}\n\nexport class FieldMetaUnknownFieldError extends Error {\n constructor(public readonly collection: string, public readonly key: string) {\n super(`fieldMeta for collection \"${collection}\" references unknown field \"${key}\". `\n + `Declare it in the schema/config or remove the fieldMeta entry.`)\n this.name = 'FieldMetaUnknownFieldError'\n }\n}\n\n/** Reject fieldMeta keys that are not known fields (typo guard), fail-loud. */\nexport function validateFieldMetaKeys(\n collection: string,\n fieldMeta: Record<string, FieldMeta>,\n knownFields: ReadonlySet<string>,\n): void {\n for (const key of Object.keys(fieldMeta)) {\n if (!knownFields.has(key)) throw new FieldMetaUnknownFieldError(collection, key)\n }\n}\n\nexport interface MergeInputs {\n channel?: FieldMeta\n zodMeta?: Partial<FieldMeta>\n inferred?: Partial<FieldMeta>\n}\nexport interface ResolvedMeta extends Partial<FieldMeta> { label: string }\n\n/** camelCase / snake_case → Title Case words. */\nexport function humanizeFieldKey(key: string): string {\n return key\n .replace(/[_-]+/g, ' ')\n .replace(/([a-z0-9])([A-Z])/g, '$1 $2')\n .trim()\n .split(/\\s+/)\n .map((w) => w.charAt(0).toUpperCase() + w.slice(1))\n .join(' ')\n}\n\n/** Merge one field's metadata: channel > zodMeta > inferred; label always present. */\nexport function resolveFieldMeta(key: string, inputs: MergeInputs): ResolvedMeta {\n const { channel, zodMeta, inferred } = inputs\n const pick = <K extends keyof FieldMeta>(k: K): FieldMeta[K] | undefined =>\n channel?.[k] ?? zodMeta?.[k] ?? inferred?.[k]\n const description = pick('description')\n const semanticType = pick('semanticType')\n const unit = pick('unit')\n const sensitivity = pick('sensitivity')\n const aggregate = pick('aggregate')\n const aliases = pick('aliases')\n const displayFor = pick('displayFor')\n const widget = pick('widget')\n const group = pick('group')\n const order = pick('order')\n return {\n label: pick('label') ?? humanizeFieldKey(key),\n ...(description !== undefined ? { description } : {}),\n ...(semanticType !== undefined ? { semanticType } : {}),\n ...(unit !== undefined ? { unit } : {}),\n ...(sensitivity !== undefined ? { sensitivity } : {}),\n ...(aggregate !== undefined ? { aggregate } : {}),\n ...(aliases !== undefined ? { aliases } : {}),\n ...(displayFor !== undefined ? { displayFor } : {}),\n ...(widget !== undefined ? { widget } : {}),\n ...(group !== undefined ? { group } : {}),\n ...(order !== undefined ? { order } : {}),\n }\n}\n","/**\n * `buildDescription` — pure assembler that merges collection config\n * (moneyFields / dictKeyFields / refs / computed / fieldMeta) with an\n * optional validator-derived `zodFields` map into a normalised\n * {@link CollectionDescription}.\n *\n * The sync path (`collection.describe()`) passes `zodFields: undefined`.\n * The async path supplies a populated map and validator-derived type\n * strings.\n *\n * @module\n */\n\nimport type { FieldMeta } from './field-meta.js'\nimport { resolveFieldMeta, validateFieldMetaKeys, FieldMetaUnknownFieldError, humanizeFieldKey } from './field-meta.js'\nimport type { CollectionMeta } from './meta.js'\nimport type { MoneyDescriptor } from '../../via/money/descriptor.js'\nimport type { ViaDescriptor, ViaPosture } from '../../kernel/via/index.js'\nimport type { DictKeyDescriptor, StaticDictDescriptor } from '../../via/i18n/dictionary.js'\nimport { isStaticDictDescriptor } from '../../via/i18n/dictionary.js'\n// #650 Task 2 — native lookup()/enumOf()/dict() fields (separate from\n// dictKeyFields above, which stays the dictKey()/staticDict() alias's own\n// input). Emits the SAME `dict` block shape for byte-parity (the tiers this\n// covers — 'static' with a table, 'reserved' — are the alias-equivalent\n// ones; a first-class 'collection' backing degrades to the \"no resolved\n// labels\" fallback, same as an unresolved dynamic dictKey).\nimport type { LookupDescriptor } from '../../via/lookup/descriptor.js'\n// #650 Task 7 — the 'lookup' binding's describeFragment payload shape (the\n// first-ever NoydbVia.describeFragment consumer, via/index.ts:136). describe.ts\n// is NOT under kernel/**, so importing this via/ type directly is fine\n// (Check 14 via-layering only restricts the kernel spine).\nimport type { LookupDescribeFragment, LookupDescribeFragmentEntry } from '../../via/lookup/binding.js'\n// #657 — the 'blob' binding's describeFragment payload shape (the second-ever\n// NoydbVia.describeFragment consumer, after lookup above). Same rationale:\n// describe.ts is not under kernel/**, so importing this via/ type is fine.\nimport type { BlobDescribeFragment, BlobDescribeFragmentEntry } from '../../via/blob/binding.js'\nimport type { I18nTextDescriptor } from '../../via/i18n/core.js'\nimport type { ComputedFields } from '../../with-formula/computed/index.js'\nimport type { RefDescriptor } from '../../kernel/refs.js'\nimport type { ClassifiedFieldSpec } from '../../via/classified/descriptor.js'\nimport { derivePersistedSchema, isZod4Schema } from '../persisted-schemas/derive.js'\nimport { loadPersistedSchema } from '../persisted-schemas/storage.js'\nimport { jsonSchemaToFields } from './fields.js'\nimport type { NoydbStore } from '../../kernel/types.js'\nimport type { EnclaveKey } from '../../kernel/enclave/index.js'\n\n// ─── Public types ──────────────────────────────────────────────────────────\n\nexport interface DescribedField {\n readonly key: string\n /**\n * Stable, opaque per-field identity (#946) — survives a rename (the field\n * keeps its id when its name/`key` changes). Sourced from the persisted\n * schema's `PersistedSchemaEnvelope.fieldIds` map, so it is only present on\n * the async `describeAsync()` path for a collection that has persisted a\n * schema (`persistJsonSchema: true`) at least once; absent on the sync\n * `describe()` path (no store I/O) and on any field with no persisted id\n * yet.\n */\n readonly id?: string\n /** Sync: inferred from config ('number'|'enum'|'string'|'array'|'unknown'). Async (Task 4): validator-derived. */\n readonly type: string\n readonly optional: boolean\n readonly constraints?: Record<string, unknown>\n readonly label: string\n readonly description?: string\n /** Card/section grouping hint for detail & form layouts. From fieldMeta/zod .meta(). */\n readonly group?: string\n /** Relative ordering hint within and across groups. Lower renders first. */\n readonly order?: number\n readonly semanticType?: string\n readonly unit?: string\n readonly sensitivity?: 'public' | 'pii' | 'secret'\n readonly aggregate?: 'sum' | 'count' | 'distinct' | 'none'\n readonly aliases?: readonly string[]\n readonly ref?: { target: string; mode: string; isArray?: true }\n readonly displayFor?: string\n readonly money?: { mode: 'fixed' | 'multi'; currency?: string; scale?: number; rounding?: string }\n readonly dict?: { name: string; static: boolean; values?: readonly { value: string; label?: string }[] }\n /**\n * Normalized lookup metadata (#650 Task 7 — the first-ever\n * `NoydbVia.describeFragment` consumer; sourced from the `'lookup'`\n * binding's fragment, NOT from config directly — see `buildDescription`).\n * Present for every `lookup()`/`enumOf()`/`dict()` field, ALONGSIDE the\n * pre-existing `dict` block above (kept byte-stable for the\n * `dictKey()`/`staticDict()` alias — this is additive, not a replacement).\n * `dimension` is omitted for a bare `enumOf()` (no backing store, no\n * dimension name — the #650 Task 2 `dimension:''` sentinel resolved).\n * `keys` is the statically-known closed-vocabulary key set (declared\n * `keys`, or a static table's own keys) — omitted when membership lives\n * only in the backing collection/dictionary.\n */\n readonly lookup?: {\n readonly dimension?: string\n readonly backing: 'static' | 'reserved' | 'collection'\n readonly vocabulary: 'open' | 'closed'\n readonly key: string\n readonly altKeys?: readonly string[]\n readonly present?: { readonly label: string; readonly by?: string }\n readonly sortBy?: string\n readonly onDelete: 'restrict' | 'cascade' | 'nullify'\n readonly keys?: readonly string[]\n }\n readonly computed?: true\n /** i18n metadata for fields declared with i18nText(). Present only when the field is i18n-enabled. */\n readonly i18n?: { readonly locales?: readonly string[]; readonly densify?: boolean }\n /** Widget hint derived from semanticType+type, overridable via fieldMeta.widget. */\n readonly widget: string\n /** Whether the field is user-editable. False for computed, id, and provenance-stamped fields. */\n readonly editable: boolean\n /** Present when the field is classified. Serialized read-projection contract. */\n readonly classified?: {\n readonly preset: string\n readonly storage: 'recoverable' | 'never' | 'digest-only'\n readonly list: 'omit' | { readonly mask: string } | { readonly rider: string }\n /**\n * Present (and `true`) only when the field opted into the equatable blind\n * index. Additive metadata — emitted UNGATED (no `withClassified()` needed;\n * `toJSONSchema()` carries it too), so it advertises *that* the field is\n * equatable beyond the DEK-consent boundary. Intended: it's a structural\n * property a schema consumer legitimately needs and discloses no value. A\n * deployment wanting it gated can gate this behind its value-bearing door.\n */\n readonly equatable?: true\n }\n /**\n * Present only for a field declared via `blobFields` (#657) — out-of-band\n * attachment storage (`collection.blob(id)`), never a sealed or plaintext\n * record field. `queryable` mirrors the binding's fixed `ViaPosture`\n * (blob content is never indexed — always `'none'`); the rest mirrors the\n * declared `blobFields[field]` policy verbatim (scalars) or as a presence\n * flag (predicate knobs — a predicate has no serializable form). Sourced\n * from the `'blob'` binding's `describeFragment()`, the same\n * `viaFragments` door the `lookup` block above is sourced from.\n */\n readonly blob?: {\n readonly retainDays?: number\n readonly evictWhen?: true\n readonly legalHold?: true\n readonly retainUntil?: true\n readonly external?: true\n readonly public?: true\n readonly backlink?: string\n readonly queryable: 'none'\n }\n /**\n * Present only for a graph-tainted derived field (#638 Task 3 — computed/\n * derivation output whose effective posture was forced away from the\n * plain baseline by a source's posture, e.g. a computed field reading a\n * classified field). `forcedBy` names the immediate declared source\n * field(s) responsible.\n */\n readonly taint?: {\n readonly posture: ViaPosture\n readonly forcedBy: readonly string[]\n }\n}\n\nexport interface CollectionDescription {\n readonly collection: string\n readonly fields: readonly DescribedField[]\n /** Collection-level descriptive metadata; label falls back to the humanized collection name. */\n readonly meta: CollectionMeta\n}\n\n/** Options for the async describe(opts) overload. */\nexport interface DescribeOptions {\n /**\n * When true, resolve dynamic-dict labels from vault.dictionary(name).list()\n * and populate dict.values[].label for dynamic dictKey fields.\n */\n readonly resolveDictLabels?: boolean\n}\n\n// ─── ZodField slot (async path wires this; sync path passes undefined) ───\n\nexport interface ZodFieldSlot {\n readonly type?: string\n readonly optional?: boolean\n readonly constraints?: Record<string, unknown>\n readonly meta?: Partial<FieldMeta>\n}\n\n// ─── RECOGNIZED zod-4 .meta() keys ────────────────────────────────────────\n//\n// Empirically verified 2026-06-25 via:\n// node -e \"const {z,toJSONSchema}=require('zod'); console.log(JSON.stringify(toJSONSchema(z.object({n:z.number().meta({label:'L',unit:'kg',sensitivity:'public',semanticType:'percent',description:'d',aggregate:'sum',aliases:['a'],displayFor:'x'})}))), null, 2)\"\n// Result: .meta() keys are emitted INLINE on each JSON Schema property object\n// at the same level as `type`/`format` — NOT nested under a `metadata` key.\n// Example: z.number().meta({ unit: 'kg' }) → { \"type\": \"number\", \"unit\": \"kg\" }\n// Unknown .meta() keys (not in this set) are ignored.\nconst ZOD_META_KEYS = new Set<string>([\n 'label', 'description', 'unit', 'semanticType', 'sensitivity', 'aggregate', 'aliases', 'displayFor',\n 'group', 'order',\n])\n\n// ─── deriveZodFields ──────────────────────────────────────────────────────\n\n/**\n * Derive per-field slots from a Standard Schema validator.\n * - Calls `derivePersistedSchema` to get the JSON Schema.\n * - Falls back to `{}` for non-Zod or unknown validators.\n * - For zod-4 schemas: reads recognized `.meta()` keys from inline property annotations.\n * - Returns: `Record<fieldKey, { type, optional, constraints?, meta? }>`.\n *\n * No static zod import — all zod access is lazy via derivePersistedSchema.\n */\n/**\n * Field KEYS from a configured validator, synchronously — the complement to\n * {@link deriveZodFields}, which derives field TYPES and must be async.\n *\n * The distinction is the whole point (#1253). `describe()`'s sync path passed\n * `zodFields: undefined`, so `fieldMeta` key-validation could never run there\n * and a typo'd key became a PHANTOM FIELD carrying its declared `sensitivity`\n * while the real field went undescribed — an inventory wrong in both\n * directions, silently, on the surface `sensitivity` exists to serve.\n *\n * That was read as unavoidable (\"the sync path cannot know the schema's\n * fields\"), which is true of types and false of keys: a Zod object exposes\n * `.shape` directly, on both v3 and v4, with no JSON-Schema derivation and no\n * `zod-to-json-schema` peer. The latter matters — on Zod 3 that peer is\n * required for the async path, so the sync path is the only one some\n * consumers can reach.\n *\n * A duck-typed probe, not a Zod dependency: an unrecognised validator returns\n * `undefined` and the caller stays silent rather than guessing, so a validator\n * hub cannot read never produces a false \"unknown field\" error.\n *\n * ZodEffects (reported by the pilot on #1249's guard, and it equally affected\n * #1253's): on Zod 3 an OBJECT-LEVEL `.refine()` wraps the object in a\n * ZodEffects whose `.shape` is undefined — the object sits at `_def.schema` —\n * so \"has a schema\" and \"hub can enumerate its fields\" silently diverged for\n * exactly the schemas most worth guarding (validated ones). Unwrapped here,\n * but ONLY through refinement effects: a `.transform()`/`.preprocess()`\n * changes the OUTPUT shape, so its inner keys would be a lie — those stay\n * `undefined` (silent), deliberately. Zod 4 keeps `.shape` through `.refine()`\n * and never enters the loop. Bounded depth, so a cyclic duck cannot hang us.\n */\nexport function schemaFieldKeys(schema: unknown): readonly string[] | undefined {\n let s: unknown = schema\n for (let depth = 0; depth < 10; depth++) {\n if (s === null || typeof s !== 'object') return undefined\n const shape: unknown = (s as { shape?: unknown }).shape\n if (shape !== null && typeof shape === 'object') {\n const keys = Object.keys(shape as Record<string, unknown>)\n return keys.length > 0 ? keys : undefined\n }\n // Zod 3 ZodEffects: follow `_def.schema` for refinements only.\n const def = (s as { _def?: { effect?: { type?: unknown }; schema?: unknown } })._def\n if (def?.schema !== undefined && def.effect?.type === 'refinement') {\n s = def.schema\n continue\n }\n return undefined\n }\n return undefined\n}\n\nexport async function deriveZodFields(\n schema: unknown,\n): Promise<Record<string, ZodFieldSlot>> {\n const envelope = await derivePersistedSchema(schema)\n if (!envelope.jsonSchema) return {}\n\n const isZod4 = isZod4Schema(schema)\n const jsonSchema = envelope.jsonSchema as Record<string, unknown>\n\n // Use the existing JSON-Schema→field mapper for type/optional/constraints.\n const fieldDescriptors = jsonSchemaToFields(jsonSchema, 'live-validator')\n\n // For zod-4: the JSON Schema properties contain inline .meta() annotations.\n // We need to walk the raw properties to extract recognized meta keys.\n const propertiesRaw = jsonSchema['properties'] as Record<string, Record<string, unknown>> | undefined\n\n const result: Record<string, ZodFieldSlot> = {}\n\n for (const [key, descriptor] of Object.entries(fieldDescriptors)) {\n let meta: Partial<FieldMeta> | undefined\n\n if (isZod4 && propertiesRaw) {\n const prop = propertiesRaw[key]\n if (prop) {\n const extracted: Partial<FieldMeta> = {}\n let hasAny = false\n for (const mk of ZOD_META_KEYS) {\n if (mk in prop) {\n (extracted as Record<string, unknown>)[mk] = prop[mk]\n hasAny = true\n }\n }\n if (hasAny) meta = extracted\n }\n }\n\n result[key] = {\n type: descriptor.type,\n optional: descriptor.optional === true,\n ...(descriptor.constraints !== undefined ? { constraints: descriptor.constraints } : {}),\n ...(meta !== undefined ? { meta } : {}),\n }\n }\n\n return result\n}\n\n// ─── resolveDescribeFieldIds (#946) ───────────────────────────────────────\n\n/**\n * Resolve the persisted-schema `fieldIds` map for `describeAsync()`'s\n * `BuildDescriptionInput` (#946). Returns a spreadable partial so the call\n * site can inline it with `...(await resolveDescribeFieldIds(...))` without\n * an intermediate local — `collection.ts`'s `describe()` bodies are under a\n * near-zero line-ceiling headroom.\n *\n * Silent-degrades to `{}` (no ids — every `DescribedField.id` stays\n * `undefined`, never a describe() crash) on any failure: `getDEK` throwing,\n * no persisted envelope yet, or an envelope with no `fieldIds` (legacy /\n * never-persisted). Mirrors the leak/degrade posture of\n * `satellites/dead-filter.ts`'s `liveBaseIdSetsForBundle`, but degrading to\n * \"no ids\" is lossless here (unlike that filter's data-exposure tradeoff) —\n * describe() simply omits `id` for every field.\n */\nexport async function resolveDescribeFieldIds(\n store: NoydbStore,\n vaultName: string,\n collectionName: string,\n getDEK: (collectionName: string) => Promise<EnclaveKey>,\n): Promise<{ fieldIds?: Record<string, string> }> {\n let dek: EnclaveKey\n try {\n dek = await getDEK(collectionName)\n } catch {\n return {}\n }\n const persisted = await loadPersistedSchema(store, vaultName, collectionName, dek)\n return persisted?.fieldIds !== undefined ? { fieldIds: persisted.fieldIds } : {}\n}\n\n// ─── buildDescription ─────────────────────────────────────────────────────\n\nexport interface BuildDescriptionInput {\n readonly collection: string\n readonly fieldMeta: Record<string, FieldMeta> | undefined\n /**\n * Kernel-visible collections only carry the opaque {@link ViaDescriptor}\n * marker (the kernel never inspects a Via feature's concrete descriptor\n * shape). Every entry here is actually a {@link MoneyDescriptor} — only\n * `money()` constructs them — narrowed locally below where its\n * mode/currency/scale are read for the describe() output.\n */\n readonly moneyFields: Record<string, ViaDescriptor> | undefined\n readonly dictKeyFields: Record<string, DictKeyDescriptor | StaticDictDescriptor> | undefined\n /** Native lookup()/enumOf()/dict() fields (#650 Task 2) — declared only via `viaFields`/`via()`, no sugar key. */\n readonly lookupFields?: Record<string, LookupDescriptor> | undefined\n readonly computed: ComputedFields | undefined\n readonly refs: Record<string, RefDescriptor>\n /** Async path fills this; sync path passes `undefined`. */\n readonly zodFields: Record<string, ZodFieldSlot> | undefined\n /**\n * The collection's configured validator, if any. The sync path passes it so\n * `fieldMeta` keys can still be validated (#1253): field KEYS are readable\n * synchronously via {@link schemaFieldKeys} even though field TYPES are not.\n * `undefined` means no validator is configured, which is itself informative\n * — the config keys are then the complete field set.\n */\n readonly schema?: unknown\n /**\n * Async path: when `resolveDictLabels` was true, this map holds\n * `{ dictName -> { value -> label } }` for dynamic dictKey fields.\n * Used to populate `dict.values[].label`.\n */\n readonly dictLabels?: Record<string, Record<string, string>> | undefined\n /** Collection-level descriptive metadata. Label falls back to humanized collection name. */\n readonly meta?: CollectionMeta | undefined\n /**\n * Map of field name → I18nTextDescriptor for fields declared with i18nText().\n * When present, describe() surfaces an `i18n` block on matching DescribedField entries.\n */\n readonly i18nFields?: Record<string, I18nTextDescriptor> | undefined\n /** Per-field classified specs (already resolved/flattened). */\n readonly classified?: Record<string, ClassifiedFieldSpec> | undefined\n /** Graph-computed taint overlay (#638 Task 3) — `Collection.via?.taint`. */\n readonly taint?: { readonly postures: ReadonlyMap<string, ViaPosture>; readonly provenance?: ReadonlyMap<string, readonly string[]> } | undefined\n /**\n * Per-binding `describeFragment()` output, keyed by binding brand (#650\n * Task 7 — first-ever consumer of `NoydbVia.describeFragment`,\n * `via/index.ts:136`). `Collection.describe()`/`describeAsync()` build this\n * from `this.via?.describeFragments()`. The `'lookup'` brand's fragment\n * feeds the `lookup` block below; `'blob'`'s feeds the `blob` block\n * (#657, the second consumer). Other brands' fragments ride along unread\n * until they gain a consumer too.\n */\n readonly viaFragments?: Record<string, Record<string, unknown>> | undefined\n /**\n * Persisted-schema `fieldName -> id` map (#946), resolved by the async\n * describe path via {@link resolveDescribeFieldIds}. The sync path passes\n * no `fieldIds` (no store I/O), so every field's `id` is `undefined` there.\n */\n readonly fieldIds?: Record<string, string> | undefined\n}\n\n// Re-export so that callers that want to catch the error don't need another import path.\nexport { FieldMetaUnknownFieldError }\n\n// ─── Widget derivation ────────────────────────────────────────────────────────\n\n/**\n * Derive a widget hint from resolved field metadata.\n * Priority: explicit override (resolvedWidget) > semanticType > dict > type > 'text'.\n */\nfunction deriveWidget(opts: {\n semanticType?: string\n type: string\n dict?: unknown\n /** #650 Task 7 — same 'select' outcome as `dict` (today always co-present for a lookup field via the existing `dict` block; kept as its own check so a future lookup-only field, with no `dict` block, still derives 'select'). */\n lookup?: unknown\n /** #657 — a blobFields-declared field derives the 'file' widget, never the 'text' default (binary content, not a text input). */\n blob?: unknown\n resolvedWidget?: string\n}): string {\n if (opts.resolvedWidget !== undefined) return opts.resolvedWidget\n switch (opts.semanticType) {\n case 'date':\n case 'datetime':\n return 'date'\n case 'currency':\n return 'money'\n case 'entity':\n return 'ref-select'\n case 'url':\n return 'url'\n case 'email':\n return 'email'\n case 'percent':\n return 'number'\n }\n if (opts.dict !== undefined || opts.lookup !== undefined) return 'select'\n if (opts.blob !== undefined) return 'file'\n if (opts.type === 'boolean') return 'checkbox'\n if (opts.type === 'number') return 'number'\n return 'text'\n}\n\n/**\n * Pure assembler: no I/O, no side effects.\n * Builds a {@link CollectionDescription} from the collection's in-memory config.\n *\n * When `zodFields` is supplied (async path), validates that every `fieldMeta` key\n * refers to a known field (config ∪ zodFields). Throws `FieldMetaUnknownFieldError`\n * on the first unknown key — this is the real validation that the vault.ts no-op\n * couldn't do (schema fields weren't knowable synchronously).\n */\nexport function buildDescription(input: BuildDescriptionInput): CollectionDescription {\n const { collection, fieldMeta, moneyFields, dictKeyFields, lookupFields, computed, refs, zodFields, schema, dictLabels, meta, i18nFields, classified, taint, fieldIds } = input\n\n // #650 Task 7 — the 'lookup' binding's describeFragment, keyed per field.\n // Deliberately routed through `viaFragments` rather than reused directly\n // from `lookupFields` above (which only feeds the pre-existing `dict`\n // block, byte-stable for the alias) — this IS the first-ever\n // `describeFragment` consumption the task wires up.\n const lookupFragments = (input.viaFragments?.['lookup'] as LookupDescribeFragment | undefined)?.lookupFields\n\n // #657 — the 'blob' binding's describeFragment, keyed per field (same\n // routing pattern as lookupFragments above). A blobFields-declared field\n // has no other config-map source (unlike money/dict/lookup/i18n/classified,\n // each of which is fed a dedicated map below) — `blobFragments` is BOTH\n // this field's block source AND (via its keys) how it joins `allKeys` at\n // all, since otherwise a blobFields-only field with no fieldMeta entry is\n // invisible in describe() (#657 finding 1).\n const blobFragments = (input.viaFragments?.['blob'] as BlobDescribeFragment | undefined)?.blobFields\n\n // Validate fieldMeta keys against the real known-field set = config keys ∪ the\n // validator's fields. Sound whenever that set can be enumerated COMPLETELY:\n //\n // async path zodFields is non-empty — derived types carry the keys\n // sync path `.shape` was readable — no derivation, no peer dep (#1253)\n //\n // Silent in EVERY other case, and the third one is the instructive one. It is\n // tempting to reason \"no validator configured, therefore the config keys are the\n // whole field set\" — that is FALSE. A collection may be typed by a TypeScript\n // generic alone (`v.collection<Sale>('sales', { moneyFields: … })`), whose fields\n // are real, are present in the data, and are legitimately named by `fieldMeta`,\n // yet appear in NO runtime config. Guarding there rejects correct code.\n //\n // So these are deliberate false NEGATIVES on both counts: an unreadable validator\n // and an absent one. A rule that over-fires teaches people to stop declaring\n // fieldMeta at all, which costs more than the typos it would catch.\n //\n // Before #1253 this ran on the async path alone, so a typo'd key on the sync path\n // became a phantom field carrying its `sensitivity` while the real field went\n // undescribed — see schemaFieldKeys() for why that was thought unavoidable.\n const validatorKeys = zodFields !== undefined && Object.keys(zodFields).length > 0\n ? Object.keys(zodFields)\n : schemaFieldKeys(schema)\n const canEnumerateFields = validatorKeys !== undefined\n if (canEnumerateFields && fieldMeta !== undefined) {\n const knownFields = new Set<string>([\n ...Object.keys(moneyFields ?? {}),\n ...Object.keys(dictKeyFields ?? {}),\n ...Object.keys(refs),\n ...Object.keys(computed ?? {}),\n ...(validatorKeys ?? []),\n ...Object.keys(i18nFields ?? {}),\n ...Object.keys(lookupFields ?? {}),\n ...Object.keys(blobFragments ?? {}),\n ...Object.keys(classified ?? {}),\n ])\n validateFieldMetaKeys(collection, fieldMeta, knownFields)\n }\n\n // Union of all config key sources — stable alphabetical order.\n const allKeys = new Set<string>([\n ...Object.keys(moneyFields ?? {}),\n ...Object.keys(dictKeyFields ?? {}),\n ...Object.keys(lookupFields ?? {}),\n ...Object.keys(refs),\n ...Object.keys(computed ?? {}),\n ...Object.keys(fieldMeta ?? {}),\n ...Object.keys(zodFields ?? {}),\n ...Object.keys(i18nFields ?? {}),\n ...Object.keys(classified ?? {}),\n ...Object.keys(blobFragments ?? {}),\n ])\n\n const fields: DescribedField[] = []\n\n for (const key of [...allKeys].sort()) {\n const zod = zodFields?.[key]\n // Narrow the opaque marker back to the concrete descriptor — see the\n // {@link BuildDescriptionInput.moneyFields} doc comment.\n const money = moneyFields?.[key] as MoneyDescriptor | undefined\n const dict = dictKeyFields?.[key]\n const lookupDesc = lookupFields?.[key]\n const refDesc = refs[key]\n const isComputed = computed !== undefined && key in computed\n const i18nDesc = i18nFields?.[key]\n const cls = classified?.[key]\n const blobFragmentEntry: BlobDescribeFragmentEntry | undefined = blobFragments?.[key]\n const taintPosture = taint?.postures.get(key)\n\n // ── Infer type + structural extras ────────────────────────────────────\n let type = zod?.type ?? 'unknown'\n const inferred: Partial<FieldMeta> = {}\n\n let moneyBlock: DescribedField['money'] | undefined\n let dictBlock: DescribedField['dict'] | undefined\n let refBlock: DescribedField['ref'] | undefined\n let lookupBlock: DescribedField['lookup'] | undefined\n let blobBlock: DescribedField['blob'] | undefined\n\n if (money) {\n type = 'number'\n inferred.semanticType = 'currency'\n inferred.aggregate = 'sum'\n const currency = money.soleCurrency()\n const scale = currency !== undefined ? money.scaleFor(currency) : undefined\n moneyBlock = {\n mode: money.mode,\n ...(currency !== undefined ? { currency } : {}),\n ...(scale !== undefined ? { scale } : {}),\n ...(money.rounding !== undefined ? { rounding: money.rounding } : {}),\n }\n } else if (refDesc) {\n type = refDesc.isArray ? 'array' : 'string'\n inferred.semanticType = 'entity'\n refBlock = {\n target: refDesc.target,\n mode: refDesc.mode,\n ...(refDesc.isArray ? { isArray: true as const } : {}),\n }\n } else if (dict) {\n type = 'enum'\n if (isStaticDictDescriptor(dict)) {\n // Static dict: labels available synchronously from the in-code table.\n const displayLocale = dict.displayLocale\n const values = dict.keys.map((k) => {\n const localeMap = dict.table[k]\n const label = displayLocale !== undefined\n ? (localeMap?.[displayLocale] ?? undefined)\n : undefined\n return label !== undefined\n ? { value: k, label }\n : { value: k }\n })\n dictBlock = { name: dict.name, static: true, values }\n } else {\n // Dynamic dictKey: keys declared in config; labels optionally resolved\n // from vault.dictionary(name).list() when resolveDictLabels was requested.\n const labelMap = dictLabels?.[dict.name]\n const labelMapHasEntries = labelMap !== undefined && Object.keys(labelMap).length > 0\n if (labelMapHasEntries) {\n // We have resolved labels — build values from the label map.\n const values = Object.entries(labelMap).map(([value, label]) => ({ value, label }))\n dictBlock = { name: dict.name, static: false, values }\n } else if (dict.keys !== undefined) {\n const values = dict.keys.map((k) => {\n const label = dict.labels?.[k]\n return label !== undefined ? { value: k, label } : { value: k }\n })\n dictBlock = { name: dict.name, static: false, values }\n } else {\n dictBlock = { name: dict.name, static: false }\n }\n }\n } else if (lookupDesc) {\n // Native lookup()/enumOf()/dict() field (#650 Task 2) — the SAME `dict`\n // block shape as above, for byte-parity with the dictKey()/staticDict()\n // alias. A table-bearing descriptor ('static' backing, the lookup(static)\n // form staticDict() compiles onto) resolves synchronously like a static\n // dict; a reserved-tier dict() resolves via `dictLabels` exactly like a\n // dynamic dictKey (review fix — was previously falling to the\n // declared-keys-only fallback below, breaking async describe parity);\n // everything else (collection/bare enum) falls to that fallback.\n type = 'enum'\n const lookupLabelMap = dictLabels?.[lookupDesc.dimension]\n const lookupLabelMapHasEntries = lookupLabelMap !== undefined && Object.keys(lookupLabelMap).length > 0\n if (lookupDesc.table !== undefined) {\n const displayLocale = lookupDesc.displayLocale\n const table = lookupDesc.table\n const values = (lookupDesc.keys ?? Object.keys(table)).map((k) => {\n const localeMap = table[k]\n const label = displayLocale !== undefined\n ? (localeMap?.[displayLocale] ?? undefined)\n : undefined\n return label !== undefined ? { value: k, label } : { value: k }\n })\n dictBlock = { name: lookupDesc.dimension, static: true, values }\n } else if (lookupLabelMapHasEntries) {\n const values = Object.entries(lookupLabelMap).map(([value, label]) => ({ value, label }))\n dictBlock = { name: lookupDesc.dimension, static: false, values }\n } else if (lookupDesc.keys !== undefined) {\n const values = lookupDesc.keys.map((k) => {\n const label = lookupDesc.labels?.[k]\n return label !== undefined ? { value: k, label } : { value: k }\n })\n dictBlock = { name: lookupDesc.dimension, static: false, values }\n } else {\n dictBlock = { name: lookupDesc.dimension, static: false }\n }\n } else if (i18nDesc) {\n // i18nText field: the stored value is a locale-map object, but the resolved\n // type exposed to consumers is 'string' (locale resolution collapses to a string).\n type = 'string'\n } else if (isComputed) {\n // type already initialized to zod?.type ?? 'unknown' above; no re-set needed\n } else if (blobFragmentEntry !== undefined) {\n // #657 finding 1 — a blobFields-only field (no schema/money/dict/\n // lookup/i18n config) was previously invisible; with a fieldMeta\n // entry it fell through to the 'unknown'/'text'/editable:true default,\n // actively wrong for binary content. `type` is a plain `string` on\n // DescribedField (not a closed union), so 'blob' is not a breaking\n // union extension.\n type = 'blob'\n blobBlock = { ...blobFragmentEntry, queryable: 'none' }\n }\n\n // ── lookup block (#650 Task 7 — first-ever NoydbVia.describeFragment\n // consumer) ── Sourced from `lookupFragments` (the binding's fragment),\n // NOT from `lookupDesc` above (which only feeds the pre-existing `dict`\n // block, byte-stable for the alias) — routing through the fragment is\n // the point: `backing`/`vocabulary`/`key`/`onDelete` have no other\n // source in this function, so their presence here is itself proof the\n // describeFragment seam (declared via/index.ts:136, zero consumers before\n // this task) carries data end to end from binding to describe() output.\n const lookupFragmentEntry: LookupDescribeFragmentEntry | undefined = lookupFragments?.[key]\n if (lookupFragmentEntry !== undefined) {\n lookupBlock = {\n ...(lookupFragmentEntry.dimension !== undefined ? { dimension: lookupFragmentEntry.dimension } : {}),\n backing: lookupFragmentEntry.backing,\n vocabulary: lookupFragmentEntry.vocabulary,\n key: lookupFragmentEntry.key,\n onDelete: lookupFragmentEntry.onDelete,\n ...(lookupFragmentEntry.altKeys !== undefined ? { altKeys: lookupFragmentEntry.altKeys } : {}),\n ...(lookupFragmentEntry.present !== undefined ? { present: lookupFragmentEntry.present } : {}),\n ...(lookupFragmentEntry.sortBy !== undefined ? { sortBy: lookupFragmentEntry.sortBy } : {}),\n ...(lookupFragmentEntry.keys !== undefined ? { keys: lookupFragmentEntry.keys } : {}),\n }\n }\n\n // Classified sensitivity feeds inferred meta at lowest precedence — channel\n // fieldMeta and zod .meta() still win via resolveFieldMeta's merge order.\n if (cls !== undefined) inferred.sensitivity = cls.sensitivity\n\n // ── Merge fieldMeta channel ────────────────────────────────────────────\n const channelEntry = fieldMeta?.[key]\n const zodMeta = zod?.meta\n const resolved = resolveFieldMeta(key, {\n ...(channelEntry !== undefined ? { channel: channelEntry } : {}),\n ...(zodMeta !== undefined ? { zodMeta } : {}),\n inferred,\n })\n\n // ── i18n block (only for i18nText fields) ─────────────────────────────\n let i18nBlock: DescribedField['i18n'] | undefined\n if (i18nDesc !== undefined) {\n const locales = i18nDesc.options.languages\n const densify = i18nDesc.options.densifyOnWrite\n i18nBlock = {\n ...(locales !== undefined ? { locales } : {}),\n ...(densify !== undefined ? { densify } : {}),\n }\n }\n\n // ── Widget (derived, overridable via resolvedWidget from fieldMeta/zodMeta) ──\n const widget = deriveWidget({\n type,\n ...(resolved.semanticType !== undefined ? { semanticType: resolved.semanticType } : {}),\n ...(dictBlock !== undefined ? { dict: dictBlock } : {}),\n ...(lookupBlock !== undefined ? { lookup: lookupBlock } : {}),\n ...(blobBlock !== undefined ? { blob: blobBlock } : {}),\n ...(resolved.widget !== undefined ? { resolvedWidget: resolved.widget } : {}),\n })\n\n // ── Editable: false for computed, id, provenance-stamped, blob fields ──\n // Provenance fields (_source, _sourceTs) are envelope-level metadata, not\n // user schema fields — they won't appear as keys in fieldMeta so this check\n // is implicitly handled (they'd never appear in allKeys). Explicitly guard\n // computed + 'id' as the two structural non-editable cases. A blobFields\n // field (#657) is never written through the record codec — it's mutated\n // exclusively via `collection.blob(id)` — so it is never editable either.\n const editable = !isComputed && key !== 'id' && blobBlock === undefined\n\n // ── Assemble the field (exactOptionalPropertyTypes-safe spreads) ───────\n const field: DescribedField = {\n key,\n ...(fieldIds?.[key] !== undefined ? { id: fieldIds[key] } : {}),\n type,\n optional: zod?.optional ?? false,\n ...(zod?.constraints !== undefined ? { constraints: zod.constraints } : {}),\n label: resolved.label,\n ...(resolved.description !== undefined ? { description: resolved.description } : {}),\n ...(resolved.group !== undefined ? { group: resolved.group } : {}),\n ...(resolved.order !== undefined ? { order: resolved.order } : {}),\n ...(resolved.semanticType !== undefined ? { semanticType: resolved.semanticType } : {}),\n ...(resolved.unit !== undefined ? { unit: resolved.unit } : {}),\n ...(resolved.sensitivity !== undefined ? { sensitivity: resolved.sensitivity } : {}),\n ...(resolved.aggregate !== undefined ? { aggregate: resolved.aggregate } : {}),\n ...(resolved.aliases !== undefined ? { aliases: resolved.aliases } : {}),\n ...(resolved.displayFor !== undefined ? { displayFor: resolved.displayFor } : {}),\n ...(refBlock !== undefined ? { ref: refBlock } : {}),\n ...(moneyBlock !== undefined ? { money: moneyBlock } : {}),\n ...(dictBlock !== undefined ? { dict: dictBlock } : {}),\n ...(lookupBlock !== undefined ? { lookup: lookupBlock } : {}),\n ...(blobBlock !== undefined ? { blob: blobBlock } : {}),\n ...(isComputed ? { computed: true as const } : {}),\n ...(i18nBlock !== undefined ? { i18n: i18nBlock } : {}),\n widget,\n editable,\n ...(cls !== undefined ? {\n classified: {\n preset: cls.preset,\n storage: cls.storage,\n list: cls.list.kind === 'omit' ? 'omit' as const\n : cls.list.kind === 'mask' ? { mask: cls.list.pattern }\n : { rider: cls.list.rider },\n ...(cls.equatable === true ? { equatable: true as const } : {}),\n },\n } : {}),\n ...(taintPosture !== undefined ? { taint: { posture: taintPosture, forcedBy: taint?.provenance?.get(key) ?? [] } } : {}),\n }\n\n fields.push(field)\n }\n\n // Build collection-level meta with label fallback to humanized collection name.\n const collectionMeta: CollectionMeta = {\n ...meta,\n label: meta?.label ?? humanizeFieldKey(collection),\n }\n\n return {\n collection,\n fields,\n meta: collectionMeta,\n }\n}\n","// 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":[]}
@@ -252,7 +252,7 @@ async function createOwnerOnAdoptedPartition(store, vaultName, opts) {
252
252
  }
253
253
  }
254
254
  if (isManaged(opts)) {
255
- const { createNoydb } = await import("./noydb-QJFZAH34.js");
255
+ const { createNoydb } = await import("./noydb-JJXPICIC.js");
256
256
  const db = await createNoydb({
257
257
  store,
258
258
  user: userId,
@@ -307,4 +307,4 @@ export {
307
307
  withCargo,
308
308
  describeExtraction
309
309
  };
310
- //# sourceMappingURL=chunk-ESVREVWN.js.map
310
+ //# sourceMappingURL=chunk-7NTBVFGD.js.map
@@ -38,11 +38,34 @@ function withDerivation(spec) {
38
38
  `withDerivation: triggerBy.collection must not equal the source "${spec.source}" (use sources[] for same-id triggers)`
39
39
  );
40
40
  }
41
- if (typeof t.on !== "string" || t.on.length === 0) {
41
+ const hasOn = typeof t.on === "string";
42
+ const hasMatch = Array.isArray(t.match);
43
+ if (hasOn === hasMatch) {
44
+ throw new ValidationError(
45
+ `withDerivation: triggerBy on "${t.collection}" needs exactly one of \`on\` or \`match\``
46
+ );
47
+ }
48
+ if (hasOn && t.on.length === 0) {
42
49
  throw new ValidationError(
43
50
  `withDerivation: triggerBy on "${t.collection}" needs a non-empty \`on\` (the FK field on the source)`
44
51
  );
45
52
  }
53
+ if (hasMatch) {
54
+ const match = t.match;
55
+ if (match.length === 0) {
56
+ throw new ValidationError(`withDerivation: triggerBy match on "${t.collection}" must be non-empty`);
57
+ }
58
+ const seen = /* @__PURE__ */ new Set();
59
+ for (const p of match) {
60
+ if (typeof p?.from !== "string" || p.from.length === 0 || typeof p?.to !== "string" || p.to.length === 0) {
61
+ throw new ValidationError(`withDerivation: triggerBy match on "${t.collection}" needs non-empty \`from\` and \`to\` in every pair`);
62
+ }
63
+ if (seen.has(p.to)) {
64
+ throw new ValidationError(`withDerivation: triggerBy match on "${t.collection}" repeats \`to: "${p.to}"\` \u2014 two pairs constraining one source field is a contradiction, not a wider match`);
65
+ }
66
+ seen.add(p.to);
67
+ }
68
+ }
46
69
  if (t.maxFanout !== void 0 && (!Number.isInteger(t.maxFanout) || t.maxFanout < 1)) {
47
70
  throw new ValidationError(
48
71
  `withDerivation: triggerBy maxFanout on "${t.collection}" must be a positive integer (got ${String(t.maxFanout)}).`
@@ -126,4 +149,4 @@ export {
126
149
  withDerivation,
127
150
  withRollup
128
151
  };
129
- //# sourceMappingURL=chunk-T6PSWLAR.js.map
152
+ //# sourceMappingURL=chunk-BUE2D7PX.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/with-formula/derivations/with-derivation.ts","../src/with-formula/derivations/with-rollup.ts"],"sourcesContent":["import { ValidationError } from '../../kernel/errors.js'\nimport type { DerivationSpec, DerivationStrategy } from './types.js'\n\n/**\n * Register a deterministic derivation: one source collection → one or\n * more typed outputs, computed by the user's `derive` function on\n * plaintext after DEK unwrap. Outputs are encrypted with the same DEK\n * as the source and written via the standard `Collection.put` path.\n *\n * See design-history/2026-05-01-dim14-derivation-v1-design.md.\n */\nexport function withDerivation<\n TSource extends Record<string, unknown>,\n TOutputs extends Record<string, Record<string, unknown> | ReadonlyArray<Record<string, unknown>>>,\n>(spec: DerivationSpec<TSource, TOutputs>): DerivationStrategy {\n if (!spec.source || spec.source.length === 0) {\n throw new ValidationError('withDerivation: source collection name is required')\n }\n if (!spec.outputs || Object.keys(spec.outputs).length === 0) {\n throw new ValidationError('withDerivation: outputs map must declare at least one output')\n }\n if (spec.deterministic !== true) {\n throw new ValidationError('withDerivation: v1 only supports deterministic derivations')\n }\n if (typeof spec.derive !== 'function') {\n throw new ValidationError('withDerivation: derive must be a function')\n }\n\n // Validate declared sibling sources. Each must be a non-empty\n // string and must differ from the primary source — a self-reference\n // would double-register the strategy under the same `_bySource` key.\n if (spec.sources !== undefined) {\n for (const extra of spec.sources) {\n if (typeof extra !== 'string' || extra.length === 0) {\n throw new ValidationError('withDerivation: each entry in sources[] must be a non-empty string')\n }\n if (extra === spec.source) {\n throw new ValidationError(\n `withDerivation: sources[] must not contain the primary source \"${spec.source}\"`,\n )\n }\n }\n }\n\n // Validate FK triggers. Each `collection` must be a non-empty\n // string differing from the primary source, and `on` a non-empty field.\n if (spec.triggerBy !== undefined) {\n for (const t of spec.triggerBy) {\n if (typeof t?.collection !== 'string' || t.collection.length === 0) {\n throw new ValidationError('withDerivation: each triggerBy entry needs a non-empty `collection`')\n }\n if (t.collection === spec.source) {\n throw new ValidationError(\n `withDerivation: triggerBy.collection must not equal the source \"${spec.source}\" (use sources[] for same-id triggers)`,\n )\n }\n const hasOn = typeof (t as { on?: unknown }).on === 'string'\n const hasMatch = Array.isArray((t as { match?: unknown }).match)\n if (hasOn === hasMatch) {\n throw new ValidationError(\n `withDerivation: triggerBy on \"${t.collection}\" needs exactly one of \\`on\\` or \\`match\\``,\n )\n }\n if (hasOn && (t as { on: string }).on.length === 0) {\n throw new ValidationError(\n `withDerivation: triggerBy on \"${t.collection}\" needs a non-empty \\`on\\` (the FK field on the source)`,\n )\n }\n if (hasMatch) {\n const match = (t as { match: ReadonlyArray<{ from: string; to: string }> }).match\n if (match.length === 0) {\n throw new ValidationError(`withDerivation: triggerBy match on \"${t.collection}\" must be non-empty`)\n }\n const seen = new Set<string>()\n for (const p of match) {\n if (typeof p?.from !== 'string' || p.from.length === 0 || typeof p?.to !== 'string' || p.to.length === 0) {\n throw new ValidationError(`withDerivation: triggerBy match on \"${t.collection}\" needs non-empty \\`from\\` and \\`to\\` in every pair`)\n }\n if (seen.has(p.to)) {\n throw new ValidationError(`withDerivation: triggerBy match on \"${t.collection}\" repeats \\`to: \"${p.to}\"\\` — two pairs constraining one source field is a contradiction, not a wider match`)\n }\n seen.add(p.to)\n }\n }\n if (t.maxFanout !== undefined && (!Number.isInteger(t.maxFanout) || t.maxFanout < 1)) {\n throw new ValidationError(\n `withDerivation: triggerBy maxFanout on \"${t.collection}\" must be a positive integer (got ${String(t.maxFanout)}).`,\n )\n }\n }\n }\n\n // Validate array-shape outputs.\n const lifecycleMode = typeof spec.lifecycle === 'string' ? spec.lifecycle : spec.lifecycle.mode\n for (const [outputKey, outputSpec] of Object.entries(spec.outputs)) {\n // Self-write output (collection === source): reverse-denorm must declare\n // `denorm` (the fields it owns) — field-level provenance.\n if (outputSpec.shape === 'record' && outputSpec.collection === spec.source) {\n if (!outputSpec.denorm || outputSpec.denorm.length === 0) {\n throw new ValidationError(\n `withDerivation: self-write output \"${outputKey}\" (collection === source \"${spec.source}\") `\n + 'must declare `denorm: [...]` naming the fields it maintains.',\n )\n }\n }\n if (outputSpec.shape === 'array') {\n if (lifecycleMode !== 'eager') {\n throw new ValidationError(\n `withDerivation: shape 'array' supports lifecycle 'eager' only in this release `\n + `Output \"${outputKey}\" declared lifecycle '${lifecycleMode}'. `\n + 'Switch to `lifecycle: \"eager\"` or use shape: \"record\".',\n )\n }\n if (typeof outputSpec.key !== 'function') {\n throw new ValidationError(\n `withDerivation: shape 'array' output \"${outputKey}\" requires \\`key: (out) => string\\`.`,\n )\n }\n if (outputSpec.maxFanout !== undefined) {\n if (!Number.isInteger(outputSpec.maxFanout) || outputSpec.maxFanout < 1) {\n throw new ValidationError(\n `withDerivation: maxFanout for output \"${outputKey}\" must be a positive integer `\n + `(got ${String(outputSpec.maxFanout)}).`,\n )\n }\n }\n }\n }\n\n return {\n __noydb_strategy: 'derivation',\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n spec: spec as DerivationSpec<any, any>,\n }\n}\n","import { ValidationError } from '../../kernel/errors.js'\nimport type { DerivationSpec, DerivationStrategy } from './types.js'\n\n/** Options for {@link withRollup} (#844b — was an inline literal, so unnameable). */\nexport interface WithRollupOptions<\n TChild extends Record<string, unknown> = Record<string, unknown>,\n TParent extends Record<string, unknown> = Record<string, unknown>,\n> {\n /** Child collection — the trigger. */\n readonly from: string\n /** FK field on the child pointing at the parent id. */\n readonly key: keyof TChild & string\n /** Parent collection carrying the maintained summary. */\n readonly into: string\n /** Field on the parent to maintain. */\n readonly field: keyof TParent & string\n /** Reduces the matching children to the value written at `field`. */\n readonly compute: (children: TChild[]) => unknown\n}\n\n/**\n * `withRollup` — aggregate many child records onto a single field of their\n * parent. The reverse of a join: instead of reading children\n * on demand, the parent carries a maintained summary.\n *\n * ```ts\n * withRollup<Sale, Buyer>({\n * from: 'sales', // child collection (the trigger)\n * key: 'buyerId', // FK on the child → parent id\n * into: 'buyers', // parent collection\n * field: 'revenueByYear', // field on the parent to maintain\n * compute: (sales) => groupSumByYear(sales, 'total'),\n * })\n * ```\n *\n * On every write OR delete of a `from` record, the parent at id `child[key]`\n * is recomputed: `compute(allChildren where child[key] === parentId)` is\n * patched onto `parent[field]`. A parent write also recomputes its own\n * aggregate (so a parent created after its children still fills in). Only the\n * `field` is touched — the rest of the parent record is never clobbered — and\n * a value-equality guard suppresses no-op writes. The aggregate is gap-free\n * with respect to child inserts, updates, and deletes.\n *\n * Desugars to a `withDerivation` strategy carrying a `rollup` marker; dispatch\n * handles it without invoking the executor. Eager-only in this slice.\n */\nexport function withRollup<\n TChild extends Record<string, unknown> = Record<string, unknown>,\n TParent extends Record<string, unknown> = Record<string, unknown>,\n>(opts: WithRollupOptions<TChild, TParent>): DerivationStrategy {\n const { from, key, into, field, compute } = opts\n if (!from || from.length === 0) {\n throw new ValidationError('withRollup: `from` (child collection) is required')\n }\n if (!into || into.length === 0) {\n throw new ValidationError('withRollup: `into` (parent collection) is required')\n }\n if (from === into) {\n throw new ValidationError('withRollup: `from` and `into` must be different collections')\n }\n if (!key || key.length === 0) {\n throw new ValidationError('withRollup: `key` (FK field on the child) is required')\n }\n if (!field || field.length === 0) {\n throw new ValidationError('withRollup: `field` (target field on the parent) is required')\n }\n if (typeof compute !== 'function') {\n throw new ValidationError('withRollup: `compute` must be a function')\n }\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const spec: DerivationSpec<any, any> = {\n source: into, // the parent record is what carries the rolled-up field\n deterministic: true,\n rollup: { from, key, field, compute: compute as (children: unknown[]) => unknown },\n // Synthetic self-write output for registry / cycle bookkeeping. Dispatch\n // patches `field` directly (value-equality guarded); the executor is not run.\n outputs: { value: { shape: 'record', collection: into, denorm: [field] } },\n derive: () => ({ value: {} }), // never invoked for a rollup strategy\n lifecycle: 'eager',\n }\n\n return { __noydb_strategy: 'derivation', spec }\n}\n"],"mappings":"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","names":[]}
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  createNoydb
3
- } from "./chunk-I6DGKPTY.js";
3
+ } from "./chunk-XMF3GKUR.js";
4
4
  import {
5
5
  deriveSchemaManifest
6
6
  } from "./chunk-BBWVXF57.js";
@@ -286,4 +286,4 @@ export {
286
286
  followRedirects,
287
287
  open
288
288
  };
289
- //# sourceMappingURL=chunk-S5AMPTNE.js.map
289
+ //# sourceMappingURL=chunk-E5OHKSBD.js.map
@@ -1,12 +1,17 @@
1
1
  import {
2
- ViaGraph
3
- } from "./chunk-UB5CRTFF.js";
2
+ normalizeTriggerBy
3
+ } from "./chunk-TPHAGLUH.js";
4
+ import {
5
+ ViaGraph,
6
+ schemaFieldKeys
7
+ } from "./chunk-7BE7ZDVQ.js";
4
8
  import {
5
9
  sha256Hex
6
10
  } from "./chunk-SL3JAAKN.js";
7
11
  import {
8
12
  DerivationCycleError,
9
- DuplicateBehaviorNameError
13
+ DuplicateBehaviorNameError,
14
+ ValidationError
10
15
  } from "./chunk-7O3Y4PPD.js";
11
16
 
12
17
  // src/with-formula/derivations/strategy-hash.ts
@@ -46,7 +51,8 @@ var DerivationRegistry = class {
46
51
  }
47
52
  const outputKeys = Object.keys(spec.outputs);
48
53
  const strategyHash = await computeStrategyHash(spec.source, outputKeys, spec.derive, spec.sources);
49
- const reg = { spec, strategyHash };
54
+ const triggers = normalizeTriggerBy(spec.triggerBy);
55
+ const reg = { spec, strategyHash, triggers };
50
56
  if (spec.name !== void 0) this._byName.set(spec.name, reg);
51
57
  const fromSource = this._bySource.get(spec.source);
52
58
  if (fromSource) fromSource.push(reg);
@@ -56,7 +62,7 @@ var DerivationRegistry = class {
56
62
  if (fromExtra) fromExtra.push(reg);
57
63
  else this._bySource.set(extra, [reg]);
58
64
  }
59
- for (const t of spec.triggerBy ?? []) {
65
+ for (const t of triggers) {
60
66
  const fromTrigger = this._bySource.get(t.collection);
61
67
  if (fromTrigger) fromTrigger.push(reg);
62
68
  else this._bySource.set(t.collection, [reg]);
@@ -81,6 +87,53 @@ var DerivationRegistry = class {
81
87
  strategiesProducingOutput(collection) {
82
88
  return this._byOutput.get(collection) ?? [];
83
89
  }
90
+ /**
91
+ * The #1253-pattern typo guard for match fields (#1249): a misspelt
92
+ * `to`/`from` silently matches nothing forever, so validate against the
93
+ * collection's enumerable field set at the earliest point it exists.
94
+ * `schemaFieldKeys(schema) === undefined` (TS-generic collection,
95
+ * unreadable validator) is DELIBERATELY silent — those fields are real
96
+ * and unenumerable. `configKeys` folds in the collection's other
97
+ * non-schema field declarations (fieldMeta/moneyFields/dictKeyFields/
98
+ * refs/computed); `denormExempt` is derived here from this registry's
99
+ * own strategies rather than taken as a parameter — a field this
100
+ * collection's own derivations write via `denorm` is never a typo.
101
+ */
102
+ validateFieldsFor(collectionName, schema, configKeys) {
103
+ const shapeKeys = schemaFieldKeys(schema);
104
+ if (shapeKeys === void 0) return;
105
+ const keys = /* @__PURE__ */ new Set([...shapeKeys, ...configKeys]);
106
+ const denormExempt = /* @__PURE__ */ new Set();
107
+ for (const reg of new Set([...this._bySource.values()].flat())) {
108
+ if (reg.spec.source !== collectionName) continue;
109
+ for (const out of Object.values(reg.spec.outputs)) {
110
+ if (out.collection === collectionName) for (const d of out.denorm ?? []) denormExempt.add(d);
111
+ }
112
+ }
113
+ const regs = new Set([...this._bySource.values()].flat());
114
+ for (const reg of regs) {
115
+ for (const t of reg.triggers) {
116
+ if (reg.spec.source === collectionName) {
117
+ for (const p of t.match) {
118
+ if (!keys.has(p.to) && !denormExempt.has(p.to)) {
119
+ throw new ValidationError(
120
+ `derivation "${reg.spec.name ?? reg.spec.source}": triggerBy match names source field "${p.to}", which "${collectionName}" does not declare \u2014 a typo here silently matches nothing forever`
121
+ );
122
+ }
123
+ }
124
+ }
125
+ if (t.collection === collectionName) {
126
+ for (const p of t.match) {
127
+ if (p.from !== "id" && !keys.has(p.from) && !denormExempt.has(p.from)) {
128
+ throw new ValidationError(
129
+ `derivation "${reg.spec.name ?? reg.spec.source}": triggerBy match reads "${p.from}" from written "${collectionName}" records, which that collection does not declare \u2014 a typo here silently matches nothing forever`
130
+ );
131
+ }
132
+ }
133
+ }
134
+ }
135
+ }
136
+ }
84
137
  /**
85
138
  * All registered strategies as a flat, deduplicated array.
86
139
  * Each strategy is indexed once per source (not once per output key),
@@ -172,4 +225,4 @@ var DerivationRegistry = class {
172
225
  export {
173
226
  DerivationRegistry
174
227
  };
175
- //# sourceMappingURL=chunk-OEA5NVSU.js.map
228
+ //# sourceMappingURL=chunk-H7XCIM3Z.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/with-formula/derivations/strategy-hash.ts","../src/with-formula/derivations/registry.ts"],"sourcesContent":["/**\n * Deterministic hash of a derivation strategy's \"shape\": source\n * collection, declared sibling sources, output keys, derive function\n * source. Used to detect\n * strategy drift: a record whose `_derivedFrom.strategyHash` doesn't\n * match the current strategy is considered stale.\n *\n * Web Crypto SHA-256 — no extra deps.\n */\nimport { sha256Hex } from '../../kernel/enclave/index.js'\n\nexport async function computeStrategyHash(\n source: string,\n outputKeys: readonly string[],\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n derive: (...args: any[]) => any,\n sources?: ReadonlyArray<string>,\n): Promise<string> {\n const canonical = JSON.stringify({\n source,\n outputs: [...outputKeys].sort(),\n derive: derive.toString(),\n // Declared sibling sources — adding/removing a trigger\n // collection invalidates cached derived records. Omitted when empty\n // so strategies without siblings keep their existing hash.\n ...(sources?.length ? { sources: [...sources].sort() } : {}),\n })\n const bytes = new TextEncoder().encode(canonical)\n return sha256Hex(bytes)\n}\n","import { DerivationCycleError, DuplicateBehaviorNameError, ValidationError } from '../../kernel/errors.js'\nimport { ViaGraph, type FieldRef, type EdgeKind, type Grain } from '../../kernel/via/graph.js'\nimport { schemaFieldKeys } from '../../with-shape/introspection/describe.js'\nimport { computeStrategyHash } from './strategy-hash.js'\nimport { normalizeTriggerBy, type NormalizedTrigger } from './trigger-match.js'\nimport type { DerivationSpec } from './types.js'\n\n/**\n * Whole-record artifact-grain field marker (#638 Task 2). A derivation's\n * `derive()`/a rollup's `compute()` reads the WHOLE source record — there is\n * no single declared field to point at — so a trigger/output collection is\n * modelled as one artifact node per collection (`kernel/via/graph.ts`'s\n * \"Artifact-grain targets... modelled as a field node whose field is the\n * artifact key\" convention). MUST match `materialized-views/registry.ts`'s\n * and `vault.ts`'s overlay-edge marker so cross-registry edges (a derivation\n * feeding into / out of an MV or overlay collection) resolve to the SAME\n * graph node.\n */\nconst WHOLE_RECORD = '*'\n\n/** Strip this registry's `.${WHOLE_RECORD}` artifact suffix off a graph cycle\n * path entry, recovering the bare collection name — preserves the exact\n * pre-#638 `DerivationCycleError` message shape (behavior lock) for a pure\n * record-shape derivation cycle. A rollup's real-field target (not suffixed\n * `.${WHOLE_RECORD}`) is left untouched. */\nfunction stripArtifactSuffix(displayId: string): string {\n const suffix = `.${WHOLE_RECORD}`\n return displayId.endsWith(suffix) ? displayId.slice(0, -suffix.length) : displayId\n}\n\ninterface RegisteredStrategy {\n // Type-erased to allow the registry to hold heterogeneous strategies.\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n spec: DerivationSpec<any, any>\n strategyHash: string\n readonly triggers: ReadonlyArray<NormalizedTrigger>\n}\n\n/**\n * Vault-internal registry of derivation strategies. Owned by `Vault`;\n * not exported.\n *\n * @internal\n */\nexport class DerivationRegistry {\n private readonly _bySource = new Map<string, RegisteredStrategy[]>()\n private readonly _byOutput = new Map<string, RegisteredStrategy[]>()\n private readonly _byName = new Map<string, RegisteredStrategy>()\n\n /**\n * Register a derivation strategy. If `spec.name` is given and already\n * registered by another derivation in this vault, throws\n * {@link DuplicateBehaviorNameError} before indexing.\n */\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n async register(spec: DerivationSpec<any, any>): Promise<void> {\n if (spec.name !== undefined && this._byName.has(spec.name)) {\n throw new DuplicateBehaviorNameError(spec.name, 'derivation')\n }\n\n const outputKeys = Object.keys(spec.outputs)\n const strategyHash = await computeStrategyHash(spec.source, outputKeys, spec.derive, spec.sources)\n const triggers = normalizeTriggerBy(spec.triggerBy)\n const reg: RegisteredStrategy = { spec, strategyHash, triggers }\n\n if (spec.name !== undefined) this._byName.set(spec.name, reg)\n\n const fromSource = this._bySource.get(spec.source)\n if (fromSource) fromSource.push(reg)\n else this._bySource.set(spec.source, [reg])\n\n // Declared sibling sources index the SAME `reg` under each\n // extra collection so `strategiesForSource(extra)` returns it and a\n // sibling write re-fires the derivation. Sibling keys also enter\n // `_bySource`, so `validate()`'s cycle DFS walks them automatically.\n for (const extra of spec.sources ?? []) {\n const fromExtra = this._bySource.get(extra)\n if (fromExtra) fromExtra.push(reg)\n else this._bySource.set(extra, [reg])\n }\n\n // FK triggers index the SAME `reg` under each parent collection so\n // a parent write re-fires the derivation (fanned out to matching source\n // records in `dispatchDerivations`). Like sources[], these keys enter\n // `_bySource` so the cycle DFS walks the trigger→output edge.\n for (const t of triggers) {\n const fromTrigger = this._bySource.get(t.collection)\n if (fromTrigger) fromTrigger.push(reg)\n else this._bySource.set(t.collection, [reg])\n }\n\n // Rollup: a write to the child `from` collection recomputes\n // the parent (= spec.source = into) at id child[key]. Index under `from`\n // so a child write fires it; spec.source (into) is already indexed above,\n // so a parent write also recomputes its own aggregate (covers the\n // parent-created-after-children case). The from→into edge enters the cycle\n // DFS automatically.\n if (spec.rollup) {\n const fromRollup = this._bySource.get(spec.rollup.from)\n if (fromRollup) fromRollup.push(reg)\n else this._bySource.set(spec.rollup.from, [reg])\n }\n\n for (const key of outputKeys) {\n const output = spec.outputs[key]\n if (!output) continue\n const outputCollection = output.collection\n const arr = this._byOutput.get(outputCollection)\n if (arr) arr.push(reg)\n else this._byOutput.set(outputCollection, [reg])\n }\n }\n\n strategiesForSource(source: string): ReadonlyArray<RegisteredStrategy> {\n return this._bySource.get(source) ?? []\n }\n\n strategiesProducingOutput(collection: string): ReadonlyArray<RegisteredStrategy> {\n return this._byOutput.get(collection) ?? []\n }\n\n /**\n * The #1253-pattern typo guard for match fields (#1249): a misspelt\n * `to`/`from` silently matches nothing forever, so validate against the\n * collection's enumerable field set at the earliest point it exists.\n * `schemaFieldKeys(schema) === undefined` (TS-generic collection,\n * unreadable validator) is DELIBERATELY silent — those fields are real\n * and unenumerable. `configKeys` folds in the collection's other\n * non-schema field declarations (fieldMeta/moneyFields/dictKeyFields/\n * refs/computed); `denormExempt` is derived here from this registry's\n * own strategies rather than taken as a parameter — a field this\n * collection's own derivations write via `denorm` is never a typo.\n */\n validateFieldsFor(collectionName: string, schema: unknown, configKeys: ReadonlyArray<string>): void {\n const shapeKeys = schemaFieldKeys(schema)\n if (shapeKeys === undefined) return\n const keys = new Set([...shapeKeys, ...configKeys])\n const denormExempt = new Set<string>()\n for (const reg of new Set([...this._bySource.values()].flat())) {\n if (reg.spec.source !== collectionName) continue\n for (const out of Object.values(reg.spec.outputs) as Array<{ collection?: string; denorm?: readonly string[] }>) {\n if (out.collection === collectionName) for (const d of out.denorm ?? []) denormExempt.add(d)\n }\n }\n const regs = new Set([...this._bySource.values()].flat())\n for (const reg of regs) {\n for (const t of reg.triggers) {\n if (reg.spec.source === collectionName) {\n for (const p of t.match) {\n if (!keys.has(p.to) && !denormExempt.has(p.to)) {\n throw new ValidationError(\n `derivation \"${reg.spec.name ?? reg.spec.source}\": triggerBy match names source field \"${p.to}\", which \"${collectionName}\" does not declare — a typo here silently matches nothing forever`)\n }\n }\n }\n if (t.collection === collectionName) {\n for (const p of t.match) {\n if (p.from !== 'id' && !keys.has(p.from) && !denormExempt.has(p.from)) {\n throw new ValidationError(\n `derivation \"${reg.spec.name ?? reg.spec.source}\": triggerBy match reads \"${p.from}\" from written \"${collectionName}\" records, which that collection does not declare — a typo here silently matches nothing forever`)\n }\n }\n }\n }\n }\n }\n\n /**\n * All registered strategies as a flat, deduplicated array.\n * Each strategy is indexed once per source (not once per output key),\n * so iterating `_bySource.values()` naturally yields each strategy\n * exactly once per source — deduplication is handled by flattening\n * the per-source arrays and collecting into a Set by identity.\n *\n * Used by `dumpSchema()` / `describeDerivations()` in the introspection\n * walker to populate the derivations map.\n */\n all(): ReadonlyArray<RegisteredStrategy> {\n const seen = new Set<RegisteredStrategy>()\n for (const strategies of this._bySource.values()) {\n for (const s of strategies) seen.add(s)\n }\n return [...seen]\n }\n\n /**\n * Graph edges for #638 Task 2: one `'derivation'`/`'record'` edge per\n * (non-self-write) output key — target = the output collection (a\n * `WHOLE_RECORD` artifact node), sources = every collection that can\n * trigger this strategy (`source`, `sources[]`, `triggerBy[].collection`,\n * and — for a rollup — `rollup.from`), mirroring EXACTLY the trigger keys\n * `register()` indexes under `_bySource`. The self-write reverse-denorm\n * output (`output.collection === source` with `denorm` declared) is\n * skipped — the SAME condition the old local DFS used; it is not a cycle\n * (see the comment `validate()` used to carry, now on this skip).\n * Rollups ADDITIONALLY get a dedicated `'rollup'`/`'aggregate'` edge\n * targeting the real `rollup.field` on the parent (`source`) — the\n * self-write output above only covers the cycle-DFS skip, not\n * dispatch/taint (Tasks 3/4/6), which need the real field.\n */\n edges(): ReadonlyArray<{ readonly target: FieldRef; readonly sources: readonly FieldRef[]; readonly kind: EdgeKind; readonly grain: Grain }> {\n const out: Array<{ target: FieldRef; sources: FieldRef[]; kind: EdgeKind; grain: Grain }> = []\n for (const reg of this.all()) {\n const spec = reg.spec\n const triggerCollections = [\n spec.source,\n ...(spec.sources ?? []),\n ...(spec.triggerBy ?? []).map((t) => t.collection),\n ...(spec.rollup ? [spec.rollup.from] : []),\n ]\n const sources: FieldRef[] = triggerCollections.map((c) => ({ collection: c, field: WHOLE_RECORD }))\n for (const key of Object.keys(spec.outputs)) {\n const output = spec.outputs[key]\n if (!output) continue\n if (output.shape === 'record' && output.collection === spec.source && output.denorm !== undefined) continue\n out.push({ target: { collection: output.collection, field: WHOLE_RECORD }, sources, kind: 'derivation', grain: 'record' })\n }\n if (spec.rollup) {\n out.push({\n target: { collection: spec.source, field: spec.rollup.field },\n sources: [{ collection: spec.rollup.from, field: WHOLE_RECORD }],\n kind: 'rollup',\n grain: 'aggregate',\n })\n }\n }\n return out\n }\n\n /**\n * Cycle detection, delegated to `ViaGraph.assertAcyclic()` (#638 Task 2 —\n * retires the local DFS). Call after all `register()` calls complete (i.e.\n * at vault open). Throws `DerivationCycleError` on the first cycle found —\n * SAME class/timing as before; the message/path is byte-identical to the\n * pre-#638 shape for a pure record-shape derivation cycle (the\n * `WHOLE_RECORD` artifact suffix is stripped back to a bare collection\n * name before re-throwing).\n *\n * `graph` is the caller's shared per-vault graph, ALREADY carrying this\n * registry's `edges()` (the caller registers them — see `Vault._initDerivations`)\n * — omit it (e.g. this registry's own unit tests) to validate this\n * registry's edges in isolation against a throwaway graph.\n */\n validate(graph?: ViaGraph): void {\n const g = graph ?? new ViaGraph()\n if (!graph) {\n for (const edge of this.edges()) g.registerDerived(edge.target, edge.sources, edge.kind, edge.grain)\n }\n try {\n g.assertAcyclic()\n } catch (e) {\n if (e instanceof DerivationCycleError) throw new DerivationCycleError(e.path.map(stripArtifactSuffix))\n throw e\n }\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;AAWA,eAAsB,oBACpB,QACA,YAEA,QACA,SACiB;AACjB,QAAM,YAAY,KAAK,UAAU;AAAA,IAC/B;AAAA,IACA,SAAS,CAAC,GAAG,UAAU,EAAE,KAAK;AAAA,IAC9B,QAAQ,OAAO,SAAS;AAAA;AAAA;AAAA;AAAA,IAIxB,GAAI,SAAS,SAAS,EAAE,SAAS,CAAC,GAAG,OAAO,EAAE,KAAK,EAAE,IAAI,CAAC;AAAA,EAC5D,CAAC;AACD,QAAM,QAAQ,IAAI,YAAY,EAAE,OAAO,SAAS;AAChD,SAAO,UAAU,KAAK;AACxB;;;ACXA,IAAM,eAAe;AAOrB,SAAS,oBAAoB,WAA2B;AACtD,QAAM,SAAS,IAAI,YAAY;AAC/B,SAAO,UAAU,SAAS,MAAM,IAAI,UAAU,MAAM,GAAG,CAAC,OAAO,MAAM,IAAI;AAC3E;AAgBO,IAAM,qBAAN,MAAyB;AAAA,EACb,YAAY,oBAAI,IAAkC;AAAA,EAClD,YAAY,oBAAI,IAAkC;AAAA,EAClD,UAAU,oBAAI,IAAgC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQ/D,MAAM,SAAS,MAA+C;AAC5D,QAAI,KAAK,SAAS,UAAa,KAAK,QAAQ,IAAI,KAAK,IAAI,GAAG;AAC1D,YAAM,IAAI,2BAA2B,KAAK,MAAM,YAAY;AAAA,IAC9D;AAEA,UAAM,aAAa,OAAO,KAAK,KAAK,OAAO;AAC3C,UAAM,eAAe,MAAM,oBAAoB,KAAK,QAAQ,YAAY,KAAK,QAAQ,KAAK,OAAO;AACjG,UAAM,WAAW,mBAAmB,KAAK,SAAS;AAClD,UAAM,MAA0B,EAAE,MAAM,cAAc,SAAS;AAE/D,QAAI,KAAK,SAAS,OAAW,MAAK,QAAQ,IAAI,KAAK,MAAM,GAAG;AAE5D,UAAM,aAAa,KAAK,UAAU,IAAI,KAAK,MAAM;AACjD,QAAI,WAAY,YAAW,KAAK,GAAG;AAAA,QAC9B,MAAK,UAAU,IAAI,KAAK,QAAQ,CAAC,GAAG,CAAC;AAM1C,eAAW,SAAS,KAAK,WAAW,CAAC,GAAG;AACtC,YAAM,YAAY,KAAK,UAAU,IAAI,KAAK;AAC1C,UAAI,UAAW,WAAU,KAAK,GAAG;AAAA,UAC5B,MAAK,UAAU,IAAI,OAAO,CAAC,GAAG,CAAC;AAAA,IACtC;AAMA,eAAW,KAAK,UAAU;AACxB,YAAM,cAAc,KAAK,UAAU,IAAI,EAAE,UAAU;AACnD,UAAI,YAAa,aAAY,KAAK,GAAG;AAAA,UAChC,MAAK,UAAU,IAAI,EAAE,YAAY,CAAC,GAAG,CAAC;AAAA,IAC7C;AAQA,QAAI,KAAK,QAAQ;AACf,YAAM,aAAa,KAAK,UAAU,IAAI,KAAK,OAAO,IAAI;AACtD,UAAI,WAAY,YAAW,KAAK,GAAG;AAAA,UAC9B,MAAK,UAAU,IAAI,KAAK,OAAO,MAAM,CAAC,GAAG,CAAC;AAAA,IACjD;AAEA,eAAW,OAAO,YAAY;AAC5B,YAAM,SAAS,KAAK,QAAQ,GAAG;AAC/B,UAAI,CAAC,OAAQ;AACb,YAAM,mBAAmB,OAAO;AAChC,YAAM,MAAM,KAAK,UAAU,IAAI,gBAAgB;AAC/C,UAAI,IAAK,KAAI,KAAK,GAAG;AAAA,UAChB,MAAK,UAAU,IAAI,kBAAkB,CAAC,GAAG,CAAC;AAAA,IACjD;AAAA,EACF;AAAA,EAEA,oBAAoB,QAAmD;AACrE,WAAO,KAAK,UAAU,IAAI,MAAM,KAAK,CAAC;AAAA,EACxC;AAAA,EAEA,0BAA0B,YAAuD;AAC/E,WAAO,KAAK,UAAU,IAAI,UAAU,KAAK,CAAC;AAAA,EAC5C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAcA,kBAAkB,gBAAwB,QAAiB,YAAyC;AAClG,UAAM,YAAY,gBAAgB,MAAM;AACxC,QAAI,cAAc,OAAW;AAC7B,UAAM,OAAO,oBAAI,IAAI,CAAC,GAAG,WAAW,GAAG,UAAU,CAAC;AAClD,UAAM,eAAe,oBAAI,IAAY;AACrC,eAAW,OAAO,IAAI,IAAI,CAAC,GAAG,KAAK,UAAU,OAAO,CAAC,EAAE,KAAK,CAAC,GAAG;AAC9D,UAAI,IAAI,KAAK,WAAW,eAAgB;AACxC,iBAAW,OAAO,OAAO,OAAO,IAAI,KAAK,OAAO,GAAiE;AAC/G,YAAI,IAAI,eAAe,eAAgB,YAAW,KAAK,IAAI,UAAU,CAAC,EAAG,cAAa,IAAI,CAAC;AAAA,MAC7F;AAAA,IACF;AACA,UAAM,OAAO,IAAI,IAAI,CAAC,GAAG,KAAK,UAAU,OAAO,CAAC,EAAE,KAAK,CAAC;AACxD,eAAW,OAAO,MAAM;AACtB,iBAAW,KAAK,IAAI,UAAU;AAC5B,YAAI,IAAI,KAAK,WAAW,gBAAgB;AACtC,qBAAW,KAAK,EAAE,OAAO;AACvB,gBAAI,CAAC,KAAK,IAAI,EAAE,EAAE,KAAK,CAAC,aAAa,IAAI,EAAE,EAAE,GAAG;AAC9C,oBAAM,IAAI;AAAA,gBACR,eAAe,IAAI,KAAK,QAAQ,IAAI,KAAK,MAAM,0CAA0C,EAAE,EAAE,aAAa,cAAc;AAAA,cAAmE;AAAA,YAC/L;AAAA,UACF;AAAA,QACF;AACA,YAAI,EAAE,eAAe,gBAAgB;AACnC,qBAAW,KAAK,EAAE,OAAO;AACvB,gBAAI,EAAE,SAAS,QAAQ,CAAC,KAAK,IAAI,EAAE,IAAI,KAAK,CAAC,aAAa,IAAI,EAAE,IAAI,GAAG;AACrE,oBAAM,IAAI;AAAA,gBACR,eAAe,IAAI,KAAK,QAAQ,IAAI,KAAK,MAAM,6BAA6B,EAAE,IAAI,mBAAmB,cAAc;AAAA,cAAkG;AAAA,YACzN;AAAA,UACF;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAYA,MAAyC;AACvC,UAAM,OAAO,oBAAI,IAAwB;AACzC,eAAW,cAAc,KAAK,UAAU,OAAO,GAAG;AAChD,iBAAW,KAAK,WAAY,MAAK,IAAI,CAAC;AAAA,IACxC;AACA,WAAO,CAAC,GAAG,IAAI;AAAA,EACjB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAiBA,QAA6I;AAC3I,UAAM,MAAsF,CAAC;AAC7F,eAAW,OAAO,KAAK,IAAI,GAAG;AAC5B,YAAM,OAAO,IAAI;AACjB,YAAM,qBAAqB;AAAA,QACzB,KAAK;AAAA,QACL,GAAI,KAAK,WAAW,CAAC;AAAA,QACrB,IAAI,KAAK,aAAa,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,UAAU;AAAA,QACjD,GAAI,KAAK,SAAS,CAAC,KAAK,OAAO,IAAI,IAAI,CAAC;AAAA,MAC1C;AACA,YAAM,UAAsB,mBAAmB,IAAI,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,aAAa,EAAE;AAClG,iBAAW,OAAO,OAAO,KAAK,KAAK,OAAO,GAAG;AAC3C,cAAM,SAAS,KAAK,QAAQ,GAAG;AAC/B,YAAI,CAAC,OAAQ;AACb,YAAI,OAAO,UAAU,YAAY,OAAO,eAAe,KAAK,UAAU,OAAO,WAAW,OAAW;AACnG,YAAI,KAAK,EAAE,QAAQ,EAAE,YAAY,OAAO,YAAY,OAAO,aAAa,GAAG,SAAS,MAAM,cAAc,OAAO,SAAS,CAAC;AAAA,MAC3H;AACA,UAAI,KAAK,QAAQ;AACf,YAAI,KAAK;AAAA,UACP,QAAQ,EAAE,YAAY,KAAK,QAAQ,OAAO,KAAK,OAAO,MAAM;AAAA,UAC5D,SAAS,CAAC,EAAE,YAAY,KAAK,OAAO,MAAM,OAAO,aAAa,CAAC;AAAA,UAC/D,MAAM;AAAA,UACN,OAAO;AAAA,QACT,CAAC;AAAA,MACH;AAAA,IACF;AACA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAgBA,SAAS,OAAwB;AAC/B,UAAM,IAAI,SAAS,IAAI,SAAS;AAChC,QAAI,CAAC,OAAO;AACV,iBAAW,QAAQ,KAAK,MAAM,EAAG,GAAE,gBAAgB,KAAK,QAAQ,KAAK,SAAS,KAAK,MAAM,KAAK,KAAK;AAAA,IACrG;AACA,QAAI;AACF,QAAE,cAAc;AAAA,IAClB,SAAS,GAAG;AACV,UAAI,aAAa,qBAAsB,OAAM,IAAI,qBAAqB,EAAE,KAAK,IAAI,mBAAmB,CAAC;AACrG,YAAM;AAAA,IACR;AAAA,EACF;AACF;","names":[]}
@@ -0,0 +1,84 @@
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
+ pending.delete(spec);
35
+ try {
36
+ const sourceColl = accessor.getCollection(spec.source);
37
+ const source = await sourceColl.get(id);
38
+ if (!source) {
39
+ continue;
40
+ }
41
+ const sourceWithId = { ...source, id };
42
+ DerivationExecutor ??= (await loadDerivationExecutor()).DerivationExecutor;
43
+ const ctx = { vault: accessor.getReadOnlyFacade() };
44
+ const result = await DerivationExecutor.run(spec, sourceWithId, 0, strategyHash, ctx);
45
+ for (const key of Object.keys(spec.outputs)) {
46
+ const out = result.outputs[key];
47
+ if (!out) continue;
48
+ if (out.kind === "failed") {
49
+ const err = out.error;
50
+ if (spec.strict) throw err;
51
+ console.warn(
52
+ `[derivation] lazy output "${key}" for source "${spec.source}" id="${id}" failed:`,
53
+ err
54
+ );
55
+ continue;
56
+ }
57
+ if (out.kind === "array") {
58
+ console.warn(
59
+ `[derivation] unexpected array-shape output "${key}" in lazy resolve path; array-shape derivations require lifecycle: "eager".`
60
+ );
61
+ continue;
62
+ }
63
+ const outSpec = spec.outputs[key];
64
+ if (!outSpec) continue;
65
+ const outputColl = accessor.getCollection(outSpec.collection);
66
+ if (out.skipped === true) {
67
+ await outputColl._internalDelete(id, accessor.getActiveTxContext());
68
+ continue;
69
+ }
70
+ await outputColl.put(id, out.value);
71
+ }
72
+ pending.delete(spec);
73
+ } catch (e) {
74
+ pending.add(spec);
75
+ throw e;
76
+ }
77
+ }
78
+ }
79
+
80
+ export {
81
+ markStale,
82
+ resolveStaleOnRead
83
+ };
84
+ //# sourceMappingURL=chunk-KL7AS3HV.js.map
@@ -0,0 +1 @@
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 // Consume the pending flag BEFORE the source read, not after: for a\n // self-write output (output.collection === spec.source, e.g. a\n // reverse-denorm patch), `sourceColl.get(id)` below re-enters this\n // same collection/id and would otherwise still see this spec pending\n // — infinite recursion. Restored on a strict failure below so a\n // future read still retries.\n pending.delete(spec)\n\n // Everything from here on can throw (source read, executor, output\n // put/delete) — restore the consumed flag on ANY throw so a later\n // read still retries, not just on the strict-failure branch below\n // (which throws through this same catch, so it does not also\n // re-add — `Set.add` is idempotent, but the branch no longer needs\n // its own restore).\n try {\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 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) throw err\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 } catch (e) {\n // Restore the stale flag consumed above so a future read retries.\n pending.add(spec)\n throw e\n }\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;AAQpC,YAAQ,OAAO,IAAI;AAQnB,QAAI;AAKF,YAAM,aAAa,SAAS,cAAc,KAAK,MAAM;AACrD,YAAM,SAAS,MAAM,WAAW,IAAI,EAAE;AACtC,UAAI,CAAC,QAAQ;AACX;AAAA,MACF;AACA,YAAM,eAAe,EAAE,GAAI,QAAoC,GAAG;AAIlE,8BAAwB,MAAM,uBAAuB,GAAG;AACxD,YAAM,MAAM,EAAE,OAAO,SAAS,kBAAkB,EAAE;AAClD,YAAM,SAAS,MAAM,mBAAmB,IAAI,MAAM,cAAc,GAAG,cAAc,GAAG;AACpF,iBAAW,OAAO,OAAO,KAAK,KAAK,OAAO,GAAG;AAC3C,cAAM,MAAM,OAAO,QAAQ,GAAG;AAC9B,YAAI,CAAC,IAAK;AACV,YAAI,IAAI,SAAS,UAAU;AACzB,gBAAM,MAAM,IAAI;AAChB,cAAI,KAAK,OAAQ,OAAM;AACvB,kBAAQ;AAAA,YACN,6BAA6B,GAAG,iBAAiB,KAAK,MAAM,SAAS,EAAE;AAAA,YACvE;AAAA,UACF;AACA;AAAA,QACF;AACA,YAAI,IAAI,SAAS,SAAS;AAKxB,kBAAQ;AAAA,YACN,+CAA+C,GAAG;AAAA,UAEpD;AACA;AAAA,QACF;AACA,cAAM,UAAU,KAAK,QAAQ,GAAG;AAChC,YAAI,CAAC,QAAS;AACd,cAAM,aAAa,SAAS,cAAc,QAAQ,UAAU;AAC5D,YAAI,IAAI,YAAY,MAAM;AAUxB,gBAAM,WAAW,gBAAgB,IAAI,SAAS,mBAAmB,CAAC;AAClE;AAAA,QACF;AACA,cAAM,WAAW,IAAI,IAAI,IAAI,KAAK;AAAA,MACpC;AACA,cAAQ,OAAO,IAAI;AAAA,IACrB,SAAS,GAAG;AAEV,cAAQ,IAAI,IAAI;AAChB,YAAM;AAAA,IACR;AAAA,EACF;AACF;","names":[]}
@@ -0,0 +1,17 @@
1
+ // src/with-shape/introspection/derivation-key.ts
2
+ function fallbackDerivationName(outputCollections, source, used) {
3
+ const base = outputCollections.length > 0 ? [...outputCollections].sort().join("+") : source;
4
+ if (!used.has(base)) return base;
5
+ let occurrence = 1;
6
+ let candidate = `${base}#${occurrence}`;
7
+ while (used.has(candidate)) {
8
+ occurrence++;
9
+ candidate = `${base}#${occurrence}`;
10
+ }
11
+ return candidate;
12
+ }
13
+
14
+ export {
15
+ fallbackDerivationName
16
+ };
17
+ //# sourceMappingURL=chunk-RXLYKVTF.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/with-shape/introspection/derivation-key.ts"],"sourcesContent":["/**\n * The deterministic fallback key for an UNNAMED derivation, shared by\n * `dumpSchema`'s `describeDerivations` (walk.ts) and `listBehaviors`'s\n * `buildDerivationEntries` (behaviors.ts) so the two surfaces always key the\n * same unnamed derivation identically.\n *\n * Base key = the derivation's output-collection names, sorted and joined by\n * `+` (or `source` when it has no outputs). On collision with any key already\n * assigned in this enumeration (named or fallback, passed in `used`), a\n * deterministic `#occurrence` suffix is appended (`base`, `base#1`, `base#2`, …)\n * so two derivations with the same output set both appear.\n *\n * Takes the already-extracted output-collection list + source (rather than a\n * `DerivationSpec`) so both the typed builder and walk.ts's duck-typed registry\n * walk can call it without a shared concrete type.\n */\nexport function fallbackDerivationName(\n outputCollections: readonly string[],\n source: string,\n used: ReadonlySet<string>,\n): string {\n const base = outputCollections.length > 0 ? [...outputCollections].sort().join('+') : source\n if (!used.has(base)) return base\n let occurrence = 1\n let candidate = `${base}#${occurrence}`\n while (used.has(candidate)) {\n occurrence++\n candidate = `${base}#${occurrence}`\n }\n return candidate\n}\n"],"mappings":";AAgBO,SAAS,uBACd,mBACA,QACA,MACQ;AACR,QAAM,OAAO,kBAAkB,SAAS,IAAI,CAAC,GAAG,iBAAiB,EAAE,KAAK,EAAE,KAAK,GAAG,IAAI;AACtF,MAAI,CAAC,KAAK,IAAI,IAAI,EAAG,QAAO;AAC5B,MAAI,aAAa;AACjB,MAAI,YAAY,GAAG,IAAI,IAAI,UAAU;AACrC,SAAO,KAAK,IAAI,SAAS,GAAG;AAC1B;AACA,gBAAY,GAAG,IAAI,IAAI,UAAU;AAAA,EACnC;AACA,SAAO;AACT;","names":[]}