@githolon/dsl 0.103.4 → 0.103.6
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 +8 -0
- package/dart/lib/src/runner/node-entry.mjs +333 -117
- package/dart/lib/src/runner/offline-install.mjs +173 -67
- package/dart/lib/src/session_bound.dart +111 -2
- package/dart/lib/src/test_harness.dart +51 -41
- package/dart/lib/src/webview_bridge.dart +714 -60
- package/dart/pubspec.lock +1 -1
- package/dart/pubspec.yaml +2 -2
- package/dart/test/acquire_session_test.dart +62 -5
- package/dart/test/cloud_bound_topology_test.dart +59 -38
- package/dart/test/session_bound_test.dart +25 -10
- package/dart/test/webview_bridge_test.dart +326 -7
- package/dart_types/CHANGELOG.md +5 -0
- package/dart_types/lib/nomos_types.dart +1 -0
- package/dart_types/lib/src/git_object_id.dart +37 -0
- package/dart_types/pubspec.yaml +1 -1
- package/package.json +1 -1
- package/runtime/generated-application-host.js +347 -113
- package/src/births.ts +16 -1
- package/src/business_birth.ts +55 -34
- package/src/codegen_dart.ts +68 -12
- package/src/codegen_dart_application.ts +114 -19
- package/src/codegen_dart_client.ts +149 -6
- package/src/codegen_proof.ts +16 -9
- package/src/codegen_provider_dart.ts +5 -0
- package/src/codegen_ts.ts +115 -19
- package/src/compile_package_main.ts +159 -43
- package/src/fields.ts +43 -0
- package/src/framework/domain_lifecycle.ts +108 -88
- package/src/framework/genesis.ts +2 -1
- package/src/framework/home.ts +6 -43
- package/src/framework/workspaces.ts +42 -25
- package/src/idof_lint.ts +65 -51
- package/src/index.ts +21 -1
- package/src/intent.ts +11 -1
- package/src/law_graph.ts +81 -11
- package/src/relation_schema.ts +18 -0
- package/src/shape_compat.ts +3 -0
- package/src/stable_ids_for_module.ts +7 -1
- package/src/value_object.ts +40 -0
|
@@ -20,10 +20,16 @@
|
|
|
20
20
|
import { z } from "zod";
|
|
21
21
|
import { aggregate, instance } from "../aggregate.js";
|
|
22
22
|
import { Lww, MapOf } from "../drivers.js";
|
|
23
|
-
import {
|
|
23
|
+
import {
|
|
24
|
+
attributionField,
|
|
25
|
+
gitObjectId,
|
|
26
|
+
sha256Digest,
|
|
27
|
+
t,
|
|
28
|
+
type Sha256Digest,
|
|
29
|
+
} from "../fields.js";
|
|
24
30
|
import { directive } from "../directive.js";
|
|
25
31
|
import { set, withMarker, addToSet, type PlannedOp } from "../ops.js";
|
|
26
|
-
import { key, relationType, self, user } from "../relation_schema.js";
|
|
32
|
+
import { key, relationRef, relationType, self, user } from "../relation_schema.js";
|
|
27
33
|
|
|
28
34
|
/** The two authenticated capabilities which may execute a lifecycle install.
|
|
29
35
|
* Callers choose a role; the serialized USDA route is an implementation detail. */
|
|
@@ -31,7 +37,31 @@ export enum DomainInstallExecutor {
|
|
|
31
37
|
StageZero = "bootstrap",
|
|
32
38
|
LifecycleController = "nomos",
|
|
33
39
|
}
|
|
34
|
-
|
|
40
|
+
|
|
41
|
+
/** One typed vocabulary shared by declaration, gate, genesis seat and generated clients. */
|
|
42
|
+
export const MODEL_LIFECYCLE_AUTHORITY = relationRef("installDomain");
|
|
43
|
+
export const DOMAIN_LIFECYCLE_DIRECTIVES = {
|
|
44
|
+
Install: "installDomain",
|
|
45
|
+
Stage: "stageDomain",
|
|
46
|
+
Promote: "promoteDomain",
|
|
47
|
+
Rollback: "rollbackDomain",
|
|
48
|
+
} as const;
|
|
49
|
+
export type DomainLifecycleDirectiveId =
|
|
50
|
+
(typeof DOMAIN_LIFECYCLE_DIRECTIVES)[keyof typeof DOMAIN_LIFECYCLE_DIRECTIVES];
|
|
51
|
+
export const INSTALL_DOMAIN_DIRECTIVE = DOMAIN_LIFECYCLE_DIRECTIVES.Install;
|
|
52
|
+
|
|
53
|
+
/** A symbolic reference resolved by the kernel against the parent's active law frontier. */
|
|
54
|
+
export type CurrentLawReference = string & { readonly __nomosCurrentLawReference: unique symbol };
|
|
55
|
+
export type LawClosureReference = Sha256Digest | CurrentLawReference;
|
|
56
|
+
|
|
57
|
+
const CURRENT_LAW_PREFIX = "nomos:current-law:";
|
|
58
|
+
|
|
59
|
+
/** Construct the only symbolic law reference admitted in a child genesis recipe. */
|
|
60
|
+
export function currentLawReference(domain: string): CurrentLawReference {
|
|
61
|
+
const name = domain.trim();
|
|
62
|
+
if (name.length === 0) throw new TypeError("a current-law reference requires a non-empty domain");
|
|
63
|
+
return `${CURRENT_LAW_PREFIX}${name}` as CurrentLawReference;
|
|
64
|
+
}
|
|
35
65
|
|
|
36
66
|
/** One complete, contract-shaped law-install call in a child genesis sequence. Keeping this
|
|
37
67
|
* constructor beside the directive schema makes omission of required install provenance a type
|
|
@@ -41,16 +71,16 @@ export interface DomainInstallGenesisOffer extends Readonly<Record<string, unkno
|
|
|
41
71
|
readonly directiveId: typeof INSTALL_DOMAIN_DIRECTIVE;
|
|
42
72
|
readonly actor: string;
|
|
43
73
|
readonly payload: {
|
|
44
|
-
readonly domainHash:
|
|
74
|
+
readonly domainHash: LawClosureReference;
|
|
45
75
|
readonly installedBy: string;
|
|
46
|
-
readonly lawClosureIdentity:
|
|
76
|
+
readonly lawClosureIdentity: LawClosureReference;
|
|
47
77
|
};
|
|
48
|
-
readonly domainHash:
|
|
78
|
+
readonly domainHash: LawClosureReference;
|
|
49
79
|
}
|
|
50
80
|
|
|
51
81
|
export function domainInstallGenesisOffer(
|
|
52
82
|
domain: DomainInstallExecutor,
|
|
53
|
-
domainHash:
|
|
83
|
+
domainHash: LawClosureReference,
|
|
54
84
|
actor: string,
|
|
55
85
|
): DomainInstallGenesisOffer {
|
|
56
86
|
return {
|
|
@@ -67,32 +97,42 @@ export function domainInstallGenesisOffer(
|
|
|
67
97
|
* delegation remains an ordinary, replayable Zanzibar edge. */
|
|
68
98
|
export const modelLifecycleRelations = relationType("workspace")
|
|
69
99
|
.asGlobal()
|
|
70
|
-
.relation(
|
|
100
|
+
.relation(MODEL_LIFECYCLE_AUTHORITY, { rewrite: self(), allow: [user(), key()] });
|
|
101
|
+
|
|
102
|
+
/** The lifecycle state machine is a framework type, not a bag of coincidentally matching strings. */
|
|
103
|
+
export enum DomainInstallationPhase {
|
|
104
|
+
Pending = "Pending",
|
|
105
|
+
Active = "Active",
|
|
106
|
+
Disabled = "Disabled",
|
|
107
|
+
Superseded = "Superseded",
|
|
108
|
+
Retiring = "Retiring",
|
|
109
|
+
Retired = "Retired",
|
|
110
|
+
Failed = "Failed",
|
|
111
|
+
}
|
|
71
112
|
|
|
72
113
|
export const DOMAIN_INSTALLATION_PHASES = [
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
114
|
+
DomainInstallationPhase.Pending,
|
|
115
|
+
DomainInstallationPhase.Active,
|
|
116
|
+
DomainInstallationPhase.Disabled,
|
|
117
|
+
DomainInstallationPhase.Superseded,
|
|
118
|
+
DomainInstallationPhase.Retiring,
|
|
119
|
+
DomainInstallationPhase.Retired,
|
|
120
|
+
DomainInstallationPhase.Failed,
|
|
80
121
|
] as const;
|
|
81
|
-
export type DomainInstallationPhase = (typeof DOMAIN_INSTALLATION_PHASES)[number];
|
|
82
122
|
|
|
83
123
|
export const DomainInstallation = aggregate("DomainInstallation", {
|
|
84
|
-
"spec.domainHash": t.
|
|
85
|
-
"spec.packageHash": t.
|
|
124
|
+
"spec.domainHash": t.sha256Digest().merge(Lww),
|
|
125
|
+
"spec.packageHash": t.sha256Digest().merge(Lww),
|
|
86
126
|
"spec.authorityScope": t.string().merge(Lww),
|
|
87
127
|
"spec.installedBy": t.string().merge(Lww),
|
|
88
|
-
"spec.lawClosureIdentity": t.
|
|
128
|
+
"spec.lawClosureIdentity": t.sha256Digest().merge(Lww),
|
|
89
129
|
"spec.dependencies": t.set(t.string()),
|
|
90
|
-
"spec.supersedes": t.
|
|
130
|
+
"spec.supersedes": t.sha256Digest().merge(Lww).optional(),
|
|
91
131
|
"spec.finalizers": t.set(t.string()),
|
|
92
132
|
"metadata.generation": t.int().merge(Lww),
|
|
93
133
|
"status.phase": t.enum(DOMAIN_INSTALLATION_PHASES).merge(Lww),
|
|
94
134
|
"status.observedGeneration": t.int().merge(Lww),
|
|
95
|
-
"status.supersededBy": t.
|
|
135
|
+
"status.supersededBy": t.sha256Digest().merge(Lww).optional(),
|
|
96
136
|
"status.conditions": t.map(t.json()).merge(MapOf(Lww)),
|
|
97
137
|
}).public();
|
|
98
138
|
|
|
@@ -106,13 +146,13 @@ export const RELEASE_RECEIPT_AGGREGATE = "nomos_internal_ReleaseReceipt_6f0d3a";
|
|
|
106
146
|
export const RELEASE_RECEIPT_PREFIX = "nomos-release-receipt:";
|
|
107
147
|
|
|
108
148
|
export const ReleaseReceipt = aggregate(RELEASE_RECEIPT_AGGREGATE, {
|
|
109
|
-
candidateHash: t.
|
|
110
|
-
fromHash: t.
|
|
149
|
+
candidateHash: t.sha256Digest().merge(Lww),
|
|
150
|
+
fromHash: t.sha256Digest().merge(Lww).optional(),
|
|
111
151
|
domain: t.string().merge(Lww),
|
|
112
|
-
atHash: t.
|
|
113
|
-
atStateRoot: t.
|
|
114
|
-
historyHash: t.
|
|
115
|
-
reportHash: t.
|
|
152
|
+
atHash: t.gitObjectId().merge(Lww),
|
|
153
|
+
atStateRoot: t.gitObjectId().merge(Lww),
|
|
154
|
+
historyHash: t.sha256Digest().merge(Lww),
|
|
155
|
+
reportHash: t.sha256Digest().merge(Lww),
|
|
116
156
|
reportJson: t.string().merge(Lww),
|
|
117
157
|
complete: t.bool(),
|
|
118
158
|
evolveOk: t.bool(),
|
|
@@ -121,26 +161,26 @@ export const ReleaseReceipt = aggregate(RELEASE_RECEIPT_AGGREGATE, {
|
|
|
121
161
|
replayedIntents: t.int().merge(Lww),
|
|
122
162
|
candidateIntents: t.int().merge(Lww),
|
|
123
163
|
divergenceCount: t.int().merge(Lww),
|
|
124
|
-
baselineRoot: t.
|
|
125
|
-
candidateRoot: t.
|
|
164
|
+
baselineRoot: t.sha256Digest().merge(Lww),
|
|
165
|
+
candidateRoot: t.sha256Digest().merge(Lww),
|
|
126
166
|
acceptanceReason: t.string().merge(Lww).optional(),
|
|
127
167
|
acceptedBy: attributionField(t.string().merge(Lww)),
|
|
128
168
|
acceptedAt: t.int().merge(Lww),
|
|
129
169
|
}).public();
|
|
130
170
|
|
|
131
|
-
export function domainInstallationAggregateId(domainHash:
|
|
171
|
+
export function domainInstallationAggregateId(domainHash: Sha256Digest): string {
|
|
132
172
|
return `domain-installation:${domainHash}`;
|
|
133
173
|
}
|
|
134
174
|
|
|
135
|
-
function domainInstallationInstance(domainHash:
|
|
175
|
+
function domainInstallationInstance(domainHash: Sha256Digest) {
|
|
136
176
|
return instance(DomainInstallation, domainInstallationAggregateId(domainHash));
|
|
137
177
|
}
|
|
138
178
|
|
|
139
|
-
export function releaseReceiptAggregateId(reportHash:
|
|
179
|
+
export function releaseReceiptAggregateId(reportHash: Sha256Digest): string {
|
|
140
180
|
return `${RELEASE_RECEIPT_PREFIX}${reportHash}`;
|
|
141
181
|
}
|
|
142
182
|
|
|
143
|
-
function releaseReceiptInstance(reportHash:
|
|
183
|
+
function releaseReceiptInstance(reportHash: Sha256Digest) {
|
|
144
184
|
return instance(ReleaseReceipt, releaseReceiptAggregateId(reportHash));
|
|
145
185
|
}
|
|
146
186
|
|
|
@@ -151,13 +191,13 @@ const dispositions = z.object({
|
|
|
151
191
|
ackBy: z.string().optional(),
|
|
152
192
|
}).optional();
|
|
153
193
|
|
|
154
|
-
const lowerHex64 =
|
|
155
|
-
const gitOid =
|
|
194
|
+
const lowerHex64 = sha256Digest();
|
|
195
|
+
const gitOid = gitObjectId();
|
|
156
196
|
const preflightReceipt = z.object({
|
|
157
197
|
candidateHash: lowerHex64,
|
|
158
198
|
domain: z.string().min(1),
|
|
159
199
|
atHash: gitOid,
|
|
160
|
-
atStateRoot:
|
|
200
|
+
atStateRoot: gitOid,
|
|
161
201
|
historyHash: lowerHex64,
|
|
162
202
|
reportHash: lowerHex64,
|
|
163
203
|
reportJson: z.string().min(2),
|
|
@@ -181,36 +221,29 @@ const preflightReceipt = z.object({
|
|
|
181
221
|
}
|
|
182
222
|
});
|
|
183
223
|
|
|
184
|
-
const
|
|
224
|
+
const installPackagePayload = {
|
|
185
225
|
domainHash: lowerHex64,
|
|
186
226
|
installedBy: z.string(),
|
|
187
227
|
lawClosureIdentity: lowerHex64,
|
|
188
228
|
authorityScope: z.string().default("workspace/root"),
|
|
189
229
|
// Omission means the empty set; keep that in the plan instead of encoding a composite default
|
|
190
230
|
// into canonical USDA merely to save `?? []` at runtime.
|
|
191
|
-
dependencies: z.array(
|
|
192
|
-
|
|
193
|
-
finalizers: z.array(z.string()).optional(),
|
|
231
|
+
dependencies: z.array(sha256Digest()).optional(),
|
|
232
|
+
finalizers: z.array(sha256Digest()).optional(),
|
|
194
233
|
};
|
|
195
234
|
|
|
196
|
-
|
|
235
|
+
const stagedPackagePayload = {
|
|
236
|
+
...installPackagePayload,
|
|
237
|
+
supersedes: sha256Digest().optional(),
|
|
238
|
+
};
|
|
239
|
+
|
|
240
|
+
export const installDomain = directive(DOMAIN_LIFECYCLE_DIRECTIVES.Install)
|
|
197
241
|
.ensures(DomainInstallation)
|
|
198
|
-
.
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
// frontier (`Superseded`, or `Retired` when `retireReplaced`) and gets the reverse
|
|
204
|
-
// `status.supersededBy` edge to this install. Absent ⇒ install-BESIDE (backward-compat:
|
|
205
|
-
// byte-identical ops to the pre-lifecycle plan). `replaces` also sets `spec.supersedes`.
|
|
206
|
-
replaces: z.string().optional(),
|
|
207
|
-
retireReplaced: z.boolean().default(false),
|
|
208
|
-
dispositions,
|
|
209
|
-
}).refine((p) => p.replaces === undefined || p.replaces !== p.domainHash, {
|
|
210
|
-
message: "a domain package cannot replace itself",
|
|
211
|
-
path: ["replaces"],
|
|
212
|
-
}),
|
|
213
|
-
)
|
|
242
|
+
// Installation adds law for domain keys that are not currently served. Replacement is a
|
|
243
|
+
// different operation with different evidence: stageDomain followed by promoteDomain. Keeping
|
|
244
|
+
// predecessor/retirement fields out of this type makes bypassing that state machine impossible
|
|
245
|
+
// for generated clients instead of relying on callers to remember a convention.
|
|
246
|
+
.payload(z.object({ ...installPackagePayload }).strict())
|
|
214
247
|
.plan((p): PlannedOp[] => {
|
|
215
248
|
const install = domainInstallationInstance(p.domainHash);
|
|
216
249
|
const ops: PlannedOp[] = [
|
|
@@ -220,38 +253,25 @@ export const installDomain = directive("installDomain")
|
|
|
220
253
|
set(install, "spec.installedBy", p.installedBy),
|
|
221
254
|
set(install, "spec.lawClosureIdentity", p.lawClosureIdentity),
|
|
222
255
|
set(install, "metadata.generation", 1),
|
|
223
|
-
set(install, "status.phase",
|
|
256
|
+
set(install, "status.phase", DomainInstallationPhase.Active),
|
|
224
257
|
set(install, "status.observedGeneration", 1),
|
|
225
258
|
];
|
|
226
259
|
if ((p.dependencies ?? []).length > 0) {
|
|
227
260
|
ops.push(addToSet(install, "spec.dependencies", p.dependencies ?? []));
|
|
228
261
|
}
|
|
229
|
-
// The predecessor back-link: `supersedes` (explicit) or `replaces` (the actionable form).
|
|
230
|
-
const supersedes = p.supersedes ?? p.replaces;
|
|
231
|
-
if (supersedes !== undefined) {
|
|
232
|
-
ops.push(set(install, "spec.supersedes", supersedes));
|
|
233
|
-
}
|
|
234
|
-
// The transition on the PRIOR install: drop it from the served frontier + point it here.
|
|
235
|
-
if (p.replaces !== undefined) {
|
|
236
|
-
const replaced = domainInstallationInstance(p.replaces);
|
|
237
|
-
ops.push(
|
|
238
|
-
set(replaced, "status.phase", p.retireReplaced ? "Retired" : "Superseded"),
|
|
239
|
-
);
|
|
240
|
-
ops.push(set(replaced, "status.supersededBy", p.domainHash));
|
|
241
|
-
}
|
|
242
262
|
if ((p.finalizers ?? []).length > 0) {
|
|
243
263
|
ops.push(addToSet(install, "spec.finalizers", p.finalizers ?? []));
|
|
244
264
|
}
|
|
245
265
|
return ops;
|
|
246
266
|
})
|
|
247
|
-
.requires(
|
|
267
|
+
.requires(MODEL_LIFECYCLE_AUTHORITY);
|
|
248
268
|
|
|
249
269
|
/** Store candidate law in the ledger without changing the served law frontier. The
|
|
250
270
|
* kernel content-verifies the package and lifecycle pair, while the Pending phase is
|
|
251
271
|
* deliberately invisible to dispatch, reads and replay's current-law map. */
|
|
252
|
-
export const stageDomain = directive(
|
|
272
|
+
export const stageDomain = directive(DOMAIN_LIFECYCLE_DIRECTIVES.Stage)
|
|
253
273
|
.ensures(DomainInstallation)
|
|
254
|
-
.payload(z.object({ ...
|
|
274
|
+
.payload(z.object({ ...stagedPackagePayload }).refine(
|
|
255
275
|
(p) => p.supersedes === undefined || p.supersedes !== p.domainHash,
|
|
256
276
|
{ message: "a staged domain package cannot supersede itself", path: ["supersedes"] },
|
|
257
277
|
))
|
|
@@ -264,7 +284,7 @@ export const stageDomain = directive("stageDomain")
|
|
|
264
284
|
set(install, "spec.installedBy", p.installedBy),
|
|
265
285
|
set(install, "spec.lawClosureIdentity", p.lawClosureIdentity),
|
|
266
286
|
set(install, "metadata.generation", 1),
|
|
267
|
-
set(install, "status.phase",
|
|
287
|
+
set(install, "status.phase", DomainInstallationPhase.Pending),
|
|
268
288
|
set(install, "status.observedGeneration", 0),
|
|
269
289
|
];
|
|
270
290
|
if ((p.dependencies ?? []).length > 0) ops.push(addToSet(install, "spec.dependencies", p.dependencies ?? []));
|
|
@@ -272,17 +292,17 @@ export const stageDomain = directive("stageDomain")
|
|
|
272
292
|
if ((p.finalizers ?? []).length > 0) ops.push(addToSet(install, "spec.finalizers", p.finalizers ?? []));
|
|
273
293
|
return ops;
|
|
274
294
|
})
|
|
275
|
-
.requires(
|
|
295
|
+
.requires(MODEL_LIFECYCLE_AUTHORITY);
|
|
276
296
|
|
|
277
297
|
/** Make a staged package current. The package bytes are already committed; the kernel
|
|
278
298
|
* re-loads them from prefix state and runs the evolve gate on this phase transition.
|
|
279
299
|
* The prior version stays Active by default so gradual-rollout clients can still have
|
|
280
300
|
* their declared era checked; the newer activation stamp alone makes this law current. */
|
|
281
|
-
export const promoteDomain = directive(
|
|
301
|
+
export const promoteDomain = directive(DOMAIN_LIFECYCLE_DIRECTIVES.Promote)
|
|
282
302
|
.mutates(DomainInstallation)
|
|
283
303
|
.payload(z.object({
|
|
284
|
-
domainHash:
|
|
285
|
-
replaces:
|
|
304
|
+
domainHash: sha256Digest(),
|
|
305
|
+
replaces: sha256Digest().optional(),
|
|
286
306
|
retireReplaced: z.boolean().default(false),
|
|
287
307
|
dispositions,
|
|
288
308
|
preflight: preflightReceipt,
|
|
@@ -297,7 +317,7 @@ export const promoteDomain = directive("promoteDomain")
|
|
|
297
317
|
const promoted = domainInstallationInstance(p.domainHash);
|
|
298
318
|
const receipt = releaseReceiptInstance(p.preflight.reportHash);
|
|
299
319
|
const ops: PlannedOp[] = [
|
|
300
|
-
set(promoted, "status.phase",
|
|
320
|
+
set(promoted, "status.phase", DomainInstallationPhase.Active),
|
|
301
321
|
set(promoted, "status.observedGeneration", 1),
|
|
302
322
|
withMarker(set(receipt, "candidateHash", p.domainHash), "ensures"),
|
|
303
323
|
...(p.replaces !== undefined ? [set(receipt, "fromHash", p.replaces)] : []),
|
|
@@ -326,23 +346,23 @@ export const promoteDomain = directive("promoteDomain")
|
|
|
326
346
|
const replaced = domainInstallationInstance(p.replaces);
|
|
327
347
|
ops.push(set(promoted, "spec.supersedes", p.replaces));
|
|
328
348
|
if (p.retireReplaced) {
|
|
329
|
-
ops.push(set(replaced, "status.phase",
|
|
349
|
+
ops.push(set(replaced, "status.phase", DomainInstallationPhase.Retired));
|
|
330
350
|
ops.push(set(replaced, "status.supersededBy", p.domainHash));
|
|
331
351
|
}
|
|
332
352
|
}
|
|
333
353
|
return ops;
|
|
334
354
|
})
|
|
335
355
|
.alsoEnsures(ReleaseReceipt)
|
|
336
|
-
.requires(
|
|
356
|
+
.requires(MODEL_LIFECYCLE_AUTHORITY);
|
|
337
357
|
|
|
338
358
|
/** Rollback is a new governed transition, never a history-pointer rewind. The prior
|
|
339
359
|
* package is promoted from committed custody and the failed candidate becomes Disabled;
|
|
340
360
|
* the kernel applies the same evolve judgment in the reverse direction. */
|
|
341
|
-
export const rollbackDomain = directive(
|
|
361
|
+
export const rollbackDomain = directive(DOMAIN_LIFECYCLE_DIRECTIVES.Rollback)
|
|
342
362
|
.mutates(DomainInstallation)
|
|
343
363
|
.payload(z.object({
|
|
344
|
-
candidateHash:
|
|
345
|
-
priorHash:
|
|
364
|
+
candidateHash: sha256Digest(),
|
|
365
|
+
priorHash: sha256Digest(),
|
|
346
366
|
dispositions,
|
|
347
367
|
preflight: preflightReceipt,
|
|
348
368
|
}).refine((p) => p.candidateHash !== p.priorHash, {
|
|
@@ -360,9 +380,9 @@ export const rollbackDomain = directive("rollbackDomain")
|
|
|
360
380
|
const prior = domainInstallationInstance(p.priorHash);
|
|
361
381
|
const receipt = releaseReceiptInstance(p.preflight.reportHash);
|
|
362
382
|
return [
|
|
363
|
-
set(candidate, "status.phase",
|
|
383
|
+
set(candidate, "status.phase", DomainInstallationPhase.Disabled),
|
|
364
384
|
set(candidate, "status.supersededBy", p.priorHash),
|
|
365
|
-
set(prior, "status.phase",
|
|
385
|
+
set(prior, "status.phase", DomainInstallationPhase.Active),
|
|
366
386
|
set(prior, "status.observedGeneration", 1),
|
|
367
387
|
withMarker(set(receipt, "candidateHash", p.priorHash), "ensures"),
|
|
368
388
|
set(receipt, "fromHash", p.candidateHash),
|
|
@@ -389,4 +409,4 @@ export const rollbackDomain = directive("rollbackDomain")
|
|
|
389
409
|
];
|
|
390
410
|
})
|
|
391
411
|
.alsoEnsures(ReleaseReceipt)
|
|
392
|
-
.requires(
|
|
412
|
+
.requires(MODEL_LIFECYCLE_AUTHORITY);
|
package/src/framework/genesis.ts
CHANGED
|
@@ -34,6 +34,7 @@ import { Lww } from "../drivers.js";
|
|
|
34
34
|
import { directive } from "../directive.js";
|
|
35
35
|
import { set, type PlannedOp } from "../ops.js";
|
|
36
36
|
import { RelationTuple, writeTuple } from "./relations.js";
|
|
37
|
+
import { MODEL_LIFECYCLE_AUTHORITY } from "./domain_lifecycle.js";
|
|
37
38
|
|
|
38
39
|
/**
|
|
39
40
|
* Deterministic singleton id of the founding warrant — MUST equal
|
|
@@ -100,7 +101,7 @@ export const seedGenesis = directive("seedGenesis")
|
|
|
100
101
|
...writeGenesisWarrant(p),
|
|
101
102
|
...writeTuple({
|
|
102
103
|
object: "workspace:self",
|
|
103
|
-
relation:
|
|
104
|
+
relation: MODEL_LIFECYCLE_AUTHORITY,
|
|
104
105
|
subject: /^(user|key):/.test(p.principal) ? p.principal : `user:${p.principal}`,
|
|
105
106
|
grantedBy: p.principal,
|
|
106
107
|
grantedAt: p.bornAt,
|
package/src/framework/home.ts
CHANGED
|
@@ -27,9 +27,7 @@
|
|
|
27
27
|
import { z } from "zod";
|
|
28
28
|
import type { ApplicationHomeGenesis } from "../application_home_genesis.js";
|
|
29
29
|
import { aggregate, instance, OWNED_READ_RELATION } from "../aggregate.js";
|
|
30
|
-
|
|
31
|
-
const sha256Hex = z.string().regex(/^[0-9a-f]{64}$/, "expected lowercase sha256 hex (the owner's device pubkey hash)");
|
|
32
|
-
import { t } from "../fields.js";
|
|
30
|
+
import { sha256Digest, t } from "../fields.js";
|
|
33
31
|
import { Lww } from "../drivers.js";
|
|
34
32
|
import { directive } from "../directive.js";
|
|
35
33
|
import { ensure } from "../authoring.js";
|
|
@@ -319,27 +317,11 @@ export function birthReceivingGroupForDomain(
|
|
|
319
317
|
owner: z.string().min(1),
|
|
320
318
|
/** The bootstrap framework controller content hash — tenant-pinned at governance compile (the child
|
|
321
319
|
* installs it IN-CHAIN as intent 0; the kernel resolves the bytes by hash from the parent's custody). */
|
|
322
|
-
frameworkHash:
|
|
320
|
+
frameworkHash: sha256Digest(),
|
|
323
321
|
/** The HOME law package domainHash — tenant-pinned at governance compile (the child's second install). */
|
|
324
|
-
lawHash:
|
|
322
|
+
lawHash: sha256Digest(),
|
|
325
323
|
/** ISO-8601 birth time, caller-stamped (the timestamp doctrine — the plan reads no clock). */
|
|
326
324
|
bornAt: z.string().min(1),
|
|
327
|
-
/** THE OWNER'S DEVICE KEY HASH (sha256hex of the ed25519 public half — the SAME key `homeCert` is signed
|
|
328
|
-
* by and the `signer` relation enrols). The platform holds it from the user's onboarding and ENROLS it
|
|
329
|
-
* in the home's genesis, so the born home is SIGNER-READY: it can sign + birth its own children (estates,
|
|
330
|
-
* catalogues) from intent 0. WITHOUT this the home is born warranted + owned but signer-less — it can
|
|
331
|
-
* never act (the co2 estate-birth bug). Mirrors `buildGenesisChain`'s `enrollSigner` for a platform.
|
|
332
|
-
* REQUIRED — a payload without it refuses at compile/validation, not an hour into a live deploy with a
|
|
333
|
-
* bricked signer-less home. (The kernel injecting it from the verified cert's `parent_key` is the
|
|
334
|
-
* eventual generalization; until then the field is loud.) */
|
|
335
|
-
ownerKeyHash: sha256Hex,
|
|
336
|
-
/** THE DEVICE-SIGNED HOME CERT (JSON `executor::birth_cert::BirthCert`), signed CLIENT-SIDE by the
|
|
337
|
-
* owner's device key `S_U` (the SAME key `ownerKeyHash` hashes + the `signer` relation enrols). The
|
|
338
|
-
* kernel's birth offer-effect VERIFIES this cert's signature against U's enrolled signer pubkey `P_U`
|
|
339
|
-
* AND that its `founder == ctx.principal()` (the gate verdict, CAS-as-law) BEFORE accepting it as
|
|
340
|
-
* `links[0]` of the travelling chain — the kernel signs nothing (it holds no secret; it only verifies).
|
|
341
|
-
* Optional: absent ⇒ the era stays inert for this birth (pre-feature lane). */
|
|
342
|
-
homeCert: z.string().optional(),
|
|
343
325
|
/** AUTHPROVIDER INHERITANCE — DEFAULT ON, opt out with `false`. Verification is PER-WORKSPACE (the
|
|
344
326
|
* kernel never walks up to the parent), so a PRIVATE child born without its own AuthProvider cannot
|
|
345
327
|
* verify any token — its own owner is locked out of verified reads (the co2 phase-6 wall). By
|
|
@@ -356,9 +338,9 @@ export function birthReceivingGroupForDomain(
|
|
|
356
338
|
.plan((p) => {
|
|
357
339
|
const founder = p.owner; // the actor for the child's own genesis steps (its self-bound owner subject)
|
|
358
340
|
const groupName = receivingGroupNameOf(p.owner);
|
|
359
|
-
// THE PARENT'S DECLARED VERIFIER
|
|
360
|
-
//
|
|
361
|
-
//
|
|
341
|
+
// THE PARENT'S DECLARED VERIFIER. This captured read supplies the inherited provider seed.
|
|
342
|
+
// Birth-certificate verification and founding-signer enrollment belong to the kernel-owned
|
|
343
|
+
// attestation step, so neither fact is duplicated in this tenant plan or payload.
|
|
362
344
|
const apRows = p.inheritAuthProvider !== false
|
|
363
345
|
? readFrameworkDeclared<Array<Record<string, unknown>>>("authProvider", { scope: "platform" })
|
|
364
346
|
: undefined;
|
|
@@ -371,15 +353,6 @@ export function birthReceivingGroupForDomain(
|
|
|
371
353
|
const v = ap?.[k];
|
|
372
354
|
return typeof v === "string" && v.length > 0 ? v : undefined;
|
|
373
355
|
};
|
|
374
|
-
// PROVENANCE (the verified-lineage thread, 2026-07-16): the kernel derives every later write's
|
|
375
|
-
// `principal_verified` from the ENROLL LEAF's `verifiedVia` — so a verified birth must stamp it or the
|
|
376
|
-
// born home's owner writes UNVERIFIED forever (and its own children are then born `verified:false`,
|
|
377
|
-
// whose chains refuse `verify_to_root`). The cert's `verified` IS the gate verdict post-admission
|
|
378
|
-
// (assemble_birth_attestation CAS-refuses a mismatch), so reading it in-plan is trustworthy — a lying
|
|
379
|
-
// cert never reaches this fold. An unverified birth stamps nothing (the unverified lane stays honest).
|
|
380
|
-
let certVerified = false;
|
|
381
|
-
try { certVerified = p.homeCert !== undefined && JSON.parse(p.homeCert).verified === true; } catch { /* malformed cert ⇒ unverified (the gate will refuse the birth anyway) */ }
|
|
382
|
-
const verifiedVia = certVerified ? apStr("issuer") : undefined;
|
|
383
356
|
const genesisChain: Array<{ domain: string; directiveId: string; actor: string; payload: Record<string, unknown>; domainHash?: string }> = [
|
|
384
357
|
// THE ORDER-DECLARED LAW INSTALL: two typed closure identities. The kernel resolves each
|
|
385
358
|
// closure from the parent's custody when it folds the birth; law bytes never ride the intent.
|
|
@@ -433,16 +406,6 @@ export function birthReceivingGroupForDomain(
|
|
|
433
406
|
bornAt: p.bornAt,
|
|
434
407
|
})),
|
|
435
408
|
})),
|
|
436
|
-
// ENROL THE OWNER'S DEVICE on the warrant-free prefix (when supplied) — enrollSigner self-binds to
|
|
437
|
-
// ctx.principal() (the owner), so this binds `ownerKeyHash` as the owner's signer BEFORE the warrant
|
|
438
|
-
// closes bootstrap. This is what makes the born home SIGNER-READY (able to sign + self-serve-birth its own
|
|
439
|
-
// estates/catalogues). Mirrors buildGenesisChain's founding-admin enrollSigner for a platform — homes were
|
|
440
|
-
// the inconsistent one. THE GENESIS ORDER IS THE CONTRACT (BREAK 1): the parent's offer-effect APPENDS
|
|
441
|
-
// `seedBirthAttestation` + `seedGenesis` (the WARRANT) AFTER these steps — the warrant lands LAST (E→W
|
|
442
|
-
// ladder: every seed runs UNSIGNED on the still-warrant-free prefix; the warrant closes it). ERA-SAFE: a
|
|
443
|
-
// home born earlier (pre-feature law, no such step in its recipe) replays byte-identical — the era rule
|
|
444
|
-
// rides the LAW hash, not this branch.
|
|
445
|
-
{ domain: groupLawDomain, directiveId: "enrollSigner", actor: founder, payload: { keyHash: p.ownerKeyHash, grantedBy: p.owner, grantedAt: 1, ...(verifiedVia !== undefined ? { verifiedVia } : {}) } },
|
|
446
409
|
];
|
|
447
410
|
// AUTHPROVIDER INHERITANCE (default ON — see the payload doc): copy the parent's declared provider
|
|
448
411
|
// (the up-front captured read above) AS DATA into the child genesis. The child owns the copy as its
|
|
@@ -68,9 +68,9 @@ import { z } from "zod";
|
|
|
68
68
|
import { aggregate, instance } from "../aggregate.js";
|
|
69
69
|
import { create, ensure } from "../authoring.js";
|
|
70
70
|
import { birth } from "../births.js";
|
|
71
|
-
import { directive } from "../directive.js";
|
|
71
|
+
import { directive, type Directive } from "../directive.js";
|
|
72
72
|
import { Lww } from "../drivers.js";
|
|
73
|
-
import { t } from "../fields.js";
|
|
73
|
+
import { sha256Digest, t, type Sha256Digest } from "../fields.js";
|
|
74
74
|
import { set, withMarker } from "../ops.js";
|
|
75
75
|
import { query } from "../query.js";
|
|
76
76
|
import { read } from "../read.js";
|
|
@@ -79,7 +79,8 @@ import { writeTuple, removeTuple, RelationTuple, relationTupleInstanceId } from
|
|
|
79
79
|
import { relationType, self, user, key } from "../relation_schema.js";
|
|
80
80
|
import { GENESIS_WARRANT_ID } from "./genesis.js";
|
|
81
81
|
import { idOf } from "../value_object.js";
|
|
82
|
-
import { signerObject, SIGNER_RELATION } from "./signing_identity.js";
|
|
82
|
+
import { enrollSigner, signerObject, SIGNER_RELATION } from "./signing_identity.js";
|
|
83
|
+
import { seedBirthPolicy } from "./birth_meter.js";
|
|
83
84
|
import {
|
|
84
85
|
DomainInstallExecutor,
|
|
85
86
|
domainInstallGenesisOffer,
|
|
@@ -92,7 +93,7 @@ import {
|
|
|
92
93
|
export { GenesisWarrant, seedGenesis } from "./genesis.js";
|
|
93
94
|
export { identityRelations, enrollSigner } from "./signing_identity.js";
|
|
94
95
|
// ERA 0 — the keyless verified first-device door rides the GOVERNANCE law too: a freshly BORN platform
|
|
95
|
-
// has a virgin roster and a cert-ATTESTED AuthProvider (the S_root-signed
|
|
96
|
+
// has a virgin roster and a cert-ATTESTED AuthProvider (the S_root-signed birth witness), so its founder's
|
|
96
97
|
// FIRST action is `enrollFirstDevice` (token-verified against the attested provider) followed by a SIGNED
|
|
97
98
|
// self-`grantAdmin` (admitted by the kernel genesis clause). Without this export a born platform is
|
|
98
99
|
// un-administrable — the admin bootstrap has no door.
|
|
@@ -109,6 +110,10 @@ export { BIRTH_ATTESTATION_ID, BirthAttestation, birthAttestation, seedBirthAtte
|
|
|
109
110
|
// onboarding signer S_P) so the kernel reads it at `signer-delegation:<user>` when assembling a home's chain.
|
|
110
111
|
export { SignerDelegation, enrollDelegation, signerDelegationByUser } from "./signing_identity.js";
|
|
111
112
|
|
|
113
|
+
/** Canonical governance-law namespace. Genesis routes refer to this declaration, never a repeated
|
|
114
|
+
* string literal, so renaming the module cannot silently strand part of the founding ceremony. */
|
|
115
|
+
export const WORKSPACES_DOMAIN = "workspaces" as const;
|
|
116
|
+
|
|
112
117
|
// ── WORKSPACE SELF-IDENTITY (the law reads its OWN install-context) ──────────────────────────────────
|
|
113
118
|
// The governance bundle runs on root AND every born platform. A workspace knows its ROLE via this
|
|
114
119
|
// singleton, at a CONSTANT fixed instance id (the RelationTuple discipline — a literal, never minted, so
|
|
@@ -232,7 +237,7 @@ export const WORKSPACE_STATUSES = [
|
|
|
232
237
|
export const GRANT_STATUSES = ["active", "revoked"] as const;
|
|
233
238
|
|
|
234
239
|
/** sha256 hex — the ONLY shape a credential may take inside the law. */
|
|
235
|
-
const sha256Hex =
|
|
240
|
+
const sha256Hex = sha256Digest();
|
|
236
241
|
|
|
237
242
|
/** Epoch-ms, caller-stamped (a plan never reads a clock). */
|
|
238
243
|
const epochMs = z.number().int().nonnegative();
|
|
@@ -535,8 +540,8 @@ export function buildGenesisChain(p: {
|
|
|
535
540
|
* the recipe PREPENDS `bootstrap/installDomain(framework)` + `nomos/installDomain(law)` — the two-layer
|
|
536
541
|
* install the genesis bootstrap does — so the child installs ITS OWN law as intent 0 and self-validates
|
|
537
542
|
* from there. Absent ⇒ the recipe resolves an already-installed law by shape (pre-G3 / test lanes). */
|
|
538
|
-
frameworkHash?:
|
|
539
|
-
lawHash?:
|
|
543
|
+
frameworkHash?: Sha256Digest;
|
|
544
|
+
lawHash?: Sha256Digest;
|
|
540
545
|
/** METERED BIRTH (slice A3 — the descendant budget). Required: a governance child may not be
|
|
541
546
|
* born without explicit BirthPolicy state. Zero is a legitimate declared "cannot birth" policy. */
|
|
542
547
|
descendantPool: number;
|
|
@@ -545,7 +550,7 @@ export function buildGenesisChain(p: {
|
|
|
545
550
|
* ancestry while `parent` stays the SUPERSEDED record's parent (a rebirth may never reparent). */
|
|
546
551
|
rebornFrom?: string;
|
|
547
552
|
}): string {
|
|
548
|
-
const founder =
|
|
553
|
+
const founder: `user:${string}` = `user:${p.principal}`;
|
|
549
554
|
// THE ORDER-DECLARED LAW INSTALL (G3): the child installs the EXACT typed USDA closures the order names.
|
|
550
555
|
// Bytes are NOT in the recipe; the kernel resolves each identity from its own custody when it folds the
|
|
551
556
|
// birth. Two layers, matching the genesis bootstrap: bootstrap controller, then law.
|
|
@@ -587,20 +592,28 @@ export function buildGenesisChain(p: {
|
|
|
587
592
|
// METERED BIRTH (slice A3 — THE POOL CHAINS TO ROOT): every governance child is BORN BOUNDED.
|
|
588
593
|
// Historical chains keep replaying under their historical law; this constructor authors only the
|
|
589
594
|
// current law, whose seedBirthPolicy directive is compiler-carried. Missing policy is invalid genesis.
|
|
595
|
+
const governanceGenesisStep = <P extends Record<string, unknown>>(
|
|
596
|
+
declaration: Directive<P>,
|
|
597
|
+
payload: P,
|
|
598
|
+
) => ({
|
|
599
|
+
domain: WORKSPACES_DOMAIN,
|
|
600
|
+
directiveId: declaration.id,
|
|
601
|
+
actor: founder,
|
|
602
|
+
payload,
|
|
603
|
+
});
|
|
590
604
|
const poolSteps = [
|
|
591
|
-
{
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
},
|
|
605
|
+
governanceGenesisStep(seedBirthPolicy, {
|
|
606
|
+
maxBirthsPerPrincipal: p.descendantPool,
|
|
607
|
+
setBy: p.principal,
|
|
608
|
+
setAt: p.bornAt,
|
|
609
|
+
}),
|
|
597
610
|
];
|
|
598
611
|
return JSON.stringify([
|
|
599
612
|
...installSteps,
|
|
600
613
|
...poolSteps,
|
|
601
614
|
// ERA 0 — THE GENESIS-ORDER CONTRACT (the birthHome ordering, unified): every recipe seed below runs
|
|
602
615
|
// UNSIGNED on the still-WARRANT-FREE prefix; the recipe carries NO seedGenesis and NO grantAdmin. The
|
|
603
|
-
// KERNEL's birth effect — after verifying the offer's S_root-signed
|
|
616
|
+
// KERNEL's birth effect — after verifying the offer's typed S_root-signed certificate against the pinned
|
|
604
617
|
// K_root — appends `seedBirthAttestation` (the travelling cert) and THEN `seedGenesis` (the warrant,
|
|
605
618
|
// self-id == name) LAST, so the platform is born WARRANTED + ATTESTED and every post-genesis write is
|
|
606
619
|
// signed AND cert-gated. A birth whose cert fails verification seeds neither and is REFUSED atomically
|
|
@@ -614,11 +627,22 @@ export function buildGenesisChain(p: {
|
|
|
614
627
|
// (`genesis_admits`: a virgin #admin roster + the warrant binding the captured principal). A baked
|
|
615
628
|
// in-genesis grantAdmin can never fold (post-warrant writes need a signature the birth effect cannot
|
|
616
629
|
// produce — the pre-era-0 recipe was structurally unbornable); pre-warrant enroll + post-birth signed
|
|
617
|
-
// self-grant is the lawful shape. (The keyless variant — enrollFirstDevice against the
|
|
630
|
+
// self-grant is the lawful shape. (The keyless variant — enrollFirstDevice against the birth certificate's
|
|
618
631
|
// ATTESTED AuthProvider — remains for founders with an IdP instead of a key.)
|
|
619
|
-
|
|
632
|
+
governanceGenesisStep(enrollSigner, {
|
|
633
|
+
keyHash: p.foundingAdminHash,
|
|
634
|
+
grantedBy: p.principal,
|
|
635
|
+
grantedAt: p.requestedAt,
|
|
636
|
+
}),
|
|
620
637
|
// No `role` — it was a vestigial node-class label (collapsed with `kind`); birthCloud ignores it now.
|
|
621
|
-
|
|
638
|
+
governanceGenesisStep(birthCloud, {
|
|
639
|
+
principal: p.principal,
|
|
640
|
+
name: p.name,
|
|
641
|
+
tier: p.tier,
|
|
642
|
+
bornAt: p.requestedAt,
|
|
643
|
+
bornUnder,
|
|
644
|
+
...(p.rebornFrom ? { rebornFrom: p.rebornFrom } : {}),
|
|
645
|
+
}),
|
|
622
646
|
]);
|
|
623
647
|
}
|
|
624
648
|
|
|
@@ -1264,13 +1288,6 @@ export const birthChild = directive("birthChild")
|
|
|
1264
1288
|
* own custody (never the host). Absent ⇒ the recipe resolves an already-installed law by shape. */
|
|
1265
1289
|
frameworkHash: sha256Hex.optional(),
|
|
1266
1290
|
lawHash: sha256Hex.optional(),
|
|
1267
|
-
/** THE PLATFORM ATTESTATION (era 0 — one attested birth lane for every holon kind): the 1-link
|
|
1268
|
-
* platform_cert JSON (`executor::birth_cert::BirthCert`), SIGNED OFFLINE BY S_root (the K_root
|
|
1269
|
-
* holder). The kernel's birth effect VERIFIES it (child == name, founder == `user:<principal>`,
|
|
1270
|
-
* signature vs the pinned K_root) and injects `seedBirthAttestation` + `seedGenesis` (the warrant)
|
|
1271
|
-
* LAST into the child's genesis. REQUIRED for a genesis birth to complete — a birth without a
|
|
1272
|
-
* valid cert is refused atomically (no unwarranted platform lane survives era 0). */
|
|
1273
|
-
platformCert: z.string().optional(),
|
|
1274
1291
|
/** GOVERNANCE POSTURE (governance_postures.md) — the born child's declared posture; default
|
|
1275
1292
|
* `managed`. Mirrored onto its lineage record so root's bailiff can consult it on the hot path
|
|
1276
1293
|
* (the tenant's own {@link GovernancePosture} singleton is authoritative). Additive/era-safe. */
|