@githolon/dsl 0.100.4 → 0.100.5
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_ensure.ts +16 -0
- package/src/build_package.ts +11 -1
- package/src/framework/birth_meter.ts +82 -0
- package/src/index.ts +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.5",
|
|
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",
|
|
@@ -32,8 +32,13 @@ import {
|
|
|
32
32
|
} from "./birth_meter.js";
|
|
33
33
|
import {
|
|
34
34
|
reclaimChild,
|
|
35
|
+
reclaimChildAsAdmin,
|
|
36
|
+
reclaimChildAsAdminRequiresRetired,
|
|
35
37
|
reclaimChildRequiresRetired,
|
|
36
38
|
retireChild,
|
|
39
|
+
retireChildAsAdmin,
|
|
40
|
+
retireChildAsAdminNotReclaimed,
|
|
41
|
+
retireChildAsAdminRequiresBirth,
|
|
37
42
|
retireChildRequiresBirth,
|
|
38
43
|
retireChildNotReclaimed,
|
|
39
44
|
} from "./framework/birth_meter.js";
|
|
@@ -82,8 +87,19 @@ export function withBirthMeterExports<M extends Record<string, unknown>>(mod: M)
|
|
|
82
87
|
if (!("BirthGrant" in out)) out["BirthGrant"] = presented(BirthGrant);
|
|
83
88
|
if (!("retireChild" in out)) out["retireChild"] = presented(retireChild);
|
|
84
89
|
if (!("reclaimChild" in out)) out["reclaimChild"] = presented(reclaimChild);
|
|
90
|
+
if (!("retireChildAsAdmin" in out)) out["retireChildAsAdmin"] = presented(retireChildAsAdmin);
|
|
91
|
+
if (!("reclaimChildAsAdmin" in out)) out["reclaimChildAsAdmin"] = presented(reclaimChildAsAdmin);
|
|
85
92
|
if (!("reclaimChildRequiresRetired" in out)) out["reclaimChildRequiresRetired"] = reclaimChildRequiresRetired;
|
|
93
|
+
if (!("reclaimChildAsAdminRequiresRetired" in out)) {
|
|
94
|
+
out["reclaimChildAsAdminRequiresRetired"] = reclaimChildAsAdminRequiresRetired;
|
|
95
|
+
}
|
|
86
96
|
if (!("retireChildRequiresBirth" in out)) out["retireChildRequiresBirth"] = retireChildRequiresBirth;
|
|
87
97
|
if (!("retireChildNotReclaimed" in out)) out["retireChildNotReclaimed"] = retireChildNotReclaimed;
|
|
98
|
+
if (!("retireChildAsAdminRequiresBirth" in out)) {
|
|
99
|
+
out["retireChildAsAdminRequiresBirth"] = retireChildAsAdminRequiresBirth;
|
|
100
|
+
}
|
|
101
|
+
if (!("retireChildAsAdminNotReclaimed" in out)) {
|
|
102
|
+
out["retireChildAsAdminNotReclaimed"] = retireChildAsAdminNotReclaimed;
|
|
103
|
+
}
|
|
88
104
|
return out as M;
|
|
89
105
|
}
|
package/src/build_package.ts
CHANGED
|
@@ -82,7 +82,10 @@ import { assertIdOfLint } from "./idof_lint.js";
|
|
|
82
82
|
import { assertSurfacedResolvers } from "./surfaced_lint.js";
|
|
83
83
|
import { expandBusinessBirthExports } from "./business_birth.js";
|
|
84
84
|
import {
|
|
85
|
+
reclaimChildAsAdminRequiresRetired,
|
|
85
86
|
reclaimChildRequiresRetired,
|
|
87
|
+
retireChildAsAdminNotReclaimed,
|
|
88
|
+
retireChildAsAdminRequiresBirth,
|
|
86
89
|
retireChildNotReclaimed,
|
|
87
90
|
retireChildRequiresBirth,
|
|
88
91
|
} from "./framework/birth_meter.js";
|
|
@@ -316,7 +319,14 @@ export function composeDomainModule(spec: DomainModuleSpec): DomainModule {
|
|
|
316
319
|
const workspaceInvariants = [...(spec.workspaceInvariants ?? [])];
|
|
317
320
|
if (declaresBirth) {
|
|
318
321
|
const haveInvariant = new Set(workspaceInvariants.map((w) => w.id));
|
|
319
|
-
for (const lifecycle of [
|
|
322
|
+
for (const lifecycle of [
|
|
323
|
+
reclaimChildRequiresRetired,
|
|
324
|
+
reclaimChildAsAdminRequiresRetired,
|
|
325
|
+
retireChildNotReclaimed,
|
|
326
|
+
retireChildAsAdminNotReclaimed,
|
|
327
|
+
retireChildRequiresBirth,
|
|
328
|
+
retireChildAsAdminRequiresBirth,
|
|
329
|
+
]) {
|
|
320
330
|
if (!haveInvariant.has(lifecycle.id)) workspaceInvariants.push(lifecycle);
|
|
321
331
|
}
|
|
322
332
|
}
|
|
@@ -86,6 +86,32 @@ export const retireChild = directive("retireChild")
|
|
|
86
86
|
];
|
|
87
87
|
});
|
|
88
88
|
|
|
89
|
+
/**
|
|
90
|
+
* The operator half of the same retirement action. This is deliberately a separate, framework-internal
|
|
91
|
+
* directive because the relation gate is structural: creator recovery and platform administration have
|
|
92
|
+
* different authority proofs even though the client presents one `retire` action and both write the same
|
|
93
|
+
* lifecycle fact. A broken creator key must never make a malformed child impossible to converge.
|
|
94
|
+
*/
|
|
95
|
+
export const retireChildAsAdmin = directive("retireChildAsAdmin")
|
|
96
|
+
.ensures(NomosChildLifecycle)
|
|
97
|
+
.payload(z.object({
|
|
98
|
+
child: z.string().min(1),
|
|
99
|
+
principal: z.string().min(1),
|
|
100
|
+
retiredBy: z.string().min(1),
|
|
101
|
+
retiredAt: z.number().int().nonnegative(),
|
|
102
|
+
}))
|
|
103
|
+
.plan((p) => {
|
|
104
|
+
const rec = instance(NomosChildLifecycle, childLifecycleId(p.child));
|
|
105
|
+
return [
|
|
106
|
+
set(rec, "child", p.child),
|
|
107
|
+
set(rec, "principal", p.principal),
|
|
108
|
+
set(rec, "status", "retired"),
|
|
109
|
+
set(rec, "retiredBy", p.retiredBy),
|
|
110
|
+
set(rec, "retiredAt", p.retiredAt),
|
|
111
|
+
];
|
|
112
|
+
})
|
|
113
|
+
.requires("admin");
|
|
114
|
+
|
|
89
115
|
export const reclaimChild = directive("reclaimChild")
|
|
90
116
|
.ensures(NomosChildLifecycle)
|
|
91
117
|
.payload(z.object({
|
|
@@ -102,6 +128,24 @@ export const reclaimChild = directive("reclaimChild")
|
|
|
102
128
|
];
|
|
103
129
|
});
|
|
104
130
|
|
|
131
|
+
/** Admin-gated recovery half of {@link reclaimChild}; hidden behind the client's one reclaim action. */
|
|
132
|
+
export const reclaimChildAsAdmin = directive("reclaimChildAsAdmin")
|
|
133
|
+
.ensures(NomosChildLifecycle)
|
|
134
|
+
.payload(z.object({
|
|
135
|
+
child: z.string().min(1),
|
|
136
|
+
reclaimedBy: z.string().min(1),
|
|
137
|
+
reclaimedAt: z.number().int().nonnegative(),
|
|
138
|
+
}))
|
|
139
|
+
.plan((p) => {
|
|
140
|
+
const rec = instance(NomosChildLifecycle, childLifecycleId(p.child));
|
|
141
|
+
return [
|
|
142
|
+
set(rec, "status", "reclaimed"),
|
|
143
|
+
set(rec, "reclaimedBy", p.reclaimedBy),
|
|
144
|
+
set(rec, "reclaimedAt", p.reclaimedAt),
|
|
145
|
+
];
|
|
146
|
+
})
|
|
147
|
+
.requires("admin");
|
|
148
|
+
|
|
105
149
|
/** Reclaim is two-phase; a child must first be explicitly withdrawn. */
|
|
106
150
|
export const reclaimChildRequiresRetired = workspaceInvariant("ReclaimChildRequiresRetired")
|
|
107
151
|
.on("reclaimChild")
|
|
@@ -115,6 +159,18 @@ export const reclaimChildRequiresRetired = workspaceInvariant("ReclaimChildRequi
|
|
|
115
159
|
: { reject: "reclaim-child-requires-retired" },
|
|
116
160
|
);
|
|
117
161
|
|
|
162
|
+
/** Operator recovery has the same two-phase law, without depending on the broken creator credential. */
|
|
163
|
+
export const reclaimChildAsAdminRequiresRetired = workspaceInvariant("ReclaimChildAsAdminRequiresRetired")
|
|
164
|
+
.on("reclaimChildAsAdmin")
|
|
165
|
+
.reads((ctx) => [
|
|
166
|
+
refAs("record", "NomosChildLifecycle", "pre:" + childLifecycleId(String(ctx.intent.child ?? ""))),
|
|
167
|
+
])
|
|
168
|
+
.assert(({ record }) =>
|
|
169
|
+
(record as { status?: string } | undefined)?.status === "retired"
|
|
170
|
+
? { accept: true }
|
|
171
|
+
: { reject: "reclaim-child-requires-retired" },
|
|
172
|
+
);
|
|
173
|
+
|
|
118
174
|
/** Reclaimed is permanent: retirement cannot move a child back out of the terminal state. */
|
|
119
175
|
export const retireChildNotReclaimed = workspaceInvariant("RetireChildNotReclaimed")
|
|
120
176
|
.on("retireChild")
|
|
@@ -127,6 +183,18 @@ export const retireChildNotReclaimed = workspaceInvariant("RetireChildNotReclaim
|
|
|
127
183
|
: { accept: true },
|
|
128
184
|
);
|
|
129
185
|
|
|
186
|
+
/** Reclaimed is terminal for operator recovery too. */
|
|
187
|
+
export const retireChildAsAdminNotReclaimed = workspaceInvariant("RetireChildAsAdminNotReclaimed")
|
|
188
|
+
.on("retireChildAsAdmin")
|
|
189
|
+
.reads((ctx) => [
|
|
190
|
+
refAs("record", "NomosChildLifecycle", "pre:" + childLifecycleId(String(ctx.intent.child ?? ""))),
|
|
191
|
+
])
|
|
192
|
+
.assert(({ record }) =>
|
|
193
|
+
(record as { status?: string } | undefined)?.status === "reclaimed"
|
|
194
|
+
? { reject: "reclaimed-child-is-terminal" }
|
|
195
|
+
: { accept: true },
|
|
196
|
+
);
|
|
197
|
+
|
|
130
198
|
/** A lifecycle cannot mint authority for arbitrary custody: retirement must name its exact birth witness. */
|
|
131
199
|
export const retireChildRequiresBirth = workspaceInvariant("RetireChildRequiresBirth")
|
|
132
200
|
.on("retireChild")
|
|
@@ -143,6 +211,20 @@ export const retireChildRequiresBirth = workspaceInvariant("RetireChildRequiresB
|
|
|
143
211
|
: { reject: "retire-child-requires-exact-birth-record" };
|
|
144
212
|
});
|
|
145
213
|
|
|
214
|
+
/** Admin recovery may act for the recorded creator, but still cannot invent or retarget custody. */
|
|
215
|
+
export const retireChildAsAdminRequiresBirth = workspaceInvariant("RetireChildAsAdminRequiresBirth")
|
|
216
|
+
.on("retireChildAsAdmin")
|
|
217
|
+
.reads((ctx) => [
|
|
218
|
+
refAs("birth", "NomosBirthRecord", "pre:" + birthRecordId(String(ctx.intent.child ?? ""))),
|
|
219
|
+
])
|
|
220
|
+
.assert(({ birth }, ctx) => {
|
|
221
|
+
const record = birth as { child?: string; principal?: string } | undefined;
|
|
222
|
+
const intent = ctx?.intent as { child?: string; principal?: string } | undefined;
|
|
223
|
+
return record?.child === intent?.child && record?.principal === intent?.principal
|
|
224
|
+
? { accept: true }
|
|
225
|
+
: { reject: "retire-child-requires-exact-birth-record" };
|
|
226
|
+
});
|
|
227
|
+
|
|
146
228
|
function sameLifecyclePrincipal(recorded: string | undefined, author: string | undefined): boolean {
|
|
147
229
|
const normalize = (s: string | undefined): string => s?.startsWith("user:") ? s.slice(5) : (s ?? "");
|
|
148
230
|
return normalize(recorded) !== "" && normalize(recorded) === normalize(author);
|
package/src/index.ts
CHANGED
|
@@ -477,10 +477,15 @@ export {
|
|
|
477
477
|
seedBirthPolicy,
|
|
478
478
|
grantBirthAllowance,
|
|
479
479
|
retireChild,
|
|
480
|
+
retireChildAsAdmin,
|
|
480
481
|
reclaimChild,
|
|
482
|
+
reclaimChildAsAdmin,
|
|
481
483
|
reclaimChildRequiresRetired,
|
|
484
|
+
reclaimChildAsAdminRequiresRetired,
|
|
482
485
|
retireChildRequiresBirth,
|
|
486
|
+
retireChildAsAdminRequiresBirth,
|
|
483
487
|
retireChildNotReclaimed,
|
|
488
|
+
retireChildAsAdminNotReclaimed,
|
|
484
489
|
} from "./framework/birth_meter.js";
|
|
485
490
|
// THE PLATFORM FOUNDING-ADMIN BOOTSTRAP (the platform analog of seedHomeCreator) — a tenant-importable
|
|
486
491
|
// factory that seats a tenant-named governance relation for a write-once, author-bound founder at genesis.
|