@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.
- package/dart/CHANGELOG.md +5 -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 +6401 -2102
- package/dart/lib/src/runner/offline-install.mjs +1538 -163
- 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 +193 -112
- package/dart/lib/src/webview_bridge.dart +2120 -569
- package/dart/pubspec.yaml +2 -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 +7 -0
- package/dart_types/lib/nomos_kernel_protocol.dart +3 -2
- 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 +2021 -234
- package/dart_types/lib/src/proto/nomos/kernel/v1/kernel.pbenum.dart +175 -8
- 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.yaml +1 -1
- package/dart_types/test/surfaced_test.dart +2 -1
- package/package.json +1 -1
- package/runtime/generated-application-host.js +5736 -2047
- package/src/authoring.ts +3 -2
- package/src/births.ts +21 -0
- package/src/build_package.ts +30 -0
- package/src/business_birth.ts +39 -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 +360 -32
- package/src/directive.ts +19 -4
- package/src/engine_entry.ts +45 -4
- package/src/framework/application_home.ts +2 -0
- package/src/framework/auth_provider_law.ts +262 -20
- package/src/framework/home.ts +32 -28
- package/src/framework/owned_custody.ts +19 -11
- package/src/framework/owned_custody_value.ts +51 -0
- package/src/framework/relations.ts +47 -21
- package/src/framework/signing_identity.ts +34 -0
- package/src/framework/workspaces.ts +31 -9
- 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 +20 -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 +38 -39
- 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/dart_types/lib/src/proto/nomos/kernel/v1/kernel.pbjson.dart +0 -6096
- package/dart_types/lib/src/proto/nomos/kernel/v1/kernel.pbserver.dart +0 -50
- package/dart_types/lib/src/proto/nomos/kernel/v1/progress.pbjson.dart +0 -178
- package/dart_types/lib/src/proto/nomos/runtime/v1/runtime.pb.dart +0 -828
- package/dart_types/lib/src/proto/nomos/runtime/v1/runtime.pbenum.dart +0 -465
- package/dart_types/lib/src/proto/nomos/runtime/v1/runtime.pbjson.dart +0 -573
- 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
|
|
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,
|
|
@@ -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
|
-
/**
|
|
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
|
|
@@ -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
|
|
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
|
-
/**
|
|
106
|
-
|
|
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
|
-
|
|
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(
|
|
156
|
-
.plan(
|
|
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(
|
|
168
|
-
.plan(
|
|
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")
|
package/src/framework/home.ts
CHANGED
|
@@ -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 {
|
|
38
|
-
|
|
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
|
|
359
|
-
?
|
|
360
|
-
:
|
|
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
|
-
|
|
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
|
-
|
|
437
|
-
|
|
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
|
-
|
|
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
|