@githolon/dsl 0.17.0 → 0.19.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/lib/nomos_client.dart +5 -0
- package/dart/lib/src/errors.dart +79 -0
- package/dart/lib/src/home.dart +20 -0
- package/dart/lib/src/webview_bridge.dart +68 -0
- package/dart/lib/src/wire.dart +117 -0
- package/dart/pubspec.lock +1 -1
- package/dart/pubspec.yaml +2 -1
- package/dart/test/home_parity_test.dart +9 -0
- package/dart/test/wire_test.dart +48 -0
- package/package.json +1 -1
- package/src/births.ts +106 -0
- package/src/build_package.ts +23 -27
- package/src/codegen_dart.ts +10 -0
- package/src/codegen_proof.ts +553 -84
- package/src/compile_package_main.ts +63 -111
- package/src/decl_shapes.ts +122 -0
- package/src/directive.ts +30 -0
- package/src/engine_entry.ts +109 -8
- package/src/framework/home.ts +179 -0
- package/src/framework/impure_capability.ts +8 -4
- package/src/framework/relations.ts +22 -0
- package/src/framework/signing_identity.ts +291 -2
- package/src/framework/workspace_invariant.ts +17 -2
- package/src/framework/workspaces.ts +193 -30
- package/src/index.ts +42 -0
- package/src/usd.ts +44 -0
- package/src/usd_layers.ts +41 -0
- package/src/wire.ts +13 -0
- package/src/wire_encode.ts +14 -2
|
@@ -67,6 +67,7 @@ export const GOVERNANCE_REVISION = "2026-06-12-capability-bindings"; // hash-bea
|
|
|
67
67
|
import { z } from "zod";
|
|
68
68
|
import { aggregate, instance } from "../aggregate.js";
|
|
69
69
|
import { create, ensure } from "../authoring.js";
|
|
70
|
+
import { birth } from "../births.js";
|
|
70
71
|
import { directive } from "../directive.js";
|
|
71
72
|
import { Lww } from "../drivers.js";
|
|
72
73
|
import { t } from "../fields.js";
|
|
@@ -262,6 +263,18 @@ export const CloudWorkspace = aggregate("CloudWorkspace", {
|
|
|
262
263
|
* the genesis directives HERE is law naming law (legitimate); the prohibition binds only the host.
|
|
263
264
|
*/
|
|
264
265
|
genesisChain: t.json().merge(Lww).optional(),
|
|
266
|
+
/**
|
|
267
|
+
* THE LINEAGE-AUTHORIZATION BINDING (proof-friendly — architecture/sovereign_adapter.md "birth is a kernel
|
|
268
|
+
* offer-effect"). The child's genesis SELF-ATTESTS who authorized it: a JSON commitment
|
|
269
|
+
* `{parent, principal, authorizedAt}` — the law-available lineage anchor — that the child's OWN chain
|
|
270
|
+
* carries, so a verifier of the child alone sees its claimed parentage, cross-checkable against the
|
|
271
|
+
* parent's chain (which holds the child's CloudWorkspace + the gated `birthChild` that minted it). It is
|
|
272
|
+
* the clean public-input shape a future recursive-proof circuit (Nova / zkWASM) would expose; the
|
|
273
|
+
* kernel-structural commitments (the parent chain head + the `birthChild` intent hash + the law hash) join
|
|
274
|
+
* it when the KERNEL produces the genesis (B's later slice — until then those ride as `""`). `t.string`
|
|
275
|
+
* (JSON), never `t.json`: it rides verbatim as a scalar (the kernel `Value` has no JSON variant — the same
|
|
276
|
+
* reasoning as the auth keys). Absent on root (the base of the world tree, born under nothing). */
|
|
277
|
+
bornUnder: t.string().merge(Lww).optional(),
|
|
265
278
|
});
|
|
266
279
|
|
|
267
280
|
// AdminGrant / CreationGrant aggregates: DELETED (full-native Zanzibar). Admin/creation authority is
|
|
@@ -431,17 +444,63 @@ function planBirthRecord(
|
|
|
431
444
|
export function buildGenesisChain(p: {
|
|
432
445
|
name: string;
|
|
433
446
|
principal: string;
|
|
447
|
+
parent: string;
|
|
434
448
|
tier: (typeof WORKSPACE_TIERS)[number];
|
|
435
449
|
foundingAdminHash: string;
|
|
436
450
|
bornAt: string;
|
|
437
451
|
requestedAt: number;
|
|
452
|
+
/** THE ORDER DECLARES THE LAW (G3 — no inheritance, no `_parentLawHash`): the content hashes of the
|
|
453
|
+
* bootstrap framework controller + the governance law package the child installs IN-CHAIN as its first
|
|
454
|
+
* intents. Frozen facts on the birth order (the parent captured them at `birthChild` time). When present,
|
|
455
|
+
* the recipe PREPENDS `bootstrap/installDomain(framework)` + `nomos/installDomain(law)` — the two-layer
|
|
456
|
+
* install the genesis bootstrap does — so the child installs ITS OWN law as intent 0 and self-validates
|
|
457
|
+
* from there. Absent ⇒ the recipe resolves an already-installed law by shape (pre-G3 / test lanes). */
|
|
458
|
+
frameworkHash?: string;
|
|
459
|
+
lawHash?: string;
|
|
460
|
+
/**
|
|
461
|
+
* THE WARRANT-FLIP (VA-1, architecture/identity_keys.md). When TRUE — set iff the genesis recipe
|
|
462
|
+
* declares an AuthProvider (an IdP vouches for this subtree's principals) — `seedGenesis` records a
|
|
463
|
+
* REAL `workspaceId` (the workspace name as the unique self-id), so the chain is born WARRANTED:
|
|
464
|
+
* `verify_captured_principal` enforces signed authorship on all lanes (author / receive / verify_chain).
|
|
465
|
+
* FALSE (the default, the legacy lane) keeps `workspaceId: ""` so the workspace stays unwarranted
|
|
466
|
+
* (host-stamped authorship, like root) — pre-VA / non-AuthProvider births replay byte-identically.
|
|
467
|
+
* NO flag-day: the era is per-workspace, decided at genesis by whether the recipe carries an AuthProvider.
|
|
468
|
+
*/
|
|
469
|
+
requiresWarrant?: boolean;
|
|
438
470
|
}): string {
|
|
439
471
|
const founder = "user:" + p.principal;
|
|
472
|
+
// THE ORDER-DECLARED LAW INSTALL (G3): the child installs the EXACT law the order names — bytes are NOT in
|
|
473
|
+
// the recipe (the kernel resolves them by hash from its own custody when it folds the birth, never the
|
|
474
|
+
// host). `domainHash` names the law; `domainPackageB64` stays empty here (the kernel supplies bytes-by-hash
|
|
475
|
+
// for the in-chain install). Two layers, matching the genesis bootstrap: bootstrap controller, then law.
|
|
476
|
+
const installSteps =
|
|
477
|
+
p.frameworkHash && p.lawHash
|
|
478
|
+
? [
|
|
479
|
+
{ domain: "bootstrap", directiveId: "installDomain", actor: founder, payload: { domainHash: p.frameworkHash }, domainHash: p.frameworkHash, domainPackageB64: "" },
|
|
480
|
+
{ domain: "nomos", directiveId: "installDomain", actor: founder, payload: { domainHash: p.lawHash }, domainHash: p.lawHash, domainPackageB64: "" },
|
|
481
|
+
]
|
|
482
|
+
: [];
|
|
483
|
+
// THE LINEAGE-AUTHORIZATION BINDING (proof-friendly): the child's birthCloud records who authorized it, so
|
|
484
|
+
// the child's OWN chain self-attests its parentage — cross-checkable against the parent's chain. The
|
|
485
|
+
// law-available anchor now; the kernel-structural commitments (parent chain head + birthChild intent hash
|
|
486
|
+
// + law hash) join as `""` placeholders until the kernel produces the genesis (B's later slice). The
|
|
487
|
+
// ordering of keys is fixed so the commitment is a stable, content-addressed shape (proof public inputs).
|
|
488
|
+
const bornUnder = JSON.stringify({
|
|
489
|
+
parent: p.parent,
|
|
490
|
+
principal: p.principal,
|
|
491
|
+
authorizedAt: p.requestedAt,
|
|
492
|
+
lawHash: "",
|
|
493
|
+
authorizationHash: "",
|
|
494
|
+
parentHead: "",
|
|
495
|
+
});
|
|
440
496
|
return JSON.stringify([
|
|
441
|
-
|
|
497
|
+
...installSteps,
|
|
498
|
+
// THE WARRANT-FLIP (VA-1): a REAL self-id iff the recipe declares an AuthProvider (born warranted ⇒
|
|
499
|
+
// signed authorship enforced on every lane); "" keeps the legacy host-stamped lane (byte-identical).
|
|
500
|
+
{ domain: "workspaces", directiveId: "seedGenesis", actor: founder, payload: { principal: founder, workspaceId: p.requiresWarrant ? p.name : "", bornAt: p.bornAt } },
|
|
442
501
|
{ domain: "workspaces", directiveId: "grantAdmin", actor: founder, payload: { keyHash: p.foundingAdminHash, label: `founding admin (born for ${p.principal})`, grantedBy: p.principal, grantedAt: p.requestedAt } },
|
|
443
502
|
// No `role` — it was a vestigial node-class label (collapsed with `kind`); birthCloud ignores it now.
|
|
444
|
-
{ domain: "workspaces", directiveId: "birthCloud", actor: "", payload: { principal: p.principal, name: p.name, tier: p.tier, bornAt: p.requestedAt } },
|
|
503
|
+
{ domain: "workspaces", directiveId: "birthCloud", actor: "", payload: { principal: p.principal, name: p.name, tier: p.tier, bornAt: p.requestedAt, bornUnder } },
|
|
445
504
|
]);
|
|
446
505
|
}
|
|
447
506
|
|
|
@@ -498,10 +557,14 @@ export const birthCloud = directive("birthCloud")
|
|
|
498
557
|
role: z.enum(WORKSPACE_ROLES).optional(),
|
|
499
558
|
tier: z.enum(WORKSPACE_TIERS).default("edge"),
|
|
500
559
|
bornAt: epochMs,
|
|
560
|
+
/** THE LINEAGE-AUTHORIZATION BINDING (proof-friendly): the JSON commitment to who authorized this
|
|
561
|
+
* birth, recorded onto the child's self-record so its OWN chain self-attests its parentage. Absent on
|
|
562
|
+
* root (born under nothing). Authored by {@link buildGenesisChain} for a child birth. */
|
|
563
|
+
bornUnder: z.string().optional(),
|
|
501
564
|
}),
|
|
502
565
|
)
|
|
503
566
|
.plan((p) => {
|
|
504
|
-
create(CloudWorkspace)
|
|
567
|
+
const rec = create(CloudWorkspace)
|
|
505
568
|
.set("name", p.name)
|
|
506
569
|
.set("parent", "") // the root of the world tree
|
|
507
570
|
.set("principal", p.principal)
|
|
@@ -511,6 +574,8 @@ export const birthCloud = directive("birthCloud")
|
|
|
511
574
|
.set("createdBy", p.principal)
|
|
512
575
|
.set("createdAt", p.bornAt)
|
|
513
576
|
.set("provisionedAt", p.bornAt);
|
|
577
|
+
// The child's chain self-attests its lineage authorization (the proof-friendly binding); root omits it.
|
|
578
|
+
if (p.bornUnder) rec.set("bornUnder", p.bornUnder);
|
|
514
579
|
// The self-identity singleton at the CONSTANT id (ungated — birthCloud is not admin-mutating), so the
|
|
515
580
|
// gate's self-record exists before any admin write. ensure() = deterministic-id sibling birth. `name` is
|
|
516
581
|
// the born marker; `role` is no longer written (vestigial node-class — see WorkspaceSelf).
|
|
@@ -609,18 +674,18 @@ export const retireWorkspace = directive("retireWorkspace")
|
|
|
609
674
|
/**
|
|
610
675
|
* reclaimWorkspace — THE LAWFUL CUSTODY DISCARD. Where {@link retireWorkspace}
|
|
611
676
|
* withdraws the law (a status, the bytes untouched), reclaim is the law AUTHORIZING
|
|
612
|
-
* the
|
|
613
|
-
*
|
|
677
|
+
* the bailiff to drop the custody: on seeing THIS fact folded, the bailiff drops the
|
|
678
|
+
* workspace's git ledger + DO storage (operator-gated soft-mark while pre-release).
|
|
614
679
|
*
|
|
615
|
-
* THE TWO-PHASE RULE
|
|
616
|
-
*
|
|
680
|
+
* THE TWO-PHASE RULE is LAW (see {@link reclaimRequiresRetired}): only a `retired`
|
|
681
|
+
* workspace may be reclaimed — the invariant reads the pre-apply status at the one gate.
|
|
617
682
|
*
|
|
618
|
-
* THE FIXED POINT: this is authored for CHILDREN only. The
|
|
619
|
-
*
|
|
620
|
-
*
|
|
621
|
-
*
|
|
622
|
-
*
|
|
623
|
-
*
|
|
683
|
+
* THE FIXED POINT: this is authored for CHILDREN only. The law refuses to reclaim ROOT —
|
|
684
|
+
* the genesis workspace is the seat of the law that authorizes reclaim, so the law can
|
|
685
|
+
* reap everything it governs EXCEPT itself. Dropping root is the one act Nomos cannot
|
|
686
|
+
* perform in-law; the custodian does it through Cloudflare directly. The custody-reclaimed
|
|
687
|
+
* fact (this status flip) is append-only and outlives the bytes: Nomos never erases the
|
|
688
|
+
* FACT that a workspace existed and was reclaimed.
|
|
624
689
|
*/
|
|
625
690
|
export const reclaimWorkspace = directive("reclaimWorkspace")
|
|
626
691
|
.mutates(CloudWorkspace)
|
|
@@ -684,6 +749,17 @@ export const birthChild = directive("birthChild")
|
|
|
684
749
|
/** GENESIS birth time as an ISO string (caller-stamped — the timestamp doctrine). Arms the warrant
|
|
685
750
|
* in the law-authored genesis chain; required for (and only used by) a genesis birth. */
|
|
686
751
|
bornAt: z.string().optional(),
|
|
752
|
+
/** THE ORDER DECLARES THE LAW (G3 — no inheritance): the content hashes of the bootstrap framework
|
|
753
|
+
* controller + the governance law package the child installs IN-CHAIN as its genesis intent 0. Frozen
|
|
754
|
+
* facts the requester presents (the parent's current law, captured at birth time). When present, the
|
|
755
|
+
* genesis recipe prepends the two-layer installDomain; the kernel resolves the BYTES by hash from its
|
|
756
|
+
* own custody (never the host). Absent ⇒ the recipe resolves an already-installed law by shape. */
|
|
757
|
+
frameworkHash: sha256Hex.optional(),
|
|
758
|
+
lawHash: sha256Hex.optional(),
|
|
759
|
+
/** THE WARRANT-FLIP (VA-1): TRUE iff the child's genesis recipe declares an AuthProvider — the child
|
|
760
|
+
* is born WARRANTED (signed authorship enforced on every lane). Absent/false ⇒ the legacy
|
|
761
|
+
* host-stamped lane (byte-identical to a pre-VA birth). Only meaningful with a genesis. */
|
|
762
|
+
requiresWarrant: z.boolean().optional(),
|
|
687
763
|
})
|
|
688
764
|
// A genesis birth (a platform) must carry a principal AND a bornAt — it owns a subtree, a lineage
|
|
689
765
|
// anchor, and a law-authored genesis chain whose warrant is stamped at bornAt.
|
|
@@ -694,13 +770,23 @@ export const birthChild = directive("birthChild")
|
|
|
694
770
|
)
|
|
695
771
|
.plan((p) => {
|
|
696
772
|
const rec = planBirthRecord(p, p.foundingAdminHash ? { foundingAdminHash: p.foundingAdminHash, pool: p.pool } : undefined);
|
|
697
|
-
// LAW AUTHORS THE GENESIS RECIPE (P1): a genesis birth carries its child's
|
|
698
|
-
// birthCloud chain
|
|
773
|
+
// LAW AUTHORS THE GENESIS RECIPE (P1): a genesis birth carries its child's [installDomain×2 →]
|
|
774
|
+
// seedGenesis→grantAdmin→birthCloud chain. (refine guarantees principal + bornAt here.)
|
|
699
775
|
if (p.foundingAdminHash) {
|
|
700
|
-
|
|
776
|
+
const genesisChain = buildGenesisChain({ name: p.name, principal: p.principal, parent: p.parent, tier: p.tier, foundingAdminHash: p.foundingAdminHash, bornAt: p.bornAt as string, requestedAt: p.requestedAt, ...(p.frameworkHash ? { frameworkHash: p.frameworkHash } : {}), ...(p.lawHash ? { lawHash: p.lawHash } : {}), ...(p.requiresWarrant ? { requiresWarrant: true } : {}) });
|
|
777
|
+
// THE GENESIS RECIPE IS ALSO A LINEAGE/AUDIT FACT on the parent's chain (the parent records what it
|
|
778
|
+
// ORDERED — law naming law, legitimate). The kernel does NOT read this field to detect the birth.
|
|
779
|
+
rec.set("genesisChain", genesisChain);
|
|
780
|
+
// THE DECLARED BIRTH EFFECT (G3 — the unix model): the recipe rides the committed intent as a declared
|
|
781
|
+
// output (`captured_ports.births`). The kernel — reading the `.births` DECLARATION off the law manifest
|
|
782
|
+
// (the `nomosBirthGate` era key, NEVER this CloudWorkspace aggregate) — takes the recipe off the intent
|
|
783
|
+
// after admit and spawns + self-validates the child through the child's OWN gate. So birth is an
|
|
784
|
+
// EXPLICIT, declared consequence of the offer, not a side-effect inferred by scanning domain state.
|
|
785
|
+
birth({ workspace: p.name, genesisChain: JSON.parse(genesisChain) });
|
|
701
786
|
}
|
|
702
787
|
return [];
|
|
703
788
|
})
|
|
789
|
+
.births() // BIRTH IS A DECLARED KERNEL PRIMITIVE (G3): emits the `nomosBirthGate` era key; the kernel folds the carried recipe
|
|
704
790
|
.requires("creator"); // THE ONE birth authority — a #creator may birth a leaf OR a platform (tier collapsed)
|
|
705
791
|
|
|
706
792
|
/** workspacesByStatus — the birth provider drains `requested` CloudWorkspaces that CARRY A foundingAdminHash
|
|
@@ -745,13 +831,31 @@ export const AuthProvider = aggregate("AuthProvider", {
|
|
|
745
831
|
scope: t.string().merge(Lww),
|
|
746
832
|
/** The token issuer (the `iss` claim tokens must carry). */
|
|
747
833
|
issuer: t.string().merge(Lww),
|
|
748
|
-
/** The provider's public JWKS endpoint (asymmetric verification keys).
|
|
834
|
+
/** The provider's public JWKS endpoint (asymmetric verification keys). The KERNEL never fetches — it's a
|
|
835
|
+
* reference for the rotation capability that re-fetches + re-sets {@link keys}. */
|
|
749
836
|
jwksUrl: t.string().merge(Lww),
|
|
837
|
+
/** THE VERIFYING KEYS — the JWKS JSON (`{keys:[{kid,kty,alg,crv,x,y,n,e,...}]}`) as an OPAQUE STRING the
|
|
838
|
+
* KERNEL parses + verifies a presented token against (sovereign-auth arc — see
|
|
839
|
+
* architecture/sovereign_adapter.md). `t.string` (NOT `t.json`): the kernel `Value` has no JSON variant,
|
|
840
|
+
* so the raw JWKS text must ride verbatim as a scalar — the kernel does `serde_json` on it, no projection
|
|
841
|
+
* parse, no double-encoding. LAW-STATE: set by {@link setAuthProvider} (gated), refreshed by the
|
|
842
|
+
* JWKS-fetch rotation capability (the host FETCHES — impure — but the law OWNS the keys + the kernel does
|
|
843
|
+
* the verification). Absent ⇒ no in-kernel verify for this workspace (shell-issuer fallback, transitional). */
|
|
844
|
+
keys: t.string().merge(Lww).optional(),
|
|
750
845
|
/** OPTIONAL expected `aud` claim. When the platform sets it, a token's `aud` MUST carry this value
|
|
751
846
|
* (defense-in-depth beyond the issuer match — e.g. a Firebase project id, an Auth0 API audience).
|
|
752
847
|
* Absent ⇒ issuer match alone. This is LAW-STATE the PLATFORM owns; the host merely enforces it as a
|
|
753
848
|
* capability (it never chooses the audience). */
|
|
754
849
|
audience: t.string().merge(Lww),
|
|
850
|
+
/** OPTIONAL — the JWT claim that carries the PRINCIPAL (default `"sub"` when absent). Azure AD's stable
|
|
851
|
+
* user id is `oid` (its `sub` is pairwise-per-app, useless as identity); some setups key on a custom
|
|
852
|
+
* claim. The KERNEL extracts THIS claim as the principal — never hardcoded to `sub`. */
|
|
853
|
+
principalClaim: t.string().merge(Lww).optional(),
|
|
854
|
+
/** OPTIONAL — the PINNED CA ROOT (base64 DER) for TRUSTLESS key rotation. When set, {@link rotateKeys}
|
|
855
|
+
* accepts a new JWKS IFF each key's `x5c` chain validates to THIS root IN THE KERNEL (RFC 5280, the IdP's
|
|
856
|
+
* CA vouches) and each key matches its leaf cert — no host trust, no admin. Pinned once via the
|
|
857
|
+
* admin-gated {@link setAuthProvider}. Absent ⇒ no trustless rotation (rotate via setAuthProvider). */
|
|
858
|
+
rootCert: t.string().merge(Lww).optional(),
|
|
755
859
|
/** Human label for audit ("acme supabase", ...). */
|
|
756
860
|
label: t.string().merge(Lww),
|
|
757
861
|
/** Who set it (attribution). */
|
|
@@ -773,6 +877,13 @@ export const setAuthProvider = directive("setAuthProvider")
|
|
|
773
877
|
issuer: z.string().regex(/^https?:\/\/\S+$/, "expected an http(s) URL"),
|
|
774
878
|
jwksUrl: z.string().regex(/^https?:\/\/\S+$/, "expected an http(s) URL"),
|
|
775
879
|
audience: z.string().min(1).optional(),
|
|
880
|
+
/** The JWKS JSON (the public verifying keys) the KERNEL checks tokens against. Optional so the
|
|
881
|
+
* rotation capability can re-set just the keys; absent keeps the prior keys (Lww). */
|
|
882
|
+
keys: z.string().min(1).optional(),
|
|
883
|
+
/** OPTIONAL — the claim carrying the principal (default "sub"; e.g. "oid" for Azure AD). */
|
|
884
|
+
principalClaim: z.string().min(1).optional(),
|
|
885
|
+
/** OPTIONAL — the pinned CA root (base64 DER) enabling trustless {@link rotateKeys}. */
|
|
886
|
+
rootCert: z.string().min(1).optional(),
|
|
776
887
|
label: z.string().min(1),
|
|
777
888
|
setBy: z.string().min(1),
|
|
778
889
|
setAt: epochMs,
|
|
@@ -785,6 +896,9 @@ export const setAuthProvider = directive("setAuthProvider")
|
|
|
785
896
|
set(ap, "issuer", p.issuer),
|
|
786
897
|
set(ap, "jwksUrl", p.jwksUrl),
|
|
787
898
|
...(p.audience ? [set(ap, "audience", p.audience)] : []),
|
|
899
|
+
...(p.keys ? [set(ap, "keys", p.keys)] : []),
|
|
900
|
+
...(p.principalClaim ? [set(ap, "principalClaim", p.principalClaim)] : []),
|
|
901
|
+
...(p.rootCert ? [set(ap, "rootCert", p.rootCert)] : []),
|
|
788
902
|
set(ap, "label", p.label),
|
|
789
903
|
set(ap, "setBy", p.setBy),
|
|
790
904
|
set(ap, "setAt", p.setAt),
|
|
@@ -792,6 +906,30 @@ export const setAuthProvider = directive("setAuthProvider")
|
|
|
792
906
|
})
|
|
793
907
|
.requires("admin");
|
|
794
908
|
|
|
909
|
+
/**
|
|
910
|
+
* rotateKeys — TRUSTLESS key rotation. Replace {@link AuthProvider.keys} with a new JWKS whose every key
|
|
911
|
+
* carries an `x5c` cert chain. DELIBERATELY UNGATED at the relation level — the KERNEL is the authority: it
|
|
912
|
+
* admits this ONLY if each key's x5c chain validates (RFC 5280) to the law-pinned {@link AuthProvider.rootCert}
|
|
913
|
+
* and each key matches its CA-vouched leaf cert (the bind). So a bailiff (host on a cron, or anyone) may
|
|
914
|
+
* FETCH the IdP's rotated JWKS and offer it, but cannot forge keys — forged keys wouldn't chain to the root.
|
|
915
|
+
* No host trust, no admin: the IdP's CA vouches. Workspaces with no pinned root rotate via setAuthProvider.
|
|
916
|
+
*/
|
|
917
|
+
export const rotateKeys = directive("rotateKeys")
|
|
918
|
+
.ensures(AuthProvider)
|
|
919
|
+
.payload(
|
|
920
|
+
z.object({
|
|
921
|
+
/** The new JWKS JSON; EACH key MUST carry an `x5c` the kernel validates to {@link AuthProvider.rootCert}. */
|
|
922
|
+
keys: z.string().min(1),
|
|
923
|
+
rotatedAt: epochMs,
|
|
924
|
+
}),
|
|
925
|
+
)
|
|
926
|
+
.plan((p) => {
|
|
927
|
+
const ap = instance(AuthProvider, "auth-provider:platform");
|
|
928
|
+
// The kernel's x5c admission gate authorizes this (validate_rotate_keys); the plan just stores the
|
|
929
|
+
// CA-vouched JWKS. The `ensures` marker upserts onto the existing AuthProvider singleton.
|
|
930
|
+
return [withMarker(set(ap, "keys", p.keys), "ensures"), set(ap, "setAt", p.rotatedAt)];
|
|
931
|
+
});
|
|
932
|
+
|
|
795
933
|
/**
|
|
796
934
|
* HostnameClaim — a CUSTOM HOSTNAME bound to a platform: `api.acme.com` serving
|
|
797
935
|
* acme's subtree. A root-custody fact (the hostname is the HOST's edge resource —
|
|
@@ -1006,14 +1144,21 @@ export const setWorkspaceByteCap = directive("setWorkspaceByteCap")
|
|
|
1006
1144
|
export const grantAdmin = directive("grantAdmin")
|
|
1007
1145
|
.ensures(RelationTuple)
|
|
1008
1146
|
.payload(
|
|
1009
|
-
z
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1147
|
+
z
|
|
1148
|
+
.object({
|
|
1149
|
+
// `principal` (a `user:<uid>` subject) is the PRINCIPAL-CENTRIC grant — authority lives on the
|
|
1150
|
+
// identity, so it survives device-key ROTATION (the signer indirection's whole point) and matches
|
|
1151
|
+
// the subject a SIGNED author resolves to. `keyHash` (a `key:<hash>` subject) is the legacy
|
|
1152
|
+
// key-centric grant (the bearer-secret lane). Exactly one — mirrors grantCreation.
|
|
1153
|
+
keyHash: sha256Hex.optional(),
|
|
1154
|
+
principal: z.string().min(1).optional(),
|
|
1155
|
+
label: z.string().min(1),
|
|
1156
|
+
grantedBy: z.string().min(1),
|
|
1157
|
+
grantedAt: epochMs,
|
|
1158
|
+
})
|
|
1159
|
+
.refine((d) => !!d.keyHash !== !!d.principal, { message: "grantAdmin needs exactly one of keyHash (a key: subject) or principal (a user: subject)", path: ["keyHash"] }),
|
|
1015
1160
|
)
|
|
1016
|
-
.plan((p) => writeTuple({ object: SELF_ADMIN_OBJECT, relation: "admin", subject: "key:" + p.keyHash, grantedBy: p.grantedBy, grantedAt: String(p.grantedAt) }))
|
|
1161
|
+
.plan((p) => writeTuple({ object: SELF_ADMIN_OBJECT, relation: "admin", subject: p.principal ? "user:" + p.principal : "key:" + p.keyHash, grantedBy: p.grantedBy, grantedAt: String(p.grantedAt) }))
|
|
1017
1162
|
.requires("admin"); // only an admin grants admin (pre-apply roster ⇒ no self-grant)
|
|
1018
1163
|
|
|
1019
1164
|
/**
|
|
@@ -1027,13 +1172,16 @@ export const grantAdmin = directive("grantAdmin")
|
|
|
1027
1172
|
export const revokeAdmin = directive("revokeAdmin")
|
|
1028
1173
|
.ensures(RelationTuple)
|
|
1029
1174
|
.payload(
|
|
1030
|
-
z
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1175
|
+
z
|
|
1176
|
+
.object({
|
|
1177
|
+
keyHash: sha256Hex.optional(),
|
|
1178
|
+
principal: z.string().min(1).optional(),
|
|
1179
|
+
revokedBy: z.string().min(1),
|
|
1180
|
+
revokedAt: epochMs,
|
|
1181
|
+
})
|
|
1182
|
+
.refine((d) => !!d.keyHash !== !!d.principal, { message: "revokeAdmin needs exactly one of keyHash or principal", path: ["keyHash"] }),
|
|
1035
1183
|
)
|
|
1036
|
-
.plan((p) => removeTuple({ object: SELF_ADMIN_OBJECT, relation: "admin", subject: "key:" + p.keyHash, grantedBy: p.revokedBy, grantedAt: String(p.revokedAt), revokedBy: p.revokedBy, revokedAt: String(p.revokedAt) }))
|
|
1184
|
+
.plan((p) => removeTuple({ object: SELF_ADMIN_OBJECT, relation: "admin", subject: p.principal ? "user:" + p.principal : "key:" + p.keyHash, grantedBy: p.revokedBy, grantedAt: String(p.revokedAt), revokedBy: p.revokedBy, revokedAt: String(p.revokedAt) }))
|
|
1037
1185
|
.requires("admin");
|
|
1038
1186
|
|
|
1039
1187
|
/**
|
|
@@ -1253,3 +1401,18 @@ export const provisionReceiptGuard = workspaceInvariant("ProvisionReceiptGuard")
|
|
|
1253
1401
|
return { reject: "provision-founding-hash-mismatch" };
|
|
1254
1402
|
return { accept: true };
|
|
1255
1403
|
});
|
|
1404
|
+
|
|
1405
|
+
/** reclaimWorkspace: THE RETIRED→RECLAIMED TWO-PHASE INTERLOCK, AS LAW (#41/#58). Reclaim authorizes the
|
|
1406
|
+
* irreversible custody discard, so only a `retired` workspace may be reclaimed — retire WITHDRAWS (a status,
|
|
1407
|
+
* reversible), reclaim DISCARDS. The invariant reads the pre-apply status of the record being reclaimed (same
|
|
1408
|
+
* shape as ProvisionReceiptGuard) and refuses unless it is exactly `retired`. Fires at the ONE wasm gate on
|
|
1409
|
+
* every peer — author, edge admission, AND verify_chain — so the interlock is identical on cloud + client +
|
|
1410
|
+
* replay; the bailiff's drop merely executes the lawful `reclaimed` fact. (Root never reaches here: the law
|
|
1411
|
+
* refuses reclaiming itself; this guards children.) */
|
|
1412
|
+
export const reclaimRequiresRetired = workspaceInvariant("ReclaimRequiresRetired")
|
|
1413
|
+
.on("reclaimWorkspace")
|
|
1414
|
+
.reads((ctx) => [refAs("ws", "CloudWorkspace", "pre:" + String((ctx.intent ?? {}).workspaceId ?? ""))])
|
|
1415
|
+
.assert(({ ws }) => {
|
|
1416
|
+
const rec = ws as { status?: string } | undefined;
|
|
1417
|
+
return rec?.status === "retired" ? { accept: true } : { reject: "reclaim-requires-retired" };
|
|
1418
|
+
});
|
package/src/index.ts
CHANGED
|
@@ -94,6 +94,18 @@ export { create, existing, ensure, type AggregateRef } from "./authoring.js";
|
|
|
94
94
|
// `nomos-compile` REFUSES law that references read() (a named compile error, never a runtime halt).
|
|
95
95
|
// The captured-reads wave (task #58) wires the capability in and lifts the refusal.
|
|
96
96
|
export { read } from "./read.js";
|
|
97
|
+
// THE BIRTH EFFECT (G3 — birth is a declared kernel primitive): a `.births()` directive's plan records the
|
|
98
|
+
// child's genesis recipe via `birth(recipe)` (reuse `buildGenesisChain`); it rides the committed intent as a
|
|
99
|
+
// declared output, and the kernel — reading the `.births` declaration off the law manifest (the
|
|
100
|
+
// `nomosBirthGate` era key, never domain state) — spawns + self-validates the child after the offer admits.
|
|
101
|
+
export { birth, type BirthRecipe } from "./births.js";
|
|
102
|
+
// THE PEER-SOVEREIGN ROOT-OF-TRUST (the BirthAttestation cert chain): a home/child law exports
|
|
103
|
+
// `requireParentAttestedBirth` to declare it requires a parent-attested-birth cert chain (the
|
|
104
|
+
// `nomosParentAttestedBirth` era key); the kernel verify doors then verify the travelling chain to K_root.
|
|
105
|
+
export {
|
|
106
|
+
requireParentAttestedBirth,
|
|
107
|
+
type ParentAttestedBirthMarker,
|
|
108
|
+
} from "./births.js";
|
|
97
109
|
export {
|
|
98
110
|
query,
|
|
99
111
|
hasManyIndexes,
|
|
@@ -247,6 +259,28 @@ export {
|
|
|
247
259
|
signerObject,
|
|
248
260
|
SIGNER_RELATION,
|
|
249
261
|
enrollSigner,
|
|
262
|
+
// VA-core — SignerEpoch + self-bind enroll/revoke + the strict-increment & verified-enroll gates.
|
|
263
|
+
SignerEpoch,
|
|
264
|
+
SIGNER_EPOCH_ID,
|
|
265
|
+
signerEpochCurrent,
|
|
266
|
+
advanceSignerEpoch,
|
|
267
|
+
signerEpochStrictlyIncreases,
|
|
268
|
+
enrollSignerSelf,
|
|
269
|
+
enrollSignerSelfRequiresVerified,
|
|
270
|
+
revokeSigner,
|
|
271
|
+
revokeSignerEpochStrictlyIncreases,
|
|
272
|
+
signerTuplesByObject,
|
|
273
|
+
signerTupleInstanceId,
|
|
274
|
+
// THE ENROLL-ONCE DELEGATION (the peer-sovereign root-of-trust) — the per-user tenant-signed cert the
|
|
275
|
+
// kernel reads to assemble U's home cert chain at birth.
|
|
276
|
+
SignerDelegation,
|
|
277
|
+
SIGNER_DELEGATION_PREFIX,
|
|
278
|
+
signerDelegationInstanceId,
|
|
279
|
+
enrollDelegation,
|
|
280
|
+
signerDelegationByUser,
|
|
281
|
+
type SignerDelegationHandle,
|
|
282
|
+
type SignerEpochHandle,
|
|
283
|
+
type SignerAuthority,
|
|
250
284
|
} from "./framework/signing_identity.js";
|
|
251
285
|
// THE HOME HOLON (home_holons_and_cross_custody.md §4) — the identity-role law: the shared-with-me access
|
|
252
286
|
// index + the owner lane to maintain it (the key directory is the `signer` relation above).
|
|
@@ -262,6 +296,14 @@ export {
|
|
|
262
296
|
seedHomeOwner,
|
|
263
297
|
writeHomeOwner,
|
|
264
298
|
homeNameBindsOwner,
|
|
299
|
+
homeNameOf,
|
|
300
|
+
birthHome,
|
|
301
|
+
// THE PEER-SOVEREIGN ROOT-OF-TRUST (the BirthAttestation cert chain).
|
|
302
|
+
BirthAttestation,
|
|
303
|
+
BIRTH_ATTESTATION_ID,
|
|
304
|
+
seedBirthAttestation,
|
|
305
|
+
birthAttestation,
|
|
306
|
+
parentAttestedBirth,
|
|
265
307
|
} from "./framework/home.js";
|
|
266
308
|
// THE GENESIS WARRANT (identity_keys.md step 0) — the in-history birth fact that lets
|
|
267
309
|
// the gate's genesis clause admit a founding control-plane grant on a virgin chain.
|
package/src/usd.ts
CHANGED
|
@@ -285,6 +285,33 @@ export interface UsdLayer {
|
|
|
285
285
|
}
|
|
286
286
|
>;
|
|
287
287
|
};
|
|
288
|
+
/**
|
|
289
|
+
* THE BIRTH GATE DECLARATION (G3 — birth is a declared kernel primitive, the unix model). Present IFF ≥1
|
|
290
|
+
* directive declares `.births()`: the SORTED set of their ids. A directive named here BIRTHS a child
|
|
291
|
+
* workspace as the defined CONSEQUENCE of its offer — after the offer admits, the kernel reads THIS
|
|
292
|
+
* declaration (the SAME way it reads `nomosReadGate`/`nomosInvariantGate` — never a domain aggregate),
|
|
293
|
+
* takes the genesis recipe the intent carried (its `captured_ports.births`), spawns the child's custody,
|
|
294
|
+
* and self-validates the child's whole genesis through the child's OWN gate (the `materialize_genesis`
|
|
295
|
+
* keystone). The kernel reads NO domain schema; it reads its own manifest declaration + the carried
|
|
296
|
+
* recipe. OMITTED for law with no birth directive (the era rule — byte-identical hash; every pre-feature
|
|
297
|
+
* chain replays green, the birth effect inert). SORTED keys for byte-determinism.
|
|
298
|
+
*/
|
|
299
|
+
readonly nomosBirthGate?: {
|
|
300
|
+
readonly v: 1;
|
|
301
|
+
/** The directive ids declared `.births()` — SORTED. */
|
|
302
|
+
readonly directives: string[];
|
|
303
|
+
};
|
|
304
|
+
/**
|
|
305
|
+
* THE PEER-SOVEREIGN ROOT-OF-TRUST (the BirthAttestation cert chain). Present IFF the law declares
|
|
306
|
+
* `requireParentAttestedBirth`: a warranted workspace running this law MUST travel with a SIGNED cert
|
|
307
|
+
* chain proving its founder was VERIFIED by its parent at birth, rooting at the pinned `K_root`. The
|
|
308
|
+
* kernel's verify doors (author / receive / verify_chain) verify the travelling chain OFFLINE — no
|
|
309
|
+
* cross-ws read. OMITTED for law that doesn't declare it (the era rule — byte-identical hash; every
|
|
310
|
+
* pre-feature + legacy/non-warranted chain replays green, the check inert).
|
|
311
|
+
*/
|
|
312
|
+
readonly nomosParentAttestedBirth?: {
|
|
313
|
+
readonly v: 1;
|
|
314
|
+
};
|
|
288
315
|
/**
|
|
289
316
|
* STABLE IDENTIFIERS (#58 — names are labels, identity is minted). The layer's
|
|
290
317
|
* domain/aggregate/field sids + label lineages, carried verbatim from the canonical
|
|
@@ -721,6 +748,21 @@ export function emitUsd(
|
|
|
721
748
|
}
|
|
722
749
|
const nomosAttestationGate =
|
|
723
750
|
Object.keys(attestationReq).length > 0 ? { v: 1 as const, directives: attestationReq } : undefined;
|
|
751
|
+
// THE BIRTH GATE (G3 — birth is a declared kernel primitive): the SORTED set of directive ids
|
|
752
|
+
// declaring `.births()`. The kernel reads this off the manifest (never a domain aggregate) to know an
|
|
753
|
+
// admitted directive births, then folds the recipe the intent carried. Omit-when-empty (era rule —
|
|
754
|
+
// a birth-free layer is byte-identical apart from the always-on stableIds key).
|
|
755
|
+
const birthDirectives = [...l.module.directives]
|
|
756
|
+
.filter((d) => (d as { birthsChild?: boolean }).birthsChild === true)
|
|
757
|
+
.map((d) => d.id)
|
|
758
|
+
.sort();
|
|
759
|
+
const nomosBirthGate =
|
|
760
|
+
birthDirectives.length > 0 ? { v: 1 as const, directives: birthDirectives } : undefined;
|
|
761
|
+
// THE PEER-SOVEREIGN ROOT-OF-TRUST: the home/child law declared it requires a parent-attested-birth
|
|
762
|
+
// cert chain (the `requireParentAttestedBirth` sentinel, discovered by composeDomainModule). Emit the
|
|
763
|
+
// era key iff true; omit otherwise (a law that doesn't require it is byte-identical — the era rule).
|
|
764
|
+
const nomosParentAttestedBirth =
|
|
765
|
+
(l.module as { parentAttestedBirth?: boolean }).parentAttestedBirth === true ? { v: 1 as const } : undefined;
|
|
724
766
|
// THE CAPTURED-READ GATE DECLARATION (#58): collected from the canonical
|
|
725
767
|
// manifest's per-directive `capturedReads` (omit-when-empty — read-free law's
|
|
726
768
|
// USD is byte-identical apart from the always-on stableIds key).
|
|
@@ -850,6 +892,8 @@ export function emitUsd(
|
|
|
850
892
|
...(nomosRelations !== undefined ? { nomosRelations } : {}),
|
|
851
893
|
...(nomosRelationGate !== undefined ? { nomosRelationGate } : {}),
|
|
852
894
|
...(nomosAttestationGate !== undefined ? { nomosAttestationGate } : {}),
|
|
895
|
+
...(nomosBirthGate !== undefined ? { nomosBirthGate } : {}),
|
|
896
|
+
...(nomosParentAttestedBirth !== undefined ? { nomosParentAttestedBirth } : {}),
|
|
853
897
|
...(Object.keys(visAggregates).length > 0
|
|
854
898
|
? { nomosReadVisibility: { v: 1 as const, aggregates: visAggregates } }
|
|
855
899
|
: {}),
|
package/src/usd_layers.ts
CHANGED
|
@@ -177,6 +177,12 @@ function domainLayerAttrLines(domain: string, layer: UsdLayer, indent: string):
|
|
|
177
177
|
...(layer.nomosAttestationGate !== undefined
|
|
178
178
|
? [`${indent}custom string nomos:attestationGateHex = ${usdaString(hexUtf8(JSON.stringify(layer.nomosAttestationGate)))}`]
|
|
179
179
|
: []),
|
|
180
|
+
...(layer.nomosBirthGate !== undefined
|
|
181
|
+
? [`${indent}custom string nomos:birthGateHex = ${usdaString(hexUtf8(JSON.stringify(layer.nomosBirthGate)))}`]
|
|
182
|
+
: []),
|
|
183
|
+
...(layer.nomosParentAttestedBirth !== undefined
|
|
184
|
+
? [`${indent}custom string nomos:parentAttestedBirthHex = ${usdaString(hexUtf8(JSON.stringify(layer.nomosParentAttestedBirth)))}`]
|
|
185
|
+
: []),
|
|
180
186
|
];
|
|
181
187
|
}
|
|
182
188
|
|
|
@@ -765,6 +771,23 @@ export function flattenLayeredUsd(layers: readonly UsdLayer[]): UsdDocument {
|
|
|
765
771
|
attestationGate = { v: 1, directives: rec };
|
|
766
772
|
}
|
|
767
773
|
}
|
|
774
|
+
// THE BIRTH GATE (G3): the SORTED union of every layer's `.births()` directive ids. Omit-when-empty
|
|
775
|
+
// (era rule). The set is the contract; order is incidental (sorted for byte-determinism).
|
|
776
|
+
let birthGate: UsdLayer["nomosBirthGate"];
|
|
777
|
+
{
|
|
778
|
+
const b = new Set<string>();
|
|
779
|
+
for (const l of stack) {
|
|
780
|
+
if (l.nomosBirthGate === undefined) continue;
|
|
781
|
+
for (const id of l.nomosBirthGate.directives) b.add(id);
|
|
782
|
+
}
|
|
783
|
+
if (b.size > 0) birthGate = { v: 1, directives: [...b].sort() };
|
|
784
|
+
}
|
|
785
|
+
// THE PEER-SOVEREIGN ROOT-OF-TRUST: the era key is present iff ANY layer in the stack declares it
|
|
786
|
+
// (a boolean union — once required, required). Omit-when-absent (era rule).
|
|
787
|
+
let parentAttestedBirth: UsdLayer["nomosParentAttestedBirth"];
|
|
788
|
+
if (stack.some((l) => l.nomosParentAttestedBirth !== undefined)) {
|
|
789
|
+
parentAttestedBirth = { v: 1 };
|
|
790
|
+
}
|
|
768
791
|
return {
|
|
769
792
|
path,
|
|
770
793
|
prims: [...prims.keys()].sort().map((p) => prims.get(p)!),
|
|
@@ -781,6 +804,8 @@ export function flattenLayeredUsd(layers: readonly UsdLayer[]): UsdDocument {
|
|
|
781
804
|
...(relations !== undefined ? { nomosRelations: relations } : {}),
|
|
782
805
|
...(relationGate !== undefined ? { nomosRelationGate: relationGate } : {}),
|
|
783
806
|
...(attestationGate !== undefined ? { nomosAttestationGate: attestationGate } : {}),
|
|
807
|
+
...(birthGate !== undefined ? { nomosBirthGate: birthGate } : {}),
|
|
808
|
+
...(parentAttestedBirth !== undefined ? { nomosParentAttestedBirth: parentAttestedBirth } : {}),
|
|
784
809
|
};
|
|
785
810
|
});
|
|
786
811
|
|
|
@@ -908,6 +933,8 @@ export function parseUsdaDocument(text: string): UsdDocument {
|
|
|
908
933
|
const relationsByDomain = new Map<string, UsdLayer["nomosRelations"]>();
|
|
909
934
|
const relationGateByDomain = new Map<string, UsdLayer["nomosRelationGate"]>();
|
|
910
935
|
const attestationGateByDomain = new Map<string, UsdLayer["nomosAttestationGate"]>();
|
|
936
|
+
const birthGateByDomain = new Map<string, UsdLayer["nomosBirthGate"]>();
|
|
937
|
+
const parentAttestedBirthByDomain = new Map<string, UsdLayer["nomosParentAttestedBirth"]>();
|
|
911
938
|
for (const p of prims) {
|
|
912
939
|
if (p.attrs.get("nomos:kind") === "domain") {
|
|
913
940
|
const domain = attrString(p, "nomos:domain");
|
|
@@ -959,6 +986,16 @@ export function parseUsdaDocument(text: string): UsdDocument {
|
|
|
959
986
|
if (typeof attGateHex === "string") {
|
|
960
987
|
attestationGateByDomain.set(domain, JSON.parse(unhexUtf8(attGateHex)) as UsdLayer["nomosAttestationGate"]);
|
|
961
988
|
}
|
|
989
|
+
// THE BIRTH GATE (G3): the hex-carried `.births()` directive set.
|
|
990
|
+
const birthGateHex = p.attrs.get("nomos:birthGateHex");
|
|
991
|
+
if (typeof birthGateHex === "string") {
|
|
992
|
+
birthGateByDomain.set(domain, JSON.parse(unhexUtf8(birthGateHex)) as UsdLayer["nomosBirthGate"]);
|
|
993
|
+
}
|
|
994
|
+
// THE PEER-SOVEREIGN ROOT-OF-TRUST: the hex-carried parent-attested-birth era marker.
|
|
995
|
+
const pabHex = p.attrs.get("nomos:parentAttestedBirthHex");
|
|
996
|
+
if (typeof pabHex === "string") {
|
|
997
|
+
parentAttestedBirthByDomain.set(domain, JSON.parse(unhexUtf8(pabHex)) as UsdLayer["nomosParentAttestedBirth"]);
|
|
998
|
+
}
|
|
962
999
|
}
|
|
963
1000
|
}
|
|
964
1001
|
|
|
@@ -1056,6 +1093,8 @@ export function parseUsdaDocument(text: string): UsdDocument {
|
|
|
1056
1093
|
const rel = relationsByDomain.get(domain);
|
|
1057
1094
|
const relGate = relationGateByDomain.get(domain);
|
|
1058
1095
|
const attGate = attestationGateByDomain.get(domain);
|
|
1096
|
+
const birthGate = birthGateByDomain.get(domain);
|
|
1097
|
+
const parentAttestedBirth = parentAttestedBirthByDomain.get(domain);
|
|
1059
1098
|
return {
|
|
1060
1099
|
path: `/Nomos/${domain}`,
|
|
1061
1100
|
prims: [...b.prims].sort(byPath),
|
|
@@ -1072,6 +1111,8 @@ export function parseUsdaDocument(text: string): UsdDocument {
|
|
|
1072
1111
|
...(rel !== undefined ? { nomosRelations: rel } : {}),
|
|
1073
1112
|
...(relGate !== undefined ? { nomosRelationGate: relGate } : {}),
|
|
1074
1113
|
...(attGate !== undefined ? { nomosAttestationGate: attGate } : {}),
|
|
1114
|
+
...(birthGate !== undefined ? { nomosBirthGate: birthGate } : {}),
|
|
1115
|
+
...(parentAttestedBirth !== undefined ? { nomosParentAttestedBirth: parentAttestedBirth } : {}),
|
|
1075
1116
|
};
|
|
1076
1117
|
})
|
|
1077
1118
|
.sort((a, b) => (a.path < b.path ? -1 : a.path > b.path ? 1 : 0));
|
package/src/wire.ts
CHANGED
|
@@ -40,6 +40,10 @@ export type WireValue =
|
|
|
40
40
|
| { Set: string[] }
|
|
41
41
|
| { Map: Record<string, WireField> }
|
|
42
42
|
| { Evidence: WireEvidenceRef }
|
|
43
|
+
// First-class canonical JSON (RFC 8785 / JCS) — the committed form of a `t.json()` leaf. The
|
|
44
|
+
// value rides INLINE (`{Json: <any json>}`), not as a string-of-JSON; the kernel canonicalises
|
|
45
|
+
// it at the boundary (`canonical_json.rs`) and decodes the variant directly into structured JSON.
|
|
46
|
+
| { Json: unknown }
|
|
43
47
|
// ── STRUCTURAL CRDT state values (only seen when reading RAW folded state back; the read MODEL
|
|
44
48
|
// collapses them via the kernel `observed_value` to Int/Str/Set, so typed reads stay scalar) ──
|
|
45
49
|
| { Counter: Record<string, { pos: number; neg: number }> }
|
|
@@ -135,6 +139,15 @@ export interface WireIntent {
|
|
|
135
139
|
* empty so a principal-free intent is byte-identical.
|
|
136
140
|
*/
|
|
137
141
|
endorse?: { sink: string; facet: string; reason: string | null }[];
|
|
142
|
+
/**
|
|
143
|
+
* THE BIRTH EFFECT (G3 — birth is a declared kernel primitive, the unix model): the genesis recipe(s) a
|
|
144
|
+
* `.births()` directive's plan produced (`birth(recipe)`). It rides the intent as a DECLARED OUTPUT — the
|
|
145
|
+
* kernel reads the `.births` DECLARATION off the law manifest (the `nomosBirthGate` era key, NOT domain
|
|
146
|
+
* state), then takes this recipe off the admitted intent and spawns + self-validates the child through the
|
|
147
|
+
* child's OWN gate. A verify-input WITNESS (rides the carried `captured_ports`, never the events nor the
|
|
148
|
+
* OID). OMITTED when empty so a birth-free intent is byte-identical to before this existed (the era rule).
|
|
149
|
+
*/
|
|
150
|
+
births?: { workspace: string; genesisChain: unknown }[];
|
|
138
151
|
}
|
|
139
152
|
|
|
140
153
|
/**
|
package/src/wire_encode.ts
CHANGED
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
import { encodeDriver } from "./drivers.js";
|
|
23
23
|
import { __resetAuthoring, __drainAuthoring } from "./authoring.js";
|
|
24
24
|
import { __resetReads, __drainReads } from "./read.js";
|
|
25
|
+
import { __resetBirths, __drainBirths } from "./births.js";
|
|
25
26
|
import type { AggregateHandle } from "./aggregate.js";
|
|
26
27
|
import type { Field, EvidenceRefValue } from "./fields.js";
|
|
27
28
|
import type { Directive, ReferentialMarker } from "./directive.js";
|
|
@@ -86,14 +87,18 @@ function encodeKernelValue(field: Field | undefined, value: unknown): WireValue
|
|
|
86
87
|
},
|
|
87
88
|
};
|
|
88
89
|
}
|
|
90
|
+
// A `t.json()` leaf lowers to the FIRST-CLASS kernel `Value::Json` (RFC 8785 / JCS), NOT a
|
|
91
|
+
// `Value::Str` holding JSON-shaped text. Checked BEFORE the scalar fallthroughs so EVERY json
|
|
92
|
+
// value — object, array, string, number, boolean, null — commits as one consistent value kind
|
|
93
|
+
// (the kernel canonicalises it at the boundary). This is the new-era encoding; a package compiled
|
|
94
|
+
// before this change froze the old `{ Str: JSON.stringify(value) }` lump and replays unchanged.
|
|
95
|
+
if (field?.kind === "json") return { Json: value };
|
|
89
96
|
// For scalar Set/SetEntry the kernel expects Str or Int. Maps store leaves;
|
|
90
97
|
// a map's entry value is a leaf scalar.
|
|
91
98
|
if (typeof value === "number" && Number.isInteger(value)) return { Int: value };
|
|
92
99
|
if (typeof value === "string") return { Str: value };
|
|
93
100
|
if (typeof value === "boolean") return { Str: String(value) };
|
|
94
101
|
// Enum values are strings; refs are id strings — both handled above.
|
|
95
|
-
// Anything else (object/array as a scalar) is JSON-encoded into a Str leaf.
|
|
96
|
-
if (field?.kind === "json") return { Str: JSON.stringify(value) };
|
|
97
102
|
throw new Error(
|
|
98
103
|
`Cannot encode value ${JSON.stringify(value)} for field '${field?.kind ?? "?"}': ` +
|
|
99
104
|
`kernel scalars are Str | Int only.`,
|
|
@@ -196,9 +201,15 @@ export function executeDirectiveToIntent<P>(
|
|
|
196
201
|
// The plan may also READ (O(1) DSL queries via `read(...)`) — captured into the footprint below.
|
|
197
202
|
__resetAuthoring();
|
|
198
203
|
__resetReads();
|
|
204
|
+
__resetBirths();
|
|
199
205
|
const returned = directive.plan(parsed, ctx);
|
|
200
206
|
const planned = [...__drainAuthoring(), ...returned];
|
|
201
207
|
const reads = __drainReads();
|
|
208
|
+
// THE BIRTH EFFECT (G3 — birth is a declared kernel primitive): a `.births()` directive's plan records
|
|
209
|
+
// the child's genesis recipe via `birth(recipe)`; it rides the intent as a DECLARED output (like a
|
|
210
|
+
// captured read), drained here. Empty for every non-birth directive — OMITTED below so a birth-free
|
|
211
|
+
// intent is byte-identical (the era rule).
|
|
212
|
+
const births = __drainBirths();
|
|
202
213
|
|
|
203
214
|
// Partition the plan output: STRIKE ops route onto the intent's `strikes` channel
|
|
204
215
|
// (the kernel's strikeout / revert facet, folded by parity); FIELD ops group into
|
|
@@ -302,6 +313,7 @@ export function executeDirectiveToIntent<P>(
|
|
|
302
313
|
events,
|
|
303
314
|
...(reads.length ? { reads } : {}),
|
|
304
315
|
...(endorse.length ? { endorse } : {}),
|
|
316
|
+
...(births.length ? { births } : {}),
|
|
305
317
|
};
|
|
306
318
|
}
|
|
307
319
|
|