@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 +1 -1
- package/src/birth_meter.ts +49 -3
- package/src/birth_meter_ensure.ts +26 -4
- package/src/build_package.ts +13 -2
- package/src/business_birth.ts +6 -0
- package/src/compile_package_main.ts +2 -2
- package/src/directive.ts +2 -2
- package/src/framework/birth_meter.ts +97 -0
- package/src/framework/framework_aggregates.ts +2 -0
- package/src/index.ts +10 -0
- package/src/read_projection_namespace.ts +1 -0
- package/src/wire_encode.ts +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@githolon/dsl",
|
|
3
|
-
"version": "0.100.
|
|
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",
|
package/src/birth_meter.ts
CHANGED
|
@@ -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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
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
|
}
|
package/src/build_package.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
|
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.
|
package/src/business_birth.ts
CHANGED
|
@@ -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 {
|
|
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
|
|
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
|
|
962
|
-
// (`birthRecordsByPrincipal`/`birthGrantByPrincipal`/`birthPolicy`
|
|
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
|
package/src/wire_encode.ts
CHANGED
|
@@ -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
|
|
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
|