@githolon/dsl 0.100.6 → 0.101.0
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/dev_cloud.dart +46 -15
- package/dart/lib/src/test_harness.dart +78 -16
- package/dart/lib/src/webview_bridge.dart +640 -126
- package/dart/pubspec.lock +1 -1
- package/dart/pubspec.yaml +2 -2
- package/dart/test/bundle_version_pairing_test.dart +11 -22
- package/dart/test/co2_product_gauntlet_test.dart +228 -0
- package/dart/test/errors_test.dart +63 -12
- package/dart/test/first_app_gauntlet_test.dart +29 -3
- package/dart/test/webview_bridge_test.dart +274 -0
- package/dart_types/CHANGELOG.md +6 -0
- package/dart_types/lib/src/errors.dart +136 -30
- package/dart_types/lib/src/surfaced.dart +48 -2
- package/dart_types/lib/src/wire.dart +3 -1
- package/dart_types/pubspec.yaml +1 -1
- package/dart_types/test/surfaced_test.dart +19 -0
- package/package.json +1 -1
- package/src/activity.ts +43 -4
- package/src/aggregate.ts +132 -22
- package/src/attested_core.ts +1 -1
- package/src/authoring.ts +119 -6
- package/src/birth_meter.ts +4 -4
- package/src/birth_meter_ensure.ts +9 -1
- package/src/births.ts +53 -22
- package/src/build_package.ts +55 -14
- package/src/business_birth.ts +48 -7
- package/src/codegen_dart.ts +267 -37
- package/src/codegen_dart_client.ts +921 -82
- package/src/codegen_openapi.ts +28 -5
- package/src/codegen_proof.ts +107 -11
- package/src/codegen_ts.ts +961 -135
- package/src/compile_package_main.ts +155 -45
- package/src/conflicts.ts +252 -0
- package/src/describe_model.ts +448 -0
- package/src/directive.ts +5 -38
- package/src/drivers.ts +12 -4
- package/src/engine_entry.ts +4 -0
- package/src/evolve_preview.ts +72 -11
- package/src/fields.ts +201 -55
- package/src/framework/application_home.ts +103 -0
- package/src/framework/application_home_domain.ts +2 -0
- package/src/framework/attested_receipts.ts +2 -2
- package/src/framework/auth_provider_law.ts +51 -24
- package/src/framework/business_corrections.ts +170 -0
- package/src/framework/disclosure.ts +1 -1
- package/src/framework/domain_lifecycle.ts +250 -16
- package/src/framework/encryption_identity.ts +1 -1
- package/src/framework/encryption_scope.ts +21 -13
- package/src/framework/era_certification.ts +1 -1
- package/src/framework/framework_aggregates.ts +2 -0
- package/src/framework/genesis.ts +1 -1
- package/src/framework/home.ts +22 -9
- package/src/framework/identity.ts +5 -5
- package/src/framework/impure_capability.ts +2 -2
- package/src/framework/owned_custody.ts +561 -0
- package/src/framework/platform_admin.ts +1 -1
- package/src/framework/relations.ts +9 -1
- package/src/framework/repair.ts +1 -1
- package/src/framework/signing_identity.ts +2 -2
- package/src/framework/source_roster.ts +1 -1
- package/src/framework/workspaces.ts +17 -10
- package/src/generated_client_names.ts +39 -0
- package/src/index.ts +20 -15
- package/src/kernel_capabilities.ts +25 -1
- package/src/law_check.ts +4 -1
- package/src/manifest.ts +356 -63
- package/src/read_projection_manifest.ts +8 -0
- package/src/read_projection_namespace.ts +4 -0
- package/src/relation.ts +1 -1
- package/src/shape_compat.ts +47 -2
- package/src/stable_ids.ts +38 -6
- package/src/stable_ids_types.ts +30 -0
- package/src/surfaced_lint.ts +0 -0
- package/src/usd.ts +62 -11
- package/src/usd_layers.ts +54 -3
- package/src/value_object.ts +23 -19
- package/src/wire.ts +1 -0
- package/src/wire_encode.ts +48 -4
- package/src/workspace_routing.ts +57 -0
- package/src/workspace_sharding.ts +9 -9
- package/src/certified_read.ts +0 -313
|
@@ -12,6 +12,8 @@ import { t } from "../fields.js";
|
|
|
12
12
|
import { Lww } from "../drivers.js";
|
|
13
13
|
import { set, withMarker } from "../ops.js";
|
|
14
14
|
import { query } from "../query.js";
|
|
15
|
+
import { GENESIS_WARRANT_ID } from "./genesis.js";
|
|
16
|
+
import { refAs, workspaceInvariant } from "./workspace_invariant.js";
|
|
15
17
|
|
|
16
18
|
const epochMs = z.number().int().nonnegative();
|
|
17
19
|
|
|
@@ -67,16 +69,13 @@ export const AuthProvider = aggregate("AuthProvider", {
|
|
|
67
69
|
setBy: t.string().merge(Lww),
|
|
68
70
|
/** Epoch-ms it was set (caller-stamped). */
|
|
69
71
|
setAt: t.int().merge(Lww),
|
|
70
|
-
});
|
|
72
|
+
}).public();
|
|
71
73
|
|
|
72
74
|
/**
|
|
73
75
|
* setAuthProvider — declare (or amend) the platform's identity provider. An
|
|
74
76
|
* Ensure on the singleton; every change is an attributed, replayable intent.
|
|
75
77
|
*/
|
|
76
|
-
|
|
77
|
-
.ensures(AuthProvider)
|
|
78
|
-
.payload(
|
|
79
|
-
z.object({
|
|
78
|
+
const authProviderPayload = z.object({
|
|
80
79
|
// NOT z.url(): zod's url() needs the URL global, which the sealed
|
|
81
80
|
// deterministic engine does not provide — a regex keeps the plan pure.
|
|
82
81
|
issuer: z.string().regex(/^https?:\/\/\S+$/, "expected an http(s) URL"),
|
|
@@ -96,26 +95,54 @@ export const setAuthProvider = directive("setAuthProvider")
|
|
|
96
95
|
label: z.string().min(1),
|
|
97
96
|
setBy: z.string().min(1),
|
|
98
97
|
setAt: epochMs,
|
|
99
|
-
})
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
type AuthProviderPayload = z.infer<typeof authProviderPayload>;
|
|
101
|
+
function writeAuthProvider(p: AuthProviderPayload) {
|
|
102
|
+
const ap = instance(AuthProvider, AUTH_PROVIDER_ID);
|
|
103
|
+
return [
|
|
104
|
+
withMarker(set(ap, "scope", "platform"), "ensures"),
|
|
105
|
+
set(ap, "issuer", p.issuer),
|
|
106
|
+
set(ap, "jwksUrl", p.jwksUrl),
|
|
107
|
+
...(p.audience ? [set(ap, "audience", p.audience)] : []),
|
|
108
|
+
...(p.keys ? [set(ap, "keys", p.keys)] : []),
|
|
109
|
+
...(p.principalClaim ? [set(ap, "principalClaim", p.principalClaim)] : []),
|
|
110
|
+
...(p.rootCert ? [set(ap, "rootCert", p.rootCert)] : []),
|
|
111
|
+
...(p.deviceBindingClaim ? [set(ap, "deviceBindingClaim", p.deviceBindingClaim)] : []),
|
|
112
|
+
...(p.requireDeviceBinding ? [set(ap, "requireDeviceBinding", p.requireDeviceBinding)] : []),
|
|
113
|
+
set(ap, "label", p.label),
|
|
114
|
+
set(ap, "setBy", p.setBy),
|
|
115
|
+
set(ap, "setAt", p.setAt),
|
|
116
|
+
];
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export const setAuthProvider = directive("setAuthProvider")
|
|
120
|
+
.ensures(AuthProvider)
|
|
121
|
+
.payload(authProviderPayload)
|
|
122
|
+
.plan(writeAuthProvider)
|
|
118
123
|
.requires("admin");
|
|
119
124
|
|
|
125
|
+
/**
|
|
126
|
+
* Private child-genesis twin of {@link setAuthProvider}. It deliberately has no role requirement because
|
|
127
|
+
* roles do not exist yet while the child's genesis is folding. Its invariant admits it only while the
|
|
128
|
+
* GenesisWarrant is absent; the kernel appends that warrant last, permanently closing this lane before the
|
|
129
|
+
* workspace becomes servable. Later verifier changes can only use the role-gated maintenance directive.
|
|
130
|
+
*/
|
|
131
|
+
export const seedAuthProvider = directive("seedAuthProvider")
|
|
132
|
+
.ensures(AuthProvider)
|
|
133
|
+
.payload(authProviderPayload)
|
|
134
|
+
.plan(writeAuthProvider);
|
|
135
|
+
|
|
136
|
+
export const authProviderSeedBeforeWarrant = workspaceInvariant("AuthProviderSeedBeforeWarrant")
|
|
137
|
+
.on("seedAuthProvider")
|
|
138
|
+
.reads(() => [refAs("warrant", "GenesisWarrant", GENESIS_WARRANT_ID)])
|
|
139
|
+
.assert(({ warrant }) => {
|
|
140
|
+
// Missing invariant refs are represented as an empty snapshot by the executor, not necessarily null.
|
|
141
|
+
const principal = (warrant as { principal?: unknown } | null | undefined)?.principal;
|
|
142
|
+
return typeof principal !== "string" || principal.length === 0
|
|
143
|
+
? { accept: true }
|
|
144
|
+
: { reject: "seedAuthProvider is a child-genesis primitive; use the role-gated setAuthProvider after birth" };
|
|
145
|
+
});
|
|
146
|
+
|
|
120
147
|
/** The declared read serving a workspace's OWN AuthProvider row (and birthHome's inheritance capture). */
|
|
121
148
|
export const authProvider = query("authProvider").key("scope").returns(AuthProvider);
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
// NOMOS — framework-owned business corrections.
|
|
2
|
+
// Tenant code sees generated `corrections.undo(reason)` / `corrections.redo(reason)` only.
|
|
3
|
+
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
import { aggregate, instance, type AggregateHandle } from "../aggregate.js";
|
|
6
|
+
import { directive } from "../directive.js";
|
|
7
|
+
import { Lww } from "../drivers.js";
|
|
8
|
+
import { attributionField, t } from "../fields.js";
|
|
9
|
+
import { set, strike } from "../ops.js";
|
|
10
|
+
import type { DomainModule } from "../codegen_dart.js";
|
|
11
|
+
|
|
12
|
+
/** Obscure stable law ids: deliberately absent from ordinary directive/aggregate codegen. */
|
|
13
|
+
export const BUSINESS_CORRECTION_AGGREGATE = "nomos_internal_CorrectionRecord_a91e73";
|
|
14
|
+
export const BUSINESS_CORRECTION_DIRECTIVE = "nomos_internal_record_business_correction_a91e73";
|
|
15
|
+
|
|
16
|
+
const correctionRecord = aggregate(BUSINESS_CORRECTION_AGGREGATE, {
|
|
17
|
+
recordId: t.string().merge(Lww),
|
|
18
|
+
kind: t.enum(["undo", "redo"] as const).merge(Lww),
|
|
19
|
+
target: t.string().merge(Lww),
|
|
20
|
+
reason: t.string().merge(Lww),
|
|
21
|
+
correctedBy: attributionField(t.string().merge(Lww)),
|
|
22
|
+
correctedAt: t.string().merge(Lww),
|
|
23
|
+
}).public();
|
|
24
|
+
|
|
25
|
+
const correction = directive(BUSINESS_CORRECTION_DIRECTIVE)
|
|
26
|
+
.ensures(correctionRecord)
|
|
27
|
+
.payload(z.object({
|
|
28
|
+
recordId: z.string().min(1),
|
|
29
|
+
target: z.string().min(1),
|
|
30
|
+
kind: z.enum(["undo", "redo"]),
|
|
31
|
+
reason: z.string().trim().min(1),
|
|
32
|
+
correctedAt: z.string().min(1),
|
|
33
|
+
}))
|
|
34
|
+
.plan((p, ctx) => {
|
|
35
|
+
const record = instance(correctionRecord, p.recordId);
|
|
36
|
+
return [
|
|
37
|
+
// Retraction and testimony are one verified intent. There is no state-only correction lane.
|
|
38
|
+
strike(p.target),
|
|
39
|
+
set(record, "recordId", p.recordId),
|
|
40
|
+
set(record, "kind", p.kind),
|
|
41
|
+
set(record, "target", p.target),
|
|
42
|
+
set(record, "reason", p.reason),
|
|
43
|
+
set(record, "correctedBy", ctx.principal().asAttribution()),
|
|
44
|
+
set(record, "correctedAt", p.correctedAt),
|
|
45
|
+
];
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
export const businessCorrectionRecord = {
|
|
49
|
+
...correctionRecord,
|
|
50
|
+
businessBirthInternal: true as const,
|
|
51
|
+
};
|
|
52
|
+
export const recordBusinessCorrection = {
|
|
53
|
+
...correction,
|
|
54
|
+
businessBirthInternal: true as const,
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
const RECORD_EXPORT = "__nomos_business_correction_record_a91e73";
|
|
58
|
+
const DIRECTIVE_EXPORT = "__nomos_business_correction_a91e73";
|
|
59
|
+
|
|
60
|
+
function declarationsOf(mod: Record<string, unknown>): unknown[] {
|
|
61
|
+
return Object.values(mod).flatMap((value) => Array.isArray(value) ? value : [value]);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function reservedCollision(concept: string): never {
|
|
65
|
+
throw new Error(
|
|
66
|
+
`Nomos internal concept '${concept}' is already taken by the framework. Sorry mate, Nomos will not ` +
|
|
67
|
+
"silently overwrite or reroute the tenant declaration; give the business concept a different name.",
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function isDirective(value: unknown): value is { id: string; payloadSchema: unknown; businessBirthInternal?: true } {
|
|
72
|
+
return value !== null
|
|
73
|
+
&& typeof value === "object"
|
|
74
|
+
&& typeof (value as { id?: unknown }).id === "string"
|
|
75
|
+
&& (value as { payloadSchema?: unknown }).payloadSchema !== undefined
|
|
76
|
+
&& typeof (value as { plan?: unknown }).plan === "function";
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Every domain with at least one public business verb carries the same private correction primitive.
|
|
81
|
+
* The package and sealed engine call this at their shared merge seam, so generated availability can
|
|
82
|
+
* never drift from installed law. Stable id/export collisions fail loudly instead of becoming magic.
|
|
83
|
+
*/
|
|
84
|
+
export function withBusinessCorrectionExports<M extends Record<string, unknown>>(mod: M): Record<string, unknown> {
|
|
85
|
+
const declarations = declarationsOf(mod);
|
|
86
|
+
const hasBusinessVerb = declarations.some(
|
|
87
|
+
(value) => isDirective(value) && value.businessBirthInternal !== true,
|
|
88
|
+
);
|
|
89
|
+
if (!hasBusinessVerb) return mod;
|
|
90
|
+
|
|
91
|
+
if (Object.prototype.hasOwnProperty.call(mod, RECORD_EXPORT) && mod[RECORD_EXPORT] !== businessCorrectionRecord) {
|
|
92
|
+
reservedCollision(RECORD_EXPORT);
|
|
93
|
+
}
|
|
94
|
+
if (Object.prototype.hasOwnProperty.call(mod, DIRECTIVE_EXPORT) && mod[DIRECTIVE_EXPORT] !== recordBusinessCorrection) {
|
|
95
|
+
reservedCollision(DIRECTIVE_EXPORT);
|
|
96
|
+
}
|
|
97
|
+
for (const value of declarations) {
|
|
98
|
+
if (
|
|
99
|
+
value !== businessCorrectionRecord
|
|
100
|
+
&& value !== null
|
|
101
|
+
&& typeof value === "object"
|
|
102
|
+
&& (value as { id?: unknown }).id === BUSINESS_CORRECTION_AGGREGATE
|
|
103
|
+
) {
|
|
104
|
+
reservedCollision(BUSINESS_CORRECTION_AGGREGATE);
|
|
105
|
+
}
|
|
106
|
+
if (
|
|
107
|
+
value !== recordBusinessCorrection
|
|
108
|
+
&& isDirective(value)
|
|
109
|
+
&& value.id === BUSINESS_CORRECTION_DIRECTIVE
|
|
110
|
+
) {
|
|
111
|
+
reservedCollision(BUSINESS_CORRECTION_DIRECTIVE);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return {
|
|
116
|
+
...mod,
|
|
117
|
+
[RECORD_EXPORT]: businessCorrectionRecord,
|
|
118
|
+
[DIRECTIVE_EXPORT]: recordBusinessCorrection,
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Layered USD lowers each authored module separately, beneath `composeDomainModule`'s normal
|
|
124
|
+
* framework-expansion seam. Carry the universal correction law once, in the weakest layer, so
|
|
125
|
+
* flatten-after-lower remains byte-identical to lower-after-compose. An already-composed layer is
|
|
126
|
+
* left alone; partial or tenant-owned declarations of either reserved id fail loudly.
|
|
127
|
+
*/
|
|
128
|
+
export function withBusinessCorrectionLayer(
|
|
129
|
+
modules: readonly DomainModule[],
|
|
130
|
+
): readonly DomainModule[] {
|
|
131
|
+
const hasBusinessVerb = modules.some((module) =>
|
|
132
|
+
module.directives.some((value) => (value as { businessBirthInternal?: true }).businessBirthInternal !== true),
|
|
133
|
+
);
|
|
134
|
+
if (!hasBusinessVerb) return modules;
|
|
135
|
+
|
|
136
|
+
const correctionAggregates = modules.flatMap((module) =>
|
|
137
|
+
module.aggregates.filter((value) => value.id === BUSINESS_CORRECTION_AGGREGATE),
|
|
138
|
+
);
|
|
139
|
+
const correctionDirectives = modules.flatMap((module) =>
|
|
140
|
+
module.directives.filter((value) => value.id === BUSINESS_CORRECTION_DIRECTIVE),
|
|
141
|
+
);
|
|
142
|
+
if (correctionAggregates.some(
|
|
143
|
+
(value) => (value as AggregateHandle & { businessBirthInternal?: true }).businessBirthInternal !== true,
|
|
144
|
+
)) {
|
|
145
|
+
reservedCollision(BUSINESS_CORRECTION_AGGREGATE);
|
|
146
|
+
}
|
|
147
|
+
if (correctionDirectives.some(
|
|
148
|
+
(value) => (value as typeof value & { businessBirthInternal?: true }).businessBirthInternal !== true,
|
|
149
|
+
)) {
|
|
150
|
+
reservedCollision(BUSINESS_CORRECTION_DIRECTIVE);
|
|
151
|
+
}
|
|
152
|
+
if ((correctionAggregates.length === 0) !== (correctionDirectives.length === 0)) {
|
|
153
|
+
throw new Error(
|
|
154
|
+
"Nomos internal business-correction law is only partially present in the layered domain; " +
|
|
155
|
+
"refusing to emit a law whose testimony and retraction halves can drift.",
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
if (correctionAggregates.length > 0) return modules;
|
|
159
|
+
|
|
160
|
+
const [weakest, ...stronger] = modules;
|
|
161
|
+
if (weakest === undefined) return modules;
|
|
162
|
+
return [
|
|
163
|
+
{
|
|
164
|
+
...weakest,
|
|
165
|
+
aggregates: [...weakest.aggregates, businessCorrectionRecord],
|
|
166
|
+
directives: [...weakest.directives, recordBusinessCorrection],
|
|
167
|
+
},
|
|
168
|
+
...stronger,
|
|
169
|
+
];
|
|
170
|
+
}
|
|
@@ -62,7 +62,7 @@ export const Disclosure = aggregate("Disclosure", {
|
|
|
62
62
|
purpose: t.string().merge(Lww), // why (audit metadata)
|
|
63
63
|
generatedBy: t.string().merge(Lww), // authoring actor
|
|
64
64
|
generatedAt: t.string().merge(Lww), // ISO-8601
|
|
65
|
-
});
|
|
65
|
+
}).public();
|
|
66
66
|
|
|
67
67
|
/**
|
|
68
68
|
* `recordDisclosure` — `.creates` a Disclosure (instance-bound by
|
|
@@ -12,13 +12,15 @@
|
|
|
12
12
|
* * `PolicyBundle` is the content-addressed package object (`policy:{hash}.bundle`);
|
|
13
13
|
* * `DomainInstallation` is the workspace resource with spec/status/phase;
|
|
14
14
|
* * `installDomain` writes BOTH in one intent and declares the `installDomain`
|
|
15
|
-
* capability.
|
|
16
|
-
*
|
|
15
|
+
* capability. `stageDomain` writes the same content as Pending without serving it;
|
|
16
|
+
* `promoteDomain` and `rollbackDomain` are phase changes whose admission is judged
|
|
17
|
+
* against the staged package. This is the richer controller API; bootstrap exposes
|
|
18
|
+
* a thinner install-only intent for stage-zero.
|
|
17
19
|
*/
|
|
18
20
|
import { z } from "zod";
|
|
19
21
|
import { aggregate, instance } from "../aggregate.js";
|
|
20
22
|
import { Lww, MapOf } from "../drivers.js";
|
|
21
|
-
import { t } from "../fields.js";
|
|
23
|
+
import { attributionField, t } from "../fields.js";
|
|
22
24
|
import { directive } from "../directive.js";
|
|
23
25
|
import { set, withMarker, addToSet, type PlannedOp } from "../ops.js";
|
|
24
26
|
|
|
@@ -35,7 +37,7 @@ export type DomainInstallationPhase = (typeof DOMAIN_INSTALLATION_PHASES)[number
|
|
|
35
37
|
|
|
36
38
|
export const PolicyBundle = aggregate("PolicyBundle", {
|
|
37
39
|
bundle: t.string().merge(Lww),
|
|
38
|
-
});
|
|
40
|
+
}).public();
|
|
39
41
|
|
|
40
42
|
export const DomainInstallation = aggregate("DomainInstallation", {
|
|
41
43
|
"spec.domainHash": t.string().merge(Lww),
|
|
@@ -51,7 +53,39 @@ export const DomainInstallation = aggregate("DomainInstallation", {
|
|
|
51
53
|
"status.observedGeneration": t.int().merge(Lww),
|
|
52
54
|
"status.supersededBy": t.string().merge(Lww).optional(),
|
|
53
55
|
"status.conditions": t.map(t.json()).merge(MapOf(Lww)),
|
|
54
|
-
});
|
|
56
|
+
}).public();
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* The accepted, exact-history evidence for one activation. This is deliberately an
|
|
60
|
+
* obscure framework concept: tenant models never author or name it. The generated
|
|
61
|
+
* lifecycle client runs pre-flight and folds this receipt in the SAME intent that
|
|
62
|
+
* activates the candidate, so recording evidence cannot make its own head anchor stale.
|
|
63
|
+
*/
|
|
64
|
+
export const RELEASE_RECEIPT_AGGREGATE = "nomos_internal_ReleaseReceipt_6f0d3a";
|
|
65
|
+
export const RELEASE_RECEIPT_PREFIX = "nomos-release-receipt:";
|
|
66
|
+
|
|
67
|
+
export const ReleaseReceipt = aggregate(RELEASE_RECEIPT_AGGREGATE, {
|
|
68
|
+
candidateHash: t.string().merge(Lww),
|
|
69
|
+
fromHash: t.string().merge(Lww).optional(),
|
|
70
|
+
domain: t.string().merge(Lww),
|
|
71
|
+
atHash: t.string().merge(Lww),
|
|
72
|
+
atStateRoot: t.string().merge(Lww),
|
|
73
|
+
historyHash: t.string().merge(Lww),
|
|
74
|
+
reportHash: t.string().merge(Lww),
|
|
75
|
+
reportJson: t.string().merge(Lww),
|
|
76
|
+
complete: t.bool(),
|
|
77
|
+
evolveOk: t.bool(),
|
|
78
|
+
rawPromotable: t.bool(),
|
|
79
|
+
projectionEqual: t.bool(),
|
|
80
|
+
replayedIntents: t.int().merge(Lww),
|
|
81
|
+
candidateIntents: t.int().merge(Lww),
|
|
82
|
+
divergenceCount: t.int().merge(Lww),
|
|
83
|
+
baselineRoot: t.string().merge(Lww),
|
|
84
|
+
candidateRoot: t.string().merge(Lww),
|
|
85
|
+
acceptanceReason: t.string().merge(Lww).optional(),
|
|
86
|
+
acceptedBy: attributionField(t.string().merge(Lww)),
|
|
87
|
+
acceptedAt: t.int().merge(Lww),
|
|
88
|
+
}).public();
|
|
55
89
|
|
|
56
90
|
export function policyAggregateId(domainHash: string): string {
|
|
57
91
|
return `policy:${domainHash}`;
|
|
@@ -69,16 +103,66 @@ function domainInstallationInstance(domainHash: string) {
|
|
|
69
103
|
return instance(DomainInstallation, domainInstallationAggregateId(domainHash));
|
|
70
104
|
}
|
|
71
105
|
|
|
106
|
+
export function releaseReceiptAggregateId(reportHash: string): string {
|
|
107
|
+
return `${RELEASE_RECEIPT_PREFIX}${reportHash}`;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function releaseReceiptInstance(reportHash: string) {
|
|
111
|
+
return instance(ReleaseReceipt, releaseReceiptAggregateId(reportHash));
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const dispositions = z.object({
|
|
115
|
+
retired: z.array(z.string()).optional(),
|
|
116
|
+
retyped: z.record(z.string(), z.string()).optional(),
|
|
117
|
+
rebinds: z.record(z.string(), z.string()).optional(),
|
|
118
|
+
ackBy: z.string().optional(),
|
|
119
|
+
}).optional();
|
|
120
|
+
|
|
121
|
+
const lowerHex64 = z.string().regex(/^[0-9a-f]{64}$/);
|
|
122
|
+
const gitOid = z.string().regex(/^[0-9a-f]{40}$/);
|
|
123
|
+
const preflightReceipt = z.object({
|
|
124
|
+
candidateHash: lowerHex64,
|
|
125
|
+
domain: z.string().min(1),
|
|
126
|
+
atHash: gitOid,
|
|
127
|
+
atStateRoot: lowerHex64,
|
|
128
|
+
historyHash: lowerHex64,
|
|
129
|
+
reportHash: lowerHex64,
|
|
130
|
+
reportJson: z.string().min(2),
|
|
131
|
+
complete: z.literal(true),
|
|
132
|
+
evolveOk: z.literal(true),
|
|
133
|
+
rawPromotable: z.boolean(),
|
|
134
|
+
projectionEqual: z.boolean(),
|
|
135
|
+
replayedIntents: z.number().int().nonnegative(),
|
|
136
|
+
candidateIntents: z.number().int().nonnegative(),
|
|
137
|
+
divergenceCount: z.number().int().nonnegative(),
|
|
138
|
+
baselineRoot: lowerHex64,
|
|
139
|
+
candidateRoot: lowerHex64,
|
|
140
|
+
acceptanceReason: z.string().trim().min(1).optional(),
|
|
141
|
+
}).superRefine((receipt, ctx) => {
|
|
142
|
+
if (!receipt.rawPromotable && receipt.acceptanceReason === undefined) {
|
|
143
|
+
ctx.addIssue({
|
|
144
|
+
code: z.ZodIssueCode.custom,
|
|
145
|
+
path: ["acceptanceReason"],
|
|
146
|
+
message: "a divergent but complete report needs an explicit business acceptance reason",
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
const packagePayload = {
|
|
152
|
+
domainHash: z.string(),
|
|
153
|
+
packageUsda: z.string(),
|
|
154
|
+
installedBy: z.string(),
|
|
155
|
+
authorityScope: z.string().default("workspace/root"),
|
|
156
|
+
dependencies: z.array(z.string()).default([]),
|
|
157
|
+
supersedes: z.string().optional(),
|
|
158
|
+
finalizers: z.array(z.string()).default([]),
|
|
159
|
+
};
|
|
160
|
+
|
|
72
161
|
export const installDomain = directive("installDomain")
|
|
73
162
|
.ensures(DomainInstallation)
|
|
74
163
|
.payload(
|
|
75
164
|
z.object({
|
|
76
|
-
|
|
77
|
-
packageUsda: z.string(),
|
|
78
|
-
installedBy: z.string(),
|
|
79
|
-
authorityScope: z.string().default("workspace/root"),
|
|
80
|
-
dependencies: z.array(z.string()).default([]),
|
|
81
|
-
supersedes: z.string().optional(),
|
|
165
|
+
...packagePayload,
|
|
82
166
|
// THE FRONTIER TRANSITION (install-lifecycle): `replaces` names a PRIOR install this
|
|
83
167
|
// deploy makes current UNEQUIVOCALLY — the prior install is flipped out of the served
|
|
84
168
|
// frontier (`Superseded`, or `Retired` when `retireReplaced`) and gets the reverse
|
|
@@ -86,7 +170,10 @@ export const installDomain = directive("installDomain")
|
|
|
86
170
|
// byte-identical ops to the pre-lifecycle plan). `replaces` also sets `spec.supersedes`.
|
|
87
171
|
replaces: z.string().optional(),
|
|
88
172
|
retireReplaced: z.boolean().default(false),
|
|
89
|
-
|
|
173
|
+
dispositions,
|
|
174
|
+
}).refine((p) => p.replaces === undefined || p.replaces !== p.domainHash, {
|
|
175
|
+
message: "a domain package cannot replace itself",
|
|
176
|
+
path: ["replaces"],
|
|
90
177
|
}),
|
|
91
178
|
)
|
|
92
179
|
.plan((p): PlannedOp[] => {
|
|
@@ -103,8 +190,8 @@ export const installDomain = directive("installDomain")
|
|
|
103
190
|
set(install, "status.phase", "Active"),
|
|
104
191
|
set(install, "status.observedGeneration", 1),
|
|
105
192
|
];
|
|
106
|
-
if (p.dependencies.length > 0) {
|
|
107
|
-
ops.push(addToSet(install, "spec.dependencies", p.dependencies));
|
|
193
|
+
if ((p.dependencies ?? []).length > 0) {
|
|
194
|
+
ops.push(addToSet(install, "spec.dependencies", p.dependencies ?? []));
|
|
108
195
|
}
|
|
109
196
|
// The predecessor back-link: `supersedes` (explicit) or `replaces` (the actionable form).
|
|
110
197
|
const supersedes = p.supersedes ?? p.replaces;
|
|
@@ -119,9 +206,156 @@ export const installDomain = directive("installDomain")
|
|
|
119
206
|
);
|
|
120
207
|
ops.push(set(replaced, "status.supersededBy", p.domainHash));
|
|
121
208
|
}
|
|
122
|
-
if (p.finalizers.length > 0) {
|
|
123
|
-
ops.push(addToSet(install, "spec.finalizers", p.finalizers));
|
|
209
|
+
if ((p.finalizers ?? []).length > 0) {
|
|
210
|
+
ops.push(addToSet(install, "spec.finalizers", p.finalizers ?? []));
|
|
211
|
+
}
|
|
212
|
+
return ops;
|
|
213
|
+
})
|
|
214
|
+
.requires("installDomain");
|
|
215
|
+
|
|
216
|
+
/** Store candidate law in the ledger without changing the served law frontier. The
|
|
217
|
+
* kernel content-verifies the package and lifecycle pair, while the Pending phase is
|
|
218
|
+
* deliberately invisible to dispatch, reads and replay's current-law map. */
|
|
219
|
+
export const stageDomain = directive("stageDomain")
|
|
220
|
+
.ensures(DomainInstallation)
|
|
221
|
+
.payload(z.object({ ...packagePayload }).refine(
|
|
222
|
+
(p) => p.supersedes === undefined || p.supersedes !== p.domainHash,
|
|
223
|
+
{ message: "a staged domain package cannot supersede itself", path: ["supersedes"] },
|
|
224
|
+
))
|
|
225
|
+
.plan((p): PlannedOp[] => {
|
|
226
|
+
const policy = policyBundleInstance(p.domainHash);
|
|
227
|
+
const install = domainInstallationInstance(p.domainHash);
|
|
228
|
+
const ops: PlannedOp[] = [
|
|
229
|
+
withMarker(set(policy, "bundle", p.packageUsda), "ensures"),
|
|
230
|
+
set(install, "spec.domainHash", p.domainHash),
|
|
231
|
+
set(install, "spec.packageHash", p.domainHash),
|
|
232
|
+
set(install, "spec.policyAggregate", policyAggregateId(p.domainHash)),
|
|
233
|
+
set(install, "spec.authorityScope", p.authorityScope),
|
|
234
|
+
set(install, "spec.installedBy", p.installedBy),
|
|
235
|
+
set(install, "metadata.generation", 1),
|
|
236
|
+
set(install, "status.phase", "Pending"),
|
|
237
|
+
set(install, "status.observedGeneration", 0),
|
|
238
|
+
];
|
|
239
|
+
if ((p.dependencies ?? []).length > 0) ops.push(addToSet(install, "spec.dependencies", p.dependencies ?? []));
|
|
240
|
+
if (p.supersedes !== undefined) ops.push(set(install, "spec.supersedes", p.supersedes));
|
|
241
|
+
if ((p.finalizers ?? []).length > 0) ops.push(addToSet(install, "spec.finalizers", p.finalizers ?? []));
|
|
242
|
+
return ops;
|
|
243
|
+
})
|
|
244
|
+
.requires("installDomain");
|
|
245
|
+
|
|
246
|
+
/** Make a staged package current. The package bytes are already committed; the kernel
|
|
247
|
+
* re-loads them from prefix state and runs the evolve gate on this phase transition.
|
|
248
|
+
* The prior version stays Active by default so gradual-rollout clients can still have
|
|
249
|
+
* their declared era checked; the newer activation stamp alone makes this law current. */
|
|
250
|
+
export const promoteDomain = directive("promoteDomain")
|
|
251
|
+
.mutates(DomainInstallation)
|
|
252
|
+
.payload(z.object({
|
|
253
|
+
domainHash: z.string(),
|
|
254
|
+
replaces: z.string().optional(),
|
|
255
|
+
retireReplaced: z.boolean().default(false),
|
|
256
|
+
dispositions,
|
|
257
|
+
preflight: preflightReceipt,
|
|
258
|
+
}).refine((p) => p.replaces === undefined || p.replaces !== p.domainHash, {
|
|
259
|
+
message: "a promoted domain package cannot replace itself",
|
|
260
|
+
path: ["replaces"],
|
|
261
|
+
}).refine((p) => p.preflight.candidateHash === p.domainHash, {
|
|
262
|
+
message: "the pre-flight receipt must name the package being promoted",
|
|
263
|
+
path: ["preflight", "candidateHash"],
|
|
264
|
+
}))
|
|
265
|
+
.plan((p, ctx): PlannedOp[] => {
|
|
266
|
+
const promoted = domainInstallationInstance(p.domainHash);
|
|
267
|
+
const receipt = releaseReceiptInstance(p.preflight.reportHash);
|
|
268
|
+
const ops: PlannedOp[] = [
|
|
269
|
+
set(promoted, "status.phase", "Active"),
|
|
270
|
+
set(promoted, "status.observedGeneration", 1),
|
|
271
|
+
withMarker(set(receipt, "candidateHash", p.domainHash), "ensures"),
|
|
272
|
+
...(p.replaces !== undefined ? [set(receipt, "fromHash", p.replaces)] : []),
|
|
273
|
+
set(receipt, "domain", p.preflight.domain),
|
|
274
|
+
set(receipt, "atHash", p.preflight.atHash),
|
|
275
|
+
set(receipt, "atStateRoot", p.preflight.atStateRoot),
|
|
276
|
+
set(receipt, "historyHash", p.preflight.historyHash),
|
|
277
|
+
set(receipt, "reportHash", p.preflight.reportHash),
|
|
278
|
+
set(receipt, "reportJson", p.preflight.reportJson),
|
|
279
|
+
set(receipt, "complete", true),
|
|
280
|
+
set(receipt, "evolveOk", true),
|
|
281
|
+
set(receipt, "rawPromotable", p.preflight.rawPromotable),
|
|
282
|
+
set(receipt, "projectionEqual", p.preflight.projectionEqual),
|
|
283
|
+
set(receipt, "replayedIntents", p.preflight.replayedIntents),
|
|
284
|
+
set(receipt, "candidateIntents", p.preflight.candidateIntents),
|
|
285
|
+
set(receipt, "divergenceCount", p.preflight.divergenceCount),
|
|
286
|
+
set(receipt, "baselineRoot", p.preflight.baselineRoot),
|
|
287
|
+
set(receipt, "candidateRoot", p.preflight.candidateRoot),
|
|
288
|
+
...(p.preflight.acceptanceReason !== undefined
|
|
289
|
+
? [set(receipt, "acceptanceReason", p.preflight.acceptanceReason)]
|
|
290
|
+
: []),
|
|
291
|
+
set(receipt, "acceptedBy", ctx.principal().asAttribution()),
|
|
292
|
+
set(receipt, "acceptedAt", ctx.clock().physical),
|
|
293
|
+
];
|
|
294
|
+
if (p.replaces !== undefined) {
|
|
295
|
+
const replaced = domainInstallationInstance(p.replaces);
|
|
296
|
+
ops.push(set(promoted, "spec.supersedes", p.replaces));
|
|
297
|
+
if (p.retireReplaced) {
|
|
298
|
+
ops.push(set(replaced, "status.phase", "Retired"));
|
|
299
|
+
ops.push(set(replaced, "status.supersededBy", p.domainHash));
|
|
300
|
+
}
|
|
124
301
|
}
|
|
125
302
|
return ops;
|
|
126
303
|
})
|
|
304
|
+
.alsoEnsures(ReleaseReceipt)
|
|
305
|
+
.requires("installDomain");
|
|
306
|
+
|
|
307
|
+
/** Rollback is a new governed transition, never a history-pointer rewind. The prior
|
|
308
|
+
* package is promoted from committed custody and the failed candidate becomes Disabled;
|
|
309
|
+
* the kernel applies the same evolve judgment in the reverse direction. */
|
|
310
|
+
export const rollbackDomain = directive("rollbackDomain")
|
|
311
|
+
.mutates(DomainInstallation)
|
|
312
|
+
.payload(z.object({
|
|
313
|
+
candidateHash: z.string(),
|
|
314
|
+
priorHash: z.string(),
|
|
315
|
+
dispositions,
|
|
316
|
+
preflight: preflightReceipt,
|
|
317
|
+
}).refine((p) => p.candidateHash !== p.priorHash, {
|
|
318
|
+
message: "rollback candidate and prior package must differ",
|
|
319
|
+
path: ["priorHash"],
|
|
320
|
+
}).refine((p) => p.preflight.candidateHash === p.priorHash, {
|
|
321
|
+
message: "the rollback pre-flight receipt must name the prior package being restored",
|
|
322
|
+
path: ["preflight", "candidateHash"],
|
|
323
|
+
}).refine((p) => (p.preflight.acceptanceReason ?? "").trim().length > 0, {
|
|
324
|
+
message: "restoring previous law needs an attributed business reason",
|
|
325
|
+
path: ["preflight", "acceptanceReason"],
|
|
326
|
+
}))
|
|
327
|
+
.plan((p, ctx): PlannedOp[] => {
|
|
328
|
+
const candidate = domainInstallationInstance(p.candidateHash);
|
|
329
|
+
const prior = domainInstallationInstance(p.priorHash);
|
|
330
|
+
const receipt = releaseReceiptInstance(p.preflight.reportHash);
|
|
331
|
+
return [
|
|
332
|
+
set(candidate, "status.phase", "Disabled"),
|
|
333
|
+
set(candidate, "status.supersededBy", p.priorHash),
|
|
334
|
+
set(prior, "status.phase", "Active"),
|
|
335
|
+
set(prior, "status.observedGeneration", 1),
|
|
336
|
+
withMarker(set(receipt, "candidateHash", p.priorHash), "ensures"),
|
|
337
|
+
set(receipt, "fromHash", p.candidateHash),
|
|
338
|
+
set(receipt, "domain", p.preflight.domain),
|
|
339
|
+
set(receipt, "atHash", p.preflight.atHash),
|
|
340
|
+
set(receipt, "atStateRoot", p.preflight.atStateRoot),
|
|
341
|
+
set(receipt, "historyHash", p.preflight.historyHash),
|
|
342
|
+
set(receipt, "reportHash", p.preflight.reportHash),
|
|
343
|
+
set(receipt, "reportJson", p.preflight.reportJson),
|
|
344
|
+
set(receipt, "complete", true),
|
|
345
|
+
set(receipt, "evolveOk", true),
|
|
346
|
+
set(receipt, "rawPromotable", p.preflight.rawPromotable),
|
|
347
|
+
set(receipt, "projectionEqual", p.preflight.projectionEqual),
|
|
348
|
+
set(receipt, "replayedIntents", p.preflight.replayedIntents),
|
|
349
|
+
set(receipt, "candidateIntents", p.preflight.candidateIntents),
|
|
350
|
+
set(receipt, "divergenceCount", p.preflight.divergenceCount),
|
|
351
|
+
set(receipt, "baselineRoot", p.preflight.baselineRoot),
|
|
352
|
+
set(receipt, "candidateRoot", p.preflight.candidateRoot),
|
|
353
|
+
...(p.preflight.acceptanceReason !== undefined
|
|
354
|
+
? [set(receipt, "acceptanceReason", p.preflight.acceptanceReason)]
|
|
355
|
+
: []),
|
|
356
|
+
set(receipt, "acceptedBy", ctx.principal().asAttribution()),
|
|
357
|
+
set(receipt, "acceptedAt", ctx.clock().physical),
|
|
358
|
+
];
|
|
359
|
+
})
|
|
360
|
+
.alsoEnsures(ReleaseReceipt)
|
|
127
361
|
.requires("installDomain");
|
|
@@ -50,7 +50,7 @@ export const EncKey = aggregate("EncKey", {
|
|
|
50
50
|
principal: t.string().merge(Lww),
|
|
51
51
|
encPublicKey: t.string().merge(Lww), // base64 X25519 (32 bytes)
|
|
52
52
|
publishedAt: t.string().merge(Lww), // ISO-8601, caller-stamped (determinism)
|
|
53
|
-
});
|
|
53
|
+
}).public();
|
|
54
54
|
export type EncKeyHandle = typeof EncKey;
|
|
55
55
|
|
|
56
56
|
/** Deterministic per-principal instance id — `enc-key-<principal>`. */
|