@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
|
@@ -68,9 +68,9 @@ import { z } from "zod";
|
|
|
68
68
|
import { aggregate, instance } from "../aggregate.js";
|
|
69
69
|
import { create, ensure } from "../authoring.js";
|
|
70
70
|
import { birth } from "../births.js";
|
|
71
|
-
import { directive } from "../directive.js";
|
|
71
|
+
import { directive, type Directive } from "../directive.js";
|
|
72
72
|
import { Lww } from "../drivers.js";
|
|
73
|
-
import { t } from "../fields.js";
|
|
73
|
+
import { sha256Digest, t, type Sha256Digest } from "../fields.js";
|
|
74
74
|
import { set, withMarker } from "../ops.js";
|
|
75
75
|
import { query } from "../query.js";
|
|
76
76
|
import { read } from "../read.js";
|
|
@@ -79,7 +79,12 @@ import { writeTuple, removeTuple, RelationTuple, relationTupleInstanceId } from
|
|
|
79
79
|
import { relationType, self, user, key } from "../relation_schema.js";
|
|
80
80
|
import { GENESIS_WARRANT_ID } from "./genesis.js";
|
|
81
81
|
import { idOf } from "../value_object.js";
|
|
82
|
-
import { signerObject, SIGNER_RELATION } from "./signing_identity.js";
|
|
82
|
+
import { enrollSigner, signerObject, SIGNER_RELATION } from "./signing_identity.js";
|
|
83
|
+
import { seedBirthPolicy } from "./birth_meter.js";
|
|
84
|
+
import {
|
|
85
|
+
DomainInstallExecutor,
|
|
86
|
+
domainInstallGenesisOffer,
|
|
87
|
+
} from "./domain_lifecycle.js";
|
|
83
88
|
// THE GENESIS BOOTSTRAP (platform-birth genesis bootstrap, Approach A): governance carries the
|
|
84
89
|
// warrant-flip primitives so the birth door can lawfully bootstrap a virgin platform's founding admin —
|
|
85
90
|
// E `enrollSigner` (the founding signer tuple) → W `seedGenesis` (the GenesisWarrant) → A signed
|
|
@@ -88,7 +93,7 @@ import { signerObject, SIGNER_RELATION } from "./signing_identity.js";
|
|
|
88
93
|
export { GenesisWarrant, seedGenesis } from "./genesis.js";
|
|
89
94
|
export { identityRelations, enrollSigner } from "./signing_identity.js";
|
|
90
95
|
// ERA 0 — the keyless verified first-device door rides the GOVERNANCE law too: a freshly BORN platform
|
|
91
|
-
// has a virgin roster and a cert-ATTESTED AuthProvider (the S_root-signed
|
|
96
|
+
// has a virgin roster and a cert-ATTESTED AuthProvider (the S_root-signed birth witness), so its founder's
|
|
92
97
|
// FIRST action is `enrollFirstDevice` (token-verified against the attested provider) followed by a SIGNED
|
|
93
98
|
// self-`grantAdmin` (admitted by the kernel genesis clause). Without this export a born platform is
|
|
94
99
|
// un-administrable — the admin bootstrap has no door.
|
|
@@ -105,6 +110,10 @@ export { BIRTH_ATTESTATION_ID, BirthAttestation, birthAttestation, seedBirthAtte
|
|
|
105
110
|
// onboarding signer S_P) so the kernel reads it at `signer-delegation:<user>` when assembling a home's chain.
|
|
106
111
|
export { SignerDelegation, enrollDelegation, signerDelegationByUser } from "./signing_identity.js";
|
|
107
112
|
|
|
113
|
+
/** Canonical governance-law namespace. Genesis routes refer to this declaration, never a repeated
|
|
114
|
+
* string literal, so renaming the module cannot silently strand part of the founding ceremony. */
|
|
115
|
+
export const WORKSPACES_DOMAIN = "workspaces" as const;
|
|
116
|
+
|
|
108
117
|
// ── WORKSPACE SELF-IDENTITY (the law reads its OWN install-context) ──────────────────────────────────
|
|
109
118
|
// The governance bundle runs on root AND every born platform. A workspace knows its ROLE via this
|
|
110
119
|
// singleton, at a CONSTANT fixed instance id (the RelationTuple discipline — a literal, never minted, so
|
|
@@ -191,6 +200,10 @@ export const workspaceRelations = relationType("workspace")
|
|
|
191
200
|
/** Where a workspace's holon runs: an edge DO or the dedicated 12GiB container. */
|
|
192
201
|
export const WORKSPACE_TIERS = ["edge", "heavy"] as const;
|
|
193
202
|
|
|
203
|
+
/** The only cloud-level creation postures. This vocabulary is shared by the stored law field,
|
|
204
|
+
* directive payload and operator tooling so an unknown posture cannot survive as an arbitrary string. */
|
|
205
|
+
export const CLOUD_CREATION_POSTURES = ["open", "invite", "closed"] as const;
|
|
206
|
+
|
|
194
207
|
/**
|
|
195
208
|
* GOVERNANCE POSTURE (architecture/governance_postures.md) — every platform DECLARES how the operator may
|
|
196
209
|
* act in its subtree. `managed` (the default when absent) folds an OperatorGrant: the operator may run
|
|
@@ -224,7 +237,7 @@ export const WORKSPACE_STATUSES = [
|
|
|
224
237
|
export const GRANT_STATUSES = ["active", "revoked"] as const;
|
|
225
238
|
|
|
226
239
|
/** sha256 hex — the ONLY shape a credential may take inside the law. */
|
|
227
|
-
const sha256Hex =
|
|
240
|
+
const sha256Hex = sha256Digest();
|
|
228
241
|
|
|
229
242
|
/** Epoch-ms, caller-stamped (a plan never reads a clock). */
|
|
230
243
|
const epochMs = z.number().int().nonnegative();
|
|
@@ -375,7 +388,7 @@ export const CloudPolicy = aggregate("CloudPolicy", {
|
|
|
375
388
|
* historical replay, but absence fails closed for new births. Merely
|
|
376
389
|
* granting somebody authority never changes the door for everybody else.
|
|
377
390
|
*/
|
|
378
|
-
creationPosture: t.
|
|
391
|
+
creationPosture: t.enum(CLOUD_CREATION_POSTURES).merge(Lww).optional(),
|
|
379
392
|
/** Who last set the policy (attribution). */
|
|
380
393
|
setBy: t.string().merge(Lww),
|
|
381
394
|
/** Epoch-ms the policy was last set (caller-stamped). */
|
|
@@ -527,8 +540,8 @@ export function buildGenesisChain(p: {
|
|
|
527
540
|
* the recipe PREPENDS `bootstrap/installDomain(framework)` + `nomos/installDomain(law)` — the two-layer
|
|
528
541
|
* install the genesis bootstrap does — so the child installs ITS OWN law as intent 0 and self-validates
|
|
529
542
|
* from there. Absent ⇒ the recipe resolves an already-installed law by shape (pre-G3 / test lanes). */
|
|
530
|
-
frameworkHash?:
|
|
531
|
-
lawHash?:
|
|
543
|
+
frameworkHash?: Sha256Digest;
|
|
544
|
+
lawHash?: Sha256Digest;
|
|
532
545
|
/** METERED BIRTH (slice A3 — the descendant budget). Required: a governance child may not be
|
|
533
546
|
* born without explicit BirthPolicy state. Zero is a legitimate declared "cannot birth" policy. */
|
|
534
547
|
descendantPool: number;
|
|
@@ -537,15 +550,23 @@ export function buildGenesisChain(p: {
|
|
|
537
550
|
* ancestry while `parent` stays the SUPERSEDED record's parent (a rebirth may never reparent). */
|
|
538
551
|
rebornFrom?: string;
|
|
539
552
|
}): string {
|
|
540
|
-
const founder =
|
|
553
|
+
const founder: `user:${string}` = `user:${p.principal}`;
|
|
541
554
|
// THE ORDER-DECLARED LAW INSTALL (G3): the child installs the EXACT typed USDA closures the order names.
|
|
542
555
|
// Bytes are NOT in the recipe; the kernel resolves each identity from its own custody when it folds the
|
|
543
556
|
// birth. Two layers, matching the genesis bootstrap: bootstrap controller, then law.
|
|
544
557
|
const installSteps =
|
|
545
558
|
p.frameworkHash && p.lawHash
|
|
546
559
|
? [
|
|
547
|
-
|
|
548
|
-
|
|
560
|
+
domainInstallGenesisOffer(
|
|
561
|
+
DomainInstallExecutor.StageZero,
|
|
562
|
+
p.frameworkHash,
|
|
563
|
+
founder,
|
|
564
|
+
),
|
|
565
|
+
domainInstallGenesisOffer(
|
|
566
|
+
DomainInstallExecutor.LifecycleController,
|
|
567
|
+
p.lawHash,
|
|
568
|
+
founder,
|
|
569
|
+
),
|
|
549
570
|
]
|
|
550
571
|
: [];
|
|
551
572
|
// THE LINEAGE-AUTHORIZATION BINDING (proof-friendly): the child's birthCloud records who authorized it, so
|
|
@@ -571,20 +592,28 @@ export function buildGenesisChain(p: {
|
|
|
571
592
|
// METERED BIRTH (slice A3 — THE POOL CHAINS TO ROOT): every governance child is BORN BOUNDED.
|
|
572
593
|
// Historical chains keep replaying under their historical law; this constructor authors only the
|
|
573
594
|
// current law, whose seedBirthPolicy directive is compiler-carried. Missing policy is invalid genesis.
|
|
595
|
+
const governanceGenesisStep = <P extends Record<string, unknown>>(
|
|
596
|
+
declaration: Directive<P>,
|
|
597
|
+
payload: P,
|
|
598
|
+
) => ({
|
|
599
|
+
domain: WORKSPACES_DOMAIN,
|
|
600
|
+
directiveId: declaration.id,
|
|
601
|
+
actor: founder,
|
|
602
|
+
payload,
|
|
603
|
+
});
|
|
574
604
|
const poolSteps = [
|
|
575
|
-
{
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
},
|
|
605
|
+
governanceGenesisStep(seedBirthPolicy, {
|
|
606
|
+
maxBirthsPerPrincipal: p.descendantPool,
|
|
607
|
+
setBy: p.principal,
|
|
608
|
+
setAt: p.bornAt,
|
|
609
|
+
}),
|
|
581
610
|
];
|
|
582
611
|
return JSON.stringify([
|
|
583
612
|
...installSteps,
|
|
584
613
|
...poolSteps,
|
|
585
614
|
// ERA 0 — THE GENESIS-ORDER CONTRACT (the birthHome ordering, unified): every recipe seed below runs
|
|
586
615
|
// UNSIGNED on the still-WARRANT-FREE prefix; the recipe carries NO seedGenesis and NO grantAdmin. The
|
|
587
|
-
// KERNEL's birth effect — after verifying the offer's S_root-signed
|
|
616
|
+
// KERNEL's birth effect — after verifying the offer's typed S_root-signed certificate against the pinned
|
|
588
617
|
// K_root — appends `seedBirthAttestation` (the travelling cert) and THEN `seedGenesis` (the warrant,
|
|
589
618
|
// self-id == name) LAST, so the platform is born WARRANTED + ATTESTED and every post-genesis write is
|
|
590
619
|
// signed AND cert-gated. A birth whose cert fails verification seeds neither and is REFUSED atomically
|
|
@@ -598,11 +627,22 @@ export function buildGenesisChain(p: {
|
|
|
598
627
|
// (`genesis_admits`: a virgin #admin roster + the warrant binding the captured principal). A baked
|
|
599
628
|
// in-genesis grantAdmin can never fold (post-warrant writes need a signature the birth effect cannot
|
|
600
629
|
// produce — the pre-era-0 recipe was structurally unbornable); pre-warrant enroll + post-birth signed
|
|
601
|
-
// self-grant is the lawful shape. (The keyless variant — enrollFirstDevice against the
|
|
630
|
+
// self-grant is the lawful shape. (The keyless variant — enrollFirstDevice against the birth certificate's
|
|
602
631
|
// ATTESTED AuthProvider — remains for founders with an IdP instead of a key.)
|
|
603
|
-
|
|
632
|
+
governanceGenesisStep(enrollSigner, {
|
|
633
|
+
keyHash: p.foundingAdminHash,
|
|
634
|
+
grantedBy: p.principal,
|
|
635
|
+
grantedAt: p.requestedAt,
|
|
636
|
+
}),
|
|
604
637
|
// No `role` — it was a vestigial node-class label (collapsed with `kind`); birthCloud ignores it now.
|
|
605
|
-
|
|
638
|
+
governanceGenesisStep(birthCloud, {
|
|
639
|
+
principal: p.principal,
|
|
640
|
+
name: p.name,
|
|
641
|
+
tier: p.tier,
|
|
642
|
+
bornAt: p.requestedAt,
|
|
643
|
+
bornUnder,
|
|
644
|
+
...(p.rebornFrom ? { rebornFrom: p.rebornFrom } : {}),
|
|
645
|
+
}),
|
|
606
646
|
]);
|
|
607
647
|
}
|
|
608
648
|
|
|
@@ -1248,13 +1288,6 @@ export const birthChild = directive("birthChild")
|
|
|
1248
1288
|
* own custody (never the host). Absent ⇒ the recipe resolves an already-installed law by shape. */
|
|
1249
1289
|
frameworkHash: sha256Hex.optional(),
|
|
1250
1290
|
lawHash: sha256Hex.optional(),
|
|
1251
|
-
/** THE PLATFORM ATTESTATION (era 0 — one attested birth lane for every holon kind): the 1-link
|
|
1252
|
-
* platform_cert JSON (`executor::birth_cert::BirthCert`), SIGNED OFFLINE BY S_root (the K_root
|
|
1253
|
-
* holder). The kernel's birth effect VERIFIES it (child == name, founder == `user:<principal>`,
|
|
1254
|
-
* signature vs the pinned K_root) and injects `seedBirthAttestation` + `seedGenesis` (the warrant)
|
|
1255
|
-
* LAST into the child's genesis. REQUIRED for a genesis birth to complete — a birth without a
|
|
1256
|
-
* valid cert is refused atomically (no unwarranted platform lane survives era 0). */
|
|
1257
|
-
platformCert: z.string().optional(),
|
|
1258
1291
|
/** GOVERNANCE POSTURE (governance_postures.md) — the born child's declared posture; default
|
|
1259
1292
|
* `managed`. Mirrored onto its lineage record so root's bailiff can consult it on the hot path
|
|
1260
1293
|
* (the tenant's own {@link GovernancePosture} singleton is authoritative). Additive/era-safe. */
|
|
@@ -1505,7 +1538,7 @@ export const setCloudPolicy = directive("setCloudPolicy")
|
|
|
1505
1538
|
// The creation-posture dial (cloud_sovereignty.md S6). Optional:
|
|
1506
1539
|
// omitted leaves the field untouched (a partial amend never silently
|
|
1507
1540
|
// resets the door).
|
|
1508
|
-
creationPosture: z.enum(
|
|
1541
|
+
creationPosture: z.enum(CLOUD_CREATION_POSTURES).optional(),
|
|
1509
1542
|
setBy: z.string().min(1),
|
|
1510
1543
|
setAt: epochMs,
|
|
1511
1544
|
}),
|
package/src/idof_lint.ts
CHANGED
|
@@ -105,72 +105,86 @@ export function assertIdOfLintForDirective(dir: Directive<unknown>, where: strin
|
|
|
105
105
|
// tenant-facing idOf lint here would force framework wire schemas to masquerade as public
|
|
106
106
|
// application schemas and, worse, reject every aggregate that opts into owned custody.
|
|
107
107
|
if ((dir as { businessBirthInternal?: unknown }).businessBirthInternal === true) return;
|
|
108
|
-
const probed = probePlanOps(dir, where);
|
|
109
|
-
if (probed === undefined) return;
|
|
110
108
|
let shape: Record<string, z.ZodTypeAny>;
|
|
111
109
|
try {
|
|
112
110
|
shape = zodObjectShape(dir.payloadSchema as z.ZodTypeAny);
|
|
113
111
|
} catch {
|
|
114
112
|
return;
|
|
115
113
|
}
|
|
116
|
-
//
|
|
117
|
-
// `
|
|
118
|
-
//
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
if (
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
114
|
+
// Probe both payload presences. The ordinary proof deliberately omits optional fields, but that
|
|
115
|
+
// would make `optional().setOptional(...)` invisible to the identity lint: exactly the shape that
|
|
116
|
+
// can smuggle a business string into a typed aggregate reference. The second probe includes every
|
|
117
|
+
// sampleable top-level optional and deliberately ignores a possibly sparse author example. It is
|
|
118
|
+
// lint-only; all existing proof/effect consumers retain the ordinary omitted-optional semantics.
|
|
119
|
+
const probedVariants = [
|
|
120
|
+
probePlanOps(dir, where),
|
|
121
|
+
probePlanOps(dir, `${where} (optional fields present)`, {
|
|
122
|
+
includeOptionalTopLevel: true,
|
|
123
|
+
useDirectiveExample: false,
|
|
124
|
+
}),
|
|
125
|
+
];
|
|
126
|
+
if (probedVariants.every((variant) => variant === undefined)) return;
|
|
127
|
+
for (const probed of probedVariants) {
|
|
128
|
+
if (probed === undefined) continue;
|
|
129
|
+
// Reverse index: sampled STRING value → the top-level payload field name(s) that produced it.
|
|
130
|
+
// `synthesizePayload` gives every string-kind field a DISTINCT `"sample-<field>"` value (or a
|
|
131
|
+
// schema-constrained fallback), so an exact match is unambiguous in practice.
|
|
132
|
+
const byValue = new Map<string, string>();
|
|
133
|
+
for (const [name, value] of Object.entries(probed.synth.value)) {
|
|
134
|
+
if (typeof value === "string") byValue.set(value, name);
|
|
135
|
+
}
|
|
136
|
+
for (const op of probed.ops) {
|
|
137
|
+
// A bound mutation addresses one existing aggregate instance. The payload field carrying that
|
|
138
|
+
// address is therefore not "some string" or merely "some branded id": it must be idOf(the exact
|
|
139
|
+
// aggregate being mutated). This is the identity spine of the generated application API. Without
|
|
140
|
+
// this guard a model can compile `entryId: z.string()`, after which every generated caller can mix
|
|
141
|
+
// unrelated record ids and is encouraged to reconstruct identity as application state.
|
|
142
|
+
const effectiveMarker = op.marker ?? (op.aggregateType === dir.aggregateId ? dir.marker : "mutates");
|
|
143
|
+
if (op.aggregateType !== undefined && (effectiveMarker === "mutates" || effectiveMarker === "archives")) {
|
|
144
|
+
const payloadField = byValue.get(op.aggregateId);
|
|
145
|
+
if (payloadField !== undefined) {
|
|
146
|
+
const fieldSchema = shape[payloadField];
|
|
147
|
+
const actualTarget = fieldSchema === undefined ? undefined : idOfMetaOf(fieldSchema);
|
|
148
|
+
if (actualTarget !== op.aggregateType) {
|
|
149
|
+
const expected = shortAggName(op.aggregateType);
|
|
150
|
+
const actual = actualTarget === undefined
|
|
151
|
+
? "an untyped string"
|
|
152
|
+
: `idOf(${shortAggName(actualTarget)})`;
|
|
153
|
+
const verb = effectiveMarker === "archives" ? "archive" : "mutate";
|
|
154
|
+
throw new IdOfLintError(
|
|
142
155
|
`directive '${dir.id}': payload field '${payloadField}' selects the ${expected} record to ${verb} ` +
|
|
143
156
|
`but carries ${actual} — declare it as idOf(${expected}) so create, update, find and watch use ` +
|
|
144
157
|
`the same generated NomosRef<${expected}>.`,
|
|
145
|
-
|
|
158
|
+
);
|
|
159
|
+
}
|
|
146
160
|
}
|
|
147
161
|
}
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
+
if (op.kind !== "set" && op.kind !== "setEntry") continue;
|
|
163
|
+
if (typeof op.value !== "string") continue;
|
|
164
|
+
const targetAgg = refTargetOf(op);
|
|
165
|
+
if (targetAgg === undefined) continue; // not a ref-kind field (or one the DSL can't name a target for)
|
|
166
|
+
const payloadField = byValue.get(op.value);
|
|
167
|
+
if (payloadField === undefined) continue; // not traceable to a top-level payload field — no verdict
|
|
168
|
+
const fieldSchema = shape[payloadField];
|
|
169
|
+
if (fieldSchema === undefined) continue;
|
|
170
|
+
const onAgg = shortAggName(op.aggregateType ?? op.aggregateId);
|
|
171
|
+
const targetName = shortAggName(targetAgg);
|
|
172
|
+
const actualTarget = idOfMetaOf(fieldSchema);
|
|
173
|
+
if (actualTarget === targetAgg) continue;
|
|
174
|
+
if (actualTarget !== undefined) {
|
|
175
|
+
throw new IdOfLintError(
|
|
176
|
+
`directive '${dir.id}': payload field '${payloadField}' feeds ref field '${onAgg}.${op.field}' ` +
|
|
177
|
+
`(-> ${targetName}) but carries idOf(${shortAggName(actualTarget)}) — declare it as ` +
|
|
178
|
+
`idOf(${targetName}) so generated callers carry NomosRef<${targetName}>.`,
|
|
179
|
+
);
|
|
180
|
+
}
|
|
181
|
+
if (!isBareString(fieldSchema) && brandedIdMetaOf(fieldSchema) === undefined) continue;
|
|
162
182
|
throw new IdOfLintError(
|
|
163
183
|
`directive '${dir.id}': payload field '${payloadField}' feeds ref field '${onAgg}.${op.field}' ` +
|
|
164
|
-
`(-> ${targetName}) but
|
|
165
|
-
`
|
|
184
|
+
`(-> ${targetName}) but is not idOf(${targetName}) — declare it as idOf(${targetName}) so generated ` +
|
|
185
|
+
`Dart/TS carry NomosRef<${targetName}>, matching the read model. (dart_codegen_parity.md P2)`,
|
|
166
186
|
);
|
|
167
187
|
}
|
|
168
|
-
if (!isBareString(fieldSchema) && brandedIdMetaOf(fieldSchema) === undefined) continue;
|
|
169
|
-
throw new IdOfLintError(
|
|
170
|
-
`directive '${dir.id}': payload field '${payloadField}' feeds ref field '${onAgg}.${op.field}' ` +
|
|
171
|
-
`(-> ${targetName}) but is not idOf(${targetName}) — declare it as idOf(${targetName}) so generated ` +
|
|
172
|
-
`Dart/TS carry NomosRef<${targetName}>, matching the read model. (dart_codegen_parity.md P2)`,
|
|
173
|
-
);
|
|
174
188
|
}
|
|
175
189
|
}
|
|
176
190
|
|
package/src/index.ts
CHANGED
|
@@ -56,10 +56,16 @@ export {
|
|
|
56
56
|
jsonWithNullSchema,
|
|
57
57
|
type EvidenceRefValue,
|
|
58
58
|
evidenceRefSchema,
|
|
59
|
+
type NomosRefValue,
|
|
60
|
+
nomosRefValueSchema,
|
|
59
61
|
type Sha256Digest,
|
|
62
|
+
type GitObjectId,
|
|
60
63
|
sha256Digest,
|
|
64
|
+
gitObjectId,
|
|
61
65
|
isSha256DigestSchema,
|
|
66
|
+
isGitObjectIdSchema,
|
|
62
67
|
NOMOS_SHA256_DIGEST_BRAND,
|
|
68
|
+
NOMOS_GIT_OBJECT_ID_BRAND,
|
|
63
69
|
// FIELD FACETS (the endorse model — security_model.md): authority/standing/attribution gate
|
|
64
70
|
// set() acceptance of PrincipalEvidence; secret/public are the confidentiality lattice.
|
|
65
71
|
authorityField,
|
|
@@ -80,6 +86,8 @@ export {
|
|
|
80
86
|
idOf,
|
|
81
87
|
idOfMetaOf,
|
|
82
88
|
idOfTargetKindOf,
|
|
89
|
+
refOf,
|
|
90
|
+
refOfMetaOf,
|
|
83
91
|
versionedRefOf,
|
|
84
92
|
versionedRefOfMetaOf,
|
|
85
93
|
brandedId,
|
|
@@ -87,7 +95,9 @@ export {
|
|
|
87
95
|
voMetaOf,
|
|
88
96
|
voSample,
|
|
89
97
|
NOMOS_VO_BRAND,
|
|
98
|
+
NOMOS_REFOF_BRAND,
|
|
90
99
|
NOMOS_VERSIONED_REF_BRAND,
|
|
100
|
+
type RefOfMeta,
|
|
91
101
|
type VersionedRefOfMeta,
|
|
92
102
|
geo,
|
|
93
103
|
type ValueObject,
|
|
@@ -617,13 +627,21 @@ export {
|
|
|
617
627
|
} from "./framework/capability.js";
|
|
618
628
|
export {
|
|
619
629
|
DOMAIN_INSTALLATION_PHASES,
|
|
630
|
+
DOMAIN_LIFECYCLE_DIRECTIVES,
|
|
631
|
+
MODEL_LIFECYCLE_AUTHORITY,
|
|
620
632
|
DomainInstallation,
|
|
633
|
+
DomainInstallExecutor,
|
|
634
|
+
DomainInstallationPhase,
|
|
635
|
+
currentLawReference,
|
|
636
|
+
domainInstallGenesisOffer,
|
|
621
637
|
domainInstallationAggregateId,
|
|
622
638
|
installDomain,
|
|
623
639
|
stageDomain,
|
|
624
640
|
promoteDomain,
|
|
625
641
|
rollbackDomain,
|
|
626
|
-
type
|
|
642
|
+
type DomainLifecycleDirectiveId,
|
|
643
|
+
type CurrentLawReference,
|
|
644
|
+
type LawClosureReference,
|
|
627
645
|
} from "./framework/domain_lifecycle.js";
|
|
628
646
|
export {
|
|
629
647
|
WORKSPACE_SYNC_DIRECTIONS,
|
|
@@ -666,6 +684,7 @@ export {
|
|
|
666
684
|
// NATIVE ZANZIBAR RELATION SCHEMA (P1) — the userset-rewrite law as pure data.
|
|
667
685
|
export {
|
|
668
686
|
relationType,
|
|
687
|
+
relationRef,
|
|
669
688
|
isRelationTypeDecl,
|
|
670
689
|
validateRelationType,
|
|
671
690
|
self,
|
|
@@ -691,6 +710,7 @@ export {
|
|
|
691
710
|
selfObject,
|
|
692
711
|
objectOf,
|
|
693
712
|
type RelationObjectToken,
|
|
713
|
+
type RelationRef,
|
|
694
714
|
type RelationTypeDecl,
|
|
695
715
|
type RelationTypeBuilder,
|
|
696
716
|
type RelationDefAst,
|
package/src/intent.ts
CHANGED
|
@@ -24,7 +24,11 @@ import type {
|
|
|
24
24
|
InvariantReadIdPart,
|
|
25
25
|
WorkspaceInvariantDecl,
|
|
26
26
|
} from "./framework/workspace_invariant.js";
|
|
27
|
-
import {
|
|
27
|
+
import {
|
|
28
|
+
BUSINESS_BIRTH_POLICY_BINDING,
|
|
29
|
+
businessBirthConsequence,
|
|
30
|
+
type BusinessBirthDirective,
|
|
31
|
+
} from "./business_birth.js";
|
|
28
32
|
import type { BusinessBirthDecl } from "./births.js";
|
|
29
33
|
import type { WorkspaceSharingPolicy } from "./workspace_sharing.js";
|
|
30
34
|
|
|
@@ -164,6 +168,7 @@ function remapNativeReads(
|
|
|
164
168
|
|
|
165
169
|
export interface BusinessIntent<I = unknown> extends Directive<I> {
|
|
166
170
|
readonly [INTENT_BRAND]: true;
|
|
171
|
+
readonly [BUSINESS_BIRTH_POLICY_BINDING]?: (domains: readonly string[]) => void;
|
|
167
172
|
readonly steps: readonly IntentStep<I, any>[];
|
|
168
173
|
/** The complete public business boundary. Runtime lowering still uses Directive.payloadSchema privately. */
|
|
169
174
|
readonly contract: IntentContract<I>;
|
|
@@ -433,6 +438,8 @@ class IntentBuilder<I> {
|
|
|
433
438
|
};
|
|
434
439
|
|
|
435
440
|
const businessBirth = (birthSteps[0]?.directive as BusinessBirthDirective | undefined)?.businessBirth;
|
|
441
|
+
const bindBusinessBirthPolicy =
|
|
442
|
+
(birthSteps[0]?.directive as BusinessBirthDirective | undefined)?.[BUSINESS_BIRTH_POLICY_BINDING];
|
|
436
443
|
return {
|
|
437
444
|
[INTENT_BRAND]: true,
|
|
438
445
|
id: this.id,
|
|
@@ -472,6 +479,9 @@ class IntentBuilder<I> {
|
|
|
472
479
|
? {}
|
|
473
480
|
: {
|
|
474
481
|
birthsChild: true,
|
|
482
|
+
...(bindBusinessBirthPolicy === undefined
|
|
483
|
+
? {}
|
|
484
|
+
: { [BUSINESS_BIRTH_POLICY_BINDING]: bindBusinessBirthPolicy }),
|
|
475
485
|
businessBirth: {
|
|
476
486
|
...businessBirth,
|
|
477
487
|
publicPayloadSchema: this.contractDeclaration.accepts,
|
package/src/law_graph.ts
CHANGED
|
@@ -31,6 +31,7 @@ import {
|
|
|
31
31
|
lowerLawValueType,
|
|
32
32
|
type LawConstant,
|
|
33
33
|
type LawLiteral,
|
|
34
|
+
type LawValueMember,
|
|
34
35
|
type LawValueConstraint,
|
|
35
36
|
type LawValueType,
|
|
36
37
|
} from "./law_value_type.js";
|
|
@@ -176,6 +177,47 @@ export interface LawPayloadContractNode {
|
|
|
176
177
|
readonly fields: readonly LawStructuralRef<"value-field">[];
|
|
177
178
|
}
|
|
178
179
|
|
|
180
|
+
/** Compiler-owned facts used to author `.owned()` effects. These are part of the
|
|
181
|
+
* internal typed USDA call contract, never the tenant-facing business contract. */
|
|
182
|
+
const OWNED_AUTHORING_PAYLOAD_MEMBERS = [
|
|
183
|
+
{
|
|
184
|
+
name: OWNED_KEY_PAYLOAD_FIELD,
|
|
185
|
+
required: false,
|
|
186
|
+
value: { kind: "string", constraints: [] },
|
|
187
|
+
},
|
|
188
|
+
{
|
|
189
|
+
name: OWNED_CUSTODY_PAYLOAD_FIELD,
|
|
190
|
+
required: false,
|
|
191
|
+
value: { kind: "string", constraints: [] },
|
|
192
|
+
},
|
|
193
|
+
{
|
|
194
|
+
name: OWNED_PREMINTED_RECORDS_PAYLOAD_FIELD,
|
|
195
|
+
required: false,
|
|
196
|
+
value: {
|
|
197
|
+
kind: "list",
|
|
198
|
+
element: {
|
|
199
|
+
kind: "record",
|
|
200
|
+
fields: [
|
|
201
|
+
{ name: "type", required: true, value: { kind: "string", constraints: [] } },
|
|
202
|
+
{ name: "id", required: true, value: { kind: "string", constraints: [] } },
|
|
203
|
+
],
|
|
204
|
+
},
|
|
205
|
+
},
|
|
206
|
+
},
|
|
207
|
+
] as const satisfies readonly LawValueMember[];
|
|
208
|
+
|
|
209
|
+
function internalPayloadContract(
|
|
210
|
+
tenantContract: LawValueType,
|
|
211
|
+
capabilities: ReadonlySet<"owned-authoring">,
|
|
212
|
+
): LawValueType {
|
|
213
|
+
if (tenantContract.kind !== "record") return tenantContract;
|
|
214
|
+
if (!capabilities.has("owned-authoring")) return tenantContract;
|
|
215
|
+
return {
|
|
216
|
+
kind: "record",
|
|
217
|
+
fields: [...tenantContract.fields, ...OWNED_AUTHORING_PAYLOAD_MEMBERS],
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
|
|
179
221
|
export interface LawReadParameter {
|
|
180
222
|
readonly name: string;
|
|
181
223
|
readonly field?: LawFieldPathRef;
|
|
@@ -1112,7 +1154,7 @@ export function discoverLawGraph(
|
|
|
1112
1154
|
const intentNodes: LawIntentNode[] = intents.map((intent) => {
|
|
1113
1155
|
const intentEntity = intentEntities.get(intent.id)!;
|
|
1114
1156
|
const businessBirth = (intent as {
|
|
1115
|
-
businessBirth?: { publicPayloadSchema?: z.ZodTypeAny };
|
|
1157
|
+
businessBirth?: { publicPayloadSchema?: z.ZodTypeAny; owned?: boolean };
|
|
1116
1158
|
}).businessBirth;
|
|
1117
1159
|
const payloadContractEntity = ownedEntity(
|
|
1118
1160
|
"contract",
|
|
@@ -1122,21 +1164,32 @@ export function discoverLawGraph(
|
|
|
1122
1164
|
`${intent.id}#accepts`,
|
|
1123
1165
|
options.source?.("intent", intent.id, module.name),
|
|
1124
1166
|
);
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1167
|
+
// A business birth has two deliberately different typed boundaries:
|
|
1168
|
+
//
|
|
1169
|
+
// * `contract.accepts` / `businessBirth.publicPayloadSchema` is the tenant-facing input;
|
|
1170
|
+
// * `payloadSchema` is the executable call contract after Nomos has added its private
|
|
1171
|
+
// principal, timestamp and (when needed) sealed-genesis facts.
|
|
1172
|
+
//
|
|
1173
|
+
// Collapsing both to `contract.accepts` made the generated client author the correct private
|
|
1174
|
+
// facts into a USDA call whose `nomos:accepts` contract did not declare them. Keep ordinary
|
|
1175
|
+
// compound intents on their public contract because they have no framework-only birth carrier;
|
|
1176
|
+
// only a declared business birth owns this second typed boundary.
|
|
1177
|
+
const tenantPayloadContract = lowerLawValueType(
|
|
1178
|
+
isBusinessIntent(intent) && businessBirth === undefined
|
|
1179
|
+
? intent.contract.accepts
|
|
1180
|
+
: intent.payloadSchema,
|
|
1128
1181
|
);
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
const reserved = [
|
|
1182
|
+
if (tenantPayloadContract.kind !== "record") {
|
|
1183
|
+
fail(`directive '${intent.id}': payload must lower to a record`);
|
|
1184
|
+
}
|
|
1185
|
+
for (const field of tenantPayloadContract.fields) {
|
|
1186
|
+
const reserved = new Set<string>([
|
|
1134
1187
|
OWNED_KEY_PAYLOAD_FIELD,
|
|
1135
1188
|
OWNED_CUSTODY_PAYLOAD_FIELD,
|
|
1136
1189
|
OWNED_PREMINTED_RECORDS_PAYLOAD_FIELD,
|
|
1137
1190
|
BUSINESS_BIRTH_SEED_PAYLOAD_FIELD,
|
|
1138
|
-
];
|
|
1139
|
-
if (!reserved.
|
|
1191
|
+
]);
|
|
1192
|
+
if (!reserved.has(field.name)) continue;
|
|
1140
1193
|
const frameworkBirthCarrier = field.name === BUSINESS_BIRTH_SEED_PAYLOAD_FIELD && businessBirth !== undefined;
|
|
1141
1194
|
if (frameworkBirthCarrier) continue;
|
|
1142
1195
|
fail(
|
|
@@ -1145,6 +1198,23 @@ export function discoverLawGraph(
|
|
|
1145
1198
|
`a different name.`,
|
|
1146
1199
|
);
|
|
1147
1200
|
}
|
|
1201
|
+
const affectedAggregateIds = [
|
|
1202
|
+
intent.aggregateId,
|
|
1203
|
+
...(intent.secondaryOutputs ?? []).map((output) => output.aggregateId),
|
|
1204
|
+
];
|
|
1205
|
+
const frameworkCapabilities = new Set<"owned-authoring">();
|
|
1206
|
+
if (
|
|
1207
|
+
businessBirth?.owned === true ||
|
|
1208
|
+
affectedAggregateIds.some((aggregateId) =>
|
|
1209
|
+
aggregateByLabel.get(aggregateId)?.node.ownerField !== undefined)
|
|
1210
|
+
) {
|
|
1211
|
+
frameworkCapabilities.add("owned-authoring");
|
|
1212
|
+
}
|
|
1213
|
+
const payloadShape = lowerAnonymousContractFields(
|
|
1214
|
+
internalPayloadContract(tenantPayloadContract, frameworkCapabilities),
|
|
1215
|
+
payloadContractEntity,
|
|
1216
|
+
);
|
|
1217
|
+
for (const node of payloadShape.nodes) anonymousValueNodes.set(node.entity.id, node);
|
|
1148
1218
|
payloadContractNodes.push({
|
|
1149
1219
|
kind: "payload-contract",
|
|
1150
1220
|
entity: payloadContractEntity,
|