@githolon/dsl 0.103.3 → 0.103.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/dart/CHANGELOG.md +15 -0
- package/dart/lib/src/runner/node-entry.mjs +411 -149
- package/dart/lib/src/runner/offline-install.mjs +222 -99
- package/dart/lib/src/session_bound.dart +111 -2
- package/dart/lib/src/test_harness.dart +117 -66
- package/dart/lib/src/webview_bridge.dart +726 -61
- package/dart/pubspec.lock +1 -1
- package/dart/pubspec.yaml +4 -5
- 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 +357 -9
- 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 +425 -145
- package/src/births.ts +16 -1
- package/src/business_birth.ts +67 -46
- 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 +142 -40
- package/src/fields.ts +43 -0
- package/src/framework/attested_receipts.ts +34 -1
- package/src/framework/domain_lifecycle.ts +140 -83
- package/src/framework/genesis.ts +2 -1
- package/src/framework/home.ts +20 -45
- package/src/framework/workspaces.ts +62 -29
- 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/law_graph_usda.ts +45 -54
- 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,42 +20,119 @@
|
|
|
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";
|
|
33
|
+
|
|
34
|
+
/** The two authenticated capabilities which may execute a lifecycle install.
|
|
35
|
+
* Callers choose a role; the serialized USDA route is an implementation detail. */
|
|
36
|
+
export enum DomainInstallExecutor {
|
|
37
|
+
StageZero = "bootstrap",
|
|
38
|
+
LifecycleController = "nomos",
|
|
39
|
+
}
|
|
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
|
+
}
|
|
65
|
+
|
|
66
|
+
/** One complete, contract-shaped law-install call in a child genesis sequence. Keeping this
|
|
67
|
+
* constructor beside the directive schema makes omission of required install provenance a type
|
|
68
|
+
* error at every framework birth site instead of a late kernel repair. */
|
|
69
|
+
export interface DomainInstallGenesisOffer extends Readonly<Record<string, unknown>> {
|
|
70
|
+
readonly domain: DomainInstallExecutor;
|
|
71
|
+
readonly directiveId: typeof INSTALL_DOMAIN_DIRECTIVE;
|
|
72
|
+
readonly actor: string;
|
|
73
|
+
readonly payload: {
|
|
74
|
+
readonly domainHash: LawClosureReference;
|
|
75
|
+
readonly installedBy: string;
|
|
76
|
+
readonly lawClosureIdentity: LawClosureReference;
|
|
77
|
+
};
|
|
78
|
+
readonly domainHash: LawClosureReference;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function domainInstallGenesisOffer(
|
|
82
|
+
domain: DomainInstallExecutor,
|
|
83
|
+
domainHash: LawClosureReference,
|
|
84
|
+
actor: string,
|
|
85
|
+
): DomainInstallGenesisOffer {
|
|
86
|
+
return {
|
|
87
|
+
domain,
|
|
88
|
+
directiveId: INSTALL_DOMAIN_DIRECTIVE,
|
|
89
|
+
actor,
|
|
90
|
+
payload: { domainHash, installedBy: actor, lawClosureIdentity: domainHash },
|
|
91
|
+
domainHash,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
27
94
|
|
|
28
95
|
/** Framework-global authority to install or roll out a model in this workspace.
|
|
29
96
|
* The verified founder receives the first leaf in `seedGenesis`; every later
|
|
30
97
|
* delegation remains an ordinary, replayable Zanzibar edge. */
|
|
31
98
|
export const modelLifecycleRelations = relationType("workspace")
|
|
32
99
|
.asGlobal()
|
|
33
|
-
.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
|
+
}
|
|
34
112
|
|
|
35
113
|
export const DOMAIN_INSTALLATION_PHASES = [
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
114
|
+
DomainInstallationPhase.Pending,
|
|
115
|
+
DomainInstallationPhase.Active,
|
|
116
|
+
DomainInstallationPhase.Disabled,
|
|
117
|
+
DomainInstallationPhase.Superseded,
|
|
118
|
+
DomainInstallationPhase.Retiring,
|
|
119
|
+
DomainInstallationPhase.Retired,
|
|
120
|
+
DomainInstallationPhase.Failed,
|
|
43
121
|
] as const;
|
|
44
|
-
export type DomainInstallationPhase = (typeof DOMAIN_INSTALLATION_PHASES)[number];
|
|
45
122
|
|
|
46
123
|
export const DomainInstallation = aggregate("DomainInstallation", {
|
|
47
|
-
"spec.domainHash": t.
|
|
48
|
-
"spec.packageHash": t.
|
|
124
|
+
"spec.domainHash": t.sha256Digest().merge(Lww),
|
|
125
|
+
"spec.packageHash": t.sha256Digest().merge(Lww),
|
|
49
126
|
"spec.authorityScope": t.string().merge(Lww),
|
|
50
127
|
"spec.installedBy": t.string().merge(Lww),
|
|
51
|
-
"spec.lawClosureIdentity": t.
|
|
128
|
+
"spec.lawClosureIdentity": t.sha256Digest().merge(Lww),
|
|
52
129
|
"spec.dependencies": t.set(t.string()),
|
|
53
|
-
"spec.supersedes": t.
|
|
130
|
+
"spec.supersedes": t.sha256Digest().merge(Lww).optional(),
|
|
54
131
|
"spec.finalizers": t.set(t.string()),
|
|
55
132
|
"metadata.generation": t.int().merge(Lww),
|
|
56
133
|
"status.phase": t.enum(DOMAIN_INSTALLATION_PHASES).merge(Lww),
|
|
57
134
|
"status.observedGeneration": t.int().merge(Lww),
|
|
58
|
-
"status.supersededBy": t.
|
|
135
|
+
"status.supersededBy": t.sha256Digest().merge(Lww).optional(),
|
|
59
136
|
"status.conditions": t.map(t.json()).merge(MapOf(Lww)),
|
|
60
137
|
}).public();
|
|
61
138
|
|
|
@@ -69,13 +146,13 @@ export const RELEASE_RECEIPT_AGGREGATE = "nomos_internal_ReleaseReceipt_6f0d3a";
|
|
|
69
146
|
export const RELEASE_RECEIPT_PREFIX = "nomos-release-receipt:";
|
|
70
147
|
|
|
71
148
|
export const ReleaseReceipt = aggregate(RELEASE_RECEIPT_AGGREGATE, {
|
|
72
|
-
candidateHash: t.
|
|
73
|
-
fromHash: t.
|
|
149
|
+
candidateHash: t.sha256Digest().merge(Lww),
|
|
150
|
+
fromHash: t.sha256Digest().merge(Lww).optional(),
|
|
74
151
|
domain: t.string().merge(Lww),
|
|
75
|
-
atHash: t.
|
|
76
|
-
atStateRoot: t.
|
|
77
|
-
historyHash: t.
|
|
78
|
-
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),
|
|
79
156
|
reportJson: t.string().merge(Lww),
|
|
80
157
|
complete: t.bool(),
|
|
81
158
|
evolveOk: t.bool(),
|
|
@@ -84,26 +161,26 @@ export const ReleaseReceipt = aggregate(RELEASE_RECEIPT_AGGREGATE, {
|
|
|
84
161
|
replayedIntents: t.int().merge(Lww),
|
|
85
162
|
candidateIntents: t.int().merge(Lww),
|
|
86
163
|
divergenceCount: t.int().merge(Lww),
|
|
87
|
-
baselineRoot: t.
|
|
88
|
-
candidateRoot: t.
|
|
164
|
+
baselineRoot: t.sha256Digest().merge(Lww),
|
|
165
|
+
candidateRoot: t.sha256Digest().merge(Lww),
|
|
89
166
|
acceptanceReason: t.string().merge(Lww).optional(),
|
|
90
167
|
acceptedBy: attributionField(t.string().merge(Lww)),
|
|
91
168
|
acceptedAt: t.int().merge(Lww),
|
|
92
169
|
}).public();
|
|
93
170
|
|
|
94
|
-
export function domainInstallationAggregateId(domainHash:
|
|
171
|
+
export function domainInstallationAggregateId(domainHash: Sha256Digest): string {
|
|
95
172
|
return `domain-installation:${domainHash}`;
|
|
96
173
|
}
|
|
97
174
|
|
|
98
|
-
function domainInstallationInstance(domainHash:
|
|
175
|
+
function domainInstallationInstance(domainHash: Sha256Digest) {
|
|
99
176
|
return instance(DomainInstallation, domainInstallationAggregateId(domainHash));
|
|
100
177
|
}
|
|
101
178
|
|
|
102
|
-
export function releaseReceiptAggregateId(reportHash:
|
|
179
|
+
export function releaseReceiptAggregateId(reportHash: Sha256Digest): string {
|
|
103
180
|
return `${RELEASE_RECEIPT_PREFIX}${reportHash}`;
|
|
104
181
|
}
|
|
105
182
|
|
|
106
|
-
function releaseReceiptInstance(reportHash:
|
|
183
|
+
function releaseReceiptInstance(reportHash: Sha256Digest) {
|
|
107
184
|
return instance(ReleaseReceipt, releaseReceiptAggregateId(reportHash));
|
|
108
185
|
}
|
|
109
186
|
|
|
@@ -114,13 +191,13 @@ const dispositions = z.object({
|
|
|
114
191
|
ackBy: z.string().optional(),
|
|
115
192
|
}).optional();
|
|
116
193
|
|
|
117
|
-
const lowerHex64 =
|
|
118
|
-
const gitOid =
|
|
194
|
+
const lowerHex64 = sha256Digest();
|
|
195
|
+
const gitOid = gitObjectId();
|
|
119
196
|
const preflightReceipt = z.object({
|
|
120
197
|
candidateHash: lowerHex64,
|
|
121
198
|
domain: z.string().min(1),
|
|
122
199
|
atHash: gitOid,
|
|
123
|
-
atStateRoot:
|
|
200
|
+
atStateRoot: gitOid,
|
|
124
201
|
historyHash: lowerHex64,
|
|
125
202
|
reportHash: lowerHex64,
|
|
126
203
|
reportJson: z.string().min(2),
|
|
@@ -144,36 +221,29 @@ const preflightReceipt = z.object({
|
|
|
144
221
|
}
|
|
145
222
|
});
|
|
146
223
|
|
|
147
|
-
const
|
|
224
|
+
const installPackagePayload = {
|
|
148
225
|
domainHash: lowerHex64,
|
|
149
226
|
installedBy: z.string(),
|
|
150
227
|
lawClosureIdentity: lowerHex64,
|
|
151
228
|
authorityScope: z.string().default("workspace/root"),
|
|
152
229
|
// Omission means the empty set; keep that in the plan instead of encoding a composite default
|
|
153
230
|
// into canonical USDA merely to save `?? []` at runtime.
|
|
154
|
-
dependencies: z.array(
|
|
155
|
-
|
|
156
|
-
|
|
231
|
+
dependencies: z.array(sha256Digest()).optional(),
|
|
232
|
+
finalizers: z.array(sha256Digest()).optional(),
|
|
233
|
+
};
|
|
234
|
+
|
|
235
|
+
const stagedPackagePayload = {
|
|
236
|
+
...installPackagePayload,
|
|
237
|
+
supersedes: sha256Digest().optional(),
|
|
157
238
|
};
|
|
158
239
|
|
|
159
|
-
export const installDomain = directive(
|
|
240
|
+
export const installDomain = directive(DOMAIN_LIFECYCLE_DIRECTIVES.Install)
|
|
160
241
|
.ensures(DomainInstallation)
|
|
161
|
-
.
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
// frontier (`Superseded`, or `Retired` when `retireReplaced`) and gets the reverse
|
|
167
|
-
// `status.supersededBy` edge to this install. Absent ⇒ install-BESIDE (backward-compat:
|
|
168
|
-
// byte-identical ops to the pre-lifecycle plan). `replaces` also sets `spec.supersedes`.
|
|
169
|
-
replaces: z.string().optional(),
|
|
170
|
-
retireReplaced: z.boolean().default(false),
|
|
171
|
-
dispositions,
|
|
172
|
-
}).refine((p) => p.replaces === undefined || p.replaces !== p.domainHash, {
|
|
173
|
-
message: "a domain package cannot replace itself",
|
|
174
|
-
path: ["replaces"],
|
|
175
|
-
}),
|
|
176
|
-
)
|
|
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())
|
|
177
247
|
.plan((p): PlannedOp[] => {
|
|
178
248
|
const install = domainInstallationInstance(p.domainHash);
|
|
179
249
|
const ops: PlannedOp[] = [
|
|
@@ -183,38 +253,25 @@ export const installDomain = directive("installDomain")
|
|
|
183
253
|
set(install, "spec.installedBy", p.installedBy),
|
|
184
254
|
set(install, "spec.lawClosureIdentity", p.lawClosureIdentity),
|
|
185
255
|
set(install, "metadata.generation", 1),
|
|
186
|
-
set(install, "status.phase",
|
|
256
|
+
set(install, "status.phase", DomainInstallationPhase.Active),
|
|
187
257
|
set(install, "status.observedGeneration", 1),
|
|
188
258
|
];
|
|
189
259
|
if ((p.dependencies ?? []).length > 0) {
|
|
190
260
|
ops.push(addToSet(install, "spec.dependencies", p.dependencies ?? []));
|
|
191
261
|
}
|
|
192
|
-
// The predecessor back-link: `supersedes` (explicit) or `replaces` (the actionable form).
|
|
193
|
-
const supersedes = p.supersedes ?? p.replaces;
|
|
194
|
-
if (supersedes !== undefined) {
|
|
195
|
-
ops.push(set(install, "spec.supersedes", supersedes));
|
|
196
|
-
}
|
|
197
|
-
// The transition on the PRIOR install: drop it from the served frontier + point it here.
|
|
198
|
-
if (p.replaces !== undefined) {
|
|
199
|
-
const replaced = domainInstallationInstance(p.replaces);
|
|
200
|
-
ops.push(
|
|
201
|
-
set(replaced, "status.phase", p.retireReplaced ? "Retired" : "Superseded"),
|
|
202
|
-
);
|
|
203
|
-
ops.push(set(replaced, "status.supersededBy", p.domainHash));
|
|
204
|
-
}
|
|
205
262
|
if ((p.finalizers ?? []).length > 0) {
|
|
206
263
|
ops.push(addToSet(install, "spec.finalizers", p.finalizers ?? []));
|
|
207
264
|
}
|
|
208
265
|
return ops;
|
|
209
266
|
})
|
|
210
|
-
.requires(
|
|
267
|
+
.requires(MODEL_LIFECYCLE_AUTHORITY);
|
|
211
268
|
|
|
212
269
|
/** Store candidate law in the ledger without changing the served law frontier. The
|
|
213
270
|
* kernel content-verifies the package and lifecycle pair, while the Pending phase is
|
|
214
271
|
* deliberately invisible to dispatch, reads and replay's current-law map. */
|
|
215
|
-
export const stageDomain = directive(
|
|
272
|
+
export const stageDomain = directive(DOMAIN_LIFECYCLE_DIRECTIVES.Stage)
|
|
216
273
|
.ensures(DomainInstallation)
|
|
217
|
-
.payload(z.object({ ...
|
|
274
|
+
.payload(z.object({ ...stagedPackagePayload }).refine(
|
|
218
275
|
(p) => p.supersedes === undefined || p.supersedes !== p.domainHash,
|
|
219
276
|
{ message: "a staged domain package cannot supersede itself", path: ["supersedes"] },
|
|
220
277
|
))
|
|
@@ -227,7 +284,7 @@ export const stageDomain = directive("stageDomain")
|
|
|
227
284
|
set(install, "spec.installedBy", p.installedBy),
|
|
228
285
|
set(install, "spec.lawClosureIdentity", p.lawClosureIdentity),
|
|
229
286
|
set(install, "metadata.generation", 1),
|
|
230
|
-
set(install, "status.phase",
|
|
287
|
+
set(install, "status.phase", DomainInstallationPhase.Pending),
|
|
231
288
|
set(install, "status.observedGeneration", 0),
|
|
232
289
|
];
|
|
233
290
|
if ((p.dependencies ?? []).length > 0) ops.push(addToSet(install, "spec.dependencies", p.dependencies ?? []));
|
|
@@ -235,17 +292,17 @@ export const stageDomain = directive("stageDomain")
|
|
|
235
292
|
if ((p.finalizers ?? []).length > 0) ops.push(addToSet(install, "spec.finalizers", p.finalizers ?? []));
|
|
236
293
|
return ops;
|
|
237
294
|
})
|
|
238
|
-
.requires(
|
|
295
|
+
.requires(MODEL_LIFECYCLE_AUTHORITY);
|
|
239
296
|
|
|
240
297
|
/** Make a staged package current. The package bytes are already committed; the kernel
|
|
241
298
|
* re-loads them from prefix state and runs the evolve gate on this phase transition.
|
|
242
299
|
* The prior version stays Active by default so gradual-rollout clients can still have
|
|
243
300
|
* their declared era checked; the newer activation stamp alone makes this law current. */
|
|
244
|
-
export const promoteDomain = directive(
|
|
301
|
+
export const promoteDomain = directive(DOMAIN_LIFECYCLE_DIRECTIVES.Promote)
|
|
245
302
|
.mutates(DomainInstallation)
|
|
246
303
|
.payload(z.object({
|
|
247
|
-
domainHash:
|
|
248
|
-
replaces:
|
|
304
|
+
domainHash: sha256Digest(),
|
|
305
|
+
replaces: sha256Digest().optional(),
|
|
249
306
|
retireReplaced: z.boolean().default(false),
|
|
250
307
|
dispositions,
|
|
251
308
|
preflight: preflightReceipt,
|
|
@@ -260,7 +317,7 @@ export const promoteDomain = directive("promoteDomain")
|
|
|
260
317
|
const promoted = domainInstallationInstance(p.domainHash);
|
|
261
318
|
const receipt = releaseReceiptInstance(p.preflight.reportHash);
|
|
262
319
|
const ops: PlannedOp[] = [
|
|
263
|
-
set(promoted, "status.phase",
|
|
320
|
+
set(promoted, "status.phase", DomainInstallationPhase.Active),
|
|
264
321
|
set(promoted, "status.observedGeneration", 1),
|
|
265
322
|
withMarker(set(receipt, "candidateHash", p.domainHash), "ensures"),
|
|
266
323
|
...(p.replaces !== undefined ? [set(receipt, "fromHash", p.replaces)] : []),
|
|
@@ -289,23 +346,23 @@ export const promoteDomain = directive("promoteDomain")
|
|
|
289
346
|
const replaced = domainInstallationInstance(p.replaces);
|
|
290
347
|
ops.push(set(promoted, "spec.supersedes", p.replaces));
|
|
291
348
|
if (p.retireReplaced) {
|
|
292
|
-
ops.push(set(replaced, "status.phase",
|
|
349
|
+
ops.push(set(replaced, "status.phase", DomainInstallationPhase.Retired));
|
|
293
350
|
ops.push(set(replaced, "status.supersededBy", p.domainHash));
|
|
294
351
|
}
|
|
295
352
|
}
|
|
296
353
|
return ops;
|
|
297
354
|
})
|
|
298
355
|
.alsoEnsures(ReleaseReceipt)
|
|
299
|
-
.requires(
|
|
356
|
+
.requires(MODEL_LIFECYCLE_AUTHORITY);
|
|
300
357
|
|
|
301
358
|
/** Rollback is a new governed transition, never a history-pointer rewind. The prior
|
|
302
359
|
* package is promoted from committed custody and the failed candidate becomes Disabled;
|
|
303
360
|
* the kernel applies the same evolve judgment in the reverse direction. */
|
|
304
|
-
export const rollbackDomain = directive(
|
|
361
|
+
export const rollbackDomain = directive(DOMAIN_LIFECYCLE_DIRECTIVES.Rollback)
|
|
305
362
|
.mutates(DomainInstallation)
|
|
306
363
|
.payload(z.object({
|
|
307
|
-
candidateHash:
|
|
308
|
-
priorHash:
|
|
364
|
+
candidateHash: sha256Digest(),
|
|
365
|
+
priorHash: sha256Digest(),
|
|
309
366
|
dispositions,
|
|
310
367
|
preflight: preflightReceipt,
|
|
311
368
|
}).refine((p) => p.candidateHash !== p.priorHash, {
|
|
@@ -323,9 +380,9 @@ export const rollbackDomain = directive("rollbackDomain")
|
|
|
323
380
|
const prior = domainInstallationInstance(p.priorHash);
|
|
324
381
|
const receipt = releaseReceiptInstance(p.preflight.reportHash);
|
|
325
382
|
return [
|
|
326
|
-
set(candidate, "status.phase",
|
|
383
|
+
set(candidate, "status.phase", DomainInstallationPhase.Disabled),
|
|
327
384
|
set(candidate, "status.supersededBy", p.priorHash),
|
|
328
|
-
set(prior, "status.phase",
|
|
385
|
+
set(prior, "status.phase", DomainInstallationPhase.Active),
|
|
329
386
|
set(prior, "status.observedGeneration", 1),
|
|
330
387
|
withMarker(set(receipt, "candidateHash", p.priorHash), "ensures"),
|
|
331
388
|
set(receipt, "fromHash", p.candidateHash),
|
|
@@ -352,4 +409,4 @@ export const rollbackDomain = directive("rollbackDomain")
|
|
|
352
409
|
];
|
|
353
410
|
})
|
|
354
411
|
.alsoEnsures(ReleaseReceipt)
|
|
355
|
-
.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";
|
|
@@ -45,6 +43,10 @@ import { relationType, self, user, selfObject } from "../relation_schema.js";
|
|
|
45
43
|
import { requireParentAttestedBirth, type ParentAttestedBirthMarker } from "../births.js";
|
|
46
44
|
import { GenesisWarrant, seedGenesis } from "./genesis.js";
|
|
47
45
|
import { idOf } from "../value_object.js";
|
|
46
|
+
import {
|
|
47
|
+
DomainInstallExecutor,
|
|
48
|
+
domainInstallGenesisOffer,
|
|
49
|
+
} from "./domain_lifecycle.js";
|
|
48
50
|
|
|
49
51
|
// `nomos.sha256` — the TRUSTLESS, pure hash computed IN the githolon (Rust sha2 bound in the sealed engine;
|
|
50
52
|
// see deterministic-rquickjs). Available as a global to plans + invariant predicates (like `nomos.mint`); it
|
|
@@ -315,27 +317,11 @@ export function birthReceivingGroupForDomain(
|
|
|
315
317
|
owner: z.string().min(1),
|
|
316
318
|
/** The bootstrap framework controller content hash — tenant-pinned at governance compile (the child
|
|
317
319
|
* installs it IN-CHAIN as intent 0; the kernel resolves the bytes by hash from the parent's custody). */
|
|
318
|
-
frameworkHash:
|
|
320
|
+
frameworkHash: sha256Digest(),
|
|
319
321
|
/** The HOME law package domainHash — tenant-pinned at governance compile (the child's second install). */
|
|
320
|
-
lawHash:
|
|
322
|
+
lawHash: sha256Digest(),
|
|
321
323
|
/** ISO-8601 birth time, caller-stamped (the timestamp doctrine — the plan reads no clock). */
|
|
322
324
|
bornAt: z.string().min(1),
|
|
323
|
-
/** THE OWNER'S DEVICE KEY HASH (sha256hex of the ed25519 public half — the SAME key `homeCert` is signed
|
|
324
|
-
* by and the `signer` relation enrols). The platform holds it from the user's onboarding and ENROLS it
|
|
325
|
-
* in the home's genesis, so the born home is SIGNER-READY: it can sign + birth its own children (estates,
|
|
326
|
-
* catalogues) from intent 0. WITHOUT this the home is born warranted + owned but signer-less — it can
|
|
327
|
-
* never act (the co2 estate-birth bug). Mirrors `buildGenesisChain`'s `enrollSigner` for a platform.
|
|
328
|
-
* REQUIRED — a payload without it refuses at compile/validation, not an hour into a live deploy with a
|
|
329
|
-
* bricked signer-less home. (The kernel injecting it from the verified cert's `parent_key` is the
|
|
330
|
-
* eventual generalization; until then the field is loud.) */
|
|
331
|
-
ownerKeyHash: sha256Hex,
|
|
332
|
-
/** THE DEVICE-SIGNED HOME CERT (JSON `executor::birth_cert::BirthCert`), signed CLIENT-SIDE by the
|
|
333
|
-
* owner's device key `S_U` (the SAME key `ownerKeyHash` hashes + the `signer` relation enrols). The
|
|
334
|
-
* kernel's birth offer-effect VERIFIES this cert's signature against U's enrolled signer pubkey `P_U`
|
|
335
|
-
* AND that its `founder == ctx.principal()` (the gate verdict, CAS-as-law) BEFORE accepting it as
|
|
336
|
-
* `links[0]` of the travelling chain — the kernel signs nothing (it holds no secret; it only verifies).
|
|
337
|
-
* Optional: absent ⇒ the era stays inert for this birth (pre-feature lane). */
|
|
338
|
-
homeCert: z.string().optional(),
|
|
339
325
|
/** AUTHPROVIDER INHERITANCE — DEFAULT ON, opt out with `false`. Verification is PER-WORKSPACE (the
|
|
340
326
|
* kernel never walks up to the parent), so a PRIVATE child born without its own AuthProvider cannot
|
|
341
327
|
* verify any token — its own owner is locked out of verified reads (the co2 phase-6 wall). By
|
|
@@ -352,9 +338,9 @@ export function birthReceivingGroupForDomain(
|
|
|
352
338
|
.plan((p) => {
|
|
353
339
|
const founder = p.owner; // the actor for the child's own genesis steps (its self-bound owner subject)
|
|
354
340
|
const groupName = receivingGroupNameOf(p.owner);
|
|
355
|
-
// THE PARENT'S DECLARED VERIFIER
|
|
356
|
-
//
|
|
357
|
-
//
|
|
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.
|
|
358
344
|
const apRows = p.inheritAuthProvider !== false
|
|
359
345
|
? readFrameworkDeclared<Array<Record<string, unknown>>>("authProvider", { scope: "platform" })
|
|
360
346
|
: undefined;
|
|
@@ -367,20 +353,19 @@ export function birthReceivingGroupForDomain(
|
|
|
367
353
|
const v = ap?.[k];
|
|
368
354
|
return typeof v === "string" && v.length > 0 ? v : undefined;
|
|
369
355
|
};
|
|
370
|
-
// PROVENANCE (the verified-lineage thread, 2026-07-16): the kernel derives every later write's
|
|
371
|
-
// `principal_verified` from the ENROLL LEAF's `verifiedVia` — so a verified birth must stamp it or the
|
|
372
|
-
// born home's owner writes UNVERIFIED forever (and its own children are then born `verified:false`,
|
|
373
|
-
// whose chains refuse `verify_to_root`). The cert's `verified` IS the gate verdict post-admission
|
|
374
|
-
// (assemble_birth_attestation CAS-refuses a mismatch), so reading it in-plan is trustworthy — a lying
|
|
375
|
-
// cert never reaches this fold. An unverified birth stamps nothing (the unverified lane stays honest).
|
|
376
|
-
let certVerified = false;
|
|
377
|
-
try { certVerified = p.homeCert !== undefined && JSON.parse(p.homeCert).verified === true; } catch { /* malformed cert ⇒ unverified (the gate will refuse the birth anyway) */ }
|
|
378
|
-
const verifiedVia = certVerified ? apStr("issuer") : undefined;
|
|
379
356
|
const genesisChain: Array<{ domain: string; directiveId: string; actor: string; payload: Record<string, unknown>; domainHash?: string }> = [
|
|
380
357
|
// THE ORDER-DECLARED LAW INSTALL: two typed closure identities. The kernel resolves each
|
|
381
358
|
// closure from the parent's custody when it folds the birth; law bytes never ride the intent.
|
|
382
|
-
|
|
383
|
-
|
|
359
|
+
domainInstallGenesisOffer(
|
|
360
|
+
DomainInstallExecutor.StageZero,
|
|
361
|
+
p.frameworkHash,
|
|
362
|
+
founder,
|
|
363
|
+
),
|
|
364
|
+
domainInstallGenesisOffer(
|
|
365
|
+
DomainInstallExecutor.LifecycleController,
|
|
366
|
+
p.lawHash,
|
|
367
|
+
founder,
|
|
368
|
+
),
|
|
384
369
|
{
|
|
385
370
|
domain: groupLawDomain,
|
|
386
371
|
directiveId: "nomosInternalSeedReceivingGroupPolicy7c1f5e",
|
|
@@ -421,16 +406,6 @@ export function birthReceivingGroupForDomain(
|
|
|
421
406
|
bornAt: p.bornAt,
|
|
422
407
|
})),
|
|
423
408
|
})),
|
|
424
|
-
// ENROL THE OWNER'S DEVICE on the warrant-free prefix (when supplied) — enrollSigner self-binds to
|
|
425
|
-
// ctx.principal() (the owner), so this binds `ownerKeyHash` as the owner's signer BEFORE the warrant
|
|
426
|
-
// closes bootstrap. This is what makes the born home SIGNER-READY (able to sign + self-serve-birth its own
|
|
427
|
-
// estates/catalogues). Mirrors buildGenesisChain's founding-admin enrollSigner for a platform — homes were
|
|
428
|
-
// the inconsistent one. THE GENESIS ORDER IS THE CONTRACT (BREAK 1): the parent's offer-effect APPENDS
|
|
429
|
-
// `seedBirthAttestation` + `seedGenesis` (the WARRANT) AFTER these steps — the warrant lands LAST (E→W
|
|
430
|
-
// ladder: every seed runs UNSIGNED on the still-warrant-free prefix; the warrant closes it). ERA-SAFE: a
|
|
431
|
-
// home born earlier (pre-feature law, no such step in its recipe) replays byte-identical — the era rule
|
|
432
|
-
// rides the LAW hash, not this branch.
|
|
433
|
-
{ domain: groupLawDomain, directiveId: "enrollSigner", actor: founder, payload: { keyHash: p.ownerKeyHash, grantedBy: p.owner, grantedAt: 1, ...(verifiedVia !== undefined ? { verifiedVia } : {}) } },
|
|
434
409
|
];
|
|
435
410
|
// AUTHPROVIDER INHERITANCE (default ON — see the payload doc): copy the parent's declared provider
|
|
436
411
|
// (the up-front captured read above) AS DATA into the child genesis. The child owns the copy as its
|