@githolon/dsl 0.32.0 → 0.34.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,456 @@
1
+ // NOMOS — Nomos Sovereign: participants act · verify · remember LOCALLY; hosted
2
+ // remotes are replaceable custody/transport, not truth. ⇒ ONE Nomos GitHolon
3
+ // wasm32-wasip1 artifact {kernel · projection · embedded
4
+ // QuickJS engine} on V8 + WASI-shim, byte-identical everywhere. V8 = portability; the one
5
+ // wasm = determinism. No native, no wasmtime, no 2nd artifact, no domain-JS on bare V8.
6
+ // If a file isn't this / hosting this / authoring for this / proving this — it's gone.
7
+
8
+ /**
9
+ * FIRST-CLASS COMPOSABLE VALUE OBJECTS — `valueObject(name, shape)` + the `geo.*`
10
+ * geometry VOs.
11
+ *
12
+ * THE TENANT GAP THIS CLOSES: a structured value on an aggregate was a hand-rolled
13
+ * `t.jsonObject()` plus a privately-kept zod schema per site — untyped in the generated
14
+ * clients, invisible in the package IR. A value object is the NAMED, REUSABLE,
15
+ * COMPOSABLE version of that same leaf:
16
+ *
17
+ * const PostalAddress = valueObject("PostalAddress", {
18
+ * streetAddress: z.string(), city: z.string(), countryCode: z.string(),
19
+ * });
20
+ * const GeoPoint = geo.point("GeoPoint"); // RFC 7946 Point
21
+ * const SiteLocation = valueObject("SiteLocation", {
22
+ * address: PostalAddress.optional(),
23
+ * point: GeoPoint.optional(),
24
+ * });
25
+ * // on an aggregate:
26
+ * location: SiteLocation.field().optional(),
27
+ *
28
+ * ONE definition produces: the zod schema (the SAME zod the engine validates — embed
29
+ * `SiteLocation.zod` in a directive payload), the inferred TS type (`SiteLocation.field()`
30
+ * types `set()` and the generated `.client.ts` names the interface), the generated Dart
31
+ * class (typed decode from the projection's json-object value), and a canonical
32
+ * descriptor that rides the package IR (the additive `nomosValueObjects` USD key) so
33
+ * tooling/visualizers can SEE the structure.
34
+ *
35
+ * STORAGE / MERGE SEMANTICS (the v1 boundary — state it, don't blur it): a VO field IS
36
+ * the existing `t.jsonObject()` value-leaf lane — ONE Lww-class leaf, replaced WHOLESALE
37
+ * on write (exactly the encrypted-envelope rule). There is NO per-subfield merge: two
38
+ * concurrent writes to different subfields of one VO converge to ONE writer's whole
39
+ * value. When subfields must merge independently, model them as aggregate fields or a
40
+ * `t.map(...)` — that is what aggregates/maps are for.
41
+ *
42
+ * ERA SAFETY: the VO metadata never enters the kernel wire schema or the canonical
43
+ * manifest — a domain's domainHash with a VO field equals the same domain with a plain
44
+ * `t.jsonObject()` field. The `nomosValueObjects` USD key is emitted IFF a domain's
45
+ * aggregates carry ≥1 VO field; a VO-free domain's package bytes are untouched.
46
+ *
47
+ * THE v1 CONSTRAINED FIELD SET (no fallback — anything else throws at compile):
48
+ * `z.string()` · `z.number()` (int or float) · `z.boolean()` · `z.enum([...])` ·
49
+ * `z.array(<any of these>)` · a nested `ValueObject` · and `.optional()` on any of
50
+ * them (arrays of optionals excluded). Unions, records, maps, tuples, dates etc. are
51
+ * deliberately OUT — this surface is forever; grow it deliberately.
52
+ */
53
+ import { z } from "zod";
54
+
55
+ import {
56
+ valueObjectField,
57
+ type Field,
58
+ type GeometryKind,
59
+ type ValueObjectDescriptor,
60
+ type ValueObjectFieldDescriptor,
61
+ type ValueObjectFieldMeta,
62
+ } from "./fields.js";
63
+ // LOCAL zod introspection (the same `_def ?? def` convention as codegen_ts.ts) —
64
+ // deliberately NOT imported from codegen_ts.js: this module rides the RUNTIME barrel
65
+ // (the engine lump bundles it, `--platform=neutral`), and codegen_ts transitively
66
+ // imports `node:crypto` via workspace_routing. Keep this file node-builtin-free.
67
+ type ZodInternalDef = {
68
+ type?: string | z.ZodTypeAny;
69
+ innerType?: z.ZodTypeAny;
70
+ element?: z.ZodTypeAny;
71
+ entries?: Record<string, string | number>;
72
+ };
73
+ function zodDef(zt: z.ZodTypeAny): ZodInternalDef {
74
+ const raw = zt as unknown as { _def?: ZodInternalDef; def?: ZodInternalDef };
75
+ return raw._def ?? raw.def ?? {};
76
+ }
77
+ function zodKind(zt: z.ZodTypeAny): string {
78
+ const t = zodDef(zt).type;
79
+ return typeof t === "string" ? t : "unknown";
80
+ }
81
+ function zodEnumValues(zt: z.ZodTypeAny): string[] {
82
+ const raw = zt as unknown as { options?: readonly unknown[] };
83
+ if (raw.options !== undefined) return raw.options.map(String);
84
+ const entries = zodDef(zt).entries;
85
+ if (entries !== undefined) return Object.values(entries).map(String);
86
+ throw new Error("valueObject: ZodEnum has no values");
87
+ }
88
+ function zodArrayElement(zt: z.ZodTypeAny): z.ZodTypeAny {
89
+ const def = zodDef(zt);
90
+ if (def.element !== undefined) return def.element;
91
+ if (typeof def.type !== "string" && def.type !== undefined) return def.type;
92
+ throw new Error("valueObject: ZodArray has no element type");
93
+ }
94
+
95
+ export type { GeometryKind, ValueObjectDescriptor, ValueObjectFieldDescriptor } from "./fields.js";
96
+
97
+ /** An `.optional()`-wrapped value object — valid ONLY inside a `valueObject(...)` shape. */
98
+ export interface OptionalValueObject<T = unknown> {
99
+ readonly __isOptionalValueObject: true;
100
+ readonly valueObject: ValueObject<T>;
101
+ }
102
+
103
+ /**
104
+ * A NAMED, COMPOSABLE value object — produced by {@link valueObject} / {@link geo}.
105
+ * Carries the zod (validation + TS inference), the canonical descriptor (the IR/codegen
106
+ * shape), and lowers onto an aggregate via {@link ValueObject.field}.
107
+ */
108
+ export interface ValueObject<T = unknown> {
109
+ readonly __isValueObject: true;
110
+ /** The declared name — the generated TS interface / Dart class name. */
111
+ readonly name: string;
112
+ /** The zod schema — the single source of truth (embed it in directive payloads). */
113
+ readonly zod: z.ZodType<T>;
114
+ /** This VO's own canonical descriptor (field names sorted; byte-deterministic). */
115
+ readonly descriptor: ValueObjectDescriptor;
116
+ /** Every transitively nested VO's descriptor (self excluded), sorted by name. */
117
+ readonly dependencies: readonly ValueObjectDescriptor[];
118
+ /** Present iff this is a `geo.*` geometry VO (the spatial-index pairing). */
119
+ readonly geometry?: GeometryKind;
120
+ /** Mark this VO optional INSIDE another `valueObject(...)` shape (nesting only —
121
+ * for an optional aggregate FIELD use `.field().optional()`). */
122
+ optional(): OptionalValueObject<T>;
123
+ /**
124
+ * Lower this VO to an aggregate FIELD: one `t.jsonObject()`-class value leaf
125
+ * (kind `json`, `jsonShape:"object"`, Lww) whose whole value is this VO —
126
+ * WHOLESALE-REPLACE merge, no per-subfield merge (v1). Chain the usual field
127
+ * modifiers: `.merge(Lww/LwwCausal/LwwBy)` (Lww-class only — the value is replaced
128
+ * whole), `.optional()`, `.encrypted()` (under the existing `requires(cap)` rules).
129
+ */
130
+ field(): Field<T, "json">;
131
+ /** Phantom to keep T load-bearing for inference. */
132
+ readonly _t?: T;
133
+ }
134
+
135
+ /** Infer the authored TS type of a ValueObject. */
136
+ export type ValueObjectValue<V> = V extends ValueObject<infer T> ? T : never;
137
+
138
+ /** A `valueObject(...)` shape entry: a constrained zod schema, a nested VO, or an optional VO. */
139
+ export type ValueObjectShapeEntry = z.ZodTypeAny | ValueObject<unknown> | OptionalValueObject<unknown>;
140
+
141
+ type EntryValue<E> =
142
+ E extends OptionalValueObject<infer T> ? T | undefined
143
+ : E extends ValueObject<infer T> ? T
144
+ : E extends z.ZodType<infer T> ? T
145
+ : never;
146
+
147
+ type OptionalKeys<S> = {
148
+ [K in keyof S]: undefined extends EntryValue<S[K]> ? K : never;
149
+ }[keyof S];
150
+
151
+ /** The inferred value type of a shape: optional entries become optional properties. */
152
+ export type ShapeValue<S> = { [K in Exclude<keyof S, OptionalKeys<S>>]: EntryValue<S[K]> } & {
153
+ [K in OptionalKeys<S>]?: EntryValue<S[K]>;
154
+ };
155
+
156
+ const isValueObject = (v: unknown): v is ValueObject<unknown> =>
157
+ typeof v === "object" && v !== null && (v as { __isValueObject?: boolean }).__isValueObject === true;
158
+
159
+ const isOptionalValueObject = (v: unknown): v is OptionalValueObject<unknown> =>
160
+ typeof v === "object" &&
161
+ v !== null &&
162
+ (v as { __isOptionalValueObject?: boolean }).__isOptionalValueObject === true;
163
+
164
+ /** Collect a VO's descriptor + its transitive dependencies into `into` (name-keyed). */
165
+ function collectDescriptors(vo: ValueObject<unknown>, into: Map<string, ValueObjectDescriptor>): void {
166
+ const existing = into.get(vo.name);
167
+ if (existing !== undefined) {
168
+ if (JSON.stringify(existing) !== JSON.stringify(vo.descriptor)) {
169
+ throw new Error(
170
+ `valueObject: two DIVERGENT value objects share the name '${vo.name}' — one name, one shape. ` +
171
+ `Rename one of them (the descriptor is identity-bearing).`,
172
+ );
173
+ }
174
+ return;
175
+ }
176
+ into.set(vo.name, vo.descriptor);
177
+ for (const dep of vo.dependencies) {
178
+ const prior = into.get(dep.name);
179
+ if (prior !== undefined && JSON.stringify(prior) !== JSON.stringify(dep)) {
180
+ throw new Error(
181
+ `valueObject: two DIVERGENT value objects share the name '${dep.name}' — one name, one shape. ` +
182
+ `Rename one of them (the descriptor is identity-bearing).`,
183
+ );
184
+ }
185
+ into.set(dep.name, dep);
186
+ }
187
+ }
188
+
189
+ /**
190
+ * Describe one CONSTRAINED zod schema as a descriptor field (NO FALLBACK — an
191
+ * out-of-set shape throws with the field's path so the author fixes the declaration,
192
+ * never ships an undescribed structure).
193
+ */
194
+ function describeZod(voName: string, path: string, schema: z.ZodTypeAny): ValueObjectFieldDescriptor {
195
+ switch (zodKind(schema)) {
196
+ case "string":
197
+ return { type: "string" };
198
+ case "number":
199
+ return { type: "number" };
200
+ case "boolean":
201
+ return { type: "boolean" };
202
+ case "enum":
203
+ return { type: "enum", values: zodEnumValues(schema) };
204
+ case "array": {
205
+ const items = describeZod(voName, `${path}[]`, zodArrayElement(schema));
206
+ if (items.optional === true) {
207
+ throw new Error(
208
+ `valueObject '${voName}': field '${path}' is an array of optionals — not expressible in the v1 set.`,
209
+ );
210
+ }
211
+ return { type: "array", items };
212
+ }
213
+ case "optional":
214
+ case "default": {
215
+ const inner = describeZod(voName, path, zodDef(schema).innerType!);
216
+ return { ...inner, optional: true };
217
+ }
218
+ default:
219
+ throw new Error(
220
+ `valueObject '${voName}': field '${path}' uses zod shape '${zodKind(schema)}', which is outside ` +
221
+ `the v1 value-object field set (string | number | boolean | enum | array | nested valueObject, ` +
222
+ `each optionally .optional()). Model richer shapes as aggregates/maps, or nest a valueObject.`,
223
+ );
224
+ }
225
+ }
226
+
227
+ const VO_NAME_RE = /^[A-Za-z][A-Za-z0-9_]*$/;
228
+
229
+ function makeValueObject<T>(
230
+ name: string,
231
+ zodSchema: z.ZodType<T>,
232
+ descriptor: ValueObjectDescriptor,
233
+ dependencies: readonly ValueObjectDescriptor[],
234
+ geometry?: GeometryKind,
235
+ ): ValueObject<T> {
236
+ if (!VO_NAME_RE.test(name)) {
237
+ throw new Error(
238
+ `valueObject: name '${name}' is not a valid identifier ([A-Za-z][A-Za-z0-9_]*) — it becomes the ` +
239
+ `generated TS interface / Dart class name.`,
240
+ );
241
+ }
242
+ const meta: ValueObjectFieldMeta = {
243
+ name,
244
+ descriptor,
245
+ dependencies,
246
+ ...(geometry !== undefined ? { geometry } : {}),
247
+ };
248
+ const vo: ValueObject<T> = {
249
+ __isValueObject: true,
250
+ name,
251
+ zod: zodSchema,
252
+ descriptor,
253
+ dependencies,
254
+ ...(geometry !== undefined ? { geometry } : {}),
255
+ optional(): OptionalValueObject<T> {
256
+ return { __isOptionalValueObject: true, valueObject: vo };
257
+ },
258
+ field(): Field<T, "json"> {
259
+ return valueObjectField(zodSchema, meta);
260
+ },
261
+ };
262
+ return vo;
263
+ }
264
+
265
+ /**
266
+ * Declare a NAMED, COMPOSABLE value object. `shape` entries come from the v1
267
+ * constrained set (see the module banner): plain zod scalars/enums/arrays, nested
268
+ * `ValueObject`s, or `SomeVo.optional()`. Returns a {@link ValueObject} carrying the
269
+ * zod schema, the inferred TS type, the canonical descriptor (+ transitive nested-VO
270
+ * descriptors), and `.field()` to place it on an aggregate.
271
+ */
272
+ export function valueObject<const Name extends string, S extends Record<string, ValueObjectShapeEntry>>(
273
+ name: Name,
274
+ shape: S,
275
+ ): ValueObject<ShapeValue<S>> {
276
+ const zodShape: Record<string, z.ZodTypeAny> = {};
277
+ const fields: Record<string, ValueObjectFieldDescriptor> = {};
278
+ const deps = new Map<string, ValueObjectDescriptor>();
279
+ for (const key of Object.keys(shape).sort()) {
280
+ const entry = shape[key]!;
281
+ if (isOptionalValueObject(entry)) {
282
+ const inner = entry.valueObject;
283
+ zodShape[key] = (inner.zod as z.ZodTypeAny).optional();
284
+ fields[key] =
285
+ inner.geometry !== undefined
286
+ ? { type: "geometry", ref: inner.name, geometry: inner.geometry, optional: true }
287
+ : { type: "object", ref: inner.name, optional: true };
288
+ collectDescriptors(inner, deps);
289
+ } else if (isValueObject(entry)) {
290
+ zodShape[key] = entry.zod as z.ZodTypeAny;
291
+ fields[key] =
292
+ entry.geometry !== undefined
293
+ ? { type: "geometry", ref: entry.name, geometry: entry.geometry }
294
+ : { type: "object", ref: entry.name };
295
+ collectDescriptors(entry, deps);
296
+ } else {
297
+ fields[key] = describeZod(name, key, entry);
298
+ zodShape[key] = entry;
299
+ }
300
+ }
301
+ if (deps.has(name)) {
302
+ throw new Error(`valueObject '${name}': a value object cannot nest itself (directly or via a dependency).`);
303
+ }
304
+ const descriptor: ValueObjectDescriptor = { name, fields };
305
+ const dependencies = [...deps.values()].sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0));
306
+ return makeValueObject(
307
+ name,
308
+ z.object(zodShape) as unknown as z.ZodType<ShapeValue<S>>,
309
+ descriptor,
310
+ dependencies,
311
+ );
312
+ }
313
+
314
+ // ── geo.* — the RFC 7946 geometry value objects ──────────────────────────────────────
315
+
316
+ /** An RFC 7946 position: `[lng, lat]` or `[lng, lat, altitude]`. */
317
+ const positionZod = z.array(z.number()).min(2).max(3);
318
+
319
+ /** The authored TS shape of a `geo.point(...)` value (RFC 7946 Point). */
320
+ export interface GeoPointValue {
321
+ type: "Point";
322
+ /** `[lng, lat]` or `[lng, lat, altitude]` (RFC 7946 position order). */
323
+ coordinates: number[];
324
+ }
325
+ /** The authored TS shape of a `geo.lineString(...)` value (RFC 7946 LineString). */
326
+ export interface GeoLineStringValue {
327
+ type: "LineString";
328
+ coordinates: number[][];
329
+ }
330
+ /** The authored TS shape of a `geo.polygon(...)` value (RFC 7946 Polygon). */
331
+ export interface GeoPolygonValue {
332
+ type: "Polygon";
333
+ coordinates: number[][][];
334
+ }
335
+
336
+ function geoZod(kind: GeometryKind, coordinates: z.ZodTypeAny): z.ZodTypeAny {
337
+ return z.object({ type: z.literal(kind), coordinates });
338
+ }
339
+
340
+ /**
341
+ * The GEO value objects — RFC 7946 geometries as named VOs, and THE BLESSED WAY to
342
+ * declare a spatial-indexable field: give the geometry its OWN aggregate field
343
+ * (`point: GeoPoint.field()`) and pair it with `spatial(id).of(Agg).on("point")` —
344
+ * the R*Tree indexes the field's GeoJSON geometry directly. (v1 boundary: the spatial
345
+ * lane indexes a TOP-LEVEL field; a geometry nested inside another VO — e.g.
346
+ * `location.point` — is data, not an indexable path.)
347
+ */
348
+ export const geo = {
349
+ /** An RFC 7946 **Point** (`{type:"Point", coordinates:[lng,lat(,alt)?]}`) — the v1
350
+ * spatial-index geometry. */
351
+ point(name = "GeoPoint"): ValueObject<GeoPointValue> {
352
+ return makeValueObject<GeoPointValue>(
353
+ name,
354
+ geoZod("Point", positionZod) as z.ZodType<GeoPointValue>,
355
+ { name, geometry: "Point" },
356
+ [],
357
+ "Point",
358
+ );
359
+ },
360
+ /** An RFC 7946 **LineString** (≥2 positions). */
361
+ lineString(name = "GeoLineString"): ValueObject<GeoLineStringValue> {
362
+ return makeValueObject<GeoLineStringValue>(
363
+ name,
364
+ geoZod("LineString", z.array(positionZod).min(2)) as z.ZodType<GeoLineStringValue>,
365
+ { name, geometry: "LineString" },
366
+ [],
367
+ "LineString",
368
+ );
369
+ },
370
+ /** An RFC 7946 **Polygon** (linear rings — ≥4 positions each, first == last by RFC;
371
+ * ring closure is the author's contract, not re-validated here). */
372
+ polygon(name = "GeoPolygon"): ValueObject<GeoPolygonValue> {
373
+ return makeValueObject<GeoPolygonValue>(
374
+ name,
375
+ geoZod("Polygon", z.array(z.array(positionZod).min(4))) as z.ZodType<GeoPolygonValue>,
376
+ { name, geometry: "Polygon" },
377
+ [],
378
+ "Polygon",
379
+ );
380
+ },
381
+ };
382
+
383
+ // ── module-plane collection (composeDomainModule / codegen) ──────────────────────────
384
+
385
+ /**
386
+ * Collect the DEDUPED, SORTED value-object descriptors used by a set of aggregates
387
+ * (each VO field's own descriptor + its transitive dependencies). Divergent same-name
388
+ * descriptors throw (one name, one shape). Returns `[]` when no field carries a VO —
389
+ * the omit-when-empty input to the `nomosValueObjects` era key.
390
+ */
391
+ export function collectValueObjectDescriptors(
392
+ aggregates: readonly { id: string; fields: Record<string, Field> }[],
393
+ ): ValueObjectDescriptor[] {
394
+ const byName = new Map<string, ValueObjectDescriptor>();
395
+ for (const agg of aggregates) {
396
+ for (const field of Object.values(agg.fields)) {
397
+ const meta = field.valueObject;
398
+ if (meta === undefined) continue;
399
+ const all = [meta.descriptor, ...meta.dependencies];
400
+ for (const d of all) {
401
+ const existing = byName.get(d.name);
402
+ if (existing !== undefined && JSON.stringify(existing) !== JSON.stringify(d)) {
403
+ throw new Error(
404
+ `valueObject: aggregate '${agg.id}' uses a value object named '${d.name}' whose shape DIVERGES ` +
405
+ `from another use of the same name — one name, one shape. Rename one of them.`,
406
+ );
407
+ }
408
+ byName.set(d.name, d);
409
+ }
410
+ }
411
+ }
412
+ return [...byName.values()].sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0));
413
+ }
414
+
415
+ /**
416
+ * ONE VO↔FIELD ATTACHMENT (the IR pointer): aggregate `aggregate`'s field `field`
417
+ * stores value object `vo`. Rides the additive `nomosValueObjects` USD key as
418
+ * `usages` so tooling (the scene projector, the explorer glyphs) can attach a VO
419
+ * molecule to the exact field that carries it. NEVER in the canonical
420
+ * manifest/domainHash — a VO field's wire schema stays a plain `jsonObject` leaf.
421
+ */
422
+ export interface ValueObjectUsage {
423
+ readonly aggregate: string;
424
+ readonly field: string;
425
+ readonly vo: string;
426
+ }
427
+
428
+ /**
429
+ * Collect the sorted VO↔field usage pointers of a set of aggregates (one entry per
430
+ * VO-typed field; sorted by aggregate then field — byte-deterministic). Returns `[]`
431
+ * when no field carries a VO — the omit-when-empty input to the `usages` entry of
432
+ * the `nomosValueObjects` era key.
433
+ */
434
+ export function collectValueObjectUsages(
435
+ aggregates: readonly { id: string; fields: Record<string, Field> }[],
436
+ ): ValueObjectUsage[] {
437
+ const usages: ValueObjectUsage[] = [];
438
+ for (const agg of aggregates) {
439
+ for (const fieldName of Object.keys(agg.fields)) {
440
+ const meta = agg.fields[fieldName]!.valueObject;
441
+ if (meta === undefined) continue;
442
+ usages.push({ aggregate: agg.id, field: fieldName, vo: meta.name });
443
+ }
444
+ }
445
+ return usages.sort((a, b) =>
446
+ a.aggregate !== b.aggregate
447
+ ? a.aggregate < b.aggregate
448
+ ? -1
449
+ : 1
450
+ : a.field < b.field
451
+ ? -1
452
+ : a.field > b.field
453
+ ? 1
454
+ : 0,
455
+ );
456
+ }
@@ -20,9 +20,16 @@
20
20
  * LATER coordinated migration — we target the current serde_json shape now.
21
21
  */
22
22
  import { encodeDriver } from "./drivers.js";
23
+ import { __qualifyAggregateType } from "./framework/relations.js";
23
24
  import { __resetAuthoring, __drainAuthoring } from "./authoring.js";
24
25
  import { __resetReads, __drainReads } from "./read.js";
25
26
  import { __resetBirths, __drainBirths } from "./births.js";
27
+ import {
28
+ birthMeterSubject,
29
+ birthMeterBornAt,
30
+ captureBirthMeterReads,
31
+ planBirthRecordOps,
32
+ } from "./birth_meter.js";
26
33
  import type { AggregateHandle } from "./aggregate.js";
27
34
  import type { Field, EvidenceRefValue } from "./fields.js";
28
35
  import type { Directive, ReferentialMarker } from "./directive.js";
@@ -203,13 +210,33 @@ export function executeDirectiveToIntent<P>(
203
210
  __resetReads();
204
211
  __resetBirths();
205
212
  const returned = directive.plan(parsed, ctx);
206
- const planned = [...__drainAuthoring(), ...returned];
207
- const reads = __drainReads();
208
213
  // THE BIRTH EFFECT (G3 — birth is a declared kernel primitive): a `.births()` directive's plan records
209
214
  // the child's genesis recipe via `birth(recipe)`; it rides the intent as a DECLARED output (like a
210
215
  // captured read), drained here. Empty for every non-birth directive — OMITTED below so a birth-free
211
216
  // intent is byte-identical (the era rule).
212
217
  const births = __drainBirths();
218
+ // METERED BIRTH (slice A — architecture/metered_birth_and_attested_reads.md). For every PLANNED birth,
219
+ // the library — HERE, in the ONE plan-output assembly both lanes (client authoring + the sealed engine's
220
+ // `plan`) funnel through, so the halves are byte-identical by construction — does two things the tenant
221
+ // never writes:
222
+ // A1 THE AUTO-RECORD: append the `NomosBirthRecord` field ops (deterministic id per child; Lww upsert)
223
+ // to the SAME intent that carries the birth effect. The kernel's `nomosBirthRecordGate` era arm
224
+ // refuses a new-era birth whose intent carries no record — this is what satisfies it.
225
+ // A2 THE AUTO-CAPTURE: capture the three meter reads (records-by-subject + grant + policy — the #58
226
+ // lane `.births()` auto-declares) so the synthesized birth-quota invariant judges over COMMITTED,
227
+ // CAS-re-derived pre-apply state on every lane.
228
+ // A `.births()` dispatch that planned NO birth (e.g. birthChild without a genesis) appends/captures
229
+ // nothing — byte-identical to before (the era rule); pre-era bundles ship an older lump and are untouched.
230
+ const meterOps: PlannedOp[] = [];
231
+ if (births.length > 0) {
232
+ const payloadRecord = parsed as unknown as Record<string, unknown>;
233
+ const subject = birthMeterSubject(payloadRecord);
234
+ captureBirthMeterReads(subject);
235
+ const bornAt = birthMeterBornAt(payloadRecord);
236
+ for (const b of births) meterOps.push(...planBirthRecordOps(b, subject, bornAt));
237
+ }
238
+ const planned = [...__drainAuthoring(), ...returned, ...meterOps];
239
+ const reads = __drainReads();
213
240
 
214
241
  // Partition the plan output: STRIKE ops route onto the intent's `strikes` channel
215
242
  // (the kernel's strikeout / revert facet, folded by parity); FIELD ops group into
@@ -293,7 +320,11 @@ export function executeDirectiveToIntent<P>(
293
320
  aggregate,
294
321
  marker: eventMarker,
295
322
  ops: [
296
- { field: "__type", op: { Set: { Str: ty } } },
323
+ // READ-PROJECTION NAMESPACING: the stamped TYPE is qualified by the dispatching domain iff
324
+ // the domain declares it non-global — so the folded row's `__type` equals the qualified
325
+ // schema key the SAME compile wrote (rows and read manifest agree by construction). Outside
326
+ // a plan dispatch (codegen probes) the type passes through bare.
327
+ { field: "__type", op: { Set: { Str: __qualifyAggregateType(ty) } } },
297
328
  { field: "__id", op: { Set: { Str: aggregate } } },
298
329
  ...ops,
299
330
  ],