@githolon/dsl 0.100.6 → 0.101.0

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 (82) hide show
  1. package/dart/CHANGELOG.md +6 -0
  2. package/dart/lib/src/dev_cloud.dart +46 -15
  3. package/dart/lib/src/test_harness.dart +78 -16
  4. package/dart/lib/src/webview_bridge.dart +640 -126
  5. package/dart/pubspec.lock +1 -1
  6. package/dart/pubspec.yaml +2 -2
  7. package/dart/test/bundle_version_pairing_test.dart +11 -22
  8. package/dart/test/co2_product_gauntlet_test.dart +228 -0
  9. package/dart/test/errors_test.dart +63 -12
  10. package/dart/test/first_app_gauntlet_test.dart +29 -3
  11. package/dart/test/webview_bridge_test.dart +274 -0
  12. package/dart_types/CHANGELOG.md +6 -0
  13. package/dart_types/lib/src/errors.dart +136 -30
  14. package/dart_types/lib/src/surfaced.dart +48 -2
  15. package/dart_types/lib/src/wire.dart +3 -1
  16. package/dart_types/pubspec.yaml +1 -1
  17. package/dart_types/test/surfaced_test.dart +19 -0
  18. package/package.json +1 -1
  19. package/src/activity.ts +43 -4
  20. package/src/aggregate.ts +132 -22
  21. package/src/attested_core.ts +1 -1
  22. package/src/authoring.ts +119 -6
  23. package/src/birth_meter.ts +4 -4
  24. package/src/birth_meter_ensure.ts +9 -1
  25. package/src/births.ts +53 -22
  26. package/src/build_package.ts +55 -14
  27. package/src/business_birth.ts +48 -7
  28. package/src/codegen_dart.ts +267 -37
  29. package/src/codegen_dart_client.ts +921 -82
  30. package/src/codegen_openapi.ts +28 -5
  31. package/src/codegen_proof.ts +107 -11
  32. package/src/codegen_ts.ts +961 -135
  33. package/src/compile_package_main.ts +155 -45
  34. package/src/conflicts.ts +252 -0
  35. package/src/describe_model.ts +448 -0
  36. package/src/directive.ts +5 -38
  37. package/src/drivers.ts +12 -4
  38. package/src/engine_entry.ts +4 -0
  39. package/src/evolve_preview.ts +72 -11
  40. package/src/fields.ts +201 -55
  41. package/src/framework/application_home.ts +103 -0
  42. package/src/framework/application_home_domain.ts +2 -0
  43. package/src/framework/attested_receipts.ts +2 -2
  44. package/src/framework/auth_provider_law.ts +51 -24
  45. package/src/framework/business_corrections.ts +170 -0
  46. package/src/framework/disclosure.ts +1 -1
  47. package/src/framework/domain_lifecycle.ts +250 -16
  48. package/src/framework/encryption_identity.ts +1 -1
  49. package/src/framework/encryption_scope.ts +21 -13
  50. package/src/framework/era_certification.ts +1 -1
  51. package/src/framework/framework_aggregates.ts +2 -0
  52. package/src/framework/genesis.ts +1 -1
  53. package/src/framework/home.ts +22 -9
  54. package/src/framework/identity.ts +5 -5
  55. package/src/framework/impure_capability.ts +2 -2
  56. package/src/framework/owned_custody.ts +561 -0
  57. package/src/framework/platform_admin.ts +1 -1
  58. package/src/framework/relations.ts +9 -1
  59. package/src/framework/repair.ts +1 -1
  60. package/src/framework/signing_identity.ts +2 -2
  61. package/src/framework/source_roster.ts +1 -1
  62. package/src/framework/workspaces.ts +17 -10
  63. package/src/generated_client_names.ts +39 -0
  64. package/src/index.ts +20 -15
  65. package/src/kernel_capabilities.ts +25 -1
  66. package/src/law_check.ts +4 -1
  67. package/src/manifest.ts +356 -63
  68. package/src/read_projection_manifest.ts +8 -0
  69. package/src/read_projection_namespace.ts +4 -0
  70. package/src/relation.ts +1 -1
  71. package/src/shape_compat.ts +47 -2
  72. package/src/stable_ids.ts +38 -6
  73. package/src/stable_ids_types.ts +30 -0
  74. package/src/surfaced_lint.ts +0 -0
  75. package/src/usd.ts +62 -11
  76. package/src/usd_layers.ts +54 -3
  77. package/src/value_object.ts +23 -19
  78. package/src/wire.ts +1 -0
  79. package/src/wire_encode.ts +48 -4
  80. package/src/workspace_routing.ts +57 -0
  81. package/src/workspace_sharding.ts +9 -9
  82. package/src/certified_read.ts +0 -313
@@ -0,0 +1,561 @@
1
+ // NOMOS — framework-owned multi-device custody for `.owned()` business state.
2
+ // Tenant code sees only generated `devices.request()` / `devices.approve(request)`.
3
+
4
+ import { z } from "zod";
5
+ import { OWNED_KEY_RELATION, OWNED_READ_RELATION, aggregate, instance, ownedKeyObject, type AggregateHandle } from "../aggregate.js";
6
+ import { Lww } from "../drivers.js";
7
+ import { directive } from "../directive.js";
8
+ import { endorsementOf } from "../principal_evidence.js";
9
+ import { isRelationTypeDecl } from "../relation_schema.js";
10
+ import { t } from "../fields.js";
11
+ import { set, withMarker } from "../ops.js";
12
+ import { query } from "../query.js";
13
+ import {
14
+ KeyScope,
15
+ WrappedKey,
16
+ keyScopeByScope,
17
+ wrappedKeysByRecipient,
18
+ writeKeyEpoch,
19
+ writeWrap,
20
+ } from "./encryption_scope.js";
21
+ import { GenesisWarrant, seedGenesis } from "./genesis.js";
22
+ import { RelationTuple, __qualifyAggregateType, removeTuple, writeTuple } from "./relations.js";
23
+ import { SIGNER_RELATION, enrollSigner, signerObject, identityRelations } from "./signing_identity.js";
24
+ import { impureCapability, impureCapabilityAggregate } from "./impure_capability.js";
25
+
26
+ /** Obscure stable law id: deliberately unavailable as a generated tenant directive method. */
27
+ export const OWNED_DEVICE_APPROVAL_DIRECTIVE = "nomos_internal_approve_owned_device_7c1f5e";
28
+ export const OWNED_INITIALIZE_DIRECTIVE = "nomos_internal_initialize_owned_identity_7c1f5e";
29
+ export const OWNED_RECOVERY_RELATION = "nomos_internal_owned_recovery_principal_7c1f5e";
30
+ export const OWNED_RECOVERY_PROTECT_DIRECTIVE = "nomos_internal_protect_owned_identity_7c1f5e";
31
+ export const OWNED_RECOVERY_REQUEST_DIRECTIVE = "nomos_internal_request_owned_recovery_7c1f5e";
32
+ export const OWNED_RECOVERY_COMPLETE_DIRECTIVE = "nomos_internal_complete_owned_recovery_7c1f5e";
33
+ export const OWNED_RECOVERY_ACCEPT_DIRECTIVE = "nomos_internal_accept_owned_recovery_7c1f5e";
34
+ const OWNED_RECOVERY_FAIL_DIRECTIVE = "nomos_internal_fail_owned_recovery_7c1f5e";
35
+ const OWNED_RECOVERY_BLOCK_DIRECTIVE = "nomos_internal_block_owned_recovery_7c1f5e";
36
+ const OWNED_RECOVERY_DEAD_LETTER_DIRECTIVE = "nomos_internal_dead_letter_owned_recovery_7c1f5e";
37
+ const keySubject = z.string().regex(/^key:[0-9a-f]{64}$/);
38
+ const principal = z.string().regex(/^user:[A-Za-z0-9][A-Za-z0-9_.-]*$/);
39
+ const b64 = z.string().regex(/^[A-Za-z0-9+/]+={0,2}$/);
40
+ const hidden = <T extends object>(value: T): T & { readonly businessBirthInternal: true } =>
41
+ ({ ...value, businessBirthInternal: true });
42
+
43
+ /** The recovery principal can recover this workspace's owned root, never the operator by default. */
44
+ export function ownedRecoveryScope(owner: string): string {
45
+ return `${ownedKeyObject(owner)}#${OWNED_RECOVERY_RELATION}`;
46
+ }
47
+
48
+ export function ownedRecoveryPolicyId(owner: string): string {
49
+ return `nomos-owned-recovery-policy:${ownedKeyObject(owner).slice("nomos-owned-principal:".length)}`;
50
+ }
51
+
52
+ /** Auditable policy fact. The secret is absent: only the owner-root commitment and designated peer live here. */
53
+ export const OwnedRecoveryPolicy = aggregate("NomosOwnedRecoveryPolicy", {
54
+ ownerPrincipal: t.string().merge(Lww),
55
+ recoveryPrincipal: t.string().merge(Lww),
56
+ scope: t.string().merge(Lww),
57
+ keyCommitment: t.string().merge(Lww),
58
+ protectedAt: t.string().merge(Lww),
59
+ status: t.string().merge(Lww),
60
+ }).public();
61
+
62
+ const recoveryOrderParams = {
63
+ ownerPrincipal: t.string().merge(Lww),
64
+ recoveryPrincipal: t.string().merge(Lww),
65
+ deviceRequest: t.string().merge(Lww),
66
+ };
67
+
68
+ /** Private framework Order -> Receipt journal. Generated business clients never expose this aggregate. */
69
+ export const OwnedRecoveryOrder = impureCapabilityAggregate("NomosOwnedRecoveryOrder", recoveryOrderParams);
70
+ const ownedRecoveryLifecycle = impureCapability({
71
+ aggregateId: OwnedRecoveryOrder.id,
72
+ obligationDirectiveId: OWNED_RECOVERY_REQUEST_DIRECTIVE,
73
+ completeDirectiveId: OWNED_RECOVERY_COMPLETE_DIRECTIVE,
74
+ failDirectiveId: OWNED_RECOVERY_FAIL_DIRECTIVE,
75
+ blockDirectiveId: OWNED_RECOVERY_BLOCK_DIRECTIVE,
76
+ deadLetterDirectiveId: OWNED_RECOVERY_DEAD_LETTER_DIRECTIVE,
77
+ params: recoveryOrderParams,
78
+ paramsSchema: {
79
+ ownerPrincipal: principal,
80
+ recoveryPrincipal: principal,
81
+ deviceRequest: z.string().min(1),
82
+ },
83
+ writeParams: (id, p) => [
84
+ set(instance(OwnedRecoveryOrder, id), "ownerPrincipal", p.ownerPrincipal),
85
+ set(instance(OwnedRecoveryOrder, id), "recoveryPrincipal", p.recoveryPrincipal),
86
+ set(instance(OwnedRecoveryOrder, id), "deviceRequest", p.deviceRequest),
87
+ ],
88
+ });
89
+
90
+ export const ownedRecoveryOrdersByRecoveryPrincipal = query("nomosOwnedRecoveryOrdersByRecoveryPrincipal")
91
+ .key("recoveryPrincipal")
92
+ .returns(OwnedRecoveryOrder);
93
+
94
+ /** Opt in once. Retrying the same policy is idempotent; replacing the principal is refused by the kernel. */
95
+ const protectOwnedIdentity = Object.assign(
96
+ directive(OWNED_RECOVERY_PROTECT_DIRECTIVE)
97
+ .ensures(OwnedRecoveryPolicy)
98
+ .payload(z.object({
99
+ recoveryPrincipal: principal,
100
+ recipientPubKey: b64,
101
+ hpkeEpk: b64,
102
+ ct: b64,
103
+ keyCommitment: z.string().regex(/^[0-9a-f]{64}$/),
104
+ protectedAt: z.string().min(1),
105
+ }))
106
+ .plan((p, ctx) => {
107
+ const me = ctx.principal().asAuthoritySubject<string>("designate my owned-data recovery principal");
108
+ const owner = endorsementOf(me)?.principal ?? "";
109
+ const scope = ownedRecoveryScope(owner);
110
+ const policy = instance(OwnedRecoveryPolicy, ownedRecoveryPolicyId(owner));
111
+ return [
112
+ withMarker(set(policy, "ownerPrincipal", owner), "ensures"),
113
+ set(policy, "recoveryPrincipal", p.recoveryPrincipal),
114
+ set(policy, "scope", scope),
115
+ set(policy, "keyCommitment", p.keyCommitment),
116
+ set(policy, "protectedAt", p.protectedAt),
117
+ set(policy, "status", "protected"),
118
+ ...writeTuple({
119
+ object: ownedKeyObject(owner),
120
+ relation: OWNED_RECOVERY_RELATION,
121
+ subject: p.recoveryPrincipal,
122
+ grantedBy: ctx.principal().asAttribution(),
123
+ grantedAt: p.protectedAt,
124
+ }),
125
+ ...writeKeyEpoch(scope, 0, p.protectedAt, [{
126
+ recipientPrincipal: p.recoveryPrincipal,
127
+ recipientPubKey: p.recipientPubKey,
128
+ hpkeEpk: p.hpkeEpk,
129
+ ct: p.ct,
130
+ }]),
131
+ ];
132
+ })
133
+ .alsoEnsures(RelationTuple)
134
+ .alsoEnsures(KeyScope)
135
+ .alsoEnsures(WrappedKey),
136
+ { businessBirthInternal: true as const },
137
+ );
138
+
139
+ const acceptOwnedRecovery = Object.assign(
140
+ directive(OWNED_RECOVERY_ACCEPT_DIRECTIVE)
141
+ .ensures(RelationTuple)
142
+ .payload(z.object({
143
+ requestId: z.string().min(1),
144
+ newKeySubject: keySubject,
145
+ ownedCustody: z.string().min(1),
146
+ recoveredAt: z.string().min(1),
147
+ }))
148
+ .plan((p, ctx) => {
149
+ const me = ctx.principal().asAuthoritySubject<string>("accept my designated recovery handoff");
150
+ const owner = endorsementOf(me)?.principal ?? "";
151
+ return writeTuple({
152
+ object: ownedKeyObject(owner),
153
+ relation: OWNED_KEY_RELATION,
154
+ subject: p.newKeySubject,
155
+ grantedBy: ctx.principal().asAttribution(),
156
+ grantedAt: p.recoveredAt,
157
+ ownedCustody: p.ownedCustody,
158
+ });
159
+ }),
160
+ { businessBirthInternal: true as const },
161
+ );
162
+
163
+ const hiddenRecoveryLifecycle = {
164
+ aggregate: hidden(OwnedRecoveryOrder),
165
+ obligation: hidden(ownedRecoveryLifecycle.obligation),
166
+ complete: hidden(ownedRecoveryLifecycle.complete),
167
+ fail: hidden(ownedRecoveryLifecycle.fail),
168
+ block: hidden(ownedRecoveryLifecycle.block),
169
+ deadLetter: hidden(ownedRecoveryLifecycle.deadLetter),
170
+ };
171
+
172
+ export type OwnedRecordAction = "share" | "unshare" | "transfer";
173
+
174
+ /** One collision-resistant framework id per aggregate/action. Tenant code never sees these ids. */
175
+ export function ownedRecordActionDirectiveId(action: OwnedRecordAction, aggregateId: string): string {
176
+ let encoded = "";
177
+ for (let i = 0; i < aggregateId.length; i += 1) encoded += aggregateId.charCodeAt(i).toString(16).padStart(4, "0");
178
+ return `nomos_internal_${action}_owned_record_${encoded}_7c1f5e`;
179
+ }
180
+
181
+ const OWNED_ACTION_PREFIX = "nomos_internal_";
182
+ const OWNED_ACTION_MARKER = "_owned_record_";
183
+
184
+ const wrap = z.object({
185
+ recipientPrincipal: principal,
186
+ recipientPubKey: b64,
187
+ hpkeEpk: b64,
188
+ ct: b64,
189
+ });
190
+ const historicWrap = wrap.extend({ epoch: z.number().int().nonnegative() });
191
+
192
+ /**
193
+ * One atomic framework act: enroll the requesting signing key and add its encrypted handoff to the
194
+ * owned-device roster. The executor independently proves the opaque request, the same-device
195
+ * Ed25519->X25519 binding, approver provenance, current key commitment and exact two tuple writes.
196
+ */
197
+ const approval = directive(OWNED_DEVICE_APPROVAL_DIRECTIVE)
198
+ .ensures(RelationTuple)
199
+ .payload(z.object({
200
+ newKeySubject: keySubject,
201
+ ownedCustody: z.string().min(1),
202
+ verifiedVia: z.string().min(1),
203
+ validFromEpoch: z.number().int().nonnegative(),
204
+ approvedAt: z.string().min(1),
205
+ }))
206
+ .plan((p, ctx) => {
207
+ const me = ctx.principal().asAuthoritySubject<string>(
208
+ "approve another device for my own Nomos business state",
209
+ );
210
+ const principal = endorsementOf(me)?.principal ?? "";
211
+ return [
212
+ ...writeTuple({
213
+ object: signerObject(principal),
214
+ relation: SIGNER_RELATION,
215
+ subject: p.newKeySubject,
216
+ grantedBy: ctx.principal().asAttribution(),
217
+ grantedAt: p.approvedAt,
218
+ verifiedVia: p.verifiedVia,
219
+ validFromEpoch: p.validFromEpoch,
220
+ }),
221
+ ...writeTuple({
222
+ object: ownedKeyObject(principal),
223
+ relation: OWNED_KEY_RELATION,
224
+ subject: p.newKeySubject,
225
+ grantedBy: ctx.principal().asAttribution(),
226
+ grantedAt: p.approvedAt,
227
+ ownedCustody: p.ownedCustody,
228
+ }),
229
+ ];
230
+ });
231
+
232
+ /** Branded internal so ordinary directive codegen never exposes its cryptographic payload. */
233
+ export const approveOwnedDevice = Object.assign(approval, {
234
+ businessBirthInternal: true as const,
235
+ });
236
+
237
+ /** Establish this principal's shared owned-data root before another principal shares to them. */
238
+ const initializeOwnedIdentity = Object.assign(
239
+ directive(OWNED_INITIALIZE_DIRECTIVE)
240
+ .ensures(RelationTuple)
241
+ .payload(z.object({
242
+ newKeySubject: keySubject,
243
+ ownedCustody: z.string().min(1),
244
+ initializedAt: z.string().min(1),
245
+ }))
246
+ .plan((p, ctx) => {
247
+ const me = ctx.principal().asAuthoritySubject<string>(
248
+ "initialize my framework-owned private-data identity",
249
+ );
250
+ const raw = endorsementOf(me)?.principal ?? "";
251
+ return writeTuple({
252
+ object: ownedKeyObject(raw),
253
+ relation: OWNED_KEY_RELATION,
254
+ subject: p.newKeySubject,
255
+ grantedBy: ctx.principal().asAttribution(),
256
+ grantedAt: p.initializedAt,
257
+ ownedCustody: p.ownedCustody,
258
+ });
259
+ }),
260
+ { businessBirthInternal: true as const },
261
+ );
262
+
263
+ function recordScope(aggregate: AggregateHandle, recordId: string): string {
264
+ return `${__qualifyAggregateType(aggregate.id)}:${recordId}#${OWNED_READ_RELATION}`;
265
+ }
266
+
267
+ function recordObject(aggregate: AggregateHandle, recordId: string): string {
268
+ return `${__qualifyAggregateType(aggregate.id)}:${recordId}`;
269
+ }
270
+
271
+ function ownedRecordActionDirectives(aggregate: AggregateHandle) {
272
+ const share = Object.assign(
273
+ directive(ownedRecordActionDirectiveId("share", aggregate.id))
274
+ .ensures(WrappedKey)
275
+ .payload(z.object({
276
+ recordId: z.string().min(1),
277
+ peer: principal,
278
+ epoch: z.number().int().nonnegative(),
279
+ sharedAt: z.string().min(1),
280
+ wraps: z.array(historicWrap),
281
+ }))
282
+ .plan((p, ctx) => [
283
+ ...writeTuple({
284
+ object: recordObject(aggregate, p.recordId),
285
+ relation: OWNED_READ_RELATION,
286
+ subject: p.peer,
287
+ grantedBy: ctx.principal().asAttribution(),
288
+ grantedAt: p.sharedAt,
289
+ }),
290
+ ...p.wraps.flatMap((entry) => writeWrap(recordScope(aggregate, p.recordId), entry.epoch, entry)),
291
+ ]),
292
+ { businessBirthInternal: true as const },
293
+ );
294
+
295
+ const unshare = Object.assign(
296
+ directive(ownedRecordActionDirectiveId("unshare", aggregate.id))
297
+ .ensures(KeyScope)
298
+ .payload(z.object({
299
+ recordId: z.string().min(1),
300
+ peer: principal,
301
+ epoch: z.number().int().positive(),
302
+ changedAt: z.string().min(1),
303
+ wraps: z.array(wrap),
304
+ }))
305
+ .plan((p, ctx) => [
306
+ ...removeTuple({
307
+ object: recordObject(aggregate, p.recordId),
308
+ relation: OWNED_READ_RELATION,
309
+ subject: p.peer,
310
+ grantedBy: ctx.principal().asAttribution(),
311
+ grantedAt: p.changedAt,
312
+ revokedBy: endorsementOf(ctx.principal().asAttribution())?.principal ?? "",
313
+ revokedAt: p.changedAt,
314
+ }),
315
+ ...writeKeyEpoch(recordScope(aggregate, p.recordId), p.epoch, p.changedAt, p.wraps),
316
+ ]),
317
+ { businessBirthInternal: true as const },
318
+ );
319
+
320
+ const ownerField = aggregate.audience?.kind === "owned" ? aggregate.audience.ownerField : "";
321
+ const transfer = Object.assign(
322
+ directive(ownedRecordActionDirectiveId("transfer", aggregate.id))
323
+ .ensures(KeyScope)
324
+ .payload(z.object({
325
+ recordId: z.string().min(1),
326
+ newOwner: principal,
327
+ previousReaders: z.array(principal),
328
+ epoch: z.number().int().positive(),
329
+ changedAt: z.string().min(1),
330
+ historicWraps: z.array(historicWrap),
331
+ }))
332
+ .plan((p, ctx) => {
333
+ const object = recordObject(aggregate, p.recordId);
334
+ const scope = recordScope(aggregate, p.recordId);
335
+ const ops = p.previousReaders
336
+ .filter((reader) => reader !== p.newOwner)
337
+ .flatMap((reader) => removeTuple({
338
+ object,
339
+ relation: OWNED_READ_RELATION,
340
+ subject: reader,
341
+ grantedBy: ctx.principal().asAttribution(),
342
+ grantedAt: p.changedAt,
343
+ revokedBy: endorsementOf(ctx.principal().asAttribution())?.principal ?? "",
344
+ revokedAt: p.changedAt,
345
+ }));
346
+ ops.push(...writeTuple({
347
+ object,
348
+ relation: OWNED_READ_RELATION,
349
+ subject: p.newOwner,
350
+ grantedBy: ctx.principal().asAttribution(),
351
+ grantedAt: p.changedAt,
352
+ }));
353
+ ops.push(set(instance(aggregate, p.recordId), ownerField as never, p.newOwner as never));
354
+ ops.push(...writeKeyEpoch(scope, p.epoch, p.changedAt, []));
355
+ for (const entry of p.historicWraps) ops.push(...writeWrap(scope, entry.epoch, entry));
356
+ return ops;
357
+ }),
358
+ { businessBirthInternal: true as const },
359
+ );
360
+
361
+ return { share, unshare, transfer };
362
+ }
363
+
364
+ const APPROVAL_EXPORT = "__nomos_owned_device_approval_7c1f5e";
365
+ const IDENTITY_EXPORT = "__nomos_owned_identity_relations_7c1f5e";
366
+ const WARRANT_EXPORT = "__nomos_owned_genesis_warrant_7c1f5e";
367
+ const SEED_EXPORT = "__nomos_owned_seed_genesis_7c1f5e";
368
+ const ENROLL_EXPORT = "__nomos_owned_enroll_signer_7c1f5e";
369
+ const INITIALIZE_EXPORT = "__nomos_owned_initialize_identity_7c1f5e";
370
+ const KEY_SCOPE_EXPORT = "__nomos_owned_key_scope_7c1f5e";
371
+ const WRAPPED_KEY_EXPORT = "__nomos_owned_wrapped_key_7c1f5e";
372
+ const KEY_SCOPE_QUERY_EXPORT = "__nomos_owned_key_scope_query_7c1f5e";
373
+ const WRAPS_QUERY_EXPORT = "__nomos_owned_wraps_query_7c1f5e";
374
+ const RECOVERY_POLICY_EXPORT = "__nomos_owned_recovery_policy_7c1f5e";
375
+ const RECOVERY_PROTECT_EXPORT = "__nomos_owned_recovery_protect_7c1f5e";
376
+ const RECOVERY_ACCEPT_EXPORT = "__nomos_owned_recovery_accept_7c1f5e";
377
+ const RECOVERY_ORDER_EXPORT = "__nomos_owned_recovery_order_7c1f5e";
378
+ const RECOVERY_REQUEST_EXPORT = "__nomos_owned_recovery_request_7c1f5e";
379
+ const RECOVERY_COMPLETE_EXPORT = "__nomos_owned_recovery_complete_7c1f5e";
380
+ const RECOVERY_FAIL_EXPORT = "__nomos_owned_recovery_fail_7c1f5e";
381
+ const RECOVERY_BLOCK_EXPORT = "__nomos_owned_recovery_block_7c1f5e";
382
+ const RECOVERY_DEAD_LETTER_EXPORT = "__nomos_owned_recovery_dead_letter_7c1f5e";
383
+ const RECOVERY_QUERY_EXPORT = "__nomos_owned_recovery_query_7c1f5e";
384
+
385
+ const ownedGenesisWarrant = hidden(GenesisWarrant);
386
+ const ownedSeedGenesis = hidden(seedGenesis);
387
+ const ownedEnrollSigner = hidden(enrollSigner);
388
+ const ownedIdentityRelations = hidden(identityRelations);
389
+
390
+ /**
391
+ * The published CLI bundles its compiler while a tenant module imports declarations from the separately
392
+ * installed @githolon/dsl tarball. Object identity therefore cannot identify framework declarations at this
393
+ * seam. The compiler's existing private brand + stable law identity can: ordinary same-named tenant values
394
+ * do not carry the brand and still fail loudly.
395
+ */
396
+ function sameFrameworkDeclaration(existing: unknown, generated: unknown): boolean {
397
+ if (existing === generated) return true;
398
+ if (
399
+ existing === null || typeof existing !== "object"
400
+ || generated === null || typeof generated !== "object"
401
+ || (existing as { businessBirthInternal?: unknown }).businessBirthInternal !== true
402
+ || (generated as { businessBirthInternal?: unknown }).businessBirthInternal !== true
403
+ ) return false;
404
+ const existingId = (existing as { id?: unknown }).id;
405
+ const generatedId = (generated as { id?: unknown }).id;
406
+ if (typeof existingId === "string" || typeof generatedId === "string") return existingId === generatedId;
407
+ if (isRelationTypeDecl(existing) && isRelationTypeDecl(generated)) {
408
+ return existing.type === generated.type && existing.global === generated.global;
409
+ }
410
+ return false;
411
+ }
412
+
413
+ function declarationsOf(mod: Record<string, unknown>): unknown[] {
414
+ return Object.values(mod).flatMap((value) => Array.isArray(value) ? value : [value]);
415
+ }
416
+
417
+ function reservedCollision(concept: string): never {
418
+ throw new Error(
419
+ `Nomos internal concept '${concept}' is already taken by the framework. Sorry mate, Nomos will not ` +
420
+ "silently overwrite or reroute the tenant declaration; give the business concept a different name.",
421
+ );
422
+ }
423
+
424
+ /**
425
+ * Add the approval mechanism only to domains that actually declare owned business state. This runs
426
+ * at the same compile/runtime merge seams as the other framework conveniences, preserving one law.
427
+ */
428
+ export function withOwnedCustodyExports<M extends Record<string, unknown>>(mod: M): Record<string, unknown> {
429
+ const ownedAggregates = Object.values(mod).filter((value): value is AggregateHandle =>
430
+ value !== null
431
+ && typeof value === "object"
432
+ && (value as { __isAggregateHandle?: unknown }).__isAggregateHandle === true
433
+ && (value as { audience?: { kind?: unknown } }).audience?.kind === "owned"
434
+ && ((value as { encryptedFields?: readonly unknown[] }).encryptedFields?.length ?? 0) > 0
435
+ );
436
+ if (ownedAggregates.length === 0) return mod;
437
+ const actions = ownedAggregates.map((aggregate) => ({ aggregate, ...ownedRecordActionDirectives(aggregate) }));
438
+ const recoveryInjections = [
439
+ [RECOVERY_POLICY_EXPORT, hidden(OwnedRecoveryPolicy)],
440
+ [RECOVERY_PROTECT_EXPORT, protectOwnedIdentity],
441
+ [RECOVERY_ACCEPT_EXPORT, acceptOwnedRecovery],
442
+ [RECOVERY_ORDER_EXPORT, hiddenRecoveryLifecycle.aggregate],
443
+ [RECOVERY_REQUEST_EXPORT, hiddenRecoveryLifecycle.obligation],
444
+ [RECOVERY_COMPLETE_EXPORT, hiddenRecoveryLifecycle.complete],
445
+ [RECOVERY_FAIL_EXPORT, hiddenRecoveryLifecycle.fail],
446
+ [RECOVERY_BLOCK_EXPORT, hiddenRecoveryLifecycle.block],
447
+ [RECOVERY_DEAD_LETTER_EXPORT, hiddenRecoveryLifecycle.deadLetter],
448
+ [RECOVERY_QUERY_EXPORT, hidden(ownedRecoveryOrdersByRecoveryPrincipal)],
449
+ ] as const;
450
+
451
+ // These names are deliberately obscure, not magical. A tenant can still reach one accidentally or
452
+ // deliberately, and stable ids can collide under a completely different JS export. Both cases fail
453
+ // loudly: framework injection must never turn the tenant's declaration into some other concept.
454
+ if (Object.prototype.hasOwnProperty.call(mod, APPROVAL_EXPORT)
455
+ && !sameFrameworkDeclaration(mod[APPROVAL_EXPORT], approveOwnedDevice)) {
456
+ reservedCollision(APPROVAL_EXPORT);
457
+ }
458
+ if (Object.prototype.hasOwnProperty.call(mod, IDENTITY_EXPORT)
459
+ && !sameFrameworkDeclaration(mod[IDENTITY_EXPORT], ownedIdentityRelations)) {
460
+ reservedCollision(IDENTITY_EXPORT);
461
+ }
462
+ for (const [key, generated] of recoveryInjections) {
463
+ if (Object.prototype.hasOwnProperty.call(mod, key) && !sameFrameworkDeclaration(mod[key], generated)) {
464
+ reservedCollision(key);
465
+ }
466
+ }
467
+ for (const [key, generated] of [
468
+ [WARRANT_EXPORT, ownedGenesisWarrant],
469
+ [SEED_EXPORT, ownedSeedGenesis],
470
+ [ENROLL_EXPORT, ownedEnrollSigner],
471
+ ] as const) {
472
+ if (Object.prototype.hasOwnProperty.call(mod, key) && !sameFrameworkDeclaration(mod[key], generated)) {
473
+ reservedCollision(key);
474
+ }
475
+ }
476
+ for (const value of declarationsOf(mod)) {
477
+ if (
478
+ !sameFrameworkDeclaration(value, approveOwnedDevice)
479
+ && value !== null
480
+ && typeof value === "object"
481
+ && (value as { id?: unknown }).id === OWNED_DEVICE_APPROVAL_DIRECTIVE
482
+ ) {
483
+ reservedCollision(OWNED_DEVICE_APPROVAL_DIRECTIVE);
484
+ }
485
+ for (const [, generated] of recoveryInjections) {
486
+ if (
487
+ !sameFrameworkDeclaration(value, generated)
488
+ && value !== null
489
+ && typeof value === "object"
490
+ && typeof (generated as { id?: unknown }).id === "string"
491
+ && (value as { id?: unknown }).id === (generated as { id: string }).id
492
+ ) {
493
+ reservedCollision((generated as { id: string }).id);
494
+ }
495
+ }
496
+ for (const [framework, generated] of [
497
+ [GenesisWarrant, ownedGenesisWarrant],
498
+ [seedGenesis, ownedSeedGenesis],
499
+ [enrollSigner, ownedEnrollSigner],
500
+ ] as const) {
501
+ if (
502
+ !sameFrameworkDeclaration(value, framework)
503
+ && !sameFrameworkDeclaration(value, generated)
504
+ && value !== null
505
+ && typeof value === "object"
506
+ && (value as { id?: unknown }).id === framework.id
507
+ ) {
508
+ reservedCollision(framework.id);
509
+ }
510
+ }
511
+ if (
512
+ !sameFrameworkDeclaration(value, ownedIdentityRelations)
513
+ && isRelationTypeDecl(value)
514
+ && value.global === true
515
+ && value.type === identityRelations.type
516
+ ) {
517
+ reservedCollision(`global relation type '${identityRelations.type}'`);
518
+ }
519
+ if (value !== null && typeof value === "object" && typeof (value as { id?: unknown }).id === "string") {
520
+ const id = (value as { id: string }).id;
521
+ const generated = actions.flatMap((entry) => [entry.share, entry.unshare, entry.transfer]);
522
+ if (id.startsWith(OWNED_ACTION_PREFIX) && id.includes(OWNED_ACTION_MARKER)
523
+ && !generated.some((candidate) => sameFrameworkDeclaration(value, candidate))) {
524
+ reservedCollision(id);
525
+ }
526
+ }
527
+ }
528
+ const result: Record<string, unknown> = {
529
+ ...mod,
530
+ [APPROVAL_EXPORT]: approveOwnedDevice,
531
+ [INITIALIZE_EXPORT]: initializeOwnedIdentity,
532
+ [IDENTITY_EXPORT]: ownedIdentityRelations,
533
+ [KEY_SCOPE_EXPORT]: hidden(KeyScope),
534
+ [WRAPPED_KEY_EXPORT]: hidden(WrappedKey),
535
+ [KEY_SCOPE_QUERY_EXPORT]: hidden(keyScopeByScope),
536
+ [WRAPS_QUERY_EXPORT]: hidden(wrappedKeysByRecipient),
537
+ [RECOVERY_POLICY_EXPORT]: hidden(OwnedRecoveryPolicy),
538
+ [RECOVERY_PROTECT_EXPORT]: protectOwnedIdentity,
539
+ [RECOVERY_ACCEPT_EXPORT]: acceptOwnedRecovery,
540
+ [RECOVERY_ORDER_EXPORT]: hiddenRecoveryLifecycle.aggregate,
541
+ [RECOVERY_REQUEST_EXPORT]: hiddenRecoveryLifecycle.obligation,
542
+ [RECOVERY_COMPLETE_EXPORT]: hiddenRecoveryLifecycle.complete,
543
+ [RECOVERY_FAIL_EXPORT]: hiddenRecoveryLifecycle.fail,
544
+ [RECOVERY_BLOCK_EXPORT]: hiddenRecoveryLifecycle.block,
545
+ [RECOVERY_DEAD_LETTER_EXPORT]: hiddenRecoveryLifecycle.deadLetter,
546
+ [RECOVERY_QUERY_EXPORT]: hidden(ownedRecoveryOrdersByRecoveryPrincipal),
547
+ // A local law test starts from an empty root, while production business state is normally born into a
548
+ // warranted child. Keep the same lawful bootstrap machinery available in both cases, but framework-only:
549
+ // the harness can seat its test identity and warrant the root without adding a tenant-facing lane.
550
+ [WARRANT_EXPORT]: ownedGenesisWarrant,
551
+ [SEED_EXPORT]: ownedSeedGenesis,
552
+ [ENROLL_EXPORT]: ownedEnrollSigner,
553
+ };
554
+ for (const { aggregate, share, unshare, transfer } of actions) {
555
+ const suffix = ownedRecordActionDirectiveId("share", aggregate.id).split("_owned_record_")[1]!;
556
+ result[`__nomos_owned_share_${suffix}`] = share;
557
+ result[`__nomos_owned_unshare_${suffix}`] = unshare;
558
+ result[`__nomos_owned_transfer_${suffix}`] = transfer;
559
+ }
560
+ return result;
561
+ }
@@ -191,7 +191,7 @@ export function seedPlatformAdmin(opts: SeedPlatformAdminOptions): PlatformAdmin
191
191
  // GLOBAL (read-projection namespacing opt-out): a dynamically-named framework aggregate —
192
192
  // platform governance law deploys under differing domain keys (root vs each platform), and the
193
193
  // founder singleton's bare identity must stay stable across them.
194
- }, { global: true });
194
+ }, { global: true }).public();
195
195
 
196
196
  /**
197
197
  * GENESIS SEED 1 — stamp the founder singleton at the fixed id. Authored by the platform's birth/genesis
@@ -191,7 +191,7 @@ export const RelationTuple = aggregate("RelationTuple", {
191
191
  object: t.string().merge(Lww),
192
192
  relation: t.string().merge(Lww),
193
193
  subjects: t.map(t.json()).merge(MapOf(Lww)),
194
- });
194
+ }).public();
195
195
  export type RelationTupleHandle = typeof RelationTuple;
196
196
 
197
197
  /** The per-subject leaf the `subjects` map stores (JSON-encoded). Frozen wire shape. */
@@ -215,6 +215,11 @@ export interface RelationSubjectLeaf {
215
215
  * every lane. OMITTED when absent (legacy / unverified enrollment) so existing chains stay byte-identical.
216
216
  */
217
217
  verifiedVia?: string;
218
+ /**
219
+ * Secure-by-default owned-data device handoff. Framework-reserved and omitted from every ordinary
220
+ * relation leaf; the opaque JSON is independently verified by the kernel before it may land.
221
+ */
222
+ ownedCustody?: string;
218
223
  }
219
224
 
220
225
  /** Strip undefined keys so the JSON leaf is byte-stable (no dropped-slot `"k":undefined`). */
@@ -247,6 +252,8 @@ interface WriteTupleInput {
247
252
  validFromEpoch?: number;
248
253
  /** VA-core: the IdP/issuer that vouched for the enrolling principal (signer relations only; omitted otherwise). */
249
254
  verifiedVia?: string;
255
+ /** Framework-internal encrypted owned-data handoff (owned device relation only). */
256
+ ownedCustody?: string;
250
257
  }
251
258
 
252
259
  /** Lower a tuple write to plan ops. The first op MATERIALIZES the (object, relation)
@@ -283,6 +290,7 @@ function lowerTuple(
283
290
  // (these never set) stays byte-identical to a pre-VA leaf (the era rule on the wire shape).
284
291
  validFromEpoch: p.validFromEpoch,
285
292
  verifiedVia: p.verifiedVia,
293
+ ownedCustody: p.ownedCustody,
286
294
  });
287
295
  const subjectEntry = setEntry(rt, "subjects", subjectRaw, JSON.stringify(leaf));
288
296
  return [
@@ -91,7 +91,7 @@ export const RepairRecord = aggregate("RepairRecord", {
91
91
  revertedBy: t.string().merge(Lww),
92
92
  /** Epoch-ms the repair was authored. */
93
93
  revertedAt: t.int().merge(Lww),
94
- });
94
+ }).public();
95
95
 
96
96
  // ── `revert` directive ──────────────────────────────────────────────────────
97
97
 
@@ -100,7 +100,7 @@ export const SIGNER_EPOCH_ID = "signer-epoch:self";
100
100
  export const SignerEpoch = aggregate("SignerEpoch", {
101
101
  scope: t.string().merge(Lww), // singleton key — always "self" (mirrors KeyScope.scope; the query key)
102
102
  currentEpoch: t.int().merge(Lww),
103
- });
103
+ }).public();
104
104
  export type SignerEpochHandle = typeof SignerEpoch;
105
105
 
106
106
  /** The current signer epoch (advisory read; the strict-increment law is the workspaceInvariant below). */
@@ -446,7 +446,7 @@ export const SignerDelegation = aggregate("SignerDelegation", {
446
446
  certJson: t.string().merge(Lww),
447
447
  /** ISO-8601, caller-stamped (determinism — no clock in the plan). */
448
448
  enrolledAt: t.string().merge(Lww),
449
- });
449
+ }).public();
450
450
  export type SignerDelegationHandle = typeof SignerDelegation;
451
451
 
452
452
  /** The deterministic singleton id of a user's signer delegation (the kernel reads THIS id by-user). */
@@ -55,7 +55,7 @@ export const NomosSourceRoster = aggregate("NomosSourceRoster", {
55
55
  rosterJson: t.json().merge(Lww),
56
56
  /** The source's `SignerEpoch` at the attested sync. */
57
57
  currentEpoch: t.int().merge(Lww),
58
- });
58
+ }).public();
59
59
 
60
60
  /** One roster row as the source's `attestorRoster` query answers it. */
61
61
  interface AttestorRosterRow {