@githolon/dsl 0.107.4 → 0.108.1

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 (93) hide show
  1. package/dart/CHANGELOG.md +24 -0
  2. package/dart/lib/src/application_framework_calls.dart +299 -0
  3. package/dart/lib/src/generated_call.dart +96 -0
  4. package/dart/lib/src/nms_generated_runtime.dart +29 -0
  5. package/dart/lib/src/node_runner_transport.dart +77 -1
  6. package/dart/lib/src/result_usda.dart +487 -0
  7. package/dart/lib/src/runner/node-entry.mjs +10634 -6122
  8. package/dart/lib/src/runner/offline-install.mjs +4187 -2716
  9. package/dart/lib/src/runtime_v2_gateway.dart +54 -0
  10. package/dart/lib/src/runtime_v2_transport.dart +117 -0
  11. package/dart/lib/src/session_bound.dart +7 -2
  12. package/dart/lib/src/test_harness.dart +373 -282
  13. package/dart/lib/src/webview_bridge.dart +2146 -725
  14. package/dart/pubspec.lock +17 -1
  15. package/dart/pubspec.yaml +3 -2
  16. package/dart/test/acquire_session_credentials_test.dart +1 -1
  17. package/dart/test/acquire_session_test.dart +68 -14
  18. package/dart/test/adv_realm_interference_test.dart +6 -4
  19. package/dart/test/cluster_births_test.dart +11 -8
  20. package/dart/test/cluster_local_autosync_test.dart +1 -1
  21. package/dart/test/cluster_scale_test.dart +3 -2
  22. package/dart/test/dlq_session_surface_test.dart +52 -35
  23. package/dart/test/first_app_gauntlet_test.dart +1 -1
  24. package/dart/test/local_sync_inert_test.dart +23 -26
  25. package/dart/test/offer_authentication_proof_test.dart +193 -0
  26. package/dart/test/result_usda_test.dart +49 -0
  27. package/dart/test/session_bound_test.dart +8 -11
  28. package/dart/test/stale_bundle_test.dart +18 -3
  29. package/dart/test/test_harness_test.dart +14 -27
  30. package/dart/test/webview_bridge_test.dart +504 -83
  31. package/dart_types/CHANGELOG.md +17 -0
  32. package/dart_types/lib/nomos_kernel_protocol.dart +11 -0
  33. package/dart_types/lib/src/custody.dart +64 -19
  34. package/dart_types/lib/src/local_work_rejected.dart +30 -45
  35. package/dart_types/lib/src/proto/nomos/custody/v1/snapshot.pb.dart +169 -0
  36. package/dart_types/lib/src/proto/nomos/custody/v1/snapshot.pbenum.dart +11 -0
  37. package/dart_types/lib/src/proto/nomos/kernel/v1/kernel.pb.dart +17945 -0
  38. package/dart_types/lib/src/proto/nomos/kernel/v1/kernel.pbenum.dart +933 -0
  39. package/dart_types/lib/src/proto/nomos/kernel/v1/progress.pb.dart +245 -0
  40. package/dart_types/lib/src/proto/nomos/kernel/v1/progress.pbenum.dart +160 -0
  41. package/dart_types/lib/src/proto/nomos/runtime/v2/runtime.pb.dart +8664 -0
  42. package/dart_types/lib/src/proto/nomos/runtime/v2/runtime.pbenum.dart +369 -0
  43. package/dart_types/lib/src/surfaced.dart +30 -25
  44. package/dart_types/pubspec.lock +16 -0
  45. package/dart_types/pubspec.yaml +3 -1
  46. package/dart_types/test/surfaced_test.dart +2 -1
  47. package/package.json +1 -1
  48. package/runtime/generated-application-host.js +5760 -1879
  49. package/src/authoring.ts +3 -2
  50. package/src/birth_meter_ensure.ts +2 -0
  51. package/src/births.ts +21 -0
  52. package/src/build_package.ts +30 -0
  53. package/src/business_birth.ts +41 -35
  54. package/src/codegen_call_usda.ts +1111 -0
  55. package/src/codegen_dart.ts +207 -282
  56. package/src/codegen_dart_application.ts +195 -252
  57. package/src/codegen_dart_client.ts +1978 -593
  58. package/src/codegen_dart_framework_calls.ts +291 -0
  59. package/src/codegen_dart_scenario.ts +7 -0
  60. package/src/codegen_proof.ts +3 -4
  61. package/src/codegen_provider_dart.ts +21 -7
  62. package/src/codegen_query_usda.ts +214 -0
  63. package/src/codegen_result_usda.ts +685 -0
  64. package/src/codegen_ts.ts +3945 -1111
  65. package/src/compile_package_main.ts +392 -46
  66. package/src/directive.ts +19 -4
  67. package/src/engine_entry.ts +45 -4
  68. package/src/framework/application_home.ts +4 -0
  69. package/src/framework/auth_provider_law.ts +296 -20
  70. package/src/framework/birth_certificate_law.ts +24 -0
  71. package/src/framework/home.ts +83 -40
  72. package/src/framework/owned_custody.ts +25 -12
  73. package/src/framework/owned_custody_value.ts +51 -0
  74. package/src/framework/relations.ts +47 -21
  75. package/src/framework/signing_identity.ts +100 -17
  76. package/src/framework/workspaces.ts +39 -10
  77. package/src/framework_call_definitions.ts +53 -0
  78. package/src/framework_read_definitions.ts +45 -0
  79. package/src/identity_frontier.ts +58 -0
  80. package/src/index.ts +22 -1
  81. package/src/intent.ts +216 -16
  82. package/src/law_check.ts +19 -1
  83. package/src/law_graph.ts +57 -8
  84. package/src/law_graph_usda.ts +73 -5
  85. package/src/law_value_type.ts +106 -19
  86. package/src/read.ts +47 -0
  87. package/src/read_projection_namespace.ts +7 -0
  88. package/src/shape_compat.ts +44 -42
  89. package/src/stable_ids.ts +37 -2
  90. package/src/test_call_usda.ts +83 -0
  91. package/src/wire.ts +36 -2
  92. package/src/wire_encode.ts +45 -1
  93. package/src/workspace_sharing.ts +27 -10
package/src/directive.ts CHANGED
@@ -195,9 +195,11 @@ export interface BusinessRecordInput {
195
195
 
196
196
  /**
197
197
  * Application-only composition for a business action whose law performs a
198
- * dynamic attested read. The generated stateful client first obtains the
199
- * source's signed answer, privately seats its identity through an internal
200
- * directive, then submits the business action carrying that attestation.
198
+ * dynamic custody-proved read. The generated stateful client privately seats
199
+ * the source's immutable genesis through an internal directive, then submits
200
+ * the business action; the shared kernel reopens and proves the sibling source
201
+ * position while evaluating that offer. This automatic v2 lane never invents
202
+ * or enrolls a signed-v1 attestor key.
201
203
  */
202
204
  export interface BusinessSourceTrust {
203
205
  /** Hidden law directive that records the accepted source identity. */
@@ -210,7 +212,7 @@ export interface BusinessSourceTrust {
210
212
  readonly queryArgs: Readonly<Record<string, string>>;
211
213
  /** Hidden trust payload field -> business payload field. */
212
214
  readonly copy: Readonly<Record<string, string>>;
213
- /** Hidden trust payload fields populated from the signed envelope. */
215
+ /** Hidden trust payload fields populated from custody identity; v2 leaves the signing-key set empty. */
214
216
  readonly genesisField: string;
215
217
  readonly keyHashesField: string;
216
218
  }
@@ -320,6 +322,14 @@ export interface Directive<P = unknown> {
320
322
  * `object` is the fallback). `undefined` ⇒ the static object — era-ruled, byte-identical replay. */
321
323
  readonly objectFrom?: { readonly recordType: string; readonly idField: string };
322
324
  };
325
+ /**
326
+ * Admit this directive when it is the exact target call carried by a verified,
327
+ * law-authored outbound effect from this workspace's attested parent. The marker
328
+ * supplies no authority of its own: it is only valid beside a static
329
+ * `.requires(relation, { object: "workspace:self" })`, whose ordinary Zanzibar
330
+ * check remains the authorization decision.
331
+ */
332
+ readonly acceptsWorkspaceEffectGate?: true;
323
333
  /**
324
334
  * CROSS-CUSTODY ATTESTATION-AUTHORIZED (home_holons §3/§5). When `true`, this directive's authorization
325
335
  * is a carried §3 SIGNED QUOTE (the asserter's pubkey + a signature over the edge), NOT an author block —
@@ -599,6 +609,8 @@ export interface RequirableDirective<P> extends Directive<P> {
599
609
  relation: string,
600
610
  opts?: { object?: string; objectFrom?: { recordType: string; idField: string } },
601
611
  ): RequirableDirective<P>;
612
+ /** Declare this exact call shape as an admissible signed parent-workspace effect. */
613
+ acceptsWorkspaceEffect(): RequirableDirective<P>;
602
614
  /**
603
615
  * Declare this directive ATTESTATION-AUTHORIZED (home_holons §3/§5): its authorization is a carried §3
604
616
  * signed quote rather than an author block — lowered into the `nomosAttestationGate` era key so the one
@@ -1281,6 +1293,9 @@ function makeRequirable<P>(base: Directive<P>): RequirableDirective<P> {
1281
1293
  },
1282
1294
  });
1283
1295
  },
1296
+ acceptsWorkspaceEffect(): RequirableDirective<P> {
1297
+ return makeRequirable({ ...base, acceptsWorkspaceEffectGate: true });
1298
+ },
1284
1299
  attestationAuthorized(owner: { ownerObject: string; ownerField: string }): RequirableDirective<P> {
1285
1300
  // CROSS-CUSTODY ATTESTATION-AUTHORIZED (home_holons §3/§5): this directive's authorization is a
1286
1301
  // carried §3 signed quote, not an author block — lowered into the `nomosAttestationGate` era key, so
@@ -179,6 +179,12 @@ export interface EngineEntryConfig {
179
179
  * the kernel knowing the package name. The kernel MAY override it (the workspace-level title).
180
180
  */
181
181
  readonly packageTitle?: string;
182
+ /** Compiler-generated nominal encoders keyed by the exact target Directive singleton. Source law
183
+ * can name a target by type, while the runtime emits only canonical USDA. */
184
+ readonly workspaceEffectTargets?: readonly {
185
+ readonly target: Directive<unknown>;
186
+ readonly encode: (payload: unknown, workspace: string) => Uint8Array;
187
+ }[];
182
188
  }
183
189
 
184
190
  interface RegistryEntry {
@@ -620,6 +626,22 @@ export interface RegisteredEngine {
620
626
  * happens inside the dispatch call and writes no globals).
621
627
  */
622
628
  export function registerEngine(config: EngineEntryConfig): RegisteredEngine {
629
+ const workspaceEffectEncoders = new Map<Directive<unknown>, (payload: unknown, workspace: string) => Uint8Array>();
630
+ for (const binding of config.workspaceEffectTargets ?? []) {
631
+ if (workspaceEffectEncoders.has(binding.target)) {
632
+ throw new Error(`engine bundle: duplicate workspace-effect encoder for '${binding.target.id}'`);
633
+ }
634
+ workspaceEffectEncoders.set(binding.target, binding.encode);
635
+ }
636
+ const encodeWorkspaceEffect = (target: Directive<unknown>, payload: unknown, workspace: string): Uint8Array => {
637
+ const encode = workspaceEffectEncoders.get(target);
638
+ if (encode === undefined) {
639
+ throw new Error(
640
+ `engine bundle: workspace-effect target '${target.id}' has no compiler-generated nominal USDA encoder`,
641
+ );
642
+ }
643
+ return encode(payload, workspace);
644
+ };
623
645
  const domainNames = Object.keys(config.domains);
624
646
  const sourceDomainFor = (requested: string): string =>
625
647
  config.domains[requested] !== undefined ? requested : (config.routedDomainFallback ?? requested);
@@ -994,7 +1016,7 @@ export function registerEngine(config: EngineEntryConfig): RegisteredEngine {
994
1016
  function plan(job: {
995
1017
  intent?: { domain?: string; directiveId?: string; payload?: unknown };
996
1018
  capturedPorts?: { principal?: string };
997
- }): WireEvent[] | { events: WireEvent[]; strikes?: string[]; endorse?: { sink: string; facet: string; reason: string | null }[]; births?: { workspace: string; genesisChain: unknown }[] } {
1019
+ }): WireEvent[] | { events: WireEvent[]; strikes?: string[]; endorse?: { sink: string; facet: string; reason: string | null }[]; births?: import("./births.js").BirthRecipe[] } {
998
1020
  const intent = job.intent ?? {};
999
1021
  const domain = intent.domain;
1000
1022
  const directiveId = intent.directiveId;
@@ -1024,7 +1046,13 @@ export function registerEngine(config: EngineEntryConfig): RegisteredEngine {
1024
1046
  });
1025
1047
  let wire: ReturnType<typeof executeDirectiveToIntent>;
1026
1048
  try {
1027
- wire = executeDirectiveToIntent(entry.directive, entry.agg, intent.payload as never, ctx);
1049
+ wire = executeDirectiveToIntent(
1050
+ entry.directive,
1051
+ entry.agg,
1052
+ intent.payload as never,
1053
+ ctx,
1054
+ encodeWorkspaceEffect,
1055
+ );
1028
1056
  } finally {
1029
1057
  __setDispatchContext(undefined);
1030
1058
  }
@@ -1050,17 +1078,30 @@ export function registerEngine(config: EngineEntryConfig): RegisteredEngine {
1050
1078
  events: WireEvent[],
1051
1079
  strikes: string[],
1052
1080
  endorse: { sink: string; facet: string; reason: string | null }[],
1053
- births: { workspace: string; genesisChain: unknown }[],
1081
+ births: import("./births.js").BirthRecipe[],
1082
+ workspaceEffects: {
1083
+ destination: { workspace: string } | { verifiedParent: true } | { verifiedSource: true };
1084
+ targetCallUsda: Uint8Array;
1085
+ }[],
1054
1086
  ) => void;
1055
1087
  };
1056
1088
  }).nomos?.__submitPlan;
1057
1089
  if (typeof submitNativePlan === "function") {
1058
- submitNativePlan(wire.events, wire.strikes, endorse ?? [], births ?? []);
1090
+ submitNativePlan(
1091
+ wire.events,
1092
+ wire.strikes,
1093
+ endorse ?? [],
1094
+ births ?? [],
1095
+ wire.workspaceEffects ?? [],
1096
+ );
1059
1097
  // The Rust sink now owns the result. Return the smallest lawful sentinel; the kernel ignores
1060
1098
  // it when a native submission is present and reconstructs compatibility bytes from its typed
1061
1099
  // events. This avoids retaining and re-traversing the large event graph at the ABI boundary.
1062
1100
  return [];
1063
1101
  }
1102
+ if (wire.workspaceEffects?.length) {
1103
+ throw new Error("workspace effects require the typed native plan sink; an old kernel cannot carry nominal target calls");
1104
+ }
1064
1105
  if (!wire.strikes.length && !(endorse && endorse.length) && !(births && births.length)) return wire.events;
1065
1106
  return {
1066
1107
  events: wire.events,
@@ -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,
@@ -49,6 +50,7 @@ import {
49
50
  advanceSignerEpoch,
50
51
  enrollDelegation,
51
52
  enrollFirstDevice,
53
+ enrollAdmissionCheckpointSigner,
52
54
  enrollSigner,
53
55
  enrollSignerSelf,
54
56
  enrollSignerSelfRequiresVerified,
@@ -110,6 +112,7 @@ export const __nomos_sharedByObject = query(sharedByObject);
110
112
  export const __nomos_GenesisWarrant = aggregate(GenesisWarrant);
111
113
  export const __nomos_seedGenesis = directive(seedGenesis);
112
114
  export const __nomos_BirthAttestation = aggregate(BirthAttestation);
115
+ export const __nomos_BirthAttestationLink = aggregate(BirthAttestationLink);
113
116
  export const __nomos_seedBirthAttestation = directive(seedBirthAttestation);
114
117
  export const __nomos_birthAttestation = query(birthAttestation);
115
118
  export const __nomos_parentAttestedBirth = internal(parentAttestedBirth);
@@ -117,6 +120,7 @@ export const __nomos_RelationTuple = aggregate(RelationTuple);
117
120
  export const __nomos_relationTuplesByObject = query(relationTuplesByObject);
118
121
  export const __nomos_identityRelations = internal(identityRelations);
119
122
  export const __nomos_enrollSigner = directive(enrollSigner);
123
+ export const __nomos_enrollAdmissionCheckpointSigner = directive(enrollAdmissionCheckpointSigner);
120
124
  export const __nomos_enrollFirstDevice = directive(enrollFirstDevice);
121
125
  export const __nomos_enrollSignerSelf = directive(enrollSignerSelf);
122
126
  export const __nomos_enrollSignerSelfRequiresVerified = internal(enrollSignerSelfRequiresVerified);
@@ -10,8 +10,9 @@ import { aggregate, instance } from "../aggregate.js";
10
10
  import { directive } from "../directive.js";
11
11
  import { t } from "../fields.js";
12
12
  import { Lww } from "../drivers.js";
13
- import { set, withMarker } from "../ops.js";
13
+ import { addToSet, set, withMarker, type PlannedOp } from "../ops.js";
14
14
  import { query } from "../query.js";
15
+ import { readFrameworkDeclaredRows } from "../read.js";
15
16
  import { GENESIS_WARRANT_ID } from "./genesis.js";
16
17
  import { readRef, readSet, refAs, workspaceInvariant } from "./workspace_invariant.js";
17
18
 
@@ -23,6 +24,11 @@ const epochMs = z.number().int().nonnegative();
23
24
  * first-class machinery; `setAuthProvider`/`rotateKeys` compile to it. */
24
25
  export const AUTH_PROVIDER_ID = "auth-provider:platform";
25
26
 
27
+ /** Stable prefix for the typed public keys owned by an AuthProvider epoch. */
28
+ export const AUTH_PROVIDER_KEY_ID_PREFIX = "auth-provider-key:";
29
+ /** Stable prefix for ordered x5c certificate entries belonging to one typed public key. */
30
+ export const AUTH_PROVIDER_CERTIFICATE_ID_PREFIX = "auth-provider-certificate:";
31
+
26
32
  export const AuthProvider = aggregate("AuthProvider", {
27
33
  /** The singleton key — always `"platform"`. */
28
34
  scope: t.string().merge(Lww),
@@ -31,14 +37,8 @@ export const AuthProvider = aggregate("AuthProvider", {
31
37
  /** The provider's public JWKS endpoint (asymmetric verification keys). The KERNEL never fetches — it's a
32
38
  * reference for the rotation capability that re-fetches + re-sets {@link keys}. */
33
39
  jwksUrl: t.string().merge(Lww),
34
- /** THE VERIFYING KEYS — the JWKS JSON (`{keys:[{kid,kty,alg,crv,x,y,n,e,...}]}`) as an OPAQUE STRING the
35
- * KERNEL parses + verifies a presented token against (sovereign-auth arc — see
36
- * architecture/sovereign_adapter.md). `t.string` (NOT `t.json`): the kernel `Value` has no JSON variant,
37
- * so the raw JWKS text must ride verbatim as a scalar — the kernel does `serde_json` on it, no projection
38
- * parse, no double-encoding. LAW-STATE: set by {@link setAuthProvider} (gated), refreshed by the
39
- * JWKS-fetch rotation capability (the host FETCHES — impure — but the law OWNS the keys + the kernel does
40
- * the verification). Absent ⇒ no in-kernel verify for this workspace (shell-issuer fallback, transitional). */
41
- keys: t.string().merge(Lww).optional(),
40
+ /** The active typed verifying-key epoch. Keys are ordinary USDA aggregates, never a serialized document. */
41
+ keyEpoch: t.int().merge(Lww).optional(),
42
42
  /** OPTIONAL expected `aud` claim. When the platform sets it, a token's `aud` MUST carry this value
43
43
  * (defense-in-depth beyond the issuer match — e.g. a Firebase project id, an Auth0 API audience).
44
44
  * Absent ⇒ issuer match alone. This is LAW-STATE the PLATFORM owns; the host merely enforces it as a
@@ -69,21 +69,107 @@ 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(),
83
+ }).public();
84
+
85
+ /** One typed RFC 7517 public key. Each JWK member is a real USDA property; no map or JSON envelope crosses
86
+ * the law boundary. `providerEpoch` is the indexed join key `${scope}:${epoch}`. */
87
+ export const AuthProviderKey = aggregate("AuthProviderKey", {
88
+ providerEpoch: t.string().merge(Lww),
89
+ epoch: t.int().merge(Lww),
90
+ ordinal: t.int().merge(Lww),
91
+ kty: t.string().merge(Lww),
92
+ kid: t.string().merge(Lww).optional(),
93
+ alg: t.string().merge(Lww).optional(),
94
+ use: t.string().merge(Lww).optional(),
95
+ crv: t.string().merge(Lww).optional(),
96
+ x: t.string().merge(Lww).optional(),
97
+ y: t.string().merge(Lww).optional(),
98
+ n: t.string().merge(Lww).optional(),
99
+ e: t.string().merge(Lww).optional(),
100
+ keyOps: t.set(t.string()),
72
101
  }).public();
73
102
 
103
+ /** One ordered x5c entry. Certificate order is semantically significant, so each certificate is an
104
+ * indexed aggregate instead of a set. The DER bytes remain the standard base64 representation. */
105
+ export const AuthProviderCertificate = aggregate("AuthProviderCertificate", {
106
+ providerEpoch: t.string().merge(Lww),
107
+ keyOrdinal: t.int().merge(Lww),
108
+ certificateOrdinal: t.int().merge(Lww),
109
+ derBase64: t.string().merge(Lww),
110
+ }).public();
111
+
112
+ export const authProviderKeys = query("authProviderKeys")
113
+ .key("providerEpoch")
114
+ .returns(AuthProviderKey);
115
+
116
+ export const authProviderCertificates = query("authProviderCertificates")
117
+ .key("providerEpoch")
118
+ .returns(AuthProviderCertificate);
119
+
120
+ const authProviderAttestation = z.object({
121
+ child: z.string().min(1),
122
+ founder: z.string().min(1),
123
+ verified: z.boolean(),
124
+ parentKey: z.string().min(1),
125
+ parentId: z.string().min(1),
126
+ epoch: z.number().int().nonnegative(),
127
+ signature: z.string().min(1),
128
+ });
129
+
130
+ /** Typed RFC 7517 key input. The adapter at the external JWKS edge parses that standard document once;
131
+ * every Nomos-facing caller receives this nominal shape and the law stores each member as USDA. */
132
+ export const authProviderPublicKey = z.object({
133
+ kty: z.enum(["EC", "RSA", "OKP"]),
134
+ kid: z.string().min(1).optional(),
135
+ alg: z.string().min(1).optional(),
136
+ use: z.string().min(1).optional(),
137
+ crv: z.string().min(1).optional(),
138
+ x: z.string().min(1).optional(),
139
+ y: z.string().min(1).optional(),
140
+ n: z.string().min(1).optional(),
141
+ e: z.string().min(1).optional(),
142
+ keyOps: z.array(z.string().min(1)).optional(),
143
+ x5c: z.array(z.string().min(1)).optional(),
144
+ }).strict().superRefine((key, ctx) => {
145
+ if (key.kty === "RSA" && (!key.n || !key.e)) {
146
+ ctx.addIssue({ code: "custom", message: "an RSA public key requires n and e" });
147
+ }
148
+ if (key.kty === "EC" && (!key.crv || !key.x || !key.y)) {
149
+ ctx.addIssue({ code: "custom", message: "an EC public key requires crv, x and y" });
150
+ }
151
+ if (key.kty === "OKP" && (!key.crv || !key.x)) {
152
+ ctx.addIssue({ code: "custom", message: "an OKP public key requires crv and x" });
153
+ }
154
+ });
155
+
156
+ export const authProviderKeySet = z.object({
157
+ epoch: z.number().int().nonnegative(),
158
+ keys: z.array(authProviderPublicKey).min(1),
159
+ }).strict();
160
+
74
161
  /**
75
162
  * setAuthProvider — declare (or amend) the platform's identity provider. An
76
163
  * Ensure on the singleton; every change is an attributed, replayable intent.
77
164
  */
78
- const authProviderPayload = z.object({
165
+ export const authProviderConfiguration = z.object({
79
166
  // NOT z.url(): zod's url() needs the URL global, which the sealed
80
167
  // deterministic engine does not provide — a regex keeps the plan pure.
81
168
  issuer: z.string().regex(/^https?:\/\/\S+$/, "expected an http(s) URL"),
82
169
  jwksUrl: z.string().regex(/^https?:\/\/\S+$/, "expected an http(s) URL"),
83
170
  audience: z.string().min(1).optional(),
84
- /** The JWKS JSON (the public verifying keys) the KERNEL checks tokens against. Optional so the
85
- * rotation capability can re-set just the keys; absent keeps the prior keys (Lww). */
86
- keys: z.string().min(1).optional(),
171
+ /** Typed public verifying keys. Omit to retain the current epoch. Serialized JWKS never enters law. */
172
+ keySet: authProviderKeySet.optional(),
87
173
  /** OPTIONAL — the claim carrying the principal (default "sub"; e.g. "oid" for Azure AD). */
88
174
  principalClaim: z.string().min(1).optional(),
89
175
  /** OPTIONAL — the pinned CA root (base64 DER) enabling trustless {@link rotateKeys}. */
@@ -95,17 +181,193 @@ const authProviderPayload = z.object({
95
181
  label: z.string().min(1),
96
182
  setBy: z.string().min(1),
97
183
  setAt: epochMs,
184
+ /** A typed, already-signed BirthCertificate body for this provider version. The signer is the
185
+ * platform key attested by its existing lineage; the kernel verifies it while walking every new
186
+ * descendant chain. Optional keeps pre-epoch histories replayable. */
187
+ attestation: authProviderAttestation.optional(),
188
+ }).strict();
189
+
190
+ export type AuthProviderConfiguration = z.infer<typeof authProviderConfiguration>;
191
+ export type AuthProviderVerifyingConfiguration = Omit<
192
+ AuthProviderConfiguration,
193
+ "label" | "setBy" | "setAt" | "attestation"
194
+ >;
195
+
196
+ /** Read the current provider through its three declared O(1) indexes and reconstruct the one nominal
197
+ * configuration value used by child genesis and provider-epoch effects. */
198
+ function readCurrentAuthProviderRows() {
199
+ try {
200
+ return readFrameworkDeclaredRows(authProvider, AuthProvider, { scope: "platform" });
201
+ } catch (error) {
202
+ if (error instanceof z.ZodError && error.issues.some((issue) =>
203
+ issue.code === "unrecognized_keys" && issue.keys.includes("keys")
204
+ )) {
205
+ throw new Error(
206
+ "the AuthProvider still uses the historical serialized key field; migrate it to a typed keySet first",
207
+ );
208
+ }
209
+ throw error;
210
+ }
211
+ }
212
+
213
+ type CurrentAuthProviderRow = ReturnType<typeof readCurrentAuthProviderRows>[number];
214
+
215
+ /** Reconstruct the verifier-bearing portion once from the three declared indexes. The provider row is
216
+ * retained so the full audit/attestation value can be added without another read or a parallel schema. */
217
+ function readCurrentAuthProviderValue(): {
218
+ readonly provider: CurrentAuthProviderRow;
219
+ readonly verifying: AuthProviderVerifyingConfiguration;
220
+ } | undefined {
221
+ const providers = readCurrentAuthProviderRows();
222
+ const provider = providers.find((row) => row.issuer !== undefined);
223
+ if (provider?.issuer === undefined || provider.issuer.length === 0) return undefined;
224
+ const keyEpoch = provider.keyEpoch;
225
+ const providerEpoch = keyEpoch === undefined ? undefined : `platform:${keyEpoch}`;
226
+ const certificates = providerEpoch === undefined
227
+ ? []
228
+ : readFrameworkDeclaredRows(
229
+ authProviderCertificates,
230
+ AuthProviderCertificate,
231
+ { providerEpoch },
232
+ );
233
+ const keys = providerEpoch === undefined
234
+ ? []
235
+ : readFrameworkDeclaredRows(authProviderKeys, AuthProviderKey, { providerEpoch })
236
+ .sort((a, b) => (a.ordinal ?? Number.MAX_SAFE_INTEGER) - (b.ordinal ?? Number.MAX_SAFE_INTEGER))
237
+ .map((key) => authProviderPublicKey.parse({
238
+ kty: key.kty,
239
+ kid: key.kid,
240
+ alg: key.alg,
241
+ use: key.use,
242
+ crv: key.crv,
243
+ x: key.x,
244
+ y: key.y,
245
+ n: key.n,
246
+ e: key.e,
247
+ keyOps: key.keyOps,
248
+ x5c: certificates
249
+ .filter((certificate) => certificate.keyOrdinal === key.ordinal)
250
+ .sort((a, b) =>
251
+ (a.certificateOrdinal ?? Number.MAX_SAFE_INTEGER) -
252
+ (b.certificateOrdinal ?? Number.MAX_SAFE_INTEGER)
253
+ )
254
+ .map((certificate) => certificate.derBase64),
255
+ }));
256
+ return {
257
+ provider,
258
+ verifying: {
259
+ issuer: provider.issuer,
260
+ jwksUrl: provider.jwksUrl !== undefined && provider.jwksUrl.length > 0
261
+ ? provider.jwksUrl
262
+ : provider.issuer,
263
+ ...(provider.audience !== undefined && provider.audience.length > 0
264
+ ? { audience: provider.audience }
265
+ : {}),
266
+ ...(keyEpoch !== undefined && keys.length > 0 ? { keySet: { epoch: keyEpoch, keys } } : {}),
267
+ ...(provider.principalClaim !== undefined && provider.principalClaim.length > 0
268
+ ? { principalClaim: provider.principalClaim }
269
+ : {}),
270
+ ...(provider.rootCert !== undefined && provider.rootCert.length > 0
271
+ ? { rootCert: provider.rootCert }
272
+ : {}),
273
+ ...(provider.deviceBindingClaim !== undefined && provider.deviceBindingClaim.length > 0
274
+ ? { deviceBindingClaim: provider.deviceBindingClaim }
275
+ : {}),
276
+ ...(provider.requireDeviceBinding === "true" || provider.requireDeviceBinding === "false"
277
+ ? { requireDeviceBinding: provider.requireDeviceBinding }
278
+ : {}),
279
+ },
280
+ };
281
+ }
282
+
283
+ /** Read the complete current provider through its three declared O(1) indexes. This is the one nominal
284
+ * value source-law workspace effects may copy; callers never supply provider members independently. */
285
+ export function readAuthProviderConfiguration(): AuthProviderConfiguration | undefined {
286
+ const current = readCurrentAuthProviderValue();
287
+ if (current === undefined) return undefined;
288
+ const { provider, verifying } = current;
289
+ const attestationMembers = [
290
+ provider.attestationChild,
291
+ provider.attestationFounder,
292
+ provider.attestationVerified,
293
+ provider.attestationParentKey,
294
+ provider.attestationParentId,
295
+ provider.attestationEpoch,
296
+ provider.attestationSignature,
297
+ ];
298
+ const hasAttestation = attestationMembers.some((member) => member !== undefined);
299
+ if (hasAttestation && attestationMembers.some((member) => member === undefined)) {
300
+ throw new Error("the AuthProvider carries an incomplete typed attestation");
301
+ }
302
+ return authProviderConfiguration.parse({
303
+ ...verifying,
304
+ label: provider.label,
305
+ setBy: provider.setBy,
306
+ setAt: provider.setAt,
307
+ ...(hasAttestation ? {
308
+ attestation: {
309
+ child: provider.attestationChild,
310
+ founder: provider.attestationFounder,
311
+ verified: provider.attestationVerified,
312
+ parentKey: provider.attestationParentKey,
313
+ parentId: provider.attestationParentId,
314
+ epoch: provider.attestationEpoch,
315
+ signature: provider.attestationSignature,
316
+ },
317
+ } : {}),
318
+ });
319
+ }
320
+
321
+ /** The verifier consumes the same nominal configuration while deliberately ignoring audit and
322
+ * lineage-attestation members that do not participate in JWT verification. */
323
+ export function readAuthProviderVerifyingConfiguration(): AuthProviderVerifyingConfiguration | undefined {
324
+ return readCurrentAuthProviderValue()?.verifying;
325
+ }
326
+
327
+ export function writeAuthProviderKeySet(keySet: z.infer<typeof authProviderKeySet>): PlannedOp[] {
328
+ const ap = instance(AuthProvider, AUTH_PROVIDER_ID);
329
+ const ops: PlannedOp[] = [set(ap, "keyEpoch", keySet.epoch)];
330
+ const providerEpoch = `platform:${keySet.epoch}`;
331
+ keySet.keys.forEach((key, keyOrdinal) => {
332
+ const keyRow = instance(AuthProviderKey, `${AUTH_PROVIDER_KEY_ID_PREFIX}${keySet.epoch}:${keyOrdinal}`);
333
+ ops.push(
334
+ withMarker(set(keyRow, "providerEpoch", providerEpoch), "ensures"),
335
+ set(keyRow, "epoch", keySet.epoch),
336
+ set(keyRow, "ordinal", keyOrdinal),
337
+ set(keyRow, "kty", key.kty),
338
+ ...(key.kid ? [set(keyRow, "kid", key.kid)] : []),
339
+ ...(key.alg ? [set(keyRow, "alg", key.alg)] : []),
340
+ ...(key.use ? [set(keyRow, "use", key.use)] : []),
341
+ ...(key.crv ? [set(keyRow, "crv", key.crv)] : []),
342
+ ...(key.x ? [set(keyRow, "x", key.x)] : []),
343
+ ...(key.y ? [set(keyRow, "y", key.y)] : []),
344
+ ...(key.n ? [set(keyRow, "n", key.n)] : []),
345
+ ...(key.e ? [set(keyRow, "e", key.e)] : []),
346
+ ...(key.keyOps && key.keyOps.length > 0 ? [addToSet(keyRow, "keyOps", key.keyOps)] : []),
347
+ );
348
+ key.x5c?.forEach((derBase64, certificateOrdinal) => {
349
+ const certificate = instance(
350
+ AuthProviderCertificate,
351
+ `${AUTH_PROVIDER_CERTIFICATE_ID_PREFIX}${keySet.epoch}:${keyOrdinal}:${certificateOrdinal}`,
352
+ );
353
+ ops.push(
354
+ withMarker(set(certificate, "providerEpoch", providerEpoch), "ensures"),
355
+ set(certificate, "keyOrdinal", keyOrdinal),
356
+ set(certificate, "certificateOrdinal", certificateOrdinal),
357
+ set(certificate, "derBase64", derBase64),
358
+ );
359
+ });
98
360
  });
361
+ return ops;
362
+ }
99
363
 
100
- type AuthProviderPayload = z.infer<typeof authProviderPayload>;
101
- function writeAuthProvider(p: AuthProviderPayload) {
364
+ export function writeAuthProviderConfiguration(p: AuthProviderConfiguration): PlannedOp[] {
102
365
  const ap = instance(AuthProvider, AUTH_PROVIDER_ID);
103
366
  return [
104
367
  withMarker(set(ap, "scope", "platform"), "ensures"),
105
368
  set(ap, "issuer", p.issuer),
106
369
  set(ap, "jwksUrl", p.jwksUrl),
107
370
  ...(p.audience ? [set(ap, "audience", p.audience)] : []),
108
- ...(p.keys ? [set(ap, "keys", p.keys)] : []),
109
371
  ...(p.principalClaim ? [set(ap, "principalClaim", p.principalClaim)] : []),
110
372
  ...(p.rootCert ? [set(ap, "rootCert", p.rootCert)] : []),
111
373
  ...(p.deviceBindingClaim ? [set(ap, "deviceBindingClaim", p.deviceBindingClaim)] : []),
@@ -113,13 +375,25 @@ function writeAuthProvider(p: AuthProviderPayload) {
113
375
  set(ap, "label", p.label),
114
376
  set(ap, "setBy", p.setBy),
115
377
  set(ap, "setAt", p.setAt),
378
+ ...(p.attestation ? [
379
+ set(ap, "attestationChild", p.attestation.child),
380
+ set(ap, "attestationFounder", p.attestation.founder),
381
+ set(ap, "attestationVerified", p.attestation.verified),
382
+ set(ap, "attestationParentKey", p.attestation.parentKey),
383
+ set(ap, "attestationParentId", p.attestation.parentId),
384
+ set(ap, "attestationEpoch", p.attestation.epoch),
385
+ set(ap, "attestationSignature", p.attestation.signature),
386
+ ] : []),
387
+ ...(p.keySet ? writeAuthProviderKeySet(p.keySet) : []),
116
388
  ];
117
389
  }
118
390
 
119
391
  export const setAuthProvider = directive("setAuthProvider")
120
392
  .ensures(AuthProvider)
121
- .payload(authProviderPayload)
122
- .plan(writeAuthProvider)
393
+ .payload(authProviderConfiguration)
394
+ .plan(writeAuthProviderConfiguration)
395
+ .alsoEnsuresMany(AuthProviderKey)
396
+ .alsoEnsuresMany(AuthProviderCertificate)
123
397
  .requires("admin");
124
398
 
125
399
  /**
@@ -130,8 +404,10 @@ export const setAuthProvider = directive("setAuthProvider")
130
404
  */
131
405
  export const seedAuthProvider = directive("seedAuthProvider")
132
406
  .ensures(AuthProvider)
133
- .payload(authProviderPayload)
134
- .plan(writeAuthProvider);
407
+ .payload(authProviderConfiguration)
408
+ .plan(writeAuthProviderConfiguration)
409
+ .alsoEnsuresMany(AuthProviderKey)
410
+ .alsoEnsuresMany(AuthProviderCertificate);
135
411
 
136
412
  export const authProviderSeedBeforeWarrant = workspaceInvariant("AuthProviderSeedBeforeWarrant")
137
413
  .on("seedAuthProvider")
@@ -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>;