@githolon/dsl 0.108.0 → 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 (94) hide show
  1. package/dart/CHANGELOG.md +5 -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 +6401 -2102
  8. package/dart/lib/src/runner/offline-install.mjs +1538 -163
  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 +193 -112
  13. package/dart/lib/src/webview_bridge.dart +2120 -569
  14. package/dart/pubspec.yaml +2 -2
  15. package/dart/test/acquire_session_credentials_test.dart +1 -1
  16. package/dart/test/acquire_session_test.dart +68 -14
  17. package/dart/test/adv_realm_interference_test.dart +6 -4
  18. package/dart/test/cluster_births_test.dart +11 -8
  19. package/dart/test/cluster_local_autosync_test.dart +1 -1
  20. package/dart/test/cluster_scale_test.dart +3 -2
  21. package/dart/test/dlq_session_surface_test.dart +52 -35
  22. package/dart/test/first_app_gauntlet_test.dart +1 -1
  23. package/dart/test/local_sync_inert_test.dart +23 -26
  24. package/dart/test/offer_authentication_proof_test.dart +193 -0
  25. package/dart/test/result_usda_test.dart +49 -0
  26. package/dart/test/session_bound_test.dart +8 -11
  27. package/dart/test/stale_bundle_test.dart +18 -3
  28. package/dart/test/test_harness_test.dart +14 -27
  29. package/dart/test/webview_bridge_test.dart +504 -83
  30. package/dart_types/CHANGELOG.md +7 -0
  31. package/dart_types/lib/nomos_kernel_protocol.dart +3 -2
  32. package/dart_types/lib/src/custody.dart +64 -19
  33. package/dart_types/lib/src/local_work_rejected.dart +30 -45
  34. package/dart_types/lib/src/proto/nomos/custody/v1/snapshot.pb.dart +169 -0
  35. package/dart_types/lib/src/proto/nomos/custody/v1/snapshot.pbenum.dart +11 -0
  36. package/dart_types/lib/src/proto/nomos/kernel/v1/kernel.pb.dart +2021 -234
  37. package/dart_types/lib/src/proto/nomos/kernel/v1/kernel.pbenum.dart +175 -8
  38. package/dart_types/lib/src/proto/nomos/runtime/v2/runtime.pb.dart +8664 -0
  39. package/dart_types/lib/src/proto/nomos/runtime/v2/runtime.pbenum.dart +369 -0
  40. package/dart_types/lib/src/surfaced.dart +30 -25
  41. package/dart_types/pubspec.yaml +1 -1
  42. package/dart_types/test/surfaced_test.dart +2 -1
  43. package/package.json +1 -1
  44. package/runtime/generated-application-host.js +5736 -2047
  45. package/src/authoring.ts +3 -2
  46. package/src/births.ts +21 -0
  47. package/src/build_package.ts +30 -0
  48. package/src/business_birth.ts +39 -35
  49. package/src/codegen_call_usda.ts +1111 -0
  50. package/src/codegen_dart.ts +207 -282
  51. package/src/codegen_dart_application.ts +195 -252
  52. package/src/codegen_dart_client.ts +1978 -593
  53. package/src/codegen_dart_framework_calls.ts +291 -0
  54. package/src/codegen_dart_scenario.ts +7 -0
  55. package/src/codegen_proof.ts +3 -4
  56. package/src/codegen_provider_dart.ts +21 -7
  57. package/src/codegen_query_usda.ts +214 -0
  58. package/src/codegen_result_usda.ts +685 -0
  59. package/src/codegen_ts.ts +3945 -1111
  60. package/src/compile_package_main.ts +360 -32
  61. package/src/directive.ts +19 -4
  62. package/src/engine_entry.ts +45 -4
  63. package/src/framework/application_home.ts +2 -0
  64. package/src/framework/auth_provider_law.ts +262 -20
  65. package/src/framework/home.ts +32 -28
  66. package/src/framework/owned_custody.ts +19 -11
  67. package/src/framework/owned_custody_value.ts +51 -0
  68. package/src/framework/relations.ts +47 -21
  69. package/src/framework/signing_identity.ts +34 -0
  70. package/src/framework/workspaces.ts +31 -9
  71. package/src/framework_call_definitions.ts +53 -0
  72. package/src/framework_read_definitions.ts +45 -0
  73. package/src/identity_frontier.ts +58 -0
  74. package/src/index.ts +20 -1
  75. package/src/intent.ts +216 -16
  76. package/src/law_check.ts +19 -1
  77. package/src/law_graph.ts +57 -8
  78. package/src/law_graph_usda.ts +73 -5
  79. package/src/law_value_type.ts +106 -19
  80. package/src/read.ts +47 -0
  81. package/src/read_projection_namespace.ts +7 -0
  82. package/src/shape_compat.ts +38 -39
  83. package/src/stable_ids.ts +37 -2
  84. package/src/test_call_usda.ts +83 -0
  85. package/src/wire.ts +36 -2
  86. package/src/wire_encode.ts +45 -1
  87. package/src/workspace_sharing.ts +27 -10
  88. package/dart_types/lib/src/proto/nomos/kernel/v1/kernel.pbjson.dart +0 -6096
  89. package/dart_types/lib/src/proto/nomos/kernel/v1/kernel.pbserver.dart +0 -50
  90. package/dart_types/lib/src/proto/nomos/kernel/v1/progress.pbjson.dart +0 -178
  91. package/dart_types/lib/src/proto/nomos/runtime/v1/runtime.pb.dart +0 -828
  92. package/dart_types/lib/src/proto/nomos/runtime/v1/runtime.pbenum.dart +0 -465
  93. package/dart_types/lib/src/proto/nomos/runtime/v1/runtime.pbjson.dart +0 -573
  94. package/dart_types/lib/src/proto/nomos/runtime/v1/runtime.pbserver.dart +0 -50
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,
@@ -50,6 +50,7 @@ import {
50
50
  advanceSignerEpoch,
51
51
  enrollDelegation,
52
52
  enrollFirstDevice,
53
+ enrollAdmissionCheckpointSigner,
53
54
  enrollSigner,
54
55
  enrollSignerSelf,
55
56
  enrollSignerSelfRequiresVerified,
@@ -119,6 +120,7 @@ export const __nomos_RelationTuple = aggregate(RelationTuple);
119
120
  export const __nomos_relationTuplesByObject = query(relationTuplesByObject);
120
121
  export const __nomos_identityRelations = internal(identityRelations);
121
122
  export const __nomos_enrollSigner = directive(enrollSigner);
123
+ export const __nomos_enrollAdmissionCheckpointSigner = directive(enrollAdmissionCheckpointSigner);
122
124
  export const __nomos_enrollFirstDevice = directive(enrollFirstDevice);
123
125
  export const __nomos_enrollSignerSelf = directive(enrollSignerSelf);
124
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
@@ -82,6 +82,41 @@ export const AuthProvider = aggregate("AuthProvider", {
82
82
  attestationSignature: t.string().merge(Lww).optional(),
83
83
  }).public();
84
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()),
101
+ }).public();
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
+
85
120
  const authProviderAttestation = z.object({
86
121
  child: z.string().min(1),
87
122
  founder: z.string().min(1),
@@ -92,19 +127,49 @@ const authProviderAttestation = z.object({
92
127
  signature: z.string().min(1),
93
128
  });
94
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
+
95
161
  /**
96
162
  * setAuthProvider — declare (or amend) the platform's identity provider. An
97
163
  * Ensure on the singleton; every change is an attributed, replayable intent.
98
164
  */
99
- const authProviderPayload = z.object({
165
+ export const authProviderConfiguration = z.object({
100
166
  // NOT z.url(): zod's url() needs the URL global, which the sealed
101
167
  // deterministic engine does not provide — a regex keeps the plan pure.
102
168
  issuer: z.string().regex(/^https?:\/\/\S+$/, "expected an http(s) URL"),
103
169
  jwksUrl: z.string().regex(/^https?:\/\/\S+$/, "expected an http(s) URL"),
104
170
  audience: z.string().min(1).optional(),
105
- /** The JWKS JSON (the public verifying keys) the KERNEL checks tokens against. Optional so the
106
- * rotation capability can re-set just the keys; absent keeps the prior keys (Lww). */
107
- 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(),
108
173
  /** OPTIONAL — the claim carrying the principal (default "sub"; e.g. "oid" for Azure AD). */
109
174
  principalClaim: z.string().min(1).optional(),
110
175
  /** OPTIONAL — the pinned CA root (base64 DER) enabling trustless {@link rotateKeys}. */
@@ -120,17 +185,189 @@ const authProviderPayload = z.object({
120
185
  * platform key attested by its existing lineage; the kernel verifies it while walking every new
121
186
  * descendant chain. Optional keeps pre-epoch histories replayable. */
122
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
+ });
123
360
  });
361
+ return ops;
362
+ }
124
363
 
125
- type AuthProviderPayload = z.infer<typeof authProviderPayload>;
126
- function writeAuthProvider(p: AuthProviderPayload) {
364
+ export function writeAuthProviderConfiguration(p: AuthProviderConfiguration): PlannedOp[] {
127
365
  const ap = instance(AuthProvider, AUTH_PROVIDER_ID);
128
366
  return [
129
367
  withMarker(set(ap, "scope", "platform"), "ensures"),
130
368
  set(ap, "issuer", p.issuer),
131
369
  set(ap, "jwksUrl", p.jwksUrl),
132
370
  ...(p.audience ? [set(ap, "audience", p.audience)] : []),
133
- ...(p.keys ? [set(ap, "keys", p.keys)] : []),
134
371
  ...(p.principalClaim ? [set(ap, "principalClaim", p.principalClaim)] : []),
135
372
  ...(p.rootCert ? [set(ap, "rootCert", p.rootCert)] : []),
136
373
  ...(p.deviceBindingClaim ? [set(ap, "deviceBindingClaim", p.deviceBindingClaim)] : []),
@@ -147,13 +384,16 @@ function writeAuthProvider(p: AuthProviderPayload) {
147
384
  set(ap, "attestationEpoch", p.attestation.epoch),
148
385
  set(ap, "attestationSignature", p.attestation.signature),
149
386
  ] : []),
387
+ ...(p.keySet ? writeAuthProviderKeySet(p.keySet) : []),
150
388
  ];
151
389
  }
152
390
 
153
391
  export const setAuthProvider = directive("setAuthProvider")
154
392
  .ensures(AuthProvider)
155
- .payload(authProviderPayload)
156
- .plan(writeAuthProvider)
393
+ .payload(authProviderConfiguration)
394
+ .plan(writeAuthProviderConfiguration)
395
+ .alsoEnsuresMany(AuthProviderKey)
396
+ .alsoEnsuresMany(AuthProviderCertificate)
157
397
  .requires("admin");
158
398
 
159
399
  /**
@@ -164,8 +404,10 @@ export const setAuthProvider = directive("setAuthProvider")
164
404
  */
165
405
  export const seedAuthProvider = directive("seedAuthProvider")
166
406
  .ensures(AuthProvider)
167
- .payload(authProviderPayload)
168
- .plan(writeAuthProvider);
407
+ .payload(authProviderConfiguration)
408
+ .plan(writeAuthProviderConfiguration)
409
+ .alsoEnsuresMany(AuthProviderKey)
410
+ .alsoEnsuresMany(AuthProviderCertificate);
169
411
 
170
412
  export const authProviderSeedBeforeWarrant = workspaceInvariant("AuthProviderSeedBeforeWarrant")
171
413
  .on("seedAuthProvider")
@@ -34,8 +34,13 @@ import { ensure } from "../authoring.js";
34
34
  import { set, withMarker, type PlannedOp } from "../ops.js";
35
35
  import { query } from "../query.js";
36
36
  import { birth } from "../births.js";
37
- import { readFrameworkDeclared } from "../read.js";
38
- import { authProvider as authProviderQuery, seedAuthProvider } from "./auth_provider_law.js";
37
+ import {
38
+ authProvider as authProviderQuery,
39
+ authProviderCertificates as authProviderCertificatesQuery,
40
+ authProviderKeys as authProviderKeysQuery,
41
+ readAuthProviderVerifyingConfiguration,
42
+ seedAuthProvider,
43
+ } from "./auth_provider_law.js";
39
44
  import { readRef, readSet, workspaceInvariant, refAs } from "./workspace_invariant.js";
40
45
  import { identityRelations } from "./signing_identity.js";
41
46
  import { RelationTuple, writeTuple } from "./relations.js";
@@ -355,18 +360,9 @@ export function birthReceivingGroupForDomain(
355
360
  // THE PARENT'S DECLARED VERIFIER. This captured read supplies the inherited provider seed.
356
361
  // Birth-certificate verification and founding-signer enrollment belong to the kernel-owned
357
362
  // attestation step, so neither fact is duplicated in this tenant plan or payload.
358
- const apRows = p.inheritAuthProvider !== false
359
- ? readFrameworkDeclared<Array<Record<string, unknown>>>("authProvider", { scope: "platform" })
360
- : undefined;
361
- const apRaw = Array.isArray(apRows) && apRows.length > 0 ? apRows[0] : undefined;
362
- // Projected rows are `{type,id,data:{…fields}}`; folded shapes may be flat — accept both.
363
- const ap = (apRaw && typeof apRaw === "object" && typeof (apRaw as { data?: unknown }).data === "object"
364
- ? (apRaw as { data: Record<string, unknown> }).data
365
- : apRaw) as Record<string, unknown> | undefined;
366
- const apStr = (k: string): string | undefined => {
367
- const v = ap?.[k];
368
- return typeof v === "string" && v.length > 0 ? v : undefined;
369
- };
363
+ const inheritedProvider = p.inheritAuthProvider === false
364
+ ? undefined
365
+ : readAuthProviderVerifyingConfiguration();
370
366
  const genesisChain: Array<{ domain: string; directiveId: string; actor: string; payload: Record<string, unknown>; domainHash?: string }> = [
371
367
  // THE ORDER-DECLARED LAW INSTALL: two typed closure identities. The kernel resolves each
372
368
  // closure from the parent's custody when it folds the birth; law bytes never ride the intent.
@@ -425,23 +421,14 @@ export function birthReceivingGroupForDomain(
425
421
  // (the up-front captured read above) AS DATA into the child genesis. The child owns the copy as its
426
422
  // own law-state; nothing is inherited-by-reference. Opt out: `inheritAuthProvider: false`.
427
423
  if (p.inheritAuthProvider !== false) {
428
- const str = apStr;
429
- const issuer = str("issuer");
430
- if (issuer !== undefined) {
424
+ if (inheritedProvider !== undefined) {
431
425
  genesisChain.push({
432
426
  domain: groupLawDomain,
433
427
  directiveId: "seedAuthProvider",
434
428
  actor: founder,
435
429
  payload: {
436
- issuer,
437
- jwksUrl: str("jwksUrl") ?? issuer, // required by the framework schema; a keys-only provider mirrors its issuer
438
- ...(str("keys") !== undefined ? { keys: str("keys") } : {}),
439
- ...(str("audience") !== undefined ? { audience: str("audience") } : {}),
440
- ...(str("principalClaim") !== undefined ? { principalClaim: str("principalClaim") } : {}),
441
- ...(str("rootCert") !== undefined ? { rootCert: str("rootCert") } : {}),
442
- ...(str("deviceBindingClaim") !== undefined ? { deviceBindingClaim: str("deviceBindingClaim") } : {}),
443
- ...(str("requireDeviceBinding") !== undefined ? { requireDeviceBinding: str("requireDeviceBinding") } : {}),
444
- label: str("label") ?? "inherited-from-parent",
430
+ ...inheritedProvider,
431
+ label: "inherited-from-parent",
445
432
  setBy: p.owner,
446
433
  setAt: 1, // genesis position — deterministic, like enrollSigner's grantedAt
447
434
  },
@@ -455,6 +442,8 @@ export function birthReceivingGroupForDomain(
455
442
  return [];
456
443
  })
457
444
  .reads(authProviderQuery) // the parent-side AuthProvider capture the inheritance copies (the #58 lane)
445
+ .reads(authProviderKeysQuery)
446
+ .reads(authProviderCertificatesQuery)
458
447
  .births(); // BIRTH IS A DECLARED KERNEL PRIMITIVE — emits the `nomosBirthGate` era key; the kernel folds the recipe
459
448
  return {
460
449
  ...birthDirective,
@@ -645,6 +634,13 @@ export const seedBirthAttestation = directive("seedBirthAttestation")
645
634
  parentKey: z.string().min(1), // base64 K_P
646
635
  epoch: z.number().int().nonnegative(),
647
636
  links: z.array(birthCertificatePayload).min(1),
637
+ /** Law-declared target roles for the verified immediate parent workspace. The kernel injects this
638
+ * value from the admitted birth recipe and supplies `parentId`; no host chooses either fact. */
639
+ parentWorkspaceRoles: z.array(z.string().regex(/^[A-Za-z][A-Za-z0-9_-]*$/)).optional(),
640
+ }).superRefine((payload, ctx) => {
641
+ if ((payload.parentWorkspaceRoles?.length ?? 0) > 0 && payload.parentId.length === 0) {
642
+ ctx.addIssue({ code: "custom", message: "parent workspace roles require a verified non-root parentId" });
643
+ }
648
644
  }),
649
645
  )
650
646
  .plan((p) => {
@@ -676,9 +672,17 @@ export const seedBirthAttestation = directive("seedBirthAttestation")
676
672
  ...(certificate.authProviderAudience ? [withMarker(set(link, "authProviderAudience", certificate.authProviderAudience), "ensures")] : []),
677
673
  ];
678
674
  });
679
- return [...header, ...links];
675
+ const parentAuthority = (p.parentWorkspaceRoles ?? []).flatMap((relation) => writeTuple({
676
+ object: "workspace:self",
677
+ relation,
678
+ subject: `workspace:${p.parentId}`,
679
+ grantedBy: `workspace:${p.parentId}`,
680
+ grantedAt: String(p.epoch),
681
+ }));
682
+ return [...header, ...links, ...parentAuthority];
680
683
  })
681
- .alsoEnsures(BirthAttestationLink);
684
+ .alsoEnsures(BirthAttestationLink)
685
+ .alsoEnsures(RelationTuple);
682
686
 
683
687
  /**
684
688
  * THE ERA DECLARATION (the `nomosParentAttestedBirth` USD key). Exporting this sentinel from the home law