@githolon/dsl 0.107.4 → 0.108.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.
Files changed (36) hide show
  1. package/dart/CHANGELOG.md +19 -0
  2. package/dart/lib/src/runner/node-entry.mjs +4044 -3831
  3. package/dart/lib/src/runner/offline-install.mjs +2633 -2537
  4. package/dart/lib/src/test_harness.dart +249 -239
  5. package/dart/lib/src/webview_bridge.dart +38 -168
  6. package/dart/pubspec.lock +17 -1
  7. package/dart/pubspec.yaml +3 -2
  8. package/dart_types/CHANGELOG.md +10 -0
  9. package/dart_types/lib/nomos_kernel_protocol.dart +10 -0
  10. package/dart_types/lib/src/proto/nomos/kernel/v1/kernel.pb.dart +16158 -0
  11. package/dart_types/lib/src/proto/nomos/kernel/v1/kernel.pbenum.dart +766 -0
  12. package/dart_types/lib/src/proto/nomos/kernel/v1/kernel.pbjson.dart +6096 -0
  13. package/dart_types/lib/src/proto/nomos/kernel/v1/kernel.pbserver.dart +50 -0
  14. package/dart_types/lib/src/proto/nomos/kernel/v1/progress.pb.dart +245 -0
  15. package/dart_types/lib/src/proto/nomos/kernel/v1/progress.pbenum.dart +160 -0
  16. package/dart_types/lib/src/proto/nomos/kernel/v1/progress.pbjson.dart +178 -0
  17. package/dart_types/lib/src/proto/nomos/runtime/v1/runtime.pb.dart +828 -0
  18. package/dart_types/lib/src/proto/nomos/runtime/v1/runtime.pbenum.dart +465 -0
  19. package/dart_types/lib/src/proto/nomos/runtime/v1/runtime.pbjson.dart +573 -0
  20. package/dart_types/lib/src/proto/nomos/runtime/v1/runtime.pbserver.dart +50 -0
  21. package/dart_types/pubspec.lock +16 -0
  22. package/dart_types/pubspec.yaml +3 -1
  23. package/package.json +1 -1
  24. package/runtime/generated-application-host.js +298 -106
  25. package/src/birth_meter_ensure.ts +2 -0
  26. package/src/business_birth.ts +2 -0
  27. package/src/compile_package_main.ts +36 -18
  28. package/src/framework/application_home.ts +2 -0
  29. package/src/framework/auth_provider_law.ts +34 -0
  30. package/src/framework/birth_certificate_law.ts +24 -0
  31. package/src/framework/home.ts +53 -14
  32. package/src/framework/owned_custody.ts +6 -1
  33. package/src/framework/signing_identity.ts +66 -17
  34. package/src/framework/workspaces.ts +8 -1
  35. package/src/index.ts +2 -0
  36. package/src/shape_compat.ts +6 -3
@@ -50,6 +50,7 @@ import { GENESIS_WARRANT_ID, GenesisWarrant, seedGenesis } from "./framework/gen
50
50
  import {
51
51
  BIRTH_ATTESTATION_ID,
52
52
  BirthAttestation,
53
+ BirthAttestationLink,
53
54
  birthAttestation,
54
55
  seedBirthAttestation,
55
56
  } from "./framework/home.js";
@@ -253,6 +254,7 @@ export function withBirthMeterExports<M extends Record<string, unknown>>(mod: M)
253
254
  internalize("__nomos_GenesisWarrant", GenesisWarrant);
254
255
  internalize("__nomos_seedGenesis", seedGenesis);
255
256
  internalize("__nomos_BirthAttestation", BirthAttestation);
257
+ internalize("__nomos_BirthAttestationLink", BirthAttestationLink);
256
258
  internalize("__nomos_seedBirthAttestation", seedBirthAttestation);
257
259
  internalize("__nomos_birthAttestation", birthAttestation);
258
260
  internalize("__nomos_AuthProvider", AuthProvider);
@@ -36,6 +36,7 @@ import { GENESIS_WARRANT_ID, GenesisWarrant, seedGenesis } from "./framework/gen
36
36
  import {
37
37
  BIRTH_ATTESTATION_ID,
38
38
  BirthAttestation,
39
+ BirthAttestationLink,
39
40
  birthAttestation,
40
41
  seedBirthAttestation,
41
42
  } from "./framework/home.js";
@@ -359,6 +360,7 @@ function lowerOne<P extends Record<string, unknown>, F extends Record<string, Fi
359
360
  __nomos_GenesisWarrant: internalSurface(GenesisWarrant),
360
361
  __nomos_seedGenesis: internalDirective(seedGenesis),
361
362
  __nomos_BirthAttestation: internalSurface(BirthAttestation),
363
+ __nomos_BirthAttestationLink: internalSurface(BirthAttestationLink),
362
364
  __nomos_seedBirthAttestation: internalDirective(seedBirthAttestation),
363
365
  __nomos_birthAttestation: internalSurface(birthAttestation),
364
366
  __nomos_RelationTuple: internalSurface(RelationTuple),
@@ -865,7 +865,10 @@ const DART_SUPPORT_RUNTIME_SRC = [
865
865
  const DART_SUPPORT_SRC = [...DART_SUPPORT_CORE_SRC, ...DART_SUPPORT_RUNTIME_SRC] as const;
866
866
  /** The local shim replacing `package:nomos_types/nomos_types.dart` inside vendored runtime files. */
867
867
  const VENDORED_TYPES_SHIM = "nomos_types.dart";
868
+ const VENDORED_KERNEL_PROTOCOL_SHIM = "nomos_kernel_protocol.dart";
868
869
  const VENDORED_META_DEP = "^1.12.0";
870
+ const VENDORED_PROTOBUF_DEP = "^5.0.0";
871
+ const VENDORED_FIXNUM_DEP = "^1.1.1";
869
872
  const VENDORED_DART_SDK_FLOOR = "3.3.0";
870
873
 
871
874
  /**
@@ -909,21 +912,12 @@ function ensureVendoredPackageRequirements(dartOut: string): void {
909
912
  `${sdkLine[1]}\">=${VENDORED_DART_SDK_FLOOR} <4.0.0\"${sdkLine[3] ?? ""}`,
910
913
  );
911
914
  }
912
- if (/^ meta:\s*/m.test(next)) {
913
- if (next !== source) writeFileSync(pubspec, next, "utf8");
914
- return;
915
- }
916
- if (!/^dependencies:\s*$/m.test(next)) {
917
- fail("generated Dart vendored support needs a dependencies: block in " + pubspec);
918
- }
919
- writeFileSync(
920
- pubspec,
921
- next.replace(
922
- /^dependencies:\s*$/m,
923
- "dependencies:\n meta: " + VENDORED_META_DEP,
924
- ),
925
- "utf8",
926
- );
915
+ if (next !== source) writeFileSync(pubspec, next, "utf8");
916
+ reconcilePubspec(dir, "", {
917
+ meta: VENDORED_META_DEP,
918
+ protobuf: VENDORED_PROTOBUF_DEP,
919
+ fixnum: VENDORED_FIXNUM_DEP,
920
+ });
927
921
  }
928
922
 
929
923
  /** The generated-owned subdirectory, relative to `dartOut` (the pub package's `lib/`). WIPED and
@@ -1357,9 +1351,33 @@ function emitDart(
1357
1351
  .replace(
1358
1352
  /import 'package:nomos_types\/src\/(?:nomos_ref|foreign_ref|versioned_nomos_ref)\.dart'/g,
1359
1353
  `import '${VENDORED_TYPES_SHIM}'`,
1354
+ )
1355
+ .replace(
1356
+ /import 'package:nomos_types\/nomos_kernel_protocol\.dart'/g,
1357
+ `import '${VENDORED_KERNEL_PROTOCOL_SHIM}'`,
1360
1358
  );
1361
1359
  writeFileSync(path.join(genDir, "src", f), src, "utf8");
1362
1360
  }
1361
+ // Protobuf is the sole kernel/host message schema. Flat vendored packages receive a generated
1362
+ // projection of that schema, never a handwritten certificate mirror. The compiler copies only the
1363
+ // generated kernel files this runtime uses; `generate:kernel-proto-dart` remains their source.
1364
+ const generatedKernelProto = path.join(DSL_DIR, "dart_types", "lib", "src", "proto", "nomos", "kernel", "v1");
1365
+ const vendoredKernelProto = path.join(genDir, "src", "proto", "nomos", "kernel", "v1");
1366
+ mkdirSync(vendoredKernelProto, { recursive: true });
1367
+ for (const f of ["kernel.pb.dart", "kernel.pbenum.dart"]) {
1368
+ copyFileSync(path.join(generatedKernelProto, f), path.join(vendoredKernelProto, f));
1369
+ }
1370
+ writeFileSync(
1371
+ path.join(genDir, "src", VENDORED_KERNEL_PROTOCOL_SHIM),
1372
+ [
1373
+ "// GENERATED from proto/nomos/kernel/v1/kernel.proto by nomos-compile. DO NOT EDIT.",
1374
+ "library;",
1375
+ "",
1376
+ "export 'proto/nomos/kernel/v1/kernel.pb.dart';",
1377
+ "",
1378
+ ].join("\n"),
1379
+ "utf8",
1380
+ );
1363
1381
  // The local shim the rewritten runtime imports resolve through (the vendored twin of the
1364
1382
  // nomos_types barrel — core files only).
1365
1383
  writeFileSync(
@@ -1660,12 +1678,12 @@ const dartTypesImportFor = (generatedSource: string): string => {
1660
1678
  * just the floor generated code needs — a lagging pin forces tenants into dependency_overrides. */
1661
1679
  // 0.3.0: SurfacedValue<T>/SurfacedObservation/PendingConflictRow — generated Surfaced-field typing +
1662
1680
  // the pendingConflicts read rows.
1663
- const NOMOS_TYPES_DEP = "^0.8.8";
1681
+ const NOMOS_TYPES_DEP = "^0.9.0";
1664
1682
  // 0.48.x: session-bound clients (XClient.bind/NomosClientInterface/DomainClientSessionMismatch/
1665
1683
  // DomainNotInstalledForSession, engine.follow, pendingConflicts lanes) — the generated client
1666
1684
  // REQUIRES this surface. 0.48.1 fixes NomosSession.watchPendingConflicts delegation.
1667
- const NOMOS_CLIENT_DEP = "^0.64.46";
1668
- const NOMOS_FLUTTER_DEP = "^0.63.76";
1685
+ const NOMOS_CLIENT_DEP = "^0.65.0";
1686
+ const NOMOS_FLUTTER_DEP = "^0.64.0";
1669
1687
  // DRIFT TRIPWIRE: pubspec_pins.test.ts asserts these equal the CURRENT versions in dsl/dart/pubspec.yaml
1670
1688
  // and dsl/dart_types/pubspec.yaml — a Dart release without this bump fails the suite, so tenants never
1671
1689
  // land back in dependency_overrides (the exact co2 report this closes, third occurrence).
@@ -23,6 +23,7 @@ import {
23
23
  import { GenesisWarrant, seedGenesis } from "./genesis.js";
24
24
  import {
25
25
  BirthAttestation,
26
+ BirthAttestationLink,
26
27
  ReceivingGroup,
27
28
  ReceivingGroupPolicy,
28
29
  SharedWithMe,
@@ -110,6 +111,7 @@ export const __nomos_sharedByObject = query(sharedByObject);
110
111
  export const __nomos_GenesisWarrant = aggregate(GenesisWarrant);
111
112
  export const __nomos_seedGenesis = directive(seedGenesis);
112
113
  export const __nomos_BirthAttestation = aggregate(BirthAttestation);
114
+ export const __nomos_BirthAttestationLink = aggregate(BirthAttestationLink);
113
115
  export const __nomos_seedBirthAttestation = directive(seedBirthAttestation);
114
116
  export const __nomos_birthAttestation = query(birthAttestation);
115
117
  export const __nomos_parentAttestedBirth = internal(parentAttestedBirth);
@@ -69,8 +69,29 @@ export const AuthProvider = aggregate("AuthProvider", {
69
69
  setBy: t.string().merge(Lww),
70
70
  /** Epoch-ms it was set (caller-stamped). */
71
71
  setAt: t.int().merge(Lww),
72
+ /** The platform-signed provider epoch inserted above new keyless delegations. These are the nominal
73
+ * BirthCertificate fields, stored separately as typed USDA properties; the provider issuer, keys and
74
+ * audience come from this same AuthProvider row and are therefore covered by the signature without a
75
+ * second serialized certificate document. Historical children retain the epoch they were born with. */
76
+ attestationChild: t.string().merge(Lww).optional(),
77
+ attestationFounder: t.string().merge(Lww).optional(),
78
+ attestationVerified: t.bool().merge(Lww).optional(),
79
+ attestationParentKey: t.string().merge(Lww).optional(),
80
+ attestationParentId: t.string().merge(Lww).optional(),
81
+ attestationEpoch: t.int().merge(Lww).optional(),
82
+ attestationSignature: t.string().merge(Lww).optional(),
72
83
  }).public();
73
84
 
85
+ const authProviderAttestation = z.object({
86
+ child: z.string().min(1),
87
+ founder: z.string().min(1),
88
+ verified: z.boolean(),
89
+ parentKey: z.string().min(1),
90
+ parentId: z.string().min(1),
91
+ epoch: z.number().int().nonnegative(),
92
+ signature: z.string().min(1),
93
+ });
94
+
74
95
  /**
75
96
  * setAuthProvider — declare (or amend) the platform's identity provider. An
76
97
  * Ensure on the singleton; every change is an attributed, replayable intent.
@@ -95,6 +116,10 @@ const authProviderPayload = z.object({
95
116
  label: z.string().min(1),
96
117
  setBy: z.string().min(1),
97
118
  setAt: epochMs,
119
+ /** A typed, already-signed BirthCertificate body for this provider version. The signer is the
120
+ * platform key attested by its existing lineage; the kernel verifies it while walking every new
121
+ * descendant chain. Optional keeps pre-epoch histories replayable. */
122
+ attestation: authProviderAttestation.optional(),
98
123
  });
99
124
 
100
125
  type AuthProviderPayload = z.infer<typeof authProviderPayload>;
@@ -113,6 +138,15 @@ function writeAuthProvider(p: AuthProviderPayload) {
113
138
  set(ap, "label", p.label),
114
139
  set(ap, "setBy", p.setBy),
115
140
  set(ap, "setAt", p.setAt),
141
+ ...(p.attestation ? [
142
+ set(ap, "attestationChild", p.attestation.child),
143
+ set(ap, "attestationFounder", p.attestation.founder),
144
+ set(ap, "attestationVerified", p.attestation.verified),
145
+ set(ap, "attestationParentKey", p.attestation.parentKey),
146
+ set(ap, "attestationParentId", p.attestation.parentId),
147
+ set(ap, "attestationEpoch", p.attestation.epoch),
148
+ set(ap, "attestationSignature", p.attestation.signature),
149
+ ] : []),
116
150
  ];
117
151
  }
118
152
 
@@ -0,0 +1,24 @@
1
+ // NOMOS — the one typed law payload for a birth-certificate link.
2
+ //
3
+ // A BirthCertificate is nominal in Rust and Protobuf and becomes ordinary typed USDA fields in law state.
4
+ // It is never lowered to a JSON string. The JWKS member is the external standardized JWKS document itself;
5
+ // the certificate envelope and chain structure remain typed Nomos data.
6
+ import { z } from "zod";
7
+
8
+ export const birthCertificatePayload = z.object({
9
+ child: z.string().min(1),
10
+ founder: z.string().min(1),
11
+ verified: z.boolean(),
12
+ parentKey: z.string().min(1),
13
+ parentId: z.string(),
14
+ epoch: z.number().int().nonnegative(),
15
+ signature: z.string().min(1),
16
+ verifiedVia: z.string().min(1).optional(),
17
+ idpToken: z.string().min(1).optional(),
18
+ capturedClock: z.number().int().nonnegative().optional(),
19
+ authProviderIssuer: z.string().min(1).optional(),
20
+ authProviderJwks: z.string().min(1).optional(),
21
+ authProviderAudience: z.string().min(1).optional(),
22
+ });
23
+
24
+ export type BirthCertificatePayload = z.infer<typeof birthCertificatePayload>;
@@ -31,7 +31,7 @@ import { sha256Digest, t } from "../fields.js";
31
31
  import { Lww } from "../drivers.js";
32
32
  import { directive } from "../directive.js";
33
33
  import { ensure } from "../authoring.js";
34
- import { set, type PlannedOp } from "../ops.js";
34
+ import { set, withMarker, type PlannedOp } from "../ops.js";
35
35
  import { query } from "../query.js";
36
36
  import { birth } from "../births.js";
37
37
  import { readFrameworkDeclared } from "../read.js";
@@ -47,6 +47,7 @@ import {
47
47
  DomainInstallExecutor,
48
48
  domainInstallGenesisOffer,
49
49
  } from "./domain_lifecycle.js";
50
+ import { birthCertificatePayload } from "./birth_certificate_law.js";
50
51
 
51
52
  // `nomos.sha256` — the TRUSTLESS, pure hash computed IN the githolon (Rust sha2 bound in the sealed engine;
52
53
  // see deterministic-rquickjs). Available as a global to plans + invariant predicates (like `nomos.mint`); it
@@ -581,6 +582,7 @@ export const sharedByObject = query("sharedByObject").key("object").returns(Shar
581
582
  * verify doors); this aggregate is the human-readable mirror.
582
583
  */
583
584
  export const BIRTH_ATTESTATION_ID = "birth-attestation:self";
585
+ export const BIRTH_ATTESTATION_LINK_PREFIX = "birth-attestation-link:";
584
586
  export const BirthAttestation = aggregate("BirthAttestation", {
585
587
  /** This home's self-id (`home-<sha256(founder)>`) — the cert's `child`. */
586
588
  child: t.string().merge(Lww),
@@ -594,12 +596,29 @@ export const BirthAttestation = aggregate("BirthAttestation", {
594
596
  parentKey: t.string().merge(Lww),
595
597
  /** The parent's SignerEpoch at the attestation (informational; the cert is judged at birth-time validity). */
596
598
  epoch: t.int().merge(Lww),
597
- /** THE TRAVELLING SIGNED CERT CHAIN (JSON: `executor::birth_cert::BirthCertChain`) — the home's own
598
- * P-signed cert FIRST, then each ancestor's birth-cert up to root. The kernel verify doors parse THIS,
599
- * verify the signature chain to the pinned `K_root` OFFLINE (no cross-ws read), and assert it binds this
600
- * home (`child`==self-id, `founder`==HomeOwner.principal, `verified`). It travels because it folds into
601
- * the home's own genesis (seeded by the parent's offer-effect), so a peer holding only the home has it. */
602
- certChain: t.string().merge(Lww),
599
+ /** Legacy replay slot for histories authored before `BirthAttestationLink`. New histories write the
600
+ * travelling chain only as ordered, typed link records below. */
601
+ certChain: t.string().merge(Lww).optional(),
602
+ }).public();
603
+
604
+ /** One exact link in the travelling certificate chain. The zero-padded index is custody order; every
605
+ * certificate member is a separate typed USDA property. `certChain` above remains only as a replay fallback
606
+ * for histories authored before this aggregate existed. */
607
+ export const BirthAttestationLink = aggregate("BirthAttestationLink", {
608
+ index: t.int().merge(Lww),
609
+ child: t.string().merge(Lww),
610
+ founder: t.string().merge(Lww),
611
+ verified: t.bool().merge(Lww),
612
+ parentKey: t.string().merge(Lww),
613
+ parentId: t.string().merge(Lww),
614
+ epoch: t.int().merge(Lww),
615
+ signature: t.string().merge(Lww),
616
+ verifiedVia: t.string().merge(Lww).optional(),
617
+ idpToken: t.string().merge(Lww).optional(),
618
+ capturedClock: t.int().merge(Lww).optional(),
619
+ authProviderIssuer: t.string().merge(Lww).optional(),
620
+ authProviderJwks: t.string().merge(Lww).optional(),
621
+ authProviderAudience: t.string().merge(Lww).optional(),
603
622
  }).public();
604
623
 
605
624
  /** Audit read: this home's birth attestation (who attested it + the verified verdict). */
@@ -611,7 +630,7 @@ export const birthAttestation = query("birthAttestation").key("child").returns(B
611
630
  * the [`BirthAttestation`] singleton so the home's own state carries its travelling cert. A CONTROL-PLANE
612
631
  * write seeded once at birth (the open session lane structurally refuses it by directive id). The values
613
632
  * are the parent's GATE VERDICT (`founder`/`verified` lowered from the captured port — CAS-as-law, never
614
- * the offer payload) + the signed chain bytes; the home's verify doors re-derive trust from the chain.
633
+ * the offer payload) + the typed signed links; the home's verify doors re-derive trust from the chain.
615
634
  */
616
635
  export const seedBirthAttestation = directive("seedBirthAttestation")
617
636
  .ensures(BirthAttestation)
@@ -625,21 +644,41 @@ export const seedBirthAttestation = directive("seedBirthAttestation")
625
644
  parentId: z.string(),
626
645
  parentKey: z.string().min(1), // base64 K_P
627
646
  epoch: z.number().int().nonnegative(),
628
- certChain: z.string().min(1), // JSON BirthCertChain
647
+ links: z.array(birthCertificatePayload).min(1),
629
648
  }),
630
649
  )
631
650
  .plan((p) => {
632
651
  const h = instance(BirthAttestation, BIRTH_ATTESTATION_ID);
633
- return [
652
+ const header = [
634
653
  set(h, "child", p.child),
635
654
  set(h, "founder", p.founder),
636
655
  set(h, "verified", p.verified),
637
656
  set(h, "parentId", p.parentId),
638
657
  set(h, "parentKey", p.parentKey),
639
658
  set(h, "epoch", p.epoch),
640
- set(h, "certChain", p.certChain),
641
659
  ];
642
- });
660
+ const links = p.links.flatMap((certificate, index) => {
661
+ const link = instance(BirthAttestationLink, `${BIRTH_ATTESTATION_LINK_PREFIX}${String(index).padStart(8, "0")}`);
662
+ return [
663
+ withMarker(set(link, "index", index), "ensures"),
664
+ withMarker(set(link, "child", certificate.child), "ensures"),
665
+ withMarker(set(link, "founder", certificate.founder), "ensures"),
666
+ withMarker(set(link, "verified", certificate.verified), "ensures"),
667
+ withMarker(set(link, "parentKey", certificate.parentKey), "ensures"),
668
+ withMarker(set(link, "parentId", certificate.parentId), "ensures"),
669
+ withMarker(set(link, "epoch", certificate.epoch), "ensures"),
670
+ withMarker(set(link, "signature", certificate.signature), "ensures"),
671
+ ...(certificate.verifiedVia ? [withMarker(set(link, "verifiedVia", certificate.verifiedVia), "ensures")] : []),
672
+ ...(certificate.idpToken ? [withMarker(set(link, "idpToken", certificate.idpToken), "ensures")] : []),
673
+ ...(certificate.capturedClock === undefined ? [] : [withMarker(set(link, "capturedClock", certificate.capturedClock), "ensures")]),
674
+ ...(certificate.authProviderIssuer ? [withMarker(set(link, "authProviderIssuer", certificate.authProviderIssuer), "ensures")] : []),
675
+ ...(certificate.authProviderJwks ? [withMarker(set(link, "authProviderJwks", certificate.authProviderJwks), "ensures")] : []),
676
+ ...(certificate.authProviderAudience ? [withMarker(set(link, "authProviderAudience", certificate.authProviderAudience), "ensures")] : []),
677
+ ];
678
+ });
679
+ return [...header, ...links];
680
+ })
681
+ .alsoEnsures(BirthAttestationLink);
643
682
 
644
683
  /**
645
684
  * THE ERA DECLARATION (the `nomosParentAttestedBirth` USD key). Exporting this sentinel from the home law
@@ -652,8 +691,8 @@ export const seedBirthAttestation = directive("seedBirthAttestation")
652
691
  export const parentAttestedBirth: ParentAttestedBirthMarker = {
653
692
  ...requireParentAttestedBirth,
654
693
  // DECLARE the owner + cert locators so the KERNEL reads them (it names neither): the home owner lives at
655
- // the `HomeOwner` singleton's `principal`, the travelling cert at the `BirthAttestation` singleton's
656
- // `certChain`. Mirrors how `.attestationAuthorized()` declares its owner-source — the kernel hardcodes none.
694
+ // the `HomeOwner` singleton's `principal`. `certObject`/`certField` retain the legacy chain locator for
695
+ // old histories; current histories are discovered through the typed `BirthAttestationLink` aggregate.
657
696
  ownerObject: RECEIVING_GROUP_ID,
658
697
  ownerField: "principal",
659
698
  certObject: BIRTH_ATTESTATION_ID,
@@ -19,7 +19,7 @@ import {
19
19
  writeWrap,
20
20
  } from "./encryption_scope.js";
21
21
  import { GenesisWarrant, seedGenesis } from "./genesis.js";
22
- import { BirthAttestation, seedBirthAttestation } from "./home.js";
22
+ import { BirthAttestation, BirthAttestationLink, seedBirthAttestation } from "./home.js";
23
23
  import { RelationTuple, __qualifyAggregateType, removeTuple, writeTuple } from "./relations.js";
24
24
  import { SIGNER_RELATION, enrollSigner, signerObject, identityRelations } from "./signing_identity.js";
25
25
  import { impureCapability, impureCapabilityAggregate } from "./impure_capability.js";
@@ -430,6 +430,7 @@ const CUSTODY_RELATIONS_EXPORT = "__nomos_owned_custody_relations_7c1f5e";
430
430
  const WARRANT_EXPORT = "__nomos_owned_genesis_warrant_7c1f5e";
431
431
  const SEED_EXPORT = "__nomos_owned_seed_genesis_7c1f5e";
432
432
  const BIRTH_ATTESTATION_EXPORT = "__nomos_owned_birth_attestation_7c1f5e";
433
+ const BIRTH_ATTESTATION_LINK_EXPORT = "__nomos_owned_birth_attestation_link_7c1f5e";
433
434
  const BIRTH_ATTESTATION_SEED_EXPORT = "__nomos_owned_seed_birth_attestation_7c1f5e";
434
435
  const ENROLL_EXPORT = "__nomos_owned_enroll_signer_7c1f5e";
435
436
  const INITIALIZE_EXPORT = "__nomos_owned_initialize_identity_7c1f5e";
@@ -452,6 +453,7 @@ const RECOVERY_CAPABILITY_EXPORT = "__nomos_owned_recovery_capability_7c1f5e";
452
453
  const ownedGenesisWarrant = hidden(GenesisWarrant);
453
454
  const ownedSeedGenesis = hidden(seedGenesis);
454
455
  const ownedBirthAttestation = hidden(BirthAttestation);
456
+ const ownedBirthAttestationLink = hidden(BirthAttestationLink);
455
457
  const ownedSeedBirthAttestation = hidden(seedBirthAttestation);
456
458
  const ownedEnrollSigner = hidden(enrollSigner);
457
459
  const ownedIdentityRelations = hidden(identityRelations);
@@ -543,6 +545,7 @@ export function withOwnedCustodyExports<M extends Record<string, unknown>>(mod:
543
545
  [WARRANT_EXPORT, ownedGenesisWarrant],
544
546
  [SEED_EXPORT, ownedSeedGenesis],
545
547
  [BIRTH_ATTESTATION_EXPORT, ownedBirthAttestation],
548
+ [BIRTH_ATTESTATION_LINK_EXPORT, ownedBirthAttestationLink],
546
549
  [BIRTH_ATTESTATION_SEED_EXPORT, ownedSeedBirthAttestation],
547
550
  [ENROLL_EXPORT, ownedEnrollSigner],
548
551
  ] as const) {
@@ -574,6 +577,7 @@ export function withOwnedCustodyExports<M extends Record<string, unknown>>(mod:
574
577
  [GenesisWarrant, ownedGenesisWarrant],
575
578
  [seedGenesis, ownedSeedGenesis],
576
579
  [BirthAttestation, ownedBirthAttestation],
580
+ [BirthAttestationLink, ownedBirthAttestationLink],
577
581
  [seedBirthAttestation, ownedSeedBirthAttestation],
578
582
  [enrollSigner, ownedEnrollSigner],
579
583
  ] as const) {
@@ -639,6 +643,7 @@ export function withOwnedCustodyExports<M extends Record<string, unknown>>(mod:
639
643
  [WARRANT_EXPORT]: ownedGenesisWarrant,
640
644
  [SEED_EXPORT]: ownedSeedGenesis,
641
645
  [BIRTH_ATTESTATION_EXPORT]: ownedBirthAttestation,
646
+ [BIRTH_ATTESTATION_LINK_EXPORT]: ownedBirthAttestationLink,
642
647
  [BIRTH_ATTESTATION_SEED_EXPORT]: ownedSeedBirthAttestation,
643
648
  [ENROLL_EXPORT]: ownedEnrollSigner,
644
649
  };
@@ -42,11 +42,12 @@
42
42
  * only revokes keys of its OWN identity).
43
43
  */
44
44
  import { z } from "zod";
45
- import { aggregate, instance } from "../aggregate.js";
45
+ import { birthCertificatePayload, type BirthCertificatePayload } from "./birth_certificate_law.js";
46
+ import { aggregate, instance, type BoundAggregate } from "../aggregate.js";
46
47
  import { t } from "../fields.js";
47
48
  import { Lww } from "../drivers.js";
48
49
  import { directive } from "../directive.js";
49
- import { set, withMarker, type PlannedOp } from "../ops.js";
50
+ import { set, withMarker, type FieldOp, type PlannedOp } from "../ops.js";
50
51
  import { query } from "../query.js";
51
52
  import { relationType, self, key } from "../relation_schema.js";
52
53
  import { readRef, readSet, workspaceInvariant, ref } from "./workspace_invariant.js";
@@ -305,8 +306,8 @@ export const enrollSignerSelfRequiresVerified = workspaceInvariant("EnrollSigner
305
306
  * DELEGATION CERT binding U's device key `P_U` to the tenant's lineage: a `BirthCert {founder: U,
306
307
  * parent_key: P_U, …}` signed by `S_P`. This singleton (keyed by U) records it in the PLATFORM's folded
307
308
  * 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).
309
+ * it assembles U's home cert chain. Every certificate member is stored as typed USDA fields; the kernel
310
+ * reconstructs and verifies the nominal link signed by S_P, whose key is attested by the platform chain.
310
311
  *
311
312
  * SELF-BOUND / control-plane: enrolled by the tenant onboarding lane (composed into the tenant's governance
312
313
  * law, off the open session lane), so a tenant cannot forge a delegation for a user another tenant verified.
@@ -322,9 +323,21 @@ export const SignerDelegation = aggregate("SignerDelegation", {
322
323
  user: t.string().merge(Lww),
323
324
  /** The user's device PUBLIC KEY (base64) the delegation binds — `P_U`, the home_cert's signer. */
324
325
  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),
326
+ /** Legacy replay slot for histories authored before the typed certificate fields below. */
327
+ certJson: t.string().merge(Lww).optional(),
328
+ certificateChild: t.string().merge(Lww).optional(),
329
+ certificateFounder: t.string().merge(Lww).optional(),
330
+ certificateVerified: t.bool().merge(Lww).optional(),
331
+ certificateParentKey: t.string().merge(Lww).optional(),
332
+ certificateParentId: t.string().merge(Lww).optional(),
333
+ certificateEpoch: t.int().merge(Lww).optional(),
334
+ certificateSignature: t.string().merge(Lww).optional(),
335
+ certificateVerifiedVia: t.string().merge(Lww).optional(),
336
+ certificateIdpToken: t.string().merge(Lww).optional(),
337
+ certificateCapturedClock: t.int().merge(Lww).optional(),
338
+ certificateAuthProviderIssuer: t.string().merge(Lww).optional(),
339
+ certificateAuthProviderJwks: t.string().merge(Lww).optional(),
340
+ certificateAuthProviderAudience: t.string().merge(Lww).optional(),
328
341
  /** ISO-8601, caller-stamped (determinism — no clock in the plan). */
329
342
  enrolledAt: t.string().merge(Lww),
330
343
  }).public();
@@ -335,6 +348,29 @@ export function signerDelegationInstanceId(user: string): string {
335
348
  return SIGNER_DELEGATION_PREFIX + user;
336
349
  }
337
350
 
351
+ function writeDelegationCertificate(
352
+ delegation: BoundAggregate<typeof SignerDelegation.id, typeof SignerDelegation.fields>,
353
+ certificate: BirthCertificatePayload,
354
+ marker?: "ensures",
355
+ ): PlannedOp[] {
356
+ const marked = (operation: FieldOp) => marker ? withMarker(operation, marker) : operation;
357
+ return [
358
+ marked(set(delegation, "certificateChild", certificate.child)),
359
+ marked(set(delegation, "certificateFounder", certificate.founder)),
360
+ marked(set(delegation, "certificateVerified", certificate.verified)),
361
+ marked(set(delegation, "certificateParentKey", certificate.parentKey)),
362
+ marked(set(delegation, "certificateParentId", certificate.parentId)),
363
+ marked(set(delegation, "certificateEpoch", certificate.epoch)),
364
+ marked(set(delegation, "certificateSignature", certificate.signature)),
365
+ ...(certificate.verifiedVia ? [marked(set(delegation, "certificateVerifiedVia", certificate.verifiedVia))] : []),
366
+ ...(certificate.idpToken ? [marked(set(delegation, "certificateIdpToken", certificate.idpToken))] : []),
367
+ ...(certificate.capturedClock === undefined ? [] : [marked(set(delegation, "certificateCapturedClock", certificate.capturedClock))]),
368
+ ...(certificate.authProviderIssuer ? [marked(set(delegation, "certificateAuthProviderIssuer", certificate.authProviderIssuer))] : []),
369
+ ...(certificate.authProviderJwks ? [marked(set(delegation, "certificateAuthProviderJwks", certificate.authProviderJwks))] : []),
370
+ ...(certificate.authProviderAudience ? [marked(set(delegation, "certificateAuthProviderAudience", certificate.authProviderAudience))] : []),
371
+ ];
372
+ }
373
+
338
374
  /**
339
375
  * `enrollFirstDevice` (architecture/keyless_verified_onboard.md) — THE KEYLESS VERIFIED FIRST-DEVICE
340
376
  * self-onboard. A brand-new principal (an IdP-verified user with NO enrolled signer key) bootstraps its
@@ -369,8 +405,11 @@ export const enrollFirstDevice = directive("enrollFirstDevice")
369
405
  /** THE CAPTURED PREMISE: the IdP's signed identity assertion (JWT), re-verified on every lane against
370
406
  * the on-chain AuthProvider keys. */
371
407
  idpToken: z.string().min(1),
372
- /** THE KEYLESS DELEGATION CERT (JSON `executor::birth_cert::BirthCert`) — the self-serve sibling of
373
- * `enrollDelegation.certJson`. Built CLIENT-SIDE via the `cert_sign` op: DEVICE-self-signed by this
408
+ /** Legacy replay input for callers predating `delegationCertificate`. New callers must provide the
409
+ * typed certificate below. */
410
+ delegationCert: z.string().min(1).optional(),
411
+ /** THE KEYLESS DELEGATION CERT — the self-serve sibling of `enrollDelegation.certificate`. Built
412
+ * CLIENT-SIDE via the typed kernel sign operation: DEVICE-self-signed by this
374
413
  * same device key `P_U` (parent_key == P_U), founder == `user:<uid>`, carrying `verifiedVia` +
375
414
  * `idpToken` + `capturedClock`. There is NO tenant `S_P` in the loop — the kernel's home-birth chain
376
415
  * walk (`verify_to_root`, keyless branch) RE-VERIFIES the token against the platform-attested
@@ -380,7 +419,7 @@ export const enrollFirstDevice = directive("enrollFirstDevice")
380
419
  * OPTIONAL — an enroll-only flow (seat the signer, no imminent home birth) omits it and seeds no
381
420
  * delegation (backward-compatible with every pre-B `enrollFirstDevice` caller). The keyless home-birth
382
421
  * ceremony ALWAYS supplies it (that is what seeds the delegation the chain needs). */
383
- delegationCert: z.string().min(1).optional(),
422
+ delegationCertificate: birthCertificatePayload.optional(),
384
423
  }),
385
424
  )
386
425
  .plan((p, ctx) => {
@@ -404,14 +443,24 @@ export const enrollFirstDevice = directive("enrollFirstDevice")
404
443
  // The delegation is keyed by the AUTHENTICATED principal — EXACTLY the `founder` the kernel's birth
405
444
  // offer-effect reads it back by (`read_signer_delegation(state, &founder)`, founder == captured principal
406
445
  // == this `rawPrincipal`, already in `user:<uid>` form). Do NOT re-prefix.
407
- // Absent delegationCert ⇒ enroll-only (no delegation seeded), byte-identical to a pre-B enroll.
408
- if (!p.delegationCert) return tupleOps;
446
+ // Absent certificate ⇒ enroll-only (no delegation seeded), byte-identical to a pre-B enroll.
447
+ if (!p.delegationCert && !p.delegationCertificate) return tupleOps;
409
448
  const d = instance(SignerDelegation, signerDelegationInstanceId(rawPrincipal));
410
449
  const delegationOps: PlannedOp[] = [
411
450
  withMarker(set(d, "user", rawPrincipal), "ensures"),
412
451
  withMarker(set(d, "deviceKey", "key:" + p.keyHash), "ensures"),
413
- withMarker(set(d, "certJson", p.delegationCert), "ensures"),
414
- withMarker(set(d, "enrolledAt", p.grantedAt), "ensures"),
452
+ ...(p.delegationCertificate
453
+ ? [
454
+ withMarker(set(d, "enrolledAt", p.grantedAt), "ensures"),
455
+ ...writeDelegationCertificate(d, p.delegationCertificate, "ensures"),
456
+ ]
457
+ : [
458
+ // Exact legacy event order is replay identity, even though Lww folding would commute these
459
+ // two writes. Historical enrollFirstDevice intents wrote certJson before enrolledAt; retain
460
+ // that byte-level outcome when re-authoring an old payload under a candidate law.
461
+ withMarker(set(d, "certJson", p.delegationCert!), "ensures"),
462
+ withMarker(set(d, "enrolledAt", p.grantedAt), "ensures"),
463
+ ]),
415
464
  ];
416
465
  return [...tupleOps, ...delegationOps];
417
466
  })
@@ -466,7 +515,7 @@ export const revokeSigner = directive("revokeSigner")
466
515
  * offer-effect can read U's delegation when it assembles U's home chain. `.ensures` (a deterministic-keyed
467
516
  * singleton — upsert-by-user). A CONTROL-PLANE write composed into the tenant's onboarding governance law
468
517
  * (off the open session lane) — only the tenant (whose `S_P` the platform chain attests) may enroll a
469
- * delegation. The `certJson` is verified by the kernel at home birth (signed by S_P, founder == U); a forged
518
+ * delegation. The typed certificate is verified by the kernel at home birth (signed by S_P, founder == U); a forged
470
519
  * delegation simply fails the chain walk / founder-binding at the child's gate.
471
520
  */
472
521
  export const enrollDelegation = directive("enrollDelegation")
@@ -475,7 +524,7 @@ export const enrollDelegation = directive("enrollDelegation")
475
524
  z.object({
476
525
  user: z.string().min(1), // the user:<uid> the delegation enrolls
477
526
  deviceKey: z.string().min(1), // base64 P_U — the user's device public key
478
- certJson: z.string().min(1), // JSON BirthCert, signed by the tenant's S_P
527
+ certificate: birthCertificatePayload,
479
528
  enrolledAt: z.string().min(1), // ISO-8601, caller-stamped
480
529
  }),
481
530
  )
@@ -484,8 +533,8 @@ export const enrollDelegation = directive("enrollDelegation")
484
533
  return [
485
534
  set(d, "user", p.user),
486
535
  set(d, "deviceKey", p.deviceKey),
487
- set(d, "certJson", p.certJson),
488
536
  set(d, "enrolledAt", p.enrolledAt),
537
+ ...writeDelegationCertificate(d, p.certificate),
489
538
  ];
490
539
  });
491
540
 
@@ -106,7 +106,14 @@ export { enrollFirstDevice, revokeSigner } from "./signing_identity.js";
106
106
  // `[platform_cert]` chain would fail `verify_to_root` (>=2 links), so its own verify doors must stay inert
107
107
  // while it still carries the cert for its children. Fully additive (new aggregate + directive, no era key ⇒
108
108
  // pre-existing chains replay byte-identical). See architecture/verified_auth_from_genesis.md.
109
- export { BIRTH_ATTESTATION_ID, BirthAttestation, birthAttestation, seedBirthAttestation } from "./home.js";
109
+ export {
110
+ BIRTH_ATTESTATION_ID,
111
+ BIRTH_ATTESTATION_LINK_PREFIX,
112
+ BirthAttestation,
113
+ BirthAttestationLink,
114
+ birthAttestation,
115
+ seedBirthAttestation,
116
+ } from "./home.js";
110
117
  // THE ENROLL-ONCE DELEGATION LANE — a platform records each tenant's device-key delegation (signed by its
111
118
  // onboarding signer S_P) so the kernel reads it at `signer-delegation:<user>` when assembling a home's chain.
112
119
  export { SignerDelegation, enrollDelegation, signerDelegationByUser } from "./signing_identity.js";
package/src/index.ts CHANGED
@@ -566,7 +566,9 @@ export {
566
566
  birthReceivingGroup,
567
567
  // THE PEER-SOVEREIGN ROOT-OF-TRUST (the BirthAttestation cert chain).
568
568
  BirthAttestation,
569
+ BirthAttestationLink,
569
570
  BIRTH_ATTESTATION_ID,
571
+ BIRTH_ATTESTATION_LINK_PREFIX,
570
572
  seedBirthAttestation,
571
573
  birthAttestation,
572
574
  parentAttestedBirth,
@@ -213,16 +213,19 @@ export function domainInterface(mod: DomainModule, stableIds?: StableIds): Domai
213
213
  for (const agg of (mod.aggregates ?? []) as Array<{
214
214
  id: string;
215
215
  fields: Record<string, Field>;
216
- internalFields?: readonly string[];
217
216
  }>) {
218
217
  if (isInternal(agg)) continue;
219
218
  const label = agg.id;
220
219
  const sa = stableIds?.aggregates?.[label];
221
220
  const aggSid = sa?.sid ?? label;
222
221
  const fieldEntries: Array<[string, { kind: string; label: string }]> = [];
223
- const internal = new Set(agg.internalFields ?? []);
224
222
  for (const [fname, f] of Object.entries(agg.fields ?? {})) {
225
- if (!f || f.kind === "hasMany" || internal.has(fname)) continue;
223
+ // This is the workspace's complete structural offer and the input to law-evolution
224
+ // checking, so framework-managed stored fields belong here too. Only the generated
225
+ // client's view hides them, in publicClientInterface below. Filtering them at both
226
+ // seams makes an unchanged private field look deleted when the current offer is
227
+ // compared with the committed identity ledger.
228
+ if (!f || f.kind === "hasMany") continue;
226
229
  const fsid = sa?.fields?.[fname]?.sid ?? fname;
227
230
  const kind = sa?.fields?.[fname]?.kind ?? aggFieldReadKind(f);
228
231
  fieldEntries.push([fsid, { kind, label: fname }]);