@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.
- package/dart/CHANGELOG.md +24 -0
- package/dart/lib/src/application_framework_calls.dart +299 -0
- package/dart/lib/src/generated_call.dart +96 -0
- package/dart/lib/src/nms_generated_runtime.dart +29 -0
- package/dart/lib/src/node_runner_transport.dart +77 -1
- package/dart/lib/src/result_usda.dart +487 -0
- package/dart/lib/src/runner/node-entry.mjs +10634 -6122
- package/dart/lib/src/runner/offline-install.mjs +4187 -2716
- package/dart/lib/src/runtime_v2_gateway.dart +54 -0
- package/dart/lib/src/runtime_v2_transport.dart +117 -0
- package/dart/lib/src/session_bound.dart +7 -2
- package/dart/lib/src/test_harness.dart +373 -282
- package/dart/lib/src/webview_bridge.dart +2146 -725
- package/dart/pubspec.lock +17 -1
- package/dart/pubspec.yaml +3 -2
- package/dart/test/acquire_session_credentials_test.dart +1 -1
- package/dart/test/acquire_session_test.dart +68 -14
- package/dart/test/adv_realm_interference_test.dart +6 -4
- package/dart/test/cluster_births_test.dart +11 -8
- package/dart/test/cluster_local_autosync_test.dart +1 -1
- package/dart/test/cluster_scale_test.dart +3 -2
- package/dart/test/dlq_session_surface_test.dart +52 -35
- package/dart/test/first_app_gauntlet_test.dart +1 -1
- package/dart/test/local_sync_inert_test.dart +23 -26
- package/dart/test/offer_authentication_proof_test.dart +193 -0
- package/dart/test/result_usda_test.dart +49 -0
- package/dart/test/session_bound_test.dart +8 -11
- package/dart/test/stale_bundle_test.dart +18 -3
- package/dart/test/test_harness_test.dart +14 -27
- package/dart/test/webview_bridge_test.dart +504 -83
- package/dart_types/CHANGELOG.md +17 -0
- package/dart_types/lib/nomos_kernel_protocol.dart +11 -0
- package/dart_types/lib/src/custody.dart +64 -19
- package/dart_types/lib/src/local_work_rejected.dart +30 -45
- package/dart_types/lib/src/proto/nomos/custody/v1/snapshot.pb.dart +169 -0
- package/dart_types/lib/src/proto/nomos/custody/v1/snapshot.pbenum.dart +11 -0
- package/dart_types/lib/src/proto/nomos/kernel/v1/kernel.pb.dart +17945 -0
- package/dart_types/lib/src/proto/nomos/kernel/v1/kernel.pbenum.dart +933 -0
- package/dart_types/lib/src/proto/nomos/kernel/v1/progress.pb.dart +245 -0
- package/dart_types/lib/src/proto/nomos/kernel/v1/progress.pbenum.dart +160 -0
- package/dart_types/lib/src/proto/nomos/runtime/v2/runtime.pb.dart +8664 -0
- package/dart_types/lib/src/proto/nomos/runtime/v2/runtime.pbenum.dart +369 -0
- package/dart_types/lib/src/surfaced.dart +30 -25
- package/dart_types/pubspec.lock +16 -0
- package/dart_types/pubspec.yaml +3 -1
- package/dart_types/test/surfaced_test.dart +2 -1
- package/package.json +1 -1
- package/runtime/generated-application-host.js +5760 -1879
- package/src/authoring.ts +3 -2
- package/src/birth_meter_ensure.ts +2 -0
- package/src/births.ts +21 -0
- package/src/build_package.ts +30 -0
- package/src/business_birth.ts +41 -35
- package/src/codegen_call_usda.ts +1111 -0
- package/src/codegen_dart.ts +207 -282
- package/src/codegen_dart_application.ts +195 -252
- package/src/codegen_dart_client.ts +1978 -593
- package/src/codegen_dart_framework_calls.ts +291 -0
- package/src/codegen_dart_scenario.ts +7 -0
- package/src/codegen_proof.ts +3 -4
- package/src/codegen_provider_dart.ts +21 -7
- package/src/codegen_query_usda.ts +214 -0
- package/src/codegen_result_usda.ts +685 -0
- package/src/codegen_ts.ts +3945 -1111
- package/src/compile_package_main.ts +392 -46
- package/src/directive.ts +19 -4
- package/src/engine_entry.ts +45 -4
- package/src/framework/application_home.ts +4 -0
- package/src/framework/auth_provider_law.ts +296 -20
- package/src/framework/birth_certificate_law.ts +24 -0
- package/src/framework/home.ts +83 -40
- package/src/framework/owned_custody.ts +25 -12
- package/src/framework/owned_custody_value.ts +51 -0
- package/src/framework/relations.ts +47 -21
- package/src/framework/signing_identity.ts +100 -17
- package/src/framework/workspaces.ts +39 -10
- package/src/framework_call_definitions.ts +53 -0
- package/src/framework_read_definitions.ts +45 -0
- package/src/identity_frontier.ts +58 -0
- package/src/index.ts +22 -1
- package/src/intent.ts +216 -16
- package/src/law_check.ts +19 -1
- package/src/law_graph.ts +57 -8
- package/src/law_graph_usda.ts +73 -5
- package/src/law_value_type.ts +106 -19
- package/src/read.ts +47 -0
- package/src/read_projection_namespace.ts +7 -0
- package/src/shape_compat.ts +44 -42
- package/src/stable_ids.ts +37 -2
- package/src/test_call_usda.ts +83 -0
- package/src/wire.ts +36 -2
- package/src/wire_encode.ts +45 -1
- 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
|
|
199
|
-
* source's
|
|
200
|
-
*
|
|
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
|
|
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
|
package/src/engine_entry.ts
CHANGED
|
@@ -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?:
|
|
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(
|
|
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:
|
|
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(
|
|
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
|
-
/**
|
|
35
|
-
|
|
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
|
|
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
|
-
/**
|
|
85
|
-
|
|
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
|
-
|
|
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(
|
|
122
|
-
.plan(
|
|
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(
|
|
134
|
-
.plan(
|
|
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>;
|