@githolon/dsl 0.100.2 → 0.100.4
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/dart/CHANGELOG.md +6 -0
- package/dart/lib/src/home.dart +1 -1
- package/dart/lib/src/webview_bridge.dart +1 -2
- package/dart/pubspec.yaml +1 -1
- 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/codegen_openapi.ts +82 -34
- package/src/compile_package_main.ts +2 -2
- package/src/directive.ts +2 -2
- package/src/engine_entry.ts +7 -6
- 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/dart/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
## 0.62.1
|
|
2
|
+
|
|
3
|
+
- Align the public Dart documentation with the one Flutter application entry point: identity comes from the
|
|
4
|
+
token passed to `NomosScope.home(application:, authToken:)`; application code no longer supplies a subject
|
|
5
|
+
or device-token callback.
|
|
6
|
+
|
|
1
7
|
## 0.62.0
|
|
2
8
|
|
|
3
9
|
- Generated business clients expose one `birth(payload)` method. The bound session supplies its verified
|
package/dart/lib/src/home.dart
CHANGED
|
@@ -10,7 +10,7 @@ import 'package:crypto/crypto.dart';
|
|
|
10
10
|
/// every peer and on the cloud. Use it directly as the `workspace`:
|
|
11
11
|
///
|
|
12
12
|
/// ```dart
|
|
13
|
-
/// NomosScope(
|
|
13
|
+
/// NomosScope.home(application: 'my-app', authToken: idToken, ...)
|
|
14
14
|
/// ```
|
|
15
15
|
///
|
|
16
16
|
/// Formula: `home-` + the first 40 hex chars of `sha256(utf8(subject))`.
|
|
@@ -1895,8 +1895,7 @@ class NomosBridge {
|
|
|
1895
1895
|
|
|
1896
1896
|
/// Derive the device key's base64 PUBLIC half from its base64 SECRET seed, inside the LOCAL kernel
|
|
1897
1897
|
/// (the secret never leaves the device). The recovery lanes use this when only the secret was
|
|
1898
|
-
/// persisted (the device-key store keeps just the seed)
|
|
1899
|
-
/// (`authTokenForDevice(publicKey)`) for the keyless re-enroll lane.
|
|
1898
|
+
/// persisted (the device-key store keeps just the seed), including low-level device-bound enrollment.
|
|
1900
1899
|
Future<String> devicePublicOf(String secret) async =>
|
|
1901
1900
|
(_asMap(await _call('devicePublicOf', {'secret': secret}))['public'] ?? '')
|
|
1902
1901
|
.toString();
|
package/dart/pubspec.yaml
CHANGED
|
@@ -11,7 +11,7 @@ description: >-
|
|
|
11
11
|
Nomos 2 Dart frontend types — the hand-written kernel wire types plus the
|
|
12
12
|
code-generated per-domain aggregate + directive-payload types. (De)serializes
|
|
13
13
|
the EXACT serde JSON the Rust kernel + projection produce/consume.
|
|
14
|
-
version: 0.62.
|
|
14
|
+
version: 0.62.1
|
|
15
15
|
|
|
16
16
|
environment:
|
|
17
17
|
sdk: ^3.5.0
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@githolon/dsl",
|
|
3
|
-
"version": "0.100.
|
|
3
|
+
"version": "0.100.4",
|
|
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,
|
package/src/codegen_openapi.ts
CHANGED
|
@@ -50,11 +50,18 @@ export interface OpenApiDoc {
|
|
|
50
50
|
openapi: "3.1.0";
|
|
51
51
|
info: Record<string, unknown>;
|
|
52
52
|
paths: Record<string, Record<string, unknown>>;
|
|
53
|
+
/**
|
|
54
|
+
* Law-derived routes needed by Nomos itself (device seating, provenance, recovery, ...).
|
|
55
|
+
* The host consumes this only on its internal describe path and strips it from public OpenAPI.
|
|
56
|
+
*/
|
|
57
|
+
"x-nomos-framework-routes"?: { paths: Record<string, Record<string, unknown>> };
|
|
53
58
|
}
|
|
54
59
|
|
|
55
60
|
export interface OpenApiFragmentOptions {
|
|
56
61
|
/** The content hash of the installed law (the spec's version + ingest cache key). */
|
|
57
62
|
lawHash?: string;
|
|
63
|
+
/** Include the private framework router manifest used by the Nomos host. Public codegen omits it. */
|
|
64
|
+
includeFrameworkRoutes?: boolean;
|
|
58
65
|
}
|
|
59
66
|
|
|
60
67
|
/** Lower a directive's payload zod to JSON Schema; a non-representable schema falls back to a
|
|
@@ -95,22 +102,10 @@ function domainKey(module: DomainModule): string {
|
|
|
95
102
|
* the merge namespaces any cross-domain collision. The offer/query distinction rides on the HTTP
|
|
96
103
|
* method (POST/GET) AND the authoritative `x-nomos.kind` — the host reads the latter, never the name.
|
|
97
104
|
*/
|
|
98
|
-
|
|
105
|
+
function generateOpenApiSurface(
|
|
99
106
|
module: DomainModule,
|
|
100
107
|
opts: OpenApiFragmentOptions = {},
|
|
101
108
|
): OpenApiDoc {
|
|
102
|
-
module = {
|
|
103
|
-
...module,
|
|
104
|
-
aggregates: module.aggregates.filter(
|
|
105
|
-
(a) => (a as { businessBirthInternal?: unknown }).businessBirthInternal !== true,
|
|
106
|
-
),
|
|
107
|
-
directives: module.directives.filter(
|
|
108
|
-
(d) => (d as { businessBirthInternal?: unknown }).businessBirthInternal !== true,
|
|
109
|
-
),
|
|
110
|
-
...(module.queries === undefined
|
|
111
|
-
? {}
|
|
112
|
-
: { queries: module.queries.filter((q) => (q as { businessBirthInternal?: unknown }).businessBirthInternal !== true) }),
|
|
113
|
-
};
|
|
114
109
|
const domain = domainKey(module);
|
|
115
110
|
const paths: Record<string, Record<string, unknown>> = {};
|
|
116
111
|
|
|
@@ -123,7 +118,6 @@ export function generateOpenApiFragment(
|
|
|
123
118
|
|
|
124
119
|
// OFFERABLE INTENTS → POST offers. The payload JSON Schema is the intent's own zod, lowered.
|
|
125
120
|
for (const dir of module.directives as AnyDirective[]) {
|
|
126
|
-
if ((dir as { businessBirthInternal?: unknown }).businessBirthInternal === true) continue;
|
|
127
121
|
const businessBirth = (dir as {
|
|
128
122
|
businessBirth?: { aggregateId: string; publicPayloadSchema: unknown };
|
|
129
123
|
}).businessBirth;
|
|
@@ -247,11 +241,79 @@ export function generateOpenApiFragment(
|
|
|
247
241
|
};
|
|
248
242
|
}
|
|
249
243
|
|
|
244
|
+
/**
|
|
245
|
+
* Generate the developer-facing OpenAPI plus, only when requested by the runtime, a separate
|
|
246
|
+
* framework routing manifest. The split is deliberate: filtering framework declarations out of
|
|
247
|
+
* `paths` is presentation; it must never make Nomos's own law declarations unroutable.
|
|
248
|
+
*/
|
|
249
|
+
export function generateOpenApiFragment(
|
|
250
|
+
module: DomainModule,
|
|
251
|
+
opts: OpenApiFragmentOptions = {},
|
|
252
|
+
): OpenApiDoc {
|
|
253
|
+
const isInternal = (value: unknown) =>
|
|
254
|
+
!!value && typeof value === "object"
|
|
255
|
+
&& (value as { businessBirthInternal?: unknown }).businessBirthInternal === true;
|
|
256
|
+
const publicModule: DomainModule = {
|
|
257
|
+
...module,
|
|
258
|
+
aggregates: module.aggregates.filter((a) => !isInternal(a)),
|
|
259
|
+
directives: module.directives.filter((d) => !isInternal(d)),
|
|
260
|
+
...(module.queries === undefined ? {} : { queries: module.queries.filter((q) => !isInternal(q)) }),
|
|
261
|
+
};
|
|
262
|
+
const publicDoc = generateOpenApiSurface(publicModule, opts);
|
|
263
|
+
if (opts.includeFrameworkRoutes !== true) return publicDoc;
|
|
264
|
+
|
|
265
|
+
const frameworkModule: DomainModule = {
|
|
266
|
+
...module,
|
|
267
|
+
aggregates: module.aggregates.filter(isInternal),
|
|
268
|
+
directives: module.directives.filter(isInternal),
|
|
269
|
+
...(module.queries === undefined ? {} : { queries: module.queries.filter(isInternal) }),
|
|
270
|
+
// Framework routing is for declared internal offers/queries. Business maintained reads remain
|
|
271
|
+
// on the public module; do not duplicate them into the private table.
|
|
272
|
+
counts: [],
|
|
273
|
+
sums: [],
|
|
274
|
+
mins: [],
|
|
275
|
+
maxes: [],
|
|
276
|
+
spatials: [],
|
|
277
|
+
};
|
|
278
|
+
const frameworkDoc = generateOpenApiSurface(frameworkModule, opts);
|
|
279
|
+
if (Object.keys(frameworkDoc.paths).length === 0) return publicDoc;
|
|
280
|
+
return {
|
|
281
|
+
...publicDoc,
|
|
282
|
+
"x-nomos-framework-routes": { paths: frameworkDoc.paths },
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
|
|
250
286
|
/** Stable JSON of an operation, to detect a genuine duplicate vs a real collision in the merge. */
|
|
251
287
|
function operationKey(op: unknown): string {
|
|
252
288
|
return JSON.stringify(op);
|
|
253
289
|
}
|
|
254
290
|
|
|
291
|
+
function mergePathsInto(
|
|
292
|
+
paths: Record<string, Record<string, unknown>>,
|
|
293
|
+
incoming: Record<string, Record<string, unknown>>,
|
|
294
|
+
): void {
|
|
295
|
+
for (const [path, methods] of Object.entries(incoming)) {
|
|
296
|
+
for (const [method, op] of Object.entries(methods)) {
|
|
297
|
+
const existing = paths[path]?.[method];
|
|
298
|
+
if (existing === undefined) {
|
|
299
|
+
(paths[path] ??= {})[method] = op;
|
|
300
|
+
} else if (operationKey(existing) === operationKey(op)) {
|
|
301
|
+
// identical (a framework operation shared by many domains) — keep the one entry.
|
|
302
|
+
} else {
|
|
303
|
+
// genuine collision: two domains, same path+method, different operations. Namespace BOTH
|
|
304
|
+
// under their domain so neither wins silently.
|
|
305
|
+
const domOf = (o: unknown): string =>
|
|
306
|
+
String((o as { "x-nomos"?: { domain?: string } })?.["x-nomos"]?.domain ?? "domain");
|
|
307
|
+
const bucket = paths[path]!;
|
|
308
|
+
(paths[`/${domOf(existing)}${path}`] ??= {})[method] = existing;
|
|
309
|
+
(paths[`/${domOf(op)}${path}`] ??= {})[method] = op;
|
|
310
|
+
delete bucket[method];
|
|
311
|
+
if (Object.keys(bucket).length === 0) delete paths[path];
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
255
317
|
/**
|
|
256
318
|
* mergeOpenApiFragments — the WORKSPACE spec = the union of its installed domains' fragments. The
|
|
257
319
|
* runtime step (driven by `currentLaw`), kept here as a pure, law-blind function:
|
|
@@ -266,27 +328,10 @@ export function mergeOpenApiFragments(
|
|
|
266
328
|
opts: { title?: string; lawHash?: string } = {},
|
|
267
329
|
): OpenApiDoc {
|
|
268
330
|
const paths: Record<string, Record<string, unknown>> = {};
|
|
331
|
+
const frameworkPaths: Record<string, Record<string, unknown>> = {};
|
|
269
332
|
for (const frag of fragments) {
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
const existing = paths[path]?.[method];
|
|
273
|
-
if (existing === undefined) {
|
|
274
|
-
(paths[path] ??= {})[method] = op;
|
|
275
|
-
} else if (operationKey(existing) === operationKey(op)) {
|
|
276
|
-
// identical (a framework operation shared by many domains) — keep the one entry.
|
|
277
|
-
} else {
|
|
278
|
-
// genuine collision: two domains, same path+method, different operations. Namespace BOTH
|
|
279
|
-
// under their domain so neither wins silently.
|
|
280
|
-
const domOf = (o: unknown): string =>
|
|
281
|
-
String((o as { "x-nomos"?: { domain?: string } })?.["x-nomos"]?.domain ?? "domain");
|
|
282
|
-
const bucket = paths[path]!;
|
|
283
|
-
(paths[`/${domOf(existing)}${path}`] ??= {})[method] = existing;
|
|
284
|
-
(paths[`/${domOf(op)}${path}`] ??= {})[method] = op;
|
|
285
|
-
delete bucket[method];
|
|
286
|
-
if (Object.keys(bucket).length === 0) delete paths[path];
|
|
287
|
-
}
|
|
288
|
-
}
|
|
289
|
-
}
|
|
333
|
+
mergePathsInto(paths, frag.paths);
|
|
334
|
+
mergePathsInto(frameworkPaths, frag["x-nomos-framework-routes"]?.paths ?? {});
|
|
290
335
|
}
|
|
291
336
|
return {
|
|
292
337
|
openapi: "3.1.0",
|
|
@@ -296,5 +341,8 @@ export function mergeOpenApiFragments(
|
|
|
296
341
|
...(opts.lawHash ? { "x-nomos-law-hash": opts.lawHash } : {}),
|
|
297
342
|
},
|
|
298
343
|
paths,
|
|
344
|
+
...(Object.keys(frameworkPaths).length > 0
|
|
345
|
+
? { "x-nomos-framework-routes": { paths: frameworkPaths } }
|
|
346
|
+
: {}),
|
|
299
347
|
};
|
|
300
348
|
}
|
|
@@ -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).
|
package/src/engine_entry.ts
CHANGED
|
@@ -931,11 +931,9 @@ export function registerEngine(config: EngineEntryConfig): RegisteredEngine {
|
|
|
931
931
|
* THE SELF-DESCRIBE VERB (G1) — the lump's OWN OpenAPI, derived from the SAME modules it
|
|
932
932
|
* dispatches. The kernel runs this over the FOLDED law (the lump IS `nomos:executable:
|
|
933
933
|
* javascriptHex` in the package USDA in custody), so the workspace's API surface is a pure,
|
|
934
|
-
* deterministic function of installed law — NOT a host-staged blob.
|
|
935
|
-
*
|
|
936
|
-
*
|
|
937
|
-
* sums/mins/maxes/spatials/workspaceInvariants from `config.openapiDecls`), `generateOpenApi
|
|
938
|
-
* Fragment`, then `mergeOpenApiFragments` — the EXACT calls compile_package_main makes.
|
|
934
|
+
* deterministic function of installed law — NOT a host-staged blob. Its public `paths` reproduce
|
|
935
|
+
* the `<name>.openapi.json` the compiler wrote; describe also carries a private, law-derived
|
|
936
|
+
* framework routing manifest which the host consumes internally and never publishes.
|
|
939
937
|
* `opts.lawHash`/`opts.title` are injected by the kernel at describe-time (the version stamp +
|
|
940
938
|
* package title), matching the compiler's `domainHash`/`"<name> (nomos package)"`.
|
|
941
939
|
*/
|
|
@@ -969,7 +967,10 @@ export function registerEngine(config: EngineEntryConfig): RegisteredEngine {
|
|
|
969
967
|
? { workspaceInvariants: [...decls.workspaceInvariants] }
|
|
970
968
|
: {}),
|
|
971
969
|
} as unknown as DomainModule;
|
|
972
|
-
fragments.push(generateOpenApiFragment(module,
|
|
970
|
+
fragments.push(generateOpenApiFragment(module, {
|
|
971
|
+
...(lawHash !== undefined ? { lawHash } : {}),
|
|
972
|
+
includeFrameworkRoutes: true,
|
|
973
|
+
}));
|
|
973
974
|
}
|
|
974
975
|
const title = opts?.title ?? config.packageTitle;
|
|
975
976
|
return mergeOpenApiFragments(fragments, {
|
|
@@ -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
|