@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
@@ -23,6 +23,7 @@ import { BirthAttestation, BirthAttestationLink, seedBirthAttestation } from "./
23
23
  import { RelationTuple, __qualifyAggregateType, removeTuple, writeTuple } from "./relations.js";
24
24
  import { SIGNER_RELATION, enrollSigner, signerObject, identityRelations } from "./signing_identity.js";
25
25
  import { impureCapability, impureCapabilityAggregate } from "./impure_capability.js";
26
+ import { OwnedCustodyValue, OwnedDeviceRequestValue } from "./owned_custody_value.js";
26
27
 
27
28
  /** Obscure stable law id: deliberately unavailable as a generated tenant directive method. */
28
29
  export const OWNED_DEVICE_APPROVAL_DIRECTIVE = "nomos_internal_approve_owned_device_7c1f5e";
@@ -68,7 +69,7 @@ export const OwnedRecoveryPolicy = aggregate("NomosOwnedRecoveryPolicy", {
68
69
  const recoveryOrderParams = {
69
70
  ownerPrincipal: t.string().merge(Lww),
70
71
  recoveryPrincipal: t.string().merge(Lww),
71
- deviceRequest: t.string().merge(Lww),
72
+ deviceRequest: OwnedDeviceRequestValue.merge(Lww),
72
73
  };
73
74
 
74
75
  /**
@@ -81,14 +82,14 @@ const recoveryOrderParams = {
81
82
  * the last thing that should degrade quietly.
82
83
  */
83
84
  const recoveryReceipt = {
84
- ownedCustody: z.string().min(1),
85
+ ownedCustody: OwnedCustodyValue.zod,
85
86
  };
86
87
 
87
88
  /** Order fields ⊕ the receipt field, so the aggregate can hold what the Receipt writes. */
88
89
  const recoveryOrderFields = {
89
90
  ...recoveryOrderParams,
90
91
  // Absent until the recovery principal's Receipt lands (the read-decode discipline).
91
- ownedCustody: t.string().merge(Lww).optional(),
92
+ ownedCustody: OwnedCustodyValue.merge(Lww).optional(),
92
93
  };
93
94
 
94
95
  /** Private framework Order -> Receipt journal. Generated business clients never expose this aggregate. */
@@ -105,7 +106,7 @@ const ownedRecoveryLifecycle = impureCapability({
105
106
  paramsSchema: {
106
107
  ownerPrincipal: principal,
107
108
  recoveryPrincipal: principal,
108
- deviceRequest: z.string().min(1),
109
+ deviceRequest: OwnedDeviceRequestValue.zod,
109
110
  },
110
111
  writeParams: (id, p) => [
111
112
  set(instance(OwnedRecoveryOrder, id), "ownerPrincipal", p.ownerPrincipal),
@@ -114,7 +115,7 @@ const ownedRecoveryLifecycle = impureCapability({
114
115
  ],
115
116
  receiptSchema: recoveryReceipt,
116
117
  writeReceipt: (id, r) => [
117
- set(instance(OwnedRecoveryOrder, id), "ownedCustody", r.ownedCustody as string),
118
+ set(instance(OwnedRecoveryOrder, id), "ownedCustody", OwnedCustodyValue.zod.parse(r.ownedCustody)),
118
119
  ],
119
120
  // This is intentionally unaddressed: opening the designated principal's ordinary generated
120
121
  // client is the provider. Declaring the capability still matters because every durable Order must
@@ -132,6 +133,13 @@ const hiddenRecoveryLifecycle = {
132
133
  deadLetter: hidden(ownedRecoveryLifecycle.deadLetter),
133
134
  reschedule: hidden(ownedRecoveryLifecycle.reschedule),
134
135
  };
136
+
137
+ /** Compiler-contract handles for the two generated recovery calls used by the
138
+ * host. They remain framework-private directives; exporting the authored
139
+ * objects here lets code generation derive the cross-language signatures
140
+ * instead of maintaining another payload mirror. */
141
+ export const requestOwnedRecovery = hiddenRecoveryLifecycle.obligation;
142
+ export const completeOwnedRecovery = hiddenRecoveryLifecycle.complete;
135
143
  const ownedRecoveryCapability = {
136
144
  ...ownedRecoveryLifecycle,
137
145
  // Capability expansion deliberately re-seats the lifecycle quartet after application-language
@@ -152,7 +160,7 @@ export const ownedRecoveryOrdersByRecoveryPrincipal = query("nomosOwnedRecoveryO
152
160
  .returns(OwnedRecoveryOrder);
153
161
 
154
162
  /** Opt in once. Retrying the same policy is idempotent; replacing the principal is refused by the kernel. */
155
- const protectOwnedIdentity = Object.assign(
163
+ export const protectOwnedIdentity = Object.assign(
156
164
  directive(OWNED_RECOVERY_PROTECT_DIRECTIVE)
157
165
  .ensures(OwnedRecoveryPolicy)
158
166
  .payload(z.object({
@@ -196,13 +204,13 @@ const protectOwnedIdentity = Object.assign(
196
204
  { businessBirthInternal: true as const, ownedAuthoringInternal: true as const },
197
205
  );
198
206
 
199
- const acceptOwnedRecovery = Object.assign(
207
+ export const acceptOwnedRecovery = Object.assign(
200
208
  directive(OWNED_RECOVERY_ACCEPT_DIRECTIVE)
201
209
  .ensures(RelationTuple)
202
210
  .payload(z.object({
203
211
  requestId: z.string().min(1),
204
212
  newKeySubject: keySubject,
205
- ownedCustody: z.string().min(1),
213
+ ownedCustody: OwnedCustodyValue.zod,
206
214
  recoveredAt: z.string().min(1),
207
215
  }))
208
216
  .plan((p, ctx) => {
@@ -249,7 +257,7 @@ const approval = directive(OWNED_DEVICE_APPROVAL_DIRECTIVE)
249
257
  .ensures(RelationTuple)
250
258
  .payload(z.object({
251
259
  newKeySubject: keySubject,
252
- ownedCustody: z.string().min(1),
260
+ ownedCustody: OwnedCustodyValue.zod,
253
261
  verifiedVia: z.string().min(1),
254
262
  validFromEpoch: z.number().int().nonnegative(),
255
263
  approvedAt: z.string().min(1),
@@ -287,12 +295,12 @@ export const approveOwnedDevice = Object.assign(approval, {
287
295
  });
288
296
 
289
297
  /** Establish this principal's shared owned-data root before another principal shares to them. */
290
- const initializeOwnedIdentity = Object.assign(
298
+ export const initializeOwnedIdentity = Object.assign(
291
299
  directive(OWNED_INITIALIZE_DIRECTIVE)
292
300
  .ensures(RelationTuple)
293
301
  .payload(z.object({
294
302
  newKeySubject: keySubject,
295
- ownedCustody: z.string().min(1),
303
+ ownedCustody: OwnedCustodyValue.zod,
296
304
  initializedAt: z.string().min(1),
297
305
  }))
298
306
  .plan((p, ctx) => {
@@ -0,0 +1,51 @@
1
+ // NOMOS — framework-owned typed custody values shared by relation facts, recovery Orders,
2
+ // generated clients and the kernel. USDA is the canonical semantic representation; these
3
+ // descriptors are the authored source from which TS/Dart encoders and decoders are generated.
4
+
5
+ import { z } from "zod";
6
+ import { valueObject, type ValueObjectValue } from "../value_object.js";
7
+
8
+ const base64 = z.string().regex(/^[A-Za-z0-9+/]+={0,2}$/);
9
+ const sha256 = z.string().regex(/^[0-9a-f]{64}$/);
10
+
11
+ export const OwnedDeviceRequestValue = valueObject("OwnedDeviceRequest", {
12
+ v: z.number().int().min(1).max(255),
13
+ workspace: z.string().min(1),
14
+ principal: z.string().min(1),
15
+ scope: z.string().min(1),
16
+ signingPublic: base64,
17
+ wrappingPublic: base64,
18
+ signature: base64,
19
+ });
20
+
21
+ export type OwnedDeviceRequest = ValueObjectValue<typeof OwnedDeviceRequestValue>;
22
+
23
+ export const OwnedCustodyValue = valueObject("OwnedCustody", {
24
+ v: z.number().int().min(1).max(255),
25
+ request: OwnedDeviceRequestValue,
26
+ hpkeEpk: base64,
27
+ ct: base64,
28
+ keyCommitment: sha256,
29
+ principalWrappingPublic: base64,
30
+ });
31
+
32
+ export type OwnedCustody = ValueObjectValue<typeof OwnedCustodyValue>;
33
+
34
+ /** Deterministic compile/proof value. It is structurally valid but intentionally has no
35
+ * cryptographic authority; the live executor will reject it if it ever reaches admission. */
36
+ export const OWNED_CUSTODY_PROBE: OwnedCustody = {
37
+ v: 1,
38
+ request: {
39
+ v: 1,
40
+ workspace: "nomos-probe",
41
+ principal: "user:nomos-probe",
42
+ scope: "nomos-probe",
43
+ signingPublic: "AA==",
44
+ wrappingPublic: "AA==",
45
+ signature: "AA==",
46
+ },
47
+ hpkeEpk: "AA==",
48
+ ct: "AA==",
49
+ keyCommitment: "0".repeat(64),
50
+ principalWrappingPublic: "AA==",
51
+ };
@@ -41,7 +41,7 @@
41
41
  */
42
42
  import { z } from "zod";
43
43
  import { aggregate, instance } from "../aggregate.js";
44
- import { t, type JsonValue } from "../fields.js";
44
+ import { t } from "../fields.js";
45
45
  import { Lww, MapOf } from "../drivers.js";
46
46
  import { directive } from "../directive.js";
47
47
  import { set, setEntry, withMarker, type PlannedOp } from "../ops.js";
@@ -49,6 +49,8 @@ import { query } from "../query.js";
49
49
  import { endorsementOf, type AttributionPrincipal, type EndorsedAuthority } from "../principal_evidence.js";
50
50
  import { isRelationObjectToken, qualifyRelationType, type RelationObjectToken } from "../relation_schema.js";
51
51
  import { qualifyReadProjectionType } from "../read_projection_namespace.js";
52
+ import { valueObject } from "../value_object.js";
53
+ import { OwnedCustodyValue, type OwnedCustody } from "./owned_custody_value.js";
52
54
 
53
55
  // ─────────────────────────────── the dispatch context (relation-namespacing) ───────────────────────────────
54
56
  //
@@ -192,46 +194,70 @@ export function relationTupleInstanceId(object: string, relation: string): strin
192
194
  * - relation Lww (the edge label)
193
195
  * - subjects MapOf(Lww) — subject → typed `RelationshipSubject` runtime leaf
194
196
  */
197
+ /** The exact law-level value stored for one subject in a relation tuple.
198
+ *
199
+ * This used to be `t.json()` and forced every generated reader to recover an
200
+ * unstructured object after the kernel had already proved a typed
201
+ * `RelationshipSubject`. Keeping the descriptor here makes the DSL graph the
202
+ * one authority for TS, Dart, USDA and the kernel's map leaf. */
203
+ export const RelationshipSubjectValue = valueObject("RelationshipSubject", {
204
+ status: z.enum(["active", "revoked"]),
205
+ grantedBy: z.string(),
206
+ grantedAt: z.string(),
207
+ revokedBy: z.string().optional(),
208
+ revokedAt: z.string().optional(),
209
+ validFromEpoch: z.number().int().nonnegative().optional(),
210
+ verifiedVia: z.string().optional(),
211
+ ownedCustody: OwnedCustodyValue.optional(),
212
+ });
213
+
195
214
  export const RelationTuple = aggregate("RelationTuple", {
196
215
  object: t.string().merge(Lww),
197
216
  relation: t.string().merge(Lww),
198
- subjects: t.map(t.json()).merge(MapOf(Lww)),
217
+ subjects: t.map(RelationshipSubjectValue.field()).merge(MapOf(Lww)),
199
218
  }).public();
200
219
  export type RelationTupleHandle = typeof RelationTuple;
201
220
 
202
221
  /** The per-subject leaf the `subjects` map stores as a typed kernel value. */
203
222
  export interface RelationSubjectLeaf {
204
- status: RelationStatus;
205
- grantedBy: string;
206
- grantedAt: string;
207
- revokedBy?: string;
208
- revokedAt?: string;
223
+ readonly status: RelationStatus;
224
+ readonly grantedBy: string;
225
+ readonly grantedAt: string;
226
+ readonly revokedBy?: string;
227
+ readonly revokedAt?: string;
209
228
  /**
210
229
  * VA-core (signer-provenance) — the SignerEpoch this leaf was authored under. OMITTED when absent so
211
230
  * every pre-VA chain (and every non-signer relation) stays byte-identical: `compact()` strips an
212
231
  * `undefined`. A revoke (`unrelateSubject`) backdates by bumping the SignerEpoch; a key whose
213
232
  * `validFromEpoch` predates the current epoch is stale (the full backdate gate is phase 4).
214
233
  */
215
- validFromEpoch?: number;
234
+ readonly validFromEpoch?: number;
216
235
  /**
217
236
  * VA-core (signer-provenance) — the IdP/issuer that VOUCHED for the enrolling principal at enroll time
218
237
  * (e.g. the `AuthProvider.issuer`). The kernel's `verify_captured_principal` DERIVES `principal_verified`
219
238
  * from THIS leaf field (provenance, not the wire): an enrollment vouched by a real IdP reads verified on
220
239
  * every lane. OMITTED when absent (legacy / unverified enrollment) so existing chains stay byte-identical.
221
240
  */
222
- verifiedVia?: string;
241
+ readonly verifiedVia?: string;
223
242
  /**
224
243
  * Secure-by-default owned-data device handoff. Framework-reserved and omitted from every ordinary
225
- * relation leaf; the opaque JSON is independently verified by the kernel before it may land.
244
+ * relation leaf; the typed encrypted handoff is independently verified by the kernel before it may land.
226
245
  */
227
- ownedCustody?: string;
246
+ readonly ownedCustody?: OwnedCustody;
228
247
  }
229
248
 
230
249
  /** Strip undefined keys so the typed leaf has one stable optional-field shape. */
231
- function compact(obj: Record<string, unknown>): RelationSubjectLeaf & Record<string, JsonValue> {
232
- const out: Record<string, unknown> = {};
233
- for (const [k, v] of Object.entries(obj)) if (v !== undefined) out[k] = v;
234
- return out as RelationSubjectLeaf & Record<string, JsonValue>;
250
+ function compact(leaf: RelationSubjectLeaf): RelationSubjectLeaf {
251
+ return {
252
+ status: leaf.status,
253
+ grantedBy: leaf.grantedBy,
254
+ grantedAt: leaf.grantedAt,
255
+ ...(leaf.revokedBy === undefined ? {} : { revokedBy: leaf.revokedBy }),
256
+ ...(leaf.revokedAt === undefined ? {} : { revokedAt: leaf.revokedAt }),
257
+ ...(leaf.validFromEpoch === undefined ? {} : { validFromEpoch: leaf.validFromEpoch }),
258
+ ...(leaf.verifiedVia === undefined ? {} : { verifiedVia: leaf.verifiedVia }),
259
+ ...(leaf.ownedCustody === undefined ? {} : { ownedCustody: leaf.ownedCustody }),
260
+ };
235
261
  }
236
262
 
237
263
  interface WriteTupleInput {
@@ -261,7 +287,7 @@ interface WriteTupleInput {
261
287
  /** VA-core: the IdP/issuer that vouched for the enrolling principal (signer relations only; omitted otherwise). */
262
288
  verifiedVia?: string;
263
289
  /** Framework-internal encrypted owned-data handoff (owned device relation only). */
264
- ownedCustody?: string;
290
+ ownedCustody?: OwnedCustody;
265
291
  }
266
292
 
267
293
  /** Lower a tuple write to plan ops. The first op MATERIALIZES the (object, relation)
@@ -297,13 +323,13 @@ function lowerTuple(
297
323
  status: p.status,
298
324
  grantedBy: grantedByRaw,
299
325
  grantedAt: p.grantedAt,
300
- revokedBy: revokedByRaw,
301
- revokedAt: p.revokedAt,
326
+ ...(revokedByRaw === undefined ? {} : { revokedBy: revokedByRaw }),
327
+ ...(p.revokedAt === undefined ? {} : { revokedAt: p.revokedAt }),
302
328
  // VA-core signer-provenance leaf fields — `compact` strips an `undefined`, so a non-signer relation
303
329
  // (these never set) stays byte-identical to a pre-VA leaf (the era rule on the wire shape).
304
- validFromEpoch: p.validFromEpoch,
305
- verifiedVia: p.verifiedVia,
306
- ownedCustody: p.ownedCustody,
330
+ ...(p.validFromEpoch === undefined ? {} : { validFromEpoch: p.validFromEpoch }),
331
+ ...(p.verifiedVia === undefined ? {} : { verifiedVia: p.verifiedVia }),
332
+ ...(p.ownedCustody === undefined ? {} : { ownedCustody: p.ownedCustody }),
307
333
  });
308
334
  const subjectEntry = {
309
335
  ...setEntry(rt, "subjects", subjectRaw, leaf),
@@ -58,6 +58,7 @@ import {
58
58
  RelationTuple,
59
59
  relationTupleInstanceId,
60
60
  } from "./relations.js";
61
+ import { MODEL_LIFECYCLE_AUTHORITY } from "./domain_lifecycle.js";
61
62
 
62
63
  /**
63
64
  * The framework `identity` relation schema — the `signer` relation, LEAF-ONLY
@@ -84,6 +85,9 @@ export function signerObject(principal: string): string {
84
85
  /** The `signer` relation name — the relation a device key holds on its `identity:<principal>` object. */
85
86
  export const SIGNER_RELATION = "signer";
86
87
 
88
+ /** Reserved workload principal for external admission receipts. It is never the business actor. */
89
+ export const ADMISSION_CHECKPOINT_PRINCIPAL = "workspace:admission-peer";
90
+
87
91
  const sha256Hex = z.string().regex(/^[0-9a-f]{64}$/, "expected lowercase sha256 hex (the device pubkey hash)");
88
92
 
89
93
  // ─────────────────────────────── PHASE 0 — SignerEpoch ───────────────────────────────
@@ -240,6 +244,36 @@ export const enrollSigner = directive("enrollSigner")
240
244
  });
241
245
  });
242
246
 
247
+ /**
248
+ * Enrol the runtime workload key which signs causal admission checkpoints. This is ordinary law:
249
+ * lifecycle authority chooses the public key, the relation is visible/revocable, and possession of
250
+ * the secret alone grants nothing. The key certifies that this workspace admitted an exact causal
251
+ * transition; the target directive's independent Zanzibar requirement remains business authority.
252
+ */
253
+ export const enrollAdmissionCheckpointSigner = directive(
254
+ "_nmsEffectEnrollAdmissionCheckpointSigner",
255
+ )
256
+ .ensures(RelationTuple)
257
+ .payload(
258
+ z.object({
259
+ keyHash: sha256Hex,
260
+ validFromEpoch: z.number().int().nonnegative(),
261
+ grantedAt: z.string().min(1),
262
+ }).strict(),
263
+ )
264
+ .plan((p, ctx) =>
265
+ writeTuple({
266
+ object: signerObject(ADMISSION_CHECKPOINT_PRINCIPAL),
267
+ relation: SIGNER_RELATION,
268
+ subject: `key:${p.keyHash}`,
269
+ grantedBy: ctx.principal().asAttribution(),
270
+ grantedAt: p.grantedAt,
271
+ validFromEpoch: p.validFromEpoch,
272
+ verifiedVia: "workspace-law",
273
+ }),
274
+ )
275
+ .requires(MODEL_LIFECYCLE_AUTHORITY, { object: "workspace:self" });
276
+
243
277
  /**
244
278
  * THE SELF-BIND ENROLL (VA-1 / B2). A device enrolls a key for ITS OWN verified identity — the bound
245
279
  * principal is `ctx.principal()` (the captured authoring principal), NOT a payload field, so a device can
@@ -1397,16 +1397,37 @@ export const setPlatformPool = directive("setPlatformPool")
1397
1397
  // here verbatim — the public surface and the law bytes are unchanged.
1398
1398
  export {
1399
1399
  AUTH_PROVIDER_ID,
1400
+ AUTH_PROVIDER_KEY_ID_PREFIX,
1401
+ AUTH_PROVIDER_CERTIFICATE_ID_PREFIX,
1400
1402
  AuthProvider,
1403
+ AuthProviderKey,
1404
+ AuthProviderCertificate,
1405
+ authProviderPublicKey,
1406
+ authProviderKeySet,
1407
+ authProviderConfiguration,
1408
+ type AuthProviderConfiguration,
1409
+ writeAuthProviderConfiguration,
1410
+ writeAuthProviderKeySet,
1401
1411
  seedAuthProvider,
1402
1412
  authProviderSeedBeforeWarrant,
1403
1413
  setAuthProvider,
1404
1414
  authProvider,
1415
+ authProviderKeys,
1416
+ authProviderCertificates,
1417
+ } from "./auth_provider_law.js";
1418
+ import {
1419
+ AUTH_PROVIDER_ID,
1420
+ AuthProvider,
1421
+ AuthProviderCertificate,
1422
+ AuthProviderKey,
1423
+ authProvider,
1424
+ authProviderKeySet,
1425
+ setAuthProvider,
1426
+ writeAuthProviderKeySet,
1405
1427
  } from "./auth_provider_law.js";
1406
- import { AUTH_PROVIDER_ID, AuthProvider, setAuthProvider, authProvider } from "./auth_provider_law.js";
1407
1428
 
1408
1429
  /**
1409
- * rotateKeys — TRUSTLESS key rotation. Replace {@link AuthProvider.keys} with a new JWKS whose every key
1430
+ * rotateKeys — TRUSTLESS key rotation. Replace the typed key epoch with public keys whose every key
1410
1431
  * carries an `x5c` cert chain. DELIBERATELY UNGATED at the relation level — the KERNEL is the authority: it
1411
1432
  * admits this ONLY if each key's x5c chain validates (RFC 5280) to the law-pinned {@link AuthProvider.rootCert}
1412
1433
  * and each key matches its CA-vouched leaf cert (the bind). So a bailiff (host on a cron, or anyone) may
@@ -1417,17 +1438,18 @@ export const rotateKeys = directive("rotateKeys")
1417
1438
  .ensures(AuthProvider)
1418
1439
  .payload(
1419
1440
  z.object({
1420
- /** The new JWKS JSON; EACH key MUST carry an `x5c` the kernel validates to {@link AuthProvider.rootCert}. */
1421
- keys: z.string().min(1),
1441
+ /** The new typed keys; EACH key MUST carry an `x5c` the kernel validates to the pinned root. */
1442
+ keySet: authProviderKeySet,
1422
1443
  rotatedAt: epochMs,
1423
- }),
1444
+ }).strict(),
1424
1445
  )
1425
1446
  .plan((p) => {
1426
1447
  const ap = instance(AuthProvider, AUTH_PROVIDER_ID);
1427
- // The kernel's x5c admission gate authorizes this (validate_rotate_keys); the plan just stores the
1428
- // CA-vouched JWKS. The `ensures` marker upserts onto the existing AuthProvider singleton.
1429
- return [withMarker(set(ap, "keys", p.keys), "ensures"), set(ap, "setAt", p.rotatedAt)];
1430
- });
1448
+ // The kernel's x5c admission gate authorizes this; the plan stores the CA-vouched typed key epoch.
1449
+ return [withMarker(set(ap, "keyEpoch", p.keySet.epoch), "ensures"), ...writeAuthProviderKeySet(p.keySet), set(ap, "setAt", p.rotatedAt)];
1450
+ })
1451
+ .alsoEnsuresMany(AuthProviderKey)
1452
+ .alsoEnsuresMany(AuthProviderCertificate);
1431
1453
 
1432
1454
  /**
1433
1455
  * HostnameClaim — a CUSTOM HOSTNAME bound to a platform: `api.acme.com` serving
@@ -0,0 +1,53 @@
1
+ // Stable framework operation names, bound to their authored directive schemas.
2
+ //
3
+ // This is deliberately metadata, not a second schema language. Every payload
4
+ // type and encoder is derived from the Directive object carried here. Both the
5
+ // TypeScript and Dart compilers consume this same list, so adding or changing a
6
+ // framework field cannot leave a handwritten host mirror behind.
7
+
8
+ import type { Directive } from "./directive.js";
9
+ import { APPLICATION_HOME_DOMAIN } from "./framework/application_home_domain.js";
10
+ import { setAuthProvider } from "./framework/auth_provider_law.js";
11
+ import { seedBirthPolicy } from "./framework/birth_meter.js";
12
+ import { installDomain, promoteDomain, rollbackDomain, stageDomain } from "./framework/domain_lifecycle.js";
13
+ import { seedGenesis } from "./framework/genesis.js";
14
+ import {
15
+ birthReceivingGroupForDomain,
16
+ seedBirthAttestation,
17
+ seedReceivingGroup,
18
+ seedReceivingGroupPolicy,
19
+ } from "./framework/home.js";
20
+ import { enrollDelegation, enrollFirstDevice, enrollSigner } from "./framework/signing_identity.js";
21
+
22
+ export interface FixedFrameworkCallDefinition {
23
+ readonly group: "identity" | "lifecycle" | "birth";
24
+ readonly kind: string;
25
+ readonly directive: Directive<any>;
26
+ /** Needed by the pre-bind Dart birth ceremony before an application exists. */
27
+ readonly dartBirth?: true;
28
+ /** A composed application-home law must contain this operation. */
29
+ readonly requiredApplicationHome?: true;
30
+ }
31
+
32
+ const receivingGroupBirth = birthReceivingGroupForDomain(
33
+ APPLICATION_HOME_DOMAIN,
34
+ 0,
35
+ [APPLICATION_HOME_DOMAIN],
36
+ );
37
+
38
+ export const fixedFrameworkCallDefinitions: readonly FixedFrameworkCallDefinition[] = [
39
+ { group: "identity", kind: "enrollFirstDevice", directive: enrollFirstDevice, dartBirth: true, requiredApplicationHome: true },
40
+ { group: "identity", kind: "enrollSigner", directive: enrollSigner, dartBirth: true, requiredApplicationHome: true },
41
+ { group: "identity", kind: "setAuthProvider", directive: setAuthProvider, requiredApplicationHome: true },
42
+ { group: "identity", kind: "birthReceivingGroup", directive: receivingGroupBirth, dartBirth: true, requiredApplicationHome: true },
43
+ { group: "lifecycle", kind: "installDomain", directive: installDomain },
44
+ { group: "lifecycle", kind: "stageDomain", directive: stageDomain },
45
+ { group: "lifecycle", kind: "promoteDomain", directive: promoteDomain },
46
+ { group: "lifecycle", kind: "rollbackDomain", directive: rollbackDomain },
47
+ { group: "birth", kind: "seedBirthPolicy", directive: seedBirthPolicy, dartBirth: true },
48
+ { group: "birth", kind: "enrollDelegation", directive: enrollDelegation, dartBirth: true },
49
+ { group: "birth", kind: "seedBirthAttestation", directive: seedBirthAttestation, dartBirth: true },
50
+ { group: "birth", kind: "seedGenesis", directive: seedGenesis, dartBirth: true },
51
+ { group: "birth", kind: "seedReceivingGroupPolicy", directive: seedReceivingGroupPolicy, dartBirth: true },
52
+ { group: "birth", kind: "seedReceivingGroup", directive: seedReceivingGroup, dartBirth: true },
53
+ ];
@@ -0,0 +1,45 @@
1
+ // Stable framework reads bound to their authored aggregate/query declarations.
2
+ //
3
+ // This is metadata, not another schema language. The compiler consumes the
4
+ // AggregateHandle/QueryDecl objects directly, so the public host contract,
5
+ // canonical query USDA and result decoder all change together with the DSL.
6
+
7
+ import type { AggregateHandle } from "./aggregate.js";
8
+ import type { QueryDecl } from "./query.js";
9
+ import { EncKey, encKeyByPrincipal } from "./framework/encryption_identity.js";
10
+ import {
11
+ KeyScope,
12
+ WrappedKey,
13
+ keyScopeByScope,
14
+ wrappedKeysByRecipient,
15
+ } from "./framework/encryption_scope.js";
16
+ import {
17
+ OwnedRecoveryOrder,
18
+ OwnedRecoveryPolicy,
19
+ ownedRecoveryOrdersByRecoveryPrincipal,
20
+ } from "./framework/owned_custody.js";
21
+ import { RelationTuple, relationTuplesByObject } from "./framework/relations.js";
22
+ import { SignerEpoch } from "./framework/signing_identity.js";
23
+
24
+ export interface FixedFrameworkReadDefinition {
25
+ readonly kind:
26
+ | "encKey"
27
+ | "keyScope"
28
+ | "ownedRecoveryOrder"
29
+ | "ownedRecoveryPolicy"
30
+ | "relationTuple"
31
+ | "signerEpoch"
32
+ | "wrappedKey";
33
+ readonly aggregate: AggregateHandle;
34
+ readonly query?: QueryDecl;
35
+ }
36
+
37
+ export const fixedFrameworkReadDefinitions: readonly FixedFrameworkReadDefinition[] = [
38
+ { kind: "encKey", aggregate: EncKey, query: encKeyByPrincipal },
39
+ { kind: "keyScope", aggregate: KeyScope, query: keyScopeByScope },
40
+ { kind: "ownedRecoveryOrder", aggregate: OwnedRecoveryOrder, query: ownedRecoveryOrdersByRecoveryPrincipal },
41
+ { kind: "ownedRecoveryPolicy", aggregate: OwnedRecoveryPolicy },
42
+ { kind: "relationTuple", aggregate: RelationTuple, query: relationTuplesByObject },
43
+ { kind: "signerEpoch", aggregate: SignerEpoch },
44
+ { kind: "wrappedKey", aggregate: WrappedKey, query: wrappedKeysByRecipient },
45
+ ];
@@ -0,0 +1,58 @@
1
+ import { execFileSync } from "node:child_process";
2
+ import { existsSync, readFileSync, realpathSync } from "node:fs";
3
+ import path from "node:path";
4
+
5
+ export type IdentityFrontierSource = Readonly<{
6
+ source: string;
7
+ custody: "git-head" | "working-file";
8
+ }>;
9
+
10
+ /**
11
+ * Read the identity frontier which is already part of custody.
12
+ *
13
+ * The working identity layer is candidate output: it may contain identities minted
14
+ * by an earlier successful local compile which have never been committed or shipped.
15
+ * Git HEAD is therefore the historical evolution frontier whenever the package is in
16
+ * Git custody. Outside Git, the checked-in/working file remains the portable fallback.
17
+ */
18
+ export function readHistoricalIdentityFrontier(
19
+ lockPath: string,
20
+ ): IdentityFrontierSource | undefined {
21
+ const directory = path.dirname(lockPath);
22
+ let repositoryRoot: string;
23
+ try {
24
+ repositoryRoot = realpathSync(execFileSync("git", ["rev-parse", "--show-toplevel"], {
25
+ cwd: directory,
26
+ encoding: "utf8",
27
+ stdio: ["ignore", "pipe", "ignore"],
28
+ }).trim());
29
+ } catch {
30
+ return existsSync(lockPath)
31
+ ? { source: readFileSync(lockPath, "utf8"), custody: "working-file" }
32
+ : undefined;
33
+ }
34
+
35
+ const canonicalLockPath = path.join(realpathSync(directory), path.basename(lockPath));
36
+ const relative = path.relative(repositoryRoot, canonicalLockPath);
37
+ if (relative === "" || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative)) {
38
+ return existsSync(lockPath)
39
+ ? { source: readFileSync(lockPath, "utf8"), custody: "working-file" }
40
+ : undefined;
41
+ }
42
+
43
+ try {
44
+ return {
45
+ source: execFileSync("git", ["show", `HEAD:${relative.split(path.sep).join("/")}`], {
46
+ cwd: repositoryRoot,
47
+ encoding: "utf8",
48
+ stdio: ["ignore", "pipe", "ignore"],
49
+ }),
50
+ custody: "git-head",
51
+ };
52
+ } catch {
53
+ // A repository with no committed identity has no historical frontier. Treating
54
+ // an untracked or candidate-only file as history would make local experimentation
55
+ // irreversible before it ever entered custody.
56
+ return undefined;
57
+ }
58
+ }
package/src/index.ts CHANGED
@@ -254,7 +254,7 @@ export { repair, repairsOf, isRepairDecl, type RepairDecl, type RepairMatcher, t
254
254
  // NOT YET DEPLOYABLE (#57): the sealed wasm engine does not provide `nomos.read` yet, so
255
255
  // `nomos-compile` REFUSES law that references read() (a named compile error, never a runtime halt).
256
256
  // The captured-reads wave (task #58) wires the capability in and lifts the refusal.
257
- export { read } from "./read.js";
257
+ export { read, readFrameworkDeclaredRows } from "./read.js";
258
258
  // RELEASE COHERENCE — standard OSGi Contract Namespace requirements derived from law shape.
259
259
  export {
260
260
  requiredKernelContracts,
@@ -319,12 +319,14 @@ export {
319
319
  // `nomosBirthGate` era key, never domain state) — spawns + self-validates the child after the offer admits.
320
320
  export {
321
321
  birth,
322
+ workspaceRole,
322
323
  workspaceNameForKey,
323
324
  WORKSPACE_NAME_SEP,
324
325
  birthKeyed,
325
326
  birthSingleton,
326
327
  birthInstance,
327
328
  type BirthRecipe,
329
+ type WorkspaceRole,
328
330
  type GenesisChainOrBuilder,
329
331
  type BusinessBirthDecl,
330
332
  type BusinessBirthSpec,
@@ -505,6 +507,8 @@ export {
505
507
  identityRelations,
506
508
  signerObject,
507
509
  SIGNER_RELATION,
510
+ ADMISSION_CHECKPOINT_PRINCIPAL,
511
+ enrollAdmissionCheckpointSigner,
508
512
  enrollSigner,
509
513
  // VA-core — SignerEpoch + self-bind enroll/revoke + the strict-increment & verified-enroll gates.
510
514
  SignerEpoch,
@@ -535,11 +539,26 @@ export {
535
539
  // the provider explicitly, without re-declaring the kernel's security schema in tenant code.
536
540
  export {
537
541
  AUTH_PROVIDER_ID,
542
+ AUTH_PROVIDER_KEY_ID_PREFIX,
543
+ AUTH_PROVIDER_CERTIFICATE_ID_PREFIX,
538
544
  AuthProvider,
545
+ AuthProviderKey,
546
+ AuthProviderCertificate,
547
+ authProviderPublicKey,
548
+ authProviderKeySet,
549
+ authProviderConfiguration,
550
+ type AuthProviderConfiguration,
551
+ type AuthProviderVerifyingConfiguration,
552
+ readAuthProviderConfiguration,
553
+ readAuthProviderVerifyingConfiguration,
554
+ writeAuthProviderConfiguration,
555
+ writeAuthProviderKeySet,
539
556
  seedAuthProvider,
540
557
  authProviderSeedBeforeWarrant,
541
558
  setAuthProvider,
542
559
  authProvider,
560
+ authProviderKeys,
561
+ authProviderCertificates,
543
562
  } from "./framework/auth_provider_law.js";
544
563
  // THE RECEIVING-GROUP HOLON — the framework-private shared signer roster + access manifest.
545
564
  export {