@githolon/dsl 0.102.4 → 0.103.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/compile_package.mjs +43 -2
- package/compile_proof.d.mts +47 -0
- package/compile_proof.mjs +343 -0
- package/dart/CHANGELOG.md +7 -0
- package/dart/lib/src/dev_cloud.dart +8 -8
- package/dart/lib/src/nms_generated_runtime.dart +2 -0
- package/dart/lib/src/runner/node-entry.mjs +5504 -1236
- package/dart/lib/src/runner/offline-install.mjs +4578 -528
- package/dart/lib/src/test_harness.dart +206 -152
- package/dart/lib/src/webview_bridge.dart +196 -89
- package/dart/pubspec.lock +1 -1
- package/dart/pubspec.yaml +2 -2
- package/dart/test/acquire_session_credentials_test.dart +4 -3
- package/dart/test/adv_realm_interference_test.dart +7 -6
- package/dart/test/cloud_bound_topology_test.dart +3 -3
- package/dart/test/cluster_births_test.dart +4 -3
- package/dart/test/cluster_device_keyed_acceptance_test.dart +10 -16
- package/dart/test/cluster_local_autosync_test.dart +3 -2
- package/dart/test/cluster_scale_test.dart +3 -2
- package/dart/test/dlq_session_surface_test.dart +7 -6
- package/dart/test/first_app_gauntlet_test.dart +14 -21
- package/dart/test/fixtures/wedge/fake-install.mjs +1 -1
- package/dart/test/four_layer_nested_topology_test.dart +4 -4
- package/dart/test/harness_phase_timeout_test.dart +2 -2
- package/dart/test/local_sync_inert_test.dart +1 -1
- package/dart/test/parent_signer_recovery_test.dart +2 -2
- package/dart/test/sealing_client_test.dart +4 -3
- package/dart/test/session_bound_test.dart +8 -7
- package/dart/test/test_harness_test.dart +13 -12
- package/dart/test/vo_read_typing_test.dart +4 -3
- package/dart/test/watch_after_offer_stall_test.dart +4 -3
- package/dart/test/webview_bridge_test.dart +102 -5
- package/dart_types/CHANGELOG.md +5 -0
- package/dart_types/lib/nomos_types.dart +9 -0
- package/dart_types/lib/src/nomos_ref.dart +20 -0
- package/dart_types/lib/src/optional_ref.dart +85 -0
- package/dart_types/lib/src/sha256_digest.dart +41 -0
- package/dart_types/lib/src/versioned_nomos_ref.dart +81 -0
- package/dart_types/pubspec.yaml +1 -1
- package/dart_types/test/optional_ref_test.dart +44 -0
- package/dart_types/test/sha256_digest_test.dart +18 -0
- package/package.json +9 -5
- package/runtime/generated-application-host.js +5365 -1105
- package/src/aggregate.ts +18 -0
- package/src/application_composition.ts +6 -32
- package/src/attested_core.ts +6 -3
- package/src/birth_meter.ts +1 -0
- package/src/birth_meter_ensure.ts +28 -2
- package/src/births.ts +1 -1
- package/src/build_package.ts +162 -538
- package/src/business_birth.ts +19 -3
- package/src/canonical_json.ts +21 -0
- package/src/check_package_main.ts +76 -35
- package/src/clear_directive.ts +19 -8
- package/src/codegen_dart.ts +305 -22
- package/src/codegen_dart_application.ts +844 -292
- package/src/codegen_dart_client.ts +1084 -407
- package/src/codegen_dart_scenario.ts +12 -12
- package/src/codegen_proof.ts +169 -39
- package/src/codegen_provider_dart.ts +17 -1
- package/src/codegen_ts.ts +298 -106
- package/src/compat_manifest.ts +2 -2
- package/src/compat_package_main.ts +8 -12
- package/src/compile_package_main.ts +1866 -597
- package/src/compose.ts +1 -1
- package/src/directive.ts +208 -15
- package/src/engine_entry.ts +165 -111
- package/src/evolve_preview.ts +43 -81
- package/src/extremum.ts +1 -1
- package/src/fields.ts +183 -4
- package/src/flow_gate.ts +75 -2
- package/src/framework/application_home.ts +2 -0
- package/src/framework/auth_provider_law.ts +2 -2
- package/src/framework/birth_meter.ts +7 -19
- package/src/framework/bootstrap.ts +0 -2
- package/src/framework/business_corrections.ts +9 -5
- package/src/framework/domain_lifecycle.ts +15 -29
- package/src/framework/encryption_scope.ts +2 -1
- package/src/framework/framework_aggregates.ts +2 -0
- package/src/framework/genesis.ts +16 -14
- package/src/framework/home.ts +20 -14
- package/src/framework/owned_custody.ts +9 -3
- package/src/framework/platform_admin.ts +3 -3
- package/src/framework/relations.ts +21 -13
- package/src/framework/signing_identity.ts +45 -40
- package/src/framework/workspace_invariant.ts +245 -0
- package/src/framework/workspaces.ts +23 -29
- package/src/index.ts +36 -16
- package/src/intent.ts +178 -6
- package/src/law_check.ts +32 -11
- package/src/law_graph.ts +2902 -0
- package/src/law_graph_usda.ts +1799 -0
- package/src/law_identity.ts +435 -0
- package/src/law_value_type.ts +230 -0
- package/src/lsp_server.ts +61 -31
- package/src/ops.ts +7 -0
- package/src/package_config.ts +141 -0
- package/src/package_dependencies.ts +108 -0
- package/src/package_dependency_usage.ts +150 -0
- package/src/package_source_imports.ts +71 -0
- package/src/plan_source_slice.ts +335 -0
- package/src/point_guard.ts +192 -0
- package/src/precondition.ts +44 -9
- package/src/read_projection_namespace.ts +1 -1
- package/src/runtime_zod.ts +44 -0
- package/src/secondary_ensure.ts +67 -14
- package/src/shape_compat.ts +1 -1
- package/src/stable_id_identity_bridge.ts +149 -0
- package/src/stable_id_lockfile.ts +278 -0
- package/src/stable_ids.ts +7 -2
- package/src/stable_ids_for_module.ts +348 -0
- package/src/stable_ids_types.ts +1 -1
- package/src/sum.ts +1 -1
- package/src/usd_state.ts +1 -1
- package/src/usda_relationship_integrity.ts +66 -0
- package/src/usda_text.ts +18 -0
- package/src/usdz.ts +78 -0
- package/src/value_object.ts +95 -0
- package/src/wire.ts +19 -0
- package/src/wire_encode.ts +22 -0
- package/src/workspace_sharding.ts +52 -36
- package/src/workspace_sharing.ts +28 -4
- package/src/zod_introspection.ts +1 -1
- package/dart/test/cluster_attested_test.dart +0 -201
- package/src/compile_engine.ts +0 -595
- package/src/manifest.ts +0 -1476
- package/src/read_projection_composition.ts +0 -117
- package/src/read_projection_manifest.ts +0 -115
- package/src/relation_composition.ts +0 -286
- package/src/usd.ts +0 -1967
- package/src/usd_layers.ts +0 -1776
|
@@ -49,7 +49,7 @@ import { directive } from "../directive.js";
|
|
|
49
49
|
import { set, withMarker, type PlannedOp } from "../ops.js";
|
|
50
50
|
import { query } from "../query.js";
|
|
51
51
|
import { relationType, self, key } from "../relation_schema.js";
|
|
52
|
-
import { workspaceInvariant, ref } from "./workspace_invariant.js";
|
|
52
|
+
import { readRef, readSet, workspaceInvariant, ref } from "./workspace_invariant.js";
|
|
53
53
|
import { endorsementOf, type EndorsedAuthority } from "../principal_evidence.js";
|
|
54
54
|
import {
|
|
55
55
|
writeTuple,
|
|
@@ -144,7 +144,7 @@ export const advanceSignerEpoch = directive("advanceSignerEpoch")
|
|
|
144
144
|
*/
|
|
145
145
|
export const signerEpochStrictlyIncreases = workspaceInvariant("SignerEpochStrictlyIncreases")
|
|
146
146
|
.on("advanceSignerEpoch")
|
|
147
|
-
.reads((
|
|
147
|
+
.reads(readSet(readRef("signerEpoch", SignerEpoch).at(SIGNER_EPOCH_ID)))
|
|
148
148
|
.assert((snap, ctx) => {
|
|
149
149
|
const applied = snap["signerEpoch"] as { currentEpoch?: number } | undefined;
|
|
150
150
|
const declared = Number((ctx?.intent as { epoch?: number } | undefined)?.epoch ?? NaN);
|
|
@@ -166,7 +166,7 @@ export const signerEpochStrictlyIncreases = workspaceInvariant("SignerEpochStric
|
|
|
166
166
|
*/
|
|
167
167
|
export const revokeSignerEpochStrictlyIncreases = workspaceInvariant("RevokeSignerEpochStrictlyIncreases")
|
|
168
168
|
.on("revokeSigner")
|
|
169
|
-
.reads((
|
|
169
|
+
.reads(readSet(readRef("signerEpoch", SignerEpoch).at(SIGNER_EPOCH_ID)))
|
|
170
170
|
.assert((snap, ctx) => {
|
|
171
171
|
const applied = snap["signerEpoch"] as { currentEpoch?: number } | undefined;
|
|
172
172
|
const declared = Number((ctx?.intent as { newEpoch?: number } | undefined)?.newEpoch ?? NaN);
|
|
@@ -289,7 +289,7 @@ export const enrollSignerSelf = directive("enrollSignerSelf")
|
|
|
289
289
|
*/
|
|
290
290
|
export const enrollSignerSelfRequiresVerified = workspaceInvariant("EnrollSignerSelfRequiresVerified")
|
|
291
291
|
.on("enrollSignerSelf")
|
|
292
|
-
.reads(()
|
|
292
|
+
.reads(readSet()) // no aggregate snapshot needed — the verdict is over the captured principal-verified flag
|
|
293
293
|
.declassify("the self-enroll gate intentionally branches on principal_verified (an authz fact, not a secret read)")
|
|
294
294
|
.assert((_snap, ctx) =>
|
|
295
295
|
ctx?.principalVerified === true
|
|
@@ -297,6 +297,44 @@ export const enrollSignerSelfRequiresVerified = workspaceInvariant("EnrollSigner
|
|
|
297
297
|
: { reject: "enroll-signer-self-requires-verified-principal" },
|
|
298
298
|
);
|
|
299
299
|
|
|
300
|
+
// ─────────────────── THE ENROLL-ONCE DELEGATION CERT (the peer-sovereign root-of-trust) ───────────────────
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* THE SIGNER DELEGATION (the enroll-once delegation cert, `executor::birth_cert`). When the TENANT verifies a
|
|
304
|
+
* user U's IdP token at U's live enroll gate, the tenant's onboarding signer `S_P` signs — ONCE — a
|
|
305
|
+
* DELEGATION CERT binding U's device key `P_U` to the tenant's lineage: a `BirthCert {founder: U,
|
|
306
|
+
* parent_key: P_U, …}` signed by `S_P`. This singleton (keyed by U) records it in the PLATFORM's folded
|
|
307
|
+
* state, so the kernel's birth offer-effect reads U's delegation by-user (trusted, not from the payload) when
|
|
308
|
+
* it assembles U's home cert chain. The cert is OPAQUE on-chain (a JSON `BirthCert`); the kernel parses +
|
|
309
|
+
* verifies it as one chain link (signed by S_P, whose pubkey is attested by the platform's own chain).
|
|
310
|
+
*
|
|
311
|
+
* SELF-BOUND / control-plane: enrolled by the tenant onboarding lane (composed into the tenant's governance
|
|
312
|
+
* law, off the open session lane), so a tenant cannot forge a delegation for a user another tenant verified.
|
|
313
|
+
* The delegation's `founder` IS the gate's binding — the kernel asserts the home cert's founder equals the
|
|
314
|
+
* delegation's founder (the crux founder-binding), so a delegation enrolled for A cannot mint a home for V.
|
|
315
|
+
*
|
|
316
|
+
* Declared before `enrollFirstDevice` because that directive's typed effect signature and optional plan arm
|
|
317
|
+
* both refer to this aggregate during module initialisation.
|
|
318
|
+
*/
|
|
319
|
+
export const SIGNER_DELEGATION_PREFIX = "signer-delegation:";
|
|
320
|
+
export const SignerDelegation = aggregate("SignerDelegation", {
|
|
321
|
+
/** The user (a `user:<uid>` subject) this delegation enrolls — the key of the singleton. */
|
|
322
|
+
user: t.string().merge(Lww),
|
|
323
|
+
/** The user's device PUBLIC KEY (base64) the delegation binds — `P_U`, the home_cert's signer. */
|
|
324
|
+
deviceKey: t.string().merge(Lww),
|
|
325
|
+
/** THE DELEGATION CERT (JSON `executor::birth_cert::BirthCert`), signed by the tenant's `S_P`. Opaque
|
|
326
|
+
* on-chain; the kernel parses + verifies it as the `links[1]` delegation in U's travelling chain. */
|
|
327
|
+
certJson: t.string().merge(Lww),
|
|
328
|
+
/** ISO-8601, caller-stamped (determinism — no clock in the plan). */
|
|
329
|
+
enrolledAt: t.string().merge(Lww),
|
|
330
|
+
}).public();
|
|
331
|
+
export type SignerDelegationHandle = typeof SignerDelegation;
|
|
332
|
+
|
|
333
|
+
/** The deterministic singleton id of a user's signer delegation (the kernel reads THIS id by-user). */
|
|
334
|
+
export function signerDelegationInstanceId(user: string): string {
|
|
335
|
+
return SIGNER_DELEGATION_PREFIX + user;
|
|
336
|
+
}
|
|
337
|
+
|
|
300
338
|
/**
|
|
301
339
|
* `enrollFirstDevice` (architecture/keyless_verified_onboard.md) — THE KEYLESS VERIFIED FIRST-DEVICE
|
|
302
340
|
* self-onboard. A brand-new principal (an IdP-verified user with NO enrolled signer key) bootstraps its
|
|
@@ -377,6 +415,7 @@ export const enrollFirstDevice = directive("enrollFirstDevice")
|
|
|
377
415
|
];
|
|
378
416
|
return [...tupleOps, ...delegationOps];
|
|
379
417
|
})
|
|
418
|
+
.alsoEnsures(SignerDelegation)
|
|
380
419
|
// THE KEYLESS GATE DECLARATION: the carried device-bound `idpToken` IS this directive's authorization
|
|
381
420
|
// (re-verified against the workspace AuthProvider on every lane), not a pre-enrolled signer. This lowers
|
|
382
421
|
// the per-directive payload FIELD MAPPING into the `nomosVerifiedFirstEnroll` era key, so the KERNEL reads
|
|
@@ -417,42 +456,8 @@ export const revokeSigner = directive("revokeSigner")
|
|
|
417
456
|
// bump the signer epoch so the revoked key ages out (the live-epoch backdate is phase 4).
|
|
418
457
|
...writeSignerEpoch(p.newEpoch),
|
|
419
458
|
];
|
|
420
|
-
})
|
|
421
|
-
|
|
422
|
-
// ─────────────────── THE ENROLL-ONCE DELEGATION CERT (the peer-sovereign root-of-trust) ───────────────────
|
|
423
|
-
|
|
424
|
-
/**
|
|
425
|
-
* THE SIGNER DELEGATION (the enroll-once delegation cert, `executor::birth_cert`). When the TENANT verifies a
|
|
426
|
-
* user U's IdP token at U's live enroll gate, the tenant's onboarding signer `S_P` signs — ONCE — a
|
|
427
|
-
* DELEGATION CERT binding U's device key `P_U` to the tenant's lineage: a `BirthCert {founder: U,
|
|
428
|
-
* parent_key: P_U, …}` signed by `S_P`. This singleton (keyed by U) records it in the PLATFORM's folded
|
|
429
|
-
* state, so the kernel's birth offer-effect reads U's delegation by-user (trusted, not from the payload) when
|
|
430
|
-
* it assembles U's home cert chain. The cert is OPAQUE on-chain (a JSON `BirthCert`); the kernel parses +
|
|
431
|
-
* verifies it as one chain link (signed by S_P, whose pubkey is attested by the platform's own chain).
|
|
432
|
-
*
|
|
433
|
-
* SELF-BOUND / control-plane: enrolled by the tenant onboarding lane (composed into the tenant's governance
|
|
434
|
-
* law, off the open session lane), so a tenant cannot forge a delegation for a user another tenant verified.
|
|
435
|
-
* The delegation's `founder` IS the gate's binding — the kernel asserts the home cert's founder equals the
|
|
436
|
-
* delegation's founder (the crux founder-binding), so a delegation enrolled for A cannot mint a home for V.
|
|
437
|
-
*/
|
|
438
|
-
export const SIGNER_DELEGATION_PREFIX = "signer-delegation:";
|
|
439
|
-
export const SignerDelegation = aggregate("SignerDelegation", {
|
|
440
|
-
/** The user (a `user:<uid>` subject) this delegation enrolls — the key of the singleton. */
|
|
441
|
-
user: t.string().merge(Lww),
|
|
442
|
-
/** The user's device PUBLIC KEY (base64) the delegation binds — `P_U`, the home_cert's signer. */
|
|
443
|
-
deviceKey: t.string().merge(Lww),
|
|
444
|
-
/** THE DELEGATION CERT (JSON `executor::birth_cert::BirthCert`), signed by the tenant's `S_P`. Opaque
|
|
445
|
-
* on-chain; the kernel parses + verifies it as the `links[1]` delegation in U's travelling chain. */
|
|
446
|
-
certJson: t.string().merge(Lww),
|
|
447
|
-
/** ISO-8601, caller-stamped (determinism — no clock in the plan). */
|
|
448
|
-
enrolledAt: t.string().merge(Lww),
|
|
449
|
-
}).public();
|
|
450
|
-
export type SignerDelegationHandle = typeof SignerDelegation;
|
|
451
|
-
|
|
452
|
-
/** The deterministic singleton id of a user's signer delegation (the kernel reads THIS id by-user). */
|
|
453
|
-
export function signerDelegationInstanceId(user: string): string {
|
|
454
|
-
return SIGNER_DELEGATION_PREFIX + user;
|
|
455
|
-
}
|
|
459
|
+
})
|
|
460
|
+
.alsoEnsures(SignerEpoch);
|
|
456
461
|
|
|
457
462
|
/**
|
|
458
463
|
* `enrollDelegation` — record a user's tenant-signed delegation cert (the enroll-once lane). The tenant's
|
|
@@ -35,6 +35,8 @@
|
|
|
35
35
|
* workspace scan / projection read / ambient "current world" read — only the declared refs.
|
|
36
36
|
*/
|
|
37
37
|
|
|
38
|
+
import type { PointGuardSpec } from "../point_guard.js";
|
|
39
|
+
|
|
38
40
|
/** A TYPED verdict — identical shape to the aggregate invariant (the engine parses both). */
|
|
39
41
|
export type WorkspaceInvariantVerdict = { accept: true } | { reject: string };
|
|
40
42
|
|
|
@@ -51,6 +53,240 @@ export interface InvariantRef {
|
|
|
51
53
|
readonly id: string;
|
|
52
54
|
}
|
|
53
55
|
|
|
56
|
+
/** A native point-read recipe. This is authored in TypeScript, custodied as typed USDA, and
|
|
57
|
+
* interpreted by Rust. It is data, not an executable JavaScript read selector. */
|
|
58
|
+
export type InvariantReadTransform = "none" | "row-key" | "stable-id";
|
|
59
|
+
|
|
60
|
+
export type InvariantReadIdPart =
|
|
61
|
+
| { readonly source: "literal"; readonly value: string }
|
|
62
|
+
| { readonly source: "payload" | "item"; readonly path: readonly string[]; readonly transform: InvariantReadTransform };
|
|
63
|
+
|
|
64
|
+
export type InvariantReadId =
|
|
65
|
+
| { readonly source: "literal"; readonly value: string }
|
|
66
|
+
| {
|
|
67
|
+
readonly source: "payload";
|
|
68
|
+
readonly path: readonly string[];
|
|
69
|
+
readonly prefix: string;
|
|
70
|
+
readonly suffix: string;
|
|
71
|
+
readonly optional: boolean;
|
|
72
|
+
}
|
|
73
|
+
| { readonly source: "template"; readonly parts: readonly InvariantReadIdPart[]; readonly optional: boolean };
|
|
74
|
+
|
|
75
|
+
export interface InvariantReadBinding {
|
|
76
|
+
readonly binding: string;
|
|
77
|
+
readonly aggregate: string;
|
|
78
|
+
readonly state: "pre" | "post";
|
|
79
|
+
readonly eachPayloadPath?: readonly string[];
|
|
80
|
+
readonly id: InvariantReadId;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function readPath(path: string | readonly string[]): readonly string[] {
|
|
84
|
+
return typeof path === "string" ? path.split(".").filter(Boolean) : [...path];
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function idLiteral(value: string): InvariantReadIdPart {
|
|
88
|
+
return { source: "literal", value };
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export function idPayload(
|
|
92
|
+
path: string | readonly string[],
|
|
93
|
+
transform: InvariantReadTransform = "none",
|
|
94
|
+
): InvariantReadIdPart {
|
|
95
|
+
return { source: "payload", path: readPath(path), transform };
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export function idItem(
|
|
99
|
+
path: string | readonly string[] = [],
|
|
100
|
+
transform: InvariantReadTransform = "none",
|
|
101
|
+
): InvariantReadIdPart {
|
|
102
|
+
return { source: "item", path: readPath(path), transform };
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export function idTemplate(
|
|
106
|
+
...parts: readonly (InvariantReadIdPart | string)[]
|
|
107
|
+
): readonly InvariantReadIdPart[] {
|
|
108
|
+
return parts.map((part) => typeof part === "string" ? idLiteral(part) : part);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const NATIVE_READ_SET = Symbol.for("nomos.workspaceInvariant.nativeReadSet.v1");
|
|
112
|
+
|
|
113
|
+
type NativeReadSet = WorkspaceInvariantReads & {
|
|
114
|
+
readonly [NATIVE_READ_SET]: readonly InvariantReadBinding[];
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
function aggregateName(aggregate: { readonly id: string } | string): string {
|
|
118
|
+
return typeof aggregate === "string" ? aggregate : aggregate.id;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/** Begin one typed, bounded aggregate read. Prefer an aggregate handle; strings remain available
|
|
122
|
+
* for framework composition where the declaration is intentionally late-bound. */
|
|
123
|
+
export function readRef(
|
|
124
|
+
binding: string,
|
|
125
|
+
aggregate: { readonly id: string } | string,
|
|
126
|
+
): {
|
|
127
|
+
at(id: string): InvariantReadBinding;
|
|
128
|
+
fromPayload(path: string | readonly string[], options?: { readonly prefix?: string; readonly suffix?: string }): InvariantReadBinding;
|
|
129
|
+
fromOptionalPayload(path: string | readonly string[], options?: { readonly prefix?: string; readonly suffix?: string }): InvariantReadBinding;
|
|
130
|
+
fromTemplate(parts: readonly InvariantReadIdPart[], options?: { readonly optional?: boolean }): InvariantReadBinding;
|
|
131
|
+
forEach(
|
|
132
|
+
path: string | readonly string[],
|
|
133
|
+
parts: readonly InvariantReadIdPart[],
|
|
134
|
+
options?: { readonly optional?: boolean },
|
|
135
|
+
): InvariantReadBinding;
|
|
136
|
+
pre(): ReturnType<typeof readRef>;
|
|
137
|
+
post(): ReturnType<typeof readRef>;
|
|
138
|
+
} {
|
|
139
|
+
const build = (state: "pre" | "post") => ({
|
|
140
|
+
at(id: string): InvariantReadBinding {
|
|
141
|
+
return { binding, aggregate: aggregateName(aggregate), state, id: { source: "literal", value: id } };
|
|
142
|
+
},
|
|
143
|
+
fromPayload(
|
|
144
|
+
path: string | readonly string[],
|
|
145
|
+
options: { readonly prefix?: string; readonly suffix?: string } = {},
|
|
146
|
+
): InvariantReadBinding {
|
|
147
|
+
return {
|
|
148
|
+
binding,
|
|
149
|
+
aggregate: aggregateName(aggregate),
|
|
150
|
+
state,
|
|
151
|
+
id: {
|
|
152
|
+
source: "payload",
|
|
153
|
+
path: readPath(path),
|
|
154
|
+
prefix: options.prefix ?? "",
|
|
155
|
+
suffix: options.suffix ?? "",
|
|
156
|
+
optional: false,
|
|
157
|
+
},
|
|
158
|
+
};
|
|
159
|
+
},
|
|
160
|
+
fromOptionalPayload(
|
|
161
|
+
path: string | readonly string[],
|
|
162
|
+
options: { readonly prefix?: string; readonly suffix?: string } = {},
|
|
163
|
+
): InvariantReadBinding {
|
|
164
|
+
const binding = build(state).fromPayload(path, options);
|
|
165
|
+
if (binding.id.source !== "payload") return binding;
|
|
166
|
+
return { ...binding, id: { ...binding.id, optional: true } };
|
|
167
|
+
},
|
|
168
|
+
fromTemplate(
|
|
169
|
+
parts: readonly InvariantReadIdPart[],
|
|
170
|
+
options: { readonly optional?: boolean } = {},
|
|
171
|
+
): InvariantReadBinding {
|
|
172
|
+
return {
|
|
173
|
+
binding,
|
|
174
|
+
aggregate: aggregateName(aggregate),
|
|
175
|
+
state,
|
|
176
|
+
id: { source: "template", parts: [...parts], optional: options.optional ?? false },
|
|
177
|
+
};
|
|
178
|
+
},
|
|
179
|
+
forEach(
|
|
180
|
+
path: string | readonly string[],
|
|
181
|
+
parts: readonly InvariantReadIdPart[],
|
|
182
|
+
options: { readonly optional?: boolean } = {},
|
|
183
|
+
): InvariantReadBinding {
|
|
184
|
+
return {
|
|
185
|
+
...build(state).fromTemplate(parts, options),
|
|
186
|
+
eachPayloadPath: readPath(path),
|
|
187
|
+
};
|
|
188
|
+
},
|
|
189
|
+
pre: () => build("pre"),
|
|
190
|
+
post: () => build("post"),
|
|
191
|
+
});
|
|
192
|
+
return build("post");
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function valueAtPath(value: unknown, path: readonly string[]): unknown {
|
|
196
|
+
let current = value;
|
|
197
|
+
for (const part of path) {
|
|
198
|
+
if (current === null || typeof current !== "object") return undefined;
|
|
199
|
+
current = (current as Record<string, unknown>)[part];
|
|
200
|
+
}
|
|
201
|
+
return current;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function transformReadPart(value: string, transform: InvariantReadTransform): string {
|
|
205
|
+
if (transform === "row-key") {
|
|
206
|
+
return value.replace(/%/g, "%25").replace(/_/g, "%5F").replace(/\|/g, "%7C");
|
|
207
|
+
}
|
|
208
|
+
if (transform === "stable-id") return value.replace(/[^A-Za-z0-9.@/-]/g, "-");
|
|
209
|
+
return value;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function resolveTemplate(
|
|
213
|
+
parts: readonly InvariantReadIdPart[],
|
|
214
|
+
intent: Record<string, unknown>,
|
|
215
|
+
item: unknown,
|
|
216
|
+
): string | undefined {
|
|
217
|
+
let result = "";
|
|
218
|
+
for (const part of parts) {
|
|
219
|
+
if (part.source === "literal") {
|
|
220
|
+
result += part.value;
|
|
221
|
+
continue;
|
|
222
|
+
}
|
|
223
|
+
const value = valueAtPath(part.source === "payload" ? intent : item, part.path);
|
|
224
|
+
if (value === undefined || value === null) return undefined;
|
|
225
|
+
if (typeof value !== "string" && typeof value !== "number") return undefined;
|
|
226
|
+
result += transformReadPart(String(value), part.transform);
|
|
227
|
+
}
|
|
228
|
+
return result;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/** Resolve a typed read-id expression for authoring-time probes and precondition planning.
|
|
232
|
+
* Runtime admission evaluates the custodied twin directly in Rust. */
|
|
233
|
+
export function resolveInvariantReadId(
|
|
234
|
+
id: InvariantReadId,
|
|
235
|
+
intent: Record<string, unknown>,
|
|
236
|
+
item?: unknown,
|
|
237
|
+
): string | undefined {
|
|
238
|
+
if (id.source === "literal") return id.value;
|
|
239
|
+
if (id.source === "payload") {
|
|
240
|
+
const value = valueAtPath(intent, id.path);
|
|
241
|
+
if (value === undefined || value === null || value === "") return id.optional ? undefined : "";
|
|
242
|
+
if (typeof value !== "string" && typeof value !== "number") return id.optional ? undefined : "";
|
|
243
|
+
return `${id.prefix}${String(value)}${id.suffix}`;
|
|
244
|
+
}
|
|
245
|
+
return resolveTemplate(id.parts, intent, item);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/** Declare a bounded set of generic point reads. The returned callable keeps authoring-time tests
|
|
249
|
+
* ergonomic, while the non-enumerable metadata is what the compiler lowers into USDA. */
|
|
250
|
+
export function readSet(...bindings: readonly InvariantReadBinding[]): WorkspaceInvariantReads {
|
|
251
|
+
const selector = ((ctx: { intent: Record<string, unknown> }) => {
|
|
252
|
+
const refs: InvariantRef[] = [];
|
|
253
|
+
for (const binding of bindings) {
|
|
254
|
+
const repeated = binding.eachPayloadPath === undefined
|
|
255
|
+
? [undefined]
|
|
256
|
+
: valueAtPath(ctx.intent, binding.eachPayloadPath);
|
|
257
|
+
if (!Array.isArray(repeated)) {
|
|
258
|
+
if (binding.eachPayloadPath !== undefined && binding.id.source === "template" && binding.id.optional) continue;
|
|
259
|
+
if (binding.eachPayloadPath !== undefined) continue;
|
|
260
|
+
}
|
|
261
|
+
const items: readonly unknown[] = Array.isArray(repeated) ? repeated : [undefined];
|
|
262
|
+
for (let index = 0; index < items.length; index += 1) {
|
|
263
|
+
let id = resolveInvariantReadId(binding.id, ctx.intent, items[index]);
|
|
264
|
+
if (id === undefined && binding.id.source !== "literal" && binding.id.optional) continue;
|
|
265
|
+
id ??= "";
|
|
266
|
+
refs.push({
|
|
267
|
+
name: binding.eachPayloadPath === undefined ? binding.binding : `${binding.binding}${index}`,
|
|
268
|
+
aggregate: binding.aggregate,
|
|
269
|
+
id: binding.state === "pre" ? `pre:${id}` : id,
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
return refs;
|
|
274
|
+
}) as NativeReadSet;
|
|
275
|
+
Object.defineProperty(selector, NATIVE_READ_SET, {
|
|
276
|
+
value: bindings.map((binding) => ({ ...binding })),
|
|
277
|
+
enumerable: false,
|
|
278
|
+
writable: false,
|
|
279
|
+
});
|
|
280
|
+
return selector;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/** Compiler-only access to a native read set. Ordinary callbacks deliberately return undefined. */
|
|
284
|
+
export function nativeReadBindings(
|
|
285
|
+
reads: WorkspaceInvariantReads,
|
|
286
|
+
): readonly InvariantReadBinding[] | undefined {
|
|
287
|
+
return (reads as Partial<NativeReadSet>)[NATIVE_READ_SET];
|
|
288
|
+
}
|
|
289
|
+
|
|
54
290
|
/** `inventory.Item` → `item`; `layout.FloorPlan` → `floorPlan` (short segment, first-lower). */
|
|
55
291
|
function deriveName(aggregateType: string): string {
|
|
56
292
|
const short = aggregateType.includes(".")
|
|
@@ -140,6 +376,8 @@ export interface WorkspaceInvariantDecl {
|
|
|
140
376
|
readonly on: string;
|
|
141
377
|
/** The executable declared read-set — ships in the engine bundle, NOT the manifest. */
|
|
142
378
|
readonly reads: WorkspaceInvariantReads;
|
|
379
|
+
/** Typed generic read recipes lowered into USDA. Absent only for not-yet-migrated callbacks. */
|
|
380
|
+
readonly __nomosReadBindings?: readonly InvariantReadBinding[];
|
|
143
381
|
/** The executable predicate — ships in the engine bundle, NOT the manifest. */
|
|
144
382
|
readonly assert: WorkspaceInvariantAssert;
|
|
145
383
|
/**
|
|
@@ -150,6 +388,11 @@ export interface WorkspaceInvariantDecl {
|
|
|
150
388
|
* a secret-correlated refusal.
|
|
151
389
|
*/
|
|
152
390
|
readonly declassify?: string;
|
|
391
|
+
/** Framework-owned evaluator whose complete semantics are part of the named kernel ABI.
|
|
392
|
+
* Tenant-authored invariants omit this and execute their sealed USDA plan. */
|
|
393
|
+
readonly __nomosEvaluator?: "birth-quota-v1" | "founder-signer-write-once-v1" | "point-guard-v1";
|
|
394
|
+
/** Typed point-state law consumed directly by the kernel when evaluator is `point-guard-v1`. */
|
|
395
|
+
readonly __nomosPointGuard?: PointGuardSpec;
|
|
153
396
|
/** Framework-only provenance used to keep compiler errors on the tenant-authored surface. */
|
|
154
397
|
readonly __nomosGenerated?: "directive-precondition";
|
|
155
398
|
/** Brand so a plain object is never mistaken for a declaration. */
|
|
@@ -173,10 +416,12 @@ export function workspaceInvariant(id: string): {
|
|
|
173
416
|
} {
|
|
174
417
|
const finish = (directiveId: string, reads: WorkspaceInvariantReads, declassify: string | undefined) => ({
|
|
175
418
|
assert(assert: WorkspaceInvariantAssert): WorkspaceInvariantDecl {
|
|
419
|
+
const readBindings = nativeReadBindings(reads);
|
|
176
420
|
return {
|
|
177
421
|
id,
|
|
178
422
|
on: directiveId,
|
|
179
423
|
reads,
|
|
424
|
+
...(readBindings === undefined ? {} : { __nomosReadBindings: readBindings }),
|
|
180
425
|
assert,
|
|
181
426
|
...(declassify !== undefined ? { declassify } : {}),
|
|
182
427
|
__isWorkspaceInvariant: true as const,
|
|
@@ -74,7 +74,7 @@ import { t } 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";
|
|
77
|
-
import { workspaceInvariant, refAs, type WorkspaceInvariantVerdict } from "./workspace_invariant.js";
|
|
77
|
+
import { readRef, readSet, workspaceInvariant, refAs, type WorkspaceInvariantVerdict } from "./workspace_invariant.js";
|
|
78
78
|
import { writeTuple, removeTuple, RelationTuple, relationTupleInstanceId } from "./relations.js";
|
|
79
79
|
import { relationType, self, user, key } from "../relation_schema.js";
|
|
80
80
|
import { GENESIS_WARRANT_ID } from "./genesis.js";
|
|
@@ -143,7 +143,7 @@ export const workspaceSelfWriteOnce = workspaceInvariant("WorkspaceSelfWriteOnce
|
|
|
143
143
|
// NEVER match the bare runtime id ⇒ the gate stays silently INERT (the #266 side-door bug,
|
|
144
144
|
// 2026-06-15). Bare matches — exactly the convention the sharding invariants already fire under.
|
|
145
145
|
.on("ensureWorkspaceSelf")
|
|
146
|
-
.reads((
|
|
146
|
+
.reads(readSet(readRef("self", WorkspaceSelf).pre().at(WORKSPACE_SELF_ID)))
|
|
147
147
|
// WRITE-ONCE on the self-record's PRESENCE via `name` (not the vestigial `role`). At genesis name + role
|
|
148
148
|
// were always co-written, so an existing born workspace has `name` ⇒ behaviour preserved; a fresh one has
|
|
149
149
|
// neither ⇒ first set admitted.
|
|
@@ -542,15 +542,14 @@ export function buildGenesisChain(p: {
|
|
|
542
542
|
rebornFrom?: string;
|
|
543
543
|
}): string {
|
|
544
544
|
const founder = "user:" + p.principal;
|
|
545
|
-
// THE ORDER-DECLARED LAW INSTALL (G3): the child installs the EXACT
|
|
546
|
-
// the recipe
|
|
547
|
-
//
|
|
548
|
-
// for the in-chain install). Two layers, matching the genesis bootstrap: bootstrap controller, then law.
|
|
545
|
+
// THE ORDER-DECLARED LAW INSTALL (G3): the child installs the EXACT typed USDA closures the order names.
|
|
546
|
+
// Bytes are NOT in the recipe; the kernel resolves each identity from its own custody when it folds the
|
|
547
|
+
// birth. Two layers, matching the genesis bootstrap: bootstrap controller, then law.
|
|
549
548
|
const installSteps =
|
|
550
549
|
p.frameworkHash && p.lawHash
|
|
551
550
|
? [
|
|
552
|
-
{ domain: "bootstrap", directiveId: "installDomain", actor: founder, payload: { domainHash: p.frameworkHash }, domainHash: p.frameworkHash
|
|
553
|
-
{ domain: "nomos", directiveId: "installDomain", actor: founder, payload: { domainHash: p.lawHash }, domainHash: p.lawHash
|
|
551
|
+
{ domain: "bootstrap", directiveId: "installDomain", actor: founder, payload: { domainHash: p.frameworkHash }, domainHash: p.frameworkHash },
|
|
552
|
+
{ domain: "nomos", directiveId: "installDomain", actor: founder, payload: { domainHash: p.lawHash }, domainHash: p.lawHash },
|
|
554
553
|
]
|
|
555
554
|
: [];
|
|
556
555
|
// THE LINEAGE-AUTHORIZATION BINDING (proof-friendly): the child's birthCloud records who authorized it, so
|
|
@@ -800,7 +799,8 @@ export const birthCloud = directive("birthCloud")
|
|
|
800
799
|
// the born marker; `role` is no longer written (vestigial node-class — see WorkspaceSelf).
|
|
801
800
|
ensure(WorkspaceSelf, WORKSPACE_SELF_ID).set("name", p.name);
|
|
802
801
|
return [];
|
|
803
|
-
})
|
|
802
|
+
})
|
|
803
|
+
.alsoEnsures(WorkspaceSelf);
|
|
804
804
|
|
|
805
805
|
/**
|
|
806
806
|
* provisionWorkspace — the worker's RETURN RECEIPT: authored AFTER it physically
|
|
@@ -917,9 +917,7 @@ export const retireOwnWorkspace = directive("retireOwnWorkspace")
|
|
|
917
917
|
* so the read manifest is unchanged (era-/evolve-safe). */
|
|
918
918
|
export const retireOwnOnly = workspaceInvariant("RetireOwnOnly")
|
|
919
919
|
.on("retireOwnWorkspace")
|
|
920
|
-
.reads((
|
|
921
|
-
refAs("target", "CloudWorkspace", "pre:" + String((ctx?.intent ?? {}).workspaceId ?? "")),
|
|
922
|
-
])
|
|
920
|
+
.reads(readSet(readRef("target", CloudWorkspace).pre().fromPayload("workspaceId")))
|
|
923
921
|
.assert((refs, ctx) => {
|
|
924
922
|
const target = (refs as { target?: { principal?: string; createdBy?: string } }).target;
|
|
925
923
|
// The captured, VERIFIED author rides as a SUBJECT (`user:<uid>`); the record's creator fields
|
|
@@ -1031,9 +1029,7 @@ export const rebirthWorkspace = directive("rebirthWorkspace")
|
|
|
1031
1029
|
*/
|
|
1032
1030
|
export const rebirthNotRoot = workspaceInvariant("RebirthNotRoot")
|
|
1033
1031
|
.on("rebirthWorkspace")
|
|
1034
|
-
.reads((
|
|
1035
|
-
refAs("target", "CloudWorkspace", "pre:" + String((ctx?.intent ?? {}).workspaceId ?? "")),
|
|
1036
|
-
])
|
|
1032
|
+
.reads(readSet(readRef("target", CloudWorkspace).pre().fromPayload("workspaceId")))
|
|
1037
1033
|
.assert((refs) => {
|
|
1038
1034
|
const target = (refs as { target?: { name?: string } }).target;
|
|
1039
1035
|
return target?.name === "root"
|
|
@@ -1051,9 +1047,7 @@ export const rebirthNotRoot = workspaceInvariant("RebirthNotRoot")
|
|
|
1051
1047
|
*/
|
|
1052
1048
|
export const rebirthRetainsLineage = workspaceInvariant("RebirthRetainsLineage")
|
|
1053
1049
|
.on("rebirthWorkspace")
|
|
1054
|
-
.reads((
|
|
1055
|
-
refAs("target", "CloudWorkspace", "pre:" + String((ctx?.intent ?? {}).workspaceId ?? "")),
|
|
1056
|
-
])
|
|
1050
|
+
.reads(readSet(readRef("target", CloudWorkspace).pre().fromPayload("workspaceId")))
|
|
1057
1051
|
.assert((refs, ctx) => {
|
|
1058
1052
|
const target = (refs as { target?: { parent?: string } }).target;
|
|
1059
1053
|
const claimed = (ctx?.intent as { parent?: string } | undefined)?.parent;
|
|
@@ -1788,7 +1782,7 @@ export { RelationTuple };
|
|
|
1788
1782
|
* owner-rebind the host session-lane used to refuse — is rejected. */
|
|
1789
1783
|
export const genesisWarrantWriteOnce = workspaceInvariant("GenesisWarrantWriteOnce")
|
|
1790
1784
|
.on("seedGenesis")
|
|
1791
|
-
.reads((
|
|
1785
|
+
.reads(readSet(readRef("warrant", "GenesisWarrant").pre().at(GENESIS_WARRANT_ID)))
|
|
1792
1786
|
.assert(({ warrant }, ctx) => {
|
|
1793
1787
|
const cur = warrant as { principal?: string; workspaceId?: string } | undefined;
|
|
1794
1788
|
if (!cur?.principal) return { accept: true }; // virgin — genesis may seed the founding warrant
|
|
@@ -1823,7 +1817,7 @@ export const founderSignerWriteOnce = workspaceInvariant("FounderSignerWriteOnce
|
|
|
1823
1817
|
* WorkspaceSelf.role exists. Genesis births on a role-less chain; materialize births each child once. */
|
|
1824
1818
|
export const birthCloudOnce = workspaceInvariant("BirthCloudOnce")
|
|
1825
1819
|
.on("birthCloud")
|
|
1826
|
-
.reads((
|
|
1820
|
+
.reads(readSet(readRef("self", WorkspaceSelf).pre().at(WORKSPACE_SELF_ID)))
|
|
1827
1821
|
// WRITE-ONCE on the self-record's PRESENCE via `name` (not the vestigial `role`) — a workspace is born once.
|
|
1828
1822
|
.assert(({ self }) =>
|
|
1829
1823
|
(self as { name?: string })?.name ? { reject: "cloud-already-born" } : { accept: true },
|
|
@@ -1837,7 +1831,7 @@ export const birthCloudOnce = workspaceInvariant("BirthCloudOnce")
|
|
|
1837
1831
|
* rest. (Worker authors ownerGrantHash=foundingAdminHash for platforms, sha256(freshSecret) for plain.) */
|
|
1838
1832
|
export const provisionReceiptGuard = workspaceInvariant("ProvisionReceiptGuard")
|
|
1839
1833
|
.on("provisionWorkspace")
|
|
1840
|
-
.reads((
|
|
1834
|
+
.reads(readSet(readRef("ws", CloudWorkspace).pre().fromPayload("workspaceId")))
|
|
1841
1835
|
.assert(({ ws }, ctx) => {
|
|
1842
1836
|
const rec = ws as { status?: string; foundingAdminHash?: string } | undefined;
|
|
1843
1837
|
if (!rec || rec.status !== "requested") return { reject: "provision-requires-requested-workspace" };
|
|
@@ -1856,7 +1850,7 @@ export const provisionReceiptGuard = workspaceInvariant("ProvisionReceiptGuard")
|
|
|
1856
1850
|
* refuses reclaiming itself; this guards children.) */
|
|
1857
1851
|
export const reclaimRequiresRetired = workspaceInvariant("ReclaimRequiresRetired")
|
|
1858
1852
|
.on("reclaimWorkspace")
|
|
1859
|
-
.reads((
|
|
1853
|
+
.reads(readSet(readRef("ws", CloudWorkspace).pre().fromPayload("workspaceId")))
|
|
1860
1854
|
.assert(({ ws }) => {
|
|
1861
1855
|
const rec = ws as { status?: string } | undefined;
|
|
1862
1856
|
return rec?.status === "retired" ? { accept: true } : { reject: "reclaim-requires-retired" };
|
|
@@ -1915,11 +1909,11 @@ function creationPostureVerdict(
|
|
|
1915
1909
|
return { reject: "creation.invalid-posture" };
|
|
1916
1910
|
}
|
|
1917
1911
|
|
|
1918
|
-
const creationPostureReads = (
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1912
|
+
const creationPostureReads = readSet(
|
|
1913
|
+
readRef("policy", CloudPolicy).at(CREATION_POLICY_ID),
|
|
1914
|
+
readRef("creators", RelationTuple).at(CREATOR_TUPLE_ID),
|
|
1915
|
+
readRef("admins", RelationTuple).at(ADMIN_TUPLE_ID),
|
|
1916
|
+
);
|
|
1923
1917
|
|
|
1924
1918
|
/** Compatibility create: open/unset ⇒ proven author; invite ⇒ creator; closed ⇒ admin. */
|
|
1925
1919
|
export const creationPostureCreate = workspaceInvariant("CreationPostureCreate")
|
|
@@ -1961,7 +1955,7 @@ export const workspaceQuotaCreate = workspaceInvariant("WorkspaceQuota")
|
|
|
1961
1955
|
.on("createWorkspace")
|
|
1962
1956
|
// The snapshot ref-set is EMPTY — the count comes from the captured reads (ctx.reads), which the standard
|
|
1963
1957
|
// ref-resolution cannot produce (it resolves single instances by id, never an enumeration).
|
|
1964
|
-
.reads(()
|
|
1958
|
+
.reads(readSet())
|
|
1965
1959
|
.assert((_snapshots, ctx) =>
|
|
1966
1960
|
quotaVerdict(ctx?.reads ?? [], String((ctx?.intent ?? {}).principal ?? "")),
|
|
1967
1961
|
);
|
|
@@ -1969,7 +1963,7 @@ export const workspaceQuotaCreate = workspaceInvariant("WorkspaceQuota")
|
|
|
1969
1963
|
/** birthChild: the SAME count quota (a leaf or platform birth draws on the principal's allowance). */
|
|
1970
1964
|
export const workspaceQuotaBirth = workspaceInvariant("WorkspaceQuotaBirth")
|
|
1971
1965
|
.on("birthChild")
|
|
1972
|
-
.reads(()
|
|
1966
|
+
.reads(readSet())
|
|
1973
1967
|
.assert((_snapshots, ctx) =>
|
|
1974
1968
|
quotaVerdict(ctx?.reads ?? [], String((ctx?.intent ?? {}).principal ?? "")),
|
|
1975
1969
|
);
|