@githolon/dsl 0.100.2 → 0.100.3

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@githolon/dsl",
3
- "version": "0.100.2",
3
+ "version": "0.100.3",
4
4
  "type": "module",
5
5
  "description": "Nomos 2 domain-authoring DSL: aggregates + directives in TS, executed and encoded to the Rust kernel's wire shapes.",
6
6
  "license": "SEE LICENSE IN LICENSE.md",
@@ -57,12 +57,18 @@ import type { BirthRecipe } from "./births.js";
57
57
  * a re-birth of the same name updates the SAME record, Lww). The kernel's `nomosBirthRecordGate`
58
58
  * era key carries this prefix so the wasm arm reads it from LAW, never hardcodes it. */
59
59
  export const NOMOS_BIRTH_RECORD_PREFIX = "nomos-birth-record:";
60
+ export const NOMOS_CHILD_LIFECYCLE_PREFIX = "nomos-child-lifecycle:";
60
61
 
61
62
  /** The birth record's deterministic aggregate instance id for a child workspace name. */
62
63
  export function birthRecordId(child: string): string {
63
64
  return NOMOS_BIRTH_RECORD_PREFIX + child;
64
65
  }
65
66
 
67
+ /** The additive lifecycle record beside the immutable historical birth witness. */
68
+ export function childLifecycleId(child: string): string {
69
+ return NOMOS_CHILD_LIFECYCLE_PREFIX + child;
70
+ }
71
+
66
72
  /**
67
73
  * NomosBirthRecord — THE STRUCTURAL BIRTH RECORD (A1). One row per child this workspace has
68
74
  * birthed, written AUTOMATICALLY in the same intent as the birth effect (the `.births()` lane).
@@ -79,6 +85,21 @@ export const NomosBirthRecord = aggregate("NomosBirthRecord", {
79
85
  bornAt: t.string().merge(Lww),
80
86
  });
81
87
 
88
+ /**
89
+ * NomosChildLifecycle — additive lifecycle for a child witnessed by NomosBirthRecord. It is deliberately
90
+ * a separate aggregate: a framework upgrade must never reinterpret the projection schema of historical
91
+ * birth rows. No lifecycle row means active; reclaimed is terminal.
92
+ */
93
+ export const NomosChildLifecycle = aggregate("NomosChildLifecycle", {
94
+ child: t.string().merge(Lww),
95
+ principal: t.string().merge(Lww),
96
+ status: t.string().merge(Lww),
97
+ retiredBy: t.string().merge(Lww),
98
+ retiredAt: t.int().merge(Lww),
99
+ reclaimedBy: t.string().merge(Lww),
100
+ reclaimedAt: t.int().merge(Lww),
101
+ });
102
+
82
103
  /** BirthPolicy — the workspace's birth-allowance singleton (id `birth-policy:workspace`), the
83
104
  * `CloudPolicy` shape one level down: the default max live births per subject in THIS workspace. */
84
105
  export const BIRTH_POLICY_ID = "birth-policy:workspace";
@@ -111,6 +132,16 @@ export const birthRecordsByPrincipal = query("birthRecordsByPrincipal")
111
132
  .key("principal")
112
133
  .returns(NomosBirthRecord);
113
134
 
135
+ /** Lifecycle rows for quota accounting; no row means the historical child remains active. */
136
+ export const childLifecyclesByPrincipal = query("childLifecyclesByPrincipal")
137
+ .key("principal")
138
+ .returns(NomosChildLifecycle);
139
+
140
+ /** Terminal lifecycle facts for the custody bailiff. */
141
+ export const childLifecyclesByStatus = query("childLifecyclesByStatus")
142
+ .key("status")
143
+ .returns(NomosChildLifecycle);
144
+
114
145
  /** The workspace's birth-policy singleton (`scope: "workspace"`). */
115
146
  export const birthPolicy = query("birthPolicy").key("scope").returns(BirthPolicy);
116
147
 
@@ -119,13 +150,20 @@ export const birthGrantByPrincipal = query("birthGrantByPrincipal")
119
150
  .key("principal")
120
151
  .returns(BirthGrant);
121
152
 
122
- /** The three meter reads every `.births()` directive auto-declares (`nomosReadGate`). */
153
+ /** The framework reads every `.births()` directive auto-declares (`nomosReadGate`). */
123
154
  export const BIRTH_METER_QUERIES: readonly QueryDecl[] = [
124
155
  birthRecordsByPrincipal,
156
+ childLifecyclesByPrincipal,
125
157
  birthPolicy,
126
158
  birthGrantByPrincipal,
127
159
  ];
128
160
 
161
+ /** Every query a birth-capable law carries, including the custody bailiff's terminal-child index. */
162
+ export const BIRTH_FRAMEWORK_QUERIES: readonly QueryDecl[] = [
163
+ ...BIRTH_METER_QUERIES,
164
+ childLifecyclesByStatus,
165
+ ];
166
+
129
167
  /**
130
168
  * THE DEFAULT BIRTH ALLOWANCE when a workspace carries neither a BirthPolicy singleton nor a grant
131
169
  * for the subject: a CONSERVATIVE floor for non-root workspaces (a home births a handful of estate
@@ -173,13 +211,14 @@ function sha256(text: string): string {
173
211
  }
174
212
 
175
213
  /**
176
- * CAPTURE the three meter reads on the write path (A2 — the plan side of the birth-quota gate; the
214
+ * CAPTURE the meter reads on the write path (A2 — the plan side of the birth-quota gate; the
177
215
  * exact `countQuotaReads` shape). Called by `executeDirectiveToIntent` for every `.births()` dispatch
178
216
  * that planned ≥1 birth — the values ride `captured_ports.reads`, CAS-re-derived + byte-compared at
179
217
  * the pre-apply position on every lane; the verdict is the synthesized invariant's.
180
218
  */
181
219
  export function captureBirthMeterReads(subject: string): void {
182
220
  __captureBirthMeterRead(birthRecordsByPrincipal, { principal: subject });
221
+ __captureBirthMeterRead(childLifecyclesByPrincipal, { principal: subject });
183
222
  __captureBirthMeterRead(birthGrantByPrincipal, { principal: subject });
184
223
  __captureBirthMeterRead(birthPolicy, { scope: "workspace" });
185
224
  }
@@ -231,7 +270,13 @@ export function birthQuotaVerdict(
231
270
  const hit = reads.find((r) => r.queryId === queryId && r.args[argKey] === argValue);
232
271
  return Array.isArray(hit?.result) ? (hit!.result as MeterRow[]) : [];
233
272
  };
234
- const count = rowsOf(birthRecordsByPrincipal.id, "principal", subject).length;
273
+ // Terminal children no longer consume the parent's live-child allowance. Lifecycle is kept in an additive
274
+ // aggregate beside the historical birth witness, so an old row with no lifecycle remains active.
275
+ const born = rowsOf(birthRecordsByPrincipal.id, "principal", subject).length;
276
+ const terminal = rowsOf(childLifecyclesByPrincipal.id, "principal", subject)
277
+ .filter((r) => r.data?.status === "retired" || r.data?.status === "reclaimed")
278
+ .length;
279
+ const count = Math.max(0, born - terminal);
235
280
  // ALLOWANCE: the LATEST BirthGrant (max grantedAt) wins; else the BirthPolicy singleton; else the floor.
236
281
  let allowance: number | undefined;
237
282
  let latestGrantedAt = -Infinity;
@@ -295,6 +340,7 @@ export function synthesizeBirthQuotaInvariants(directive: {
295
340
  /** The meter aggregates every `.births()`-declaring domain auto-ensures (the RelationTuple courtesy). */
296
341
  export const BIRTH_METER_AGGREGATES: readonly AggregateHandle[] = [
297
342
  NomosBirthRecord,
343
+ NomosChildLifecycle,
298
344
  BirthPolicy,
299
345
  BirthGrant,
300
346
  ] as unknown as readonly AggregateHandle[];
@@ -26,9 +26,17 @@ import { directive } from "./directive.js";
26
26
  import type { Directive } from "./directive.js";
27
27
  import {
28
28
  NomosBirthRecord,
29
+ NomosChildLifecycle,
29
30
  BirthPolicy,
30
31
  BirthGrant,
31
32
  } from "./birth_meter.js";
33
+ import {
34
+ reclaimChild,
35
+ reclaimChildRequiresRetired,
36
+ retireChild,
37
+ retireChildRequiresBirth,
38
+ retireChildNotReclaimed,
39
+ } from "./framework/birth_meter.js";
32
40
 
33
41
  /**
34
42
  * The sanctioning declaration — an `ensures(NomosBirthRecord)` directive whose plan emits ZERO ops.
@@ -59,9 +67,23 @@ export function withBirthMeterExports<M extends Record<string, unknown>>(mod: M)
59
67
  );
60
68
  if (!declaresBirth) return mod;
61
69
  const out: Record<string, unknown> = { ...mod };
62
- if (!("nomosBirthRecordEnsure" in out)) out["nomosBirthRecordEnsure"] = nomosBirthRecordEnsure;
63
- if (!("NomosBirthRecord" in out)) out["NomosBirthRecord"] = NomosBirthRecord;
64
- if (!("BirthPolicy" in out)) out["BirthPolicy"] = BirthPolicy;
65
- if (!("BirthGrant" in out)) out["BirthGrant"] = BirthGrant;
70
+ // Business birth hides framework maintenance declarations from the generated application facade while
71
+ // retaining them in certified law for the operator's structural offer discovery. A non-business
72
+ // `.births()` law keeps its existing explicit generated surface.
73
+ const businessInternal = Object.values(mod).some(
74
+ (v) => v !== null && typeof v === "object" && (v as { businessBirth?: unknown }).businessBirth !== undefined,
75
+ );
76
+ const presented = <T extends object>(v: T): T =>
77
+ businessInternal ? ({ ...v, businessBirthInternal: true } as T) : v;
78
+ if (!("nomosBirthRecordEnsure" in out)) out["nomosBirthRecordEnsure"] = presented(nomosBirthRecordEnsure);
79
+ if (!("NomosBirthRecord" in out)) out["NomosBirthRecord"] = presented(NomosBirthRecord);
80
+ if (!("NomosChildLifecycle" in out)) out["NomosChildLifecycle"] = presented(NomosChildLifecycle);
81
+ if (!("BirthPolicy" in out)) out["BirthPolicy"] = presented(BirthPolicy);
82
+ if (!("BirthGrant" in out)) out["BirthGrant"] = presented(BirthGrant);
83
+ if (!("retireChild" in out)) out["retireChild"] = presented(retireChild);
84
+ if (!("reclaimChild" in out)) out["reclaimChild"] = presented(reclaimChild);
85
+ if (!("reclaimChildRequiresRetired" in out)) out["reclaimChildRequiresRetired"] = reclaimChildRequiresRetired;
86
+ if (!("retireChildRequiresBirth" in out)) out["retireChildRequiresBirth"] = retireChildRequiresBirth;
87
+ if (!("retireChildNotReclaimed" in out)) out["retireChildNotReclaimed"] = retireChildNotReclaimed;
66
88
  return out as M;
67
89
  }
@@ -56,7 +56,7 @@ import type { WorkspaceInvariantDecl } from "./framework/workspace_invariant.js"
56
56
  import { synthesizePreconditionInvariants } from "./precondition.js";
57
57
  import {
58
58
  BIRTH_METER_AGGREGATES,
59
- BIRTH_METER_QUERIES,
59
+ BIRTH_FRAMEWORK_QUERIES,
60
60
  synthesizeBirthQuotaInvariants,
61
61
  } from "./birth_meter.js";
62
62
  import { withBirthMeterExports } from "./birth_meter_ensure.js";
@@ -81,6 +81,11 @@ import { discoverUnsanctionedEnsures, sanctioningEnsureDirective } from "./secon
81
81
  import { assertIdOfLint } from "./idof_lint.js";
82
82
  import { assertSurfacedResolvers } from "./surfaced_lint.js";
83
83
  import { expandBusinessBirthExports } from "./business_birth.js";
84
+ import {
85
+ reclaimChildRequiresRetired,
86
+ retireChildNotReclaimed,
87
+ retireChildRequiresBirth,
88
+ } from "./framework/birth_meter.js";
84
89
 
85
90
  // ─────────────────────────────────────────────────────────────────────────────────
86
91
  // composeDomainModule — the named-keys `module(...)`
@@ -309,6 +314,12 @@ export function composeDomainModule(spec: DomainModuleSpec): DomainModule {
309
314
  const maxes = [...(spec.maxes ?? [])];
310
315
  const workspaceTypes = [...(spec.workspaceTypes ?? [])];
311
316
  const workspaceInvariants = [...(spec.workspaceInvariants ?? [])];
317
+ if (declaresBirth) {
318
+ const haveInvariant = new Set(workspaceInvariants.map((w) => w.id));
319
+ for (const lifecycle of [reclaimChildRequiresRetired, retireChildNotReclaimed, retireChildRequiresBirth]) {
320
+ if (!haveInvariant.has(lifecycle.id)) workspaceInvariants.push(lifecycle);
321
+ }
322
+ }
312
323
  // DESUGAR the write-path precondition primitives (`.requiresExisting`/`.creates(Agg,idFn)`/`.where`):
313
324
  // each directive's descriptors synthesize workspace invariants (LANE B) appended to the SAME list a
314
325
  // hand-authored `workspaceInvariant(...)` occupies — so downstream treats them identically. Era-safe:
@@ -321,7 +332,7 @@ export function composeDomainModule(spec: DomainModuleSpec): DomainModule {
321
332
  // The meter's declared queries route like any exported query (read manifest + captured-read re-derive).
322
333
  if (declaresBirth) {
323
334
  const haveQuery = new Set(queries.map((q) => q.id));
324
- for (const q of BIRTH_METER_QUERIES) {
335
+ for (const q of BIRTH_FRAMEWORK_QUERIES) {
325
336
  if (!haveQuery.has(q.id)) queries.push(q);
326
337
  }
327
338
  // The inheritance read (`.reads("authProvider")` on birthHome) routes like any declared query.
@@ -15,10 +15,13 @@ import { AuthProvider, authProvider, setAuthProvider } from "./framework/auth_pr
15
15
  import {
16
16
  BirthGrant,
17
17
  BirthPolicy,
18
+ NomosChildLifecycle,
18
19
  NomosBirthRecord,
19
20
  birthGrantByPrincipal,
20
21
  birthPolicy,
21
22
  birthRecordsByPrincipal,
23
+ childLifecyclesByPrincipal,
24
+ childLifecyclesByStatus,
22
25
  } from "./birth_meter.js";
23
26
  import { nomosBirthRecordEnsure } from "./birth_meter_ensure.js";
24
27
  import { GENESIS_WARRANT_ID, GenesisWarrant, seedGenesis } from "./framework/genesis.js";
@@ -238,10 +241,13 @@ function lowerOne(decl: BusinessBirthDecl, domain: string): Record<string, unkno
238
241
  // Exact export keys prevent the generic `.births()` auto-compose seam from inserting a second,
239
242
  // unmarked copy. The values are still the canonical framework declarations.
240
243
  NomosBirthRecord: internalSurface(NomosBirthRecord),
244
+ NomosChildLifecycle: internalSurface(NomosChildLifecycle),
241
245
  BirthPolicy: internalSurface(BirthPolicy),
242
246
  BirthGrant: internalSurface(BirthGrant),
243
247
  nomosBirthRecordEnsure: internalDirective(nomosBirthRecordEnsure),
244
248
  __nomos_birthRecordsByPrincipal: internalSurface(birthRecordsByPrincipal),
249
+ __nomos_childLifecyclesByPrincipal: internalSurface(childLifecyclesByPrincipal),
250
+ __nomos_childLifecyclesByStatus: internalSurface(childLifecyclesByStatus),
245
251
  __nomos_birthPolicy: internalSurface(birthPolicy),
246
252
  __nomos_birthGrantByPrincipal: internalSurface(birthGrantByPrincipal),
247
253
  __nomos_parentAttestedBirth: businessParentAttestedBirth,
@@ -91,7 +91,7 @@ import {
91
91
  type Mod,
92
92
  } from "./build_package.js";
93
93
  import { synthesizePreconditionInvariants } from "./precondition.js";
94
- import { BIRTH_METER_QUERIES, synthesizeBirthQuotaInvariants } from "./birth_meter.js";
94
+ import { BIRTH_FRAMEWORK_QUERIES, synthesizeBirthQuotaInvariants } from "./birth_meter.js";
95
95
  import { withBirthMeterExports } from "./birth_meter_ensure.js";
96
96
  import { expandBusinessBirthExports } from "./business_birth.js";
97
97
  import { expandRoleExports, rolesOf } from "./role.js";
@@ -1772,7 +1772,7 @@ async function main(): Promise<void> {
1772
1772
  // the same auto-ensure `composeDomainModule` applies to the manifest half.
1773
1773
  if (directivesOf(merged).some((dir) => (dir as { birthsChild?: boolean }).birthsChild === true)) {
1774
1774
  const haveQuery = new Set(queries.map((q) => q.id));
1775
- for (const q of BIRTH_METER_QUERIES) {
1775
+ for (const q of BIRTH_FRAMEWORK_QUERIES) {
1776
1776
  if (!haveQuery.has(q.id)) queries.push(q);
1777
1777
  }
1778
1778
  }
package/src/directive.ts CHANGED
@@ -958,8 +958,8 @@ function makeRequirable<P>(base: Directive<P>): RequirableDirective<P> {
958
958
  // BIRTH AS A DECLARED KERNEL PRIMITIVE (G3): mark this directive a birth — its plan's `birth(recipe)`
959
959
  // rides the intent, and the kernel (reading the `nomosBirthGate` era key, not domain state) executes
960
960
  // the birth after admit. Era-ruled: a non-birthing directive omits the flag and is byte-identical.
961
- // METERED BIRTH (slice A): `.births()` ALSO auto-declares the three birth-meter captured reads
962
- // (`birthRecordsByPrincipal`/`birthGrantByPrincipal`/`birthPolicy` — the #58 lane), so the
961
+ // METERED BIRTH (slice A): `.births()` ALSO auto-declares the birth-meter reads
962
+ // (`birthRecordsByPrincipal`/`childLifecyclesByPrincipal`/`birthGrantByPrincipal`/`birthPolicy`), so the
963
963
  // `nomosReadGate` era key carries them by construction and the meter reads the `.births()` dispatch
964
964
  // auto-captures (see `executeDirectiveToIntent`) CAS-verify on every lane. The era is the bundle:
965
965
  // recompiling under this DSL is what arms the meter (every pre-era bundle replays byte-green).
@@ -27,23 +27,33 @@ import { z } from "zod";
27
27
  import { instance } from "../aggregate.js";
28
28
  import { directive } from "../directive.js";
29
29
  import { set, type PlannedOp } from "../ops.js";
30
+ import { refAs, workspaceInvariant } from "./workspace_invariant.js";
30
31
  import {
31
32
  BirthPolicy,
32
33
  BirthGrant,
34
+ NomosChildLifecycle,
35
+ NomosBirthRecord,
33
36
  BIRTH_POLICY_ID,
37
+ birthRecordId,
38
+ childLifecycleId,
34
39
  birthGrantId,
35
40
  } from "../birth_meter.js";
36
41
 
37
42
  // Re-export the whole meter core so `@githolon/dsl` framework consumers get one import site.
38
43
  export {
39
44
  NomosBirthRecord,
45
+ NomosChildLifecycle,
40
46
  BirthPolicy,
41
47
  BirthGrant,
42
48
  BIRTH_POLICY_ID,
43
49
  NOMOS_BIRTH_RECORD_PREFIX,
50
+ NOMOS_CHILD_LIFECYCLE_PREFIX,
44
51
  birthRecordId,
52
+ childLifecycleId,
45
53
  birthGrantId,
46
54
  birthRecordsByPrincipal,
55
+ childLifecyclesByPrincipal,
56
+ childLifecyclesByStatus,
47
57
  birthPolicy,
48
58
  birthGrantByPrincipal,
49
59
  DEFAULT_MAX_BIRTHS_PER_PRINCIPAL,
@@ -51,6 +61,93 @@ export {
51
61
  birthQuotaInvariantId,
52
62
  } from "../birth_meter.js";
53
63
 
64
+ /**
65
+ * retireChild / reclaimChild are the lifecycle of the SAME child born by `.births()`. They are operator
66
+ * maintenance beneath the application API, not alternative creation lanes. The durable parent-side
67
+ * NomosBirthRecord remains the immutable birth witness; an additive NomosChildLifecycle is the authority
68
+ * for withdrawal/disposal, so a framework upgrade never changes the schema of historical birth rows.
69
+ */
70
+ export const retireChild = directive("retireChild")
71
+ .ensures(NomosChildLifecycle)
72
+ .payload(z.object({
73
+ child: z.string().min(1),
74
+ principal: z.string().min(1),
75
+ retiredBy: z.string().min(1),
76
+ retiredAt: z.number().int().nonnegative(),
77
+ }))
78
+ .plan((p) => {
79
+ const rec = instance(NomosChildLifecycle, childLifecycleId(p.child));
80
+ return [
81
+ set(rec, "child", p.child),
82
+ set(rec, "principal", p.principal),
83
+ set(rec, "status", "retired"),
84
+ set(rec, "retiredBy", p.retiredBy),
85
+ set(rec, "retiredAt", p.retiredAt),
86
+ ];
87
+ });
88
+
89
+ export const reclaimChild = directive("reclaimChild")
90
+ .ensures(NomosChildLifecycle)
91
+ .payload(z.object({
92
+ child: z.string().min(1),
93
+ reclaimedBy: z.string().min(1),
94
+ reclaimedAt: z.number().int().nonnegative(),
95
+ }))
96
+ .plan((p) => {
97
+ const rec = instance(NomosChildLifecycle, childLifecycleId(p.child));
98
+ return [
99
+ set(rec, "status", "reclaimed"),
100
+ set(rec, "reclaimedBy", p.reclaimedBy),
101
+ set(rec, "reclaimedAt", p.reclaimedAt),
102
+ ];
103
+ });
104
+
105
+ /** Reclaim is two-phase; a child must first be explicitly withdrawn. */
106
+ export const reclaimChildRequiresRetired = workspaceInvariant("ReclaimChildRequiresRetired")
107
+ .on("reclaimChild")
108
+ .reads((ctx) => [
109
+ refAs("record", "NomosChildLifecycle", "pre:" + childLifecycleId(String(ctx.intent.child ?? ""))),
110
+ ])
111
+ .assert(({ record }, ctx) =>
112
+ (record as { status?: string; principal?: string } | undefined)?.status === "retired"
113
+ && sameLifecyclePrincipal((record as { principal?: string }).principal, ctx?.principal)
114
+ ? { accept: true }
115
+ : { reject: "reclaim-child-requires-retired" },
116
+ );
117
+
118
+ /** Reclaimed is permanent: retirement cannot move a child back out of the terminal state. */
119
+ export const retireChildNotReclaimed = workspaceInvariant("RetireChildNotReclaimed")
120
+ .on("retireChild")
121
+ .reads((ctx) => [
122
+ refAs("record", "NomosChildLifecycle", "pre:" + childLifecycleId(String(ctx.intent.child ?? ""))),
123
+ ])
124
+ .assert(({ record }) =>
125
+ (record as { status?: string } | undefined)?.status === "reclaimed"
126
+ ? { reject: "reclaimed-child-is-terminal" }
127
+ : { accept: true },
128
+ );
129
+
130
+ /** A lifecycle cannot mint authority for arbitrary custody: retirement must name its exact birth witness. */
131
+ export const retireChildRequiresBirth = workspaceInvariant("RetireChildRequiresBirth")
132
+ .on("retireChild")
133
+ .reads((ctx) => [
134
+ refAs("birth", "NomosBirthRecord", "pre:" + birthRecordId(String(ctx.intent.child ?? ""))),
135
+ ])
136
+ .assert(({ birth }, ctx) => {
137
+ const record = birth as { child?: string; principal?: string } | undefined;
138
+ const intent = ctx?.intent as { child?: string; principal?: string } | undefined;
139
+ return record?.child === intent?.child
140
+ && record?.principal === intent?.principal
141
+ && sameLifecyclePrincipal(record?.principal, ctx?.principal)
142
+ ? { accept: true }
143
+ : { reject: "retire-child-requires-exact-birth-record" };
144
+ });
145
+
146
+ function sameLifecyclePrincipal(recorded: string | undefined, author: string | undefined): boolean {
147
+ const normalize = (s: string | undefined): string => s?.startsWith("user:") ? s.slice(5) : (s ?? "");
148
+ return normalize(recorded) !== "" && normalize(recorded) === normalize(author);
149
+ }
150
+
54
151
  /** The BirthPolicy singleton upsert ops (shared by the governed set and the genesis seed). */
55
152
  function birthPolicyOps(p: { maxBirthsPerPrincipal: number; setBy: string; setAt: string }): PlannedOp[] {
56
153
  const pol = instance(BirthPolicy, BIRTH_POLICY_ID);
@@ -35,6 +35,7 @@ import type { QueryDecl } from "../query.js";
35
35
  import { nomosSourceFrontierEnsure } from "../attested.js";
36
36
  import * as birthMeter from "../birth_meter.js";
37
37
  import * as birthMeterEnsure from "../birth_meter_ensure.js";
38
+ import * as birthMeterFramework from "./birth_meter.js";
38
39
  import * as disclosure from "./disclosure.js";
39
40
  import * as domainLifecycle from "./domain_lifecycle.js";
40
41
  import * as encryptionIdentity from "./encryption_identity.js";
@@ -56,6 +57,7 @@ const FRAMEWORK_NAMESPACES: ReadonlyArray<Record<string, unknown>> = [
56
57
  // read models/accessors) once PER domain — the exact ambiguous_export co2 hit on its barrel.
57
58
  birthMeter,
58
59
  birthMeterEnsure,
60
+ birthMeterFramework,
59
61
  disclosure,
60
62
  domainLifecycle,
61
63
  encryptionIdentity,
package/src/index.ts CHANGED
@@ -456,13 +456,18 @@ export {
456
456
  // tenant composes in to set/override allowances, plus the constants/predicates tests and tools read.
457
457
  export {
458
458
  NomosBirthRecord,
459
+ NomosChildLifecycle,
459
460
  BirthPolicy,
460
461
  BirthGrant,
461
462
  BIRTH_POLICY_ID,
462
463
  NOMOS_BIRTH_RECORD_PREFIX,
464
+ NOMOS_CHILD_LIFECYCLE_PREFIX,
463
465
  birthRecordId,
466
+ childLifecycleId,
464
467
  birthGrantId,
465
468
  birthRecordsByPrincipal,
469
+ childLifecyclesByPrincipal,
470
+ childLifecyclesByStatus,
466
471
  birthPolicy,
467
472
  birthGrantByPrincipal,
468
473
  DEFAULT_MAX_BIRTHS_PER_PRINCIPAL,
@@ -471,6 +476,11 @@ export {
471
476
  setBirthPolicy,
472
477
  seedBirthPolicy,
473
478
  grantBirthAllowance,
479
+ retireChild,
480
+ reclaimChild,
481
+ reclaimChildRequiresRetired,
482
+ retireChildRequiresBirth,
483
+ retireChildNotReclaimed,
474
484
  } from "./framework/birth_meter.js";
475
485
  // THE PLATFORM FOUNDING-ADMIN BOOTSTRAP (the platform analog of seedHomeCreator) — a tenant-importable
476
486
  // factory that seats a tenant-named governance relation for a write-once, author-bound founder at genesis.
@@ -81,6 +81,7 @@ export const GLOBAL_READ_PROJECTION_AGGREGATE_IDS: ReadonlySet<string> = new Set
81
81
  // metered birth (slice A) — NomosBirthRecord is kernel-read by the birth-record
82
82
  // gate (the `nomos-birth-record:` prefix); the policy/grant singletons ride with it
83
83
  "NomosBirthRecord",
84
+ "NomosChildLifecycle",
84
85
  "BirthPolicy",
85
86
  "BirthGrant",
86
87
  // home holons
@@ -306,7 +306,7 @@ export function executeDirectiveToIntent<P>(
306
306
  // A1 THE AUTO-RECORD: append the `NomosBirthRecord` field ops (deterministic id per child; Lww upsert)
307
307
  // to the SAME intent that carries the birth effect. The kernel's `nomosBirthRecordGate` era arm
308
308
  // refuses a new-era birth whose intent carries no record — this is what satisfies it.
309
- // A2 THE AUTO-CAPTURE: capture the three meter reads (records-by-subject + grant + policy — the #58
309
+ // A2 THE AUTO-CAPTURE: capture the meter reads (births + terminal lifecycle + grant + policy — the #58
310
310
  // lane `.births()` auto-declares) so the synthesized birth-quota invariant judges over COMMITTED,
311
311
  // CAS-re-derived pre-apply state on every lane.
312
312
  // A `.births()` dispatch that planned NO birth (e.g. birthChild without a genesis) appends/captures