@githolon/dsl 0.105.5 → 0.105.7
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 +31 -0
- package/dart/lib/src/runner/node-entry.mjs +159 -32
- package/dart/lib/src/runner/offline-install.mjs +107 -43
- package/dart/lib/src/webview_bridge.dart +135 -18
- package/dart/pubspec.lock +1 -1
- package/dart/pubspec.yaml +2 -2
- package/dart/test/acquire_session_test.dart +37 -0
- package/dart/test/residency_refusal_test.dart +49 -0
- package/dart/test/sealing_client_test.dart +1 -1
- package/dart/test/webview_bridge_test.dart +19 -0
- package/dart_types/CHANGELOG.md +5 -0
- package/dart_types/lib/src/subscriptions.dart +5 -1
- package/dart_types/pubspec.yaml +1 -1
- package/package.json +1 -1
- package/runtime/generated-application-host.js +142 -31
- package/src/aggregate.ts +2 -1
- package/src/birth_meter_ensure.ts +38 -1
- package/src/check_package_main.ts +25 -54
- package/src/codegen_ts.ts +27 -1
- package/src/compat_package_main.ts +36 -30
- package/src/compile_package_main.ts +40 -39
- package/src/decl_shapes.ts +1 -1
- package/src/engine_entry.ts +2 -2
- package/src/evolve_preview.ts +7 -7
- package/src/framework/owned_custody.ts +78 -22
- package/src/identity_model.ts +123 -0
- package/src/identity_usda.ts +548 -0
- package/src/law_graph.ts +21 -12
- package/src/law_identity.ts +15 -0
- package/src/lsp_server.ts +14 -37
- package/src/stable_ids.ts +1 -1
- package/src/stable_id_lockfile.ts +0 -278
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
// Tenant code sees only generated `devices.request()` / `devices.approve(request)`.
|
|
3
3
|
|
|
4
4
|
import { z } from "zod";
|
|
5
|
-
import { OWNED_KEY_RELATION, OWNED_READ_RELATION, aggregate, instance, ownedKeyObject, type AggregateHandle } from "../aggregate.js";
|
|
5
|
+
import { OWNED_KEY_RELATION, OWNED_PRINCIPAL_OBJECT_TYPE, OWNED_READ_RELATION, aggregate, instance, ownedKeyObject, type AggregateHandle } from "../aggregate.js";
|
|
6
6
|
import { Lww } from "../drivers.js";
|
|
7
7
|
import { directive } from "../directive.js";
|
|
8
8
|
import { endorsementOf } from "../principal_evidence.js";
|
|
9
|
-
import { isRelationTypeDecl } from "../relation_schema.js";
|
|
9
|
+
import { isRelationTypeDecl, key, relationType, self, user } from "../relation_schema.js";
|
|
10
10
|
import { t } from "../fields.js";
|
|
11
11
|
import { set, withMarker } from "../ops.js";
|
|
12
12
|
import { query } from "../query.js";
|
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
writeWrap,
|
|
20
20
|
} from "./encryption_scope.js";
|
|
21
21
|
import { GenesisWarrant, seedGenesis } from "./genesis.js";
|
|
22
|
+
import { BirthAttestation, seedBirthAttestation } from "./home.js";
|
|
22
23
|
import { RelationTuple, __qualifyAggregateType, removeTuple, writeTuple } from "./relations.js";
|
|
23
24
|
import { SIGNER_RELATION, enrollSigner, signerObject, identityRelations } from "./signing_identity.js";
|
|
24
25
|
import { impureCapability, impureCapabilityAggregate } from "./impure_capability.js";
|
|
@@ -27,6 +28,10 @@ import { impureCapability, impureCapabilityAggregate } from "./impure_capability
|
|
|
27
28
|
export const OWNED_DEVICE_APPROVAL_DIRECTIVE = "nomos_internal_approve_owned_device_7c1f5e";
|
|
28
29
|
export const OWNED_INITIALIZE_DIRECTIVE = "nomos_internal_initialize_owned_identity_7c1f5e";
|
|
29
30
|
export const OWNED_RECOVERY_RELATION = "nomos_internal_owned_recovery_principal_7c1f5e";
|
|
31
|
+
export const ownedCustodyRelations = relationType(OWNED_PRINCIPAL_OBJECT_TYPE)
|
|
32
|
+
.asGlobal()
|
|
33
|
+
.relation(OWNED_KEY_RELATION, { rewrite: self(), allow: [key()] })
|
|
34
|
+
.relation(OWNED_RECOVERY_RELATION, { rewrite: self(), allow: [user()] });
|
|
30
35
|
export const OWNED_RECOVERY_PROTECT_DIRECTIVE = "nomos_internal_protect_owned_identity_7c1f5e";
|
|
31
36
|
export const OWNED_RECOVERY_REQUEST_DIRECTIVE = "nomos_internal_request_owned_recovery_7c1f5e";
|
|
32
37
|
export const OWNED_RECOVERY_COMPLETE_DIRECTIVE = "nomos_internal_complete_owned_recovery_7c1f5e";
|
|
@@ -109,7 +114,33 @@ const ownedRecoveryLifecycle = impureCapability({
|
|
|
109
114
|
writeReceipt: (id, r) => [
|
|
110
115
|
set(instance(OwnedRecoveryOrder, id), "ownedCustody", r.ownedCustody as string),
|
|
111
116
|
],
|
|
117
|
+
// This is intentionally unaddressed: opening the designated principal's ordinary generated
|
|
118
|
+
// client is the provider. Declaring the capability still matters because every durable Order must
|
|
119
|
+
// have one typed provider contract; the kernel may then retain the sealed obligation locally until
|
|
120
|
+
// that principal opens the workspace, rather than inventing an untyped host work description.
|
|
121
|
+
capability: "nomos.owned-recovery",
|
|
112
122
|
});
|
|
123
|
+
const hiddenRecoveryLifecycle = {
|
|
124
|
+
aggregate: hidden(OwnedRecoveryOrder),
|
|
125
|
+
obligation: hidden(ownedRecoveryLifecycle.obligation),
|
|
126
|
+
complete: hidden(ownedRecoveryLifecycle.complete),
|
|
127
|
+
fail: hidden(ownedRecoveryLifecycle.fail),
|
|
128
|
+
block: hidden(ownedRecoveryLifecycle.block),
|
|
129
|
+
deadLetter: hidden(ownedRecoveryLifecycle.deadLetter),
|
|
130
|
+
};
|
|
131
|
+
const ownedRecoveryCapability = {
|
|
132
|
+
...ownedRecoveryLifecycle,
|
|
133
|
+
// Capability expansion deliberately re-seats the lifecycle quartet after application-language
|
|
134
|
+
// closure. Carry the private declarations there too; pointing back at the raw impure-capability
|
|
135
|
+
// declarations silently reintroduced framework verbs into proof selection and generated names.
|
|
136
|
+
aggregate: hiddenRecoveryLifecycle.aggregate,
|
|
137
|
+
obligation: hiddenRecoveryLifecycle.obligation,
|
|
138
|
+
complete: hiddenRecoveryLifecycle.complete,
|
|
139
|
+
fail: hiddenRecoveryLifecycle.fail,
|
|
140
|
+
block: hiddenRecoveryLifecycle.block,
|
|
141
|
+
deadLetter: hiddenRecoveryLifecycle.deadLetter,
|
|
142
|
+
__isCapabilityDecl: true as const,
|
|
143
|
+
};
|
|
113
144
|
|
|
114
145
|
export const ownedRecoveryOrdersByRecoveryPrincipal = query("nomosOwnedRecoveryOrdersByRecoveryPrincipal")
|
|
115
146
|
.key("recoveryPrincipal")
|
|
@@ -157,7 +188,7 @@ const protectOwnedIdentity = Object.assign(
|
|
|
157
188
|
.alsoEnsures(RelationTuple)
|
|
158
189
|
.alsoEnsures(KeyScope)
|
|
159
190
|
.alsoEnsures(WrappedKey),
|
|
160
|
-
{ businessBirthInternal: true as const },
|
|
191
|
+
{ businessBirthInternal: true as const, ownedAuthoringInternal: true as const },
|
|
161
192
|
);
|
|
162
193
|
|
|
163
194
|
const acceptOwnedRecovery = Object.assign(
|
|
@@ -181,18 +212,9 @@ const acceptOwnedRecovery = Object.assign(
|
|
|
181
212
|
ownedCustody: p.ownedCustody,
|
|
182
213
|
});
|
|
183
214
|
}),
|
|
184
|
-
{ businessBirthInternal: true as const },
|
|
215
|
+
{ businessBirthInternal: true as const, ownedAuthoringInternal: true as const },
|
|
185
216
|
);
|
|
186
217
|
|
|
187
|
-
const hiddenRecoveryLifecycle = {
|
|
188
|
-
aggregate: hidden(OwnedRecoveryOrder),
|
|
189
|
-
obligation: hidden(ownedRecoveryLifecycle.obligation),
|
|
190
|
-
complete: hidden(ownedRecoveryLifecycle.complete),
|
|
191
|
-
fail: hidden(ownedRecoveryLifecycle.fail),
|
|
192
|
-
block: hidden(ownedRecoveryLifecycle.block),
|
|
193
|
-
deadLetter: hidden(ownedRecoveryLifecycle.deadLetter),
|
|
194
|
-
};
|
|
195
|
-
|
|
196
218
|
export type OwnedRecordAction = "share" | "unshare" | "transfer";
|
|
197
219
|
|
|
198
220
|
/** One collision-resistant framework id per aggregate/action. Tenant code never sees these ids. */
|
|
@@ -256,6 +278,7 @@ const approval = directive(OWNED_DEVICE_APPROVAL_DIRECTIVE)
|
|
|
256
278
|
/** Branded internal so ordinary directive codegen never exposes its cryptographic payload. */
|
|
257
279
|
export const approveOwnedDevice = Object.assign(approval, {
|
|
258
280
|
businessBirthInternal: true as const,
|
|
281
|
+
ownedAuthoringInternal: true as const,
|
|
259
282
|
});
|
|
260
283
|
|
|
261
284
|
/** Establish this principal's shared owned-data root before another principal shares to them. */
|
|
@@ -281,7 +304,7 @@ const initializeOwnedIdentity = Object.assign(
|
|
|
281
304
|
ownedCustody: p.ownedCustody,
|
|
282
305
|
});
|
|
283
306
|
}),
|
|
284
|
-
{ businessBirthInternal: true as const },
|
|
307
|
+
{ businessBirthInternal: true as const, ownedAuthoringInternal: true as const },
|
|
285
308
|
);
|
|
286
309
|
|
|
287
310
|
function recordScope(aggregate: AggregateHandle, recordId: string): string {
|
|
@@ -295,7 +318,10 @@ function recordObject(aggregate: AggregateHandle, recordId: string): string {
|
|
|
295
318
|
function ownedRecordActionDirectives(aggregate: AggregateHandle) {
|
|
296
319
|
const share = Object.assign(
|
|
297
320
|
directive(ownedRecordActionDirectiveId("share", aggregate.id))
|
|
298
|
-
.
|
|
321
|
+
// The owned business record is the semantic target. Relation tuples and wraps are framework
|
|
322
|
+
// effects of sharing it; making a wrap the primary target causes authenticated law to describe
|
|
323
|
+
// this as a key action and the kernel correctly refuses `share(record, person)`.
|
|
324
|
+
.mutates(aggregate)
|
|
299
325
|
.payload(z.object({
|
|
300
326
|
recordId: z.string().min(1),
|
|
301
327
|
peer: principal,
|
|
@@ -313,13 +339,14 @@ function ownedRecordActionDirectives(aggregate: AggregateHandle) {
|
|
|
313
339
|
}),
|
|
314
340
|
...p.wraps.flatMap((entry) => writeWrap(recordScope(aggregate, p.recordId), entry.epoch, entry)),
|
|
315
341
|
])
|
|
316
|
-
.alsoEnsures(RelationTuple)
|
|
317
|
-
|
|
342
|
+
.alsoEnsures(RelationTuple)
|
|
343
|
+
.alsoEnsuresMany(WrappedKey),
|
|
344
|
+
{ businessBirthInternal: true as const, ownedAuthoringInternal: true as const },
|
|
318
345
|
);
|
|
319
346
|
|
|
320
347
|
const unshare = Object.assign(
|
|
321
348
|
directive(ownedRecordActionDirectiveId("unshare", aggregate.id))
|
|
322
|
-
.
|
|
349
|
+
.mutates(aggregate)
|
|
323
350
|
.payload(z.object({
|
|
324
351
|
recordId: z.string().min(1),
|
|
325
352
|
peer: principal,
|
|
@@ -340,14 +367,15 @@ function ownedRecordActionDirectives(aggregate: AggregateHandle) {
|
|
|
340
367
|
...writeKeyEpoch(recordScope(aggregate, p.recordId), p.epoch, p.changedAt, p.wraps),
|
|
341
368
|
])
|
|
342
369
|
.alsoEnsures(RelationTuple)
|
|
370
|
+
.alsoEnsures(KeyScope)
|
|
343
371
|
.alsoEnsuresMany(WrappedKey),
|
|
344
|
-
{ businessBirthInternal: true as const },
|
|
372
|
+
{ businessBirthInternal: true as const, ownedAuthoringInternal: true as const },
|
|
345
373
|
);
|
|
346
374
|
|
|
347
375
|
const ownerField = aggregate.audience?.kind === "owned" ? aggregate.audience.ownerField : "";
|
|
348
376
|
const transfer = Object.assign(
|
|
349
377
|
directive(ownedRecordActionDirectiveId("transfer", aggregate.id))
|
|
350
|
-
.
|
|
378
|
+
.mutates(aggregate)
|
|
351
379
|
.payload(z.object({
|
|
352
380
|
recordId: z.string().min(1),
|
|
353
381
|
newOwner: principal,
|
|
@@ -383,9 +411,9 @@ function ownedRecordActionDirectives(aggregate: AggregateHandle) {
|
|
|
383
411
|
return ops;
|
|
384
412
|
})
|
|
385
413
|
.alsoEnsuresMany(RelationTuple)
|
|
386
|
-
.
|
|
414
|
+
.alsoEnsures(KeyScope)
|
|
387
415
|
.alsoEnsuresMany(WrappedKey),
|
|
388
|
-
{ businessBirthInternal: true as const },
|
|
416
|
+
{ businessBirthInternal: true as const, ownedAuthoringInternal: true as const },
|
|
389
417
|
);
|
|
390
418
|
|
|
391
419
|
return { share, unshare, transfer };
|
|
@@ -393,8 +421,11 @@ function ownedRecordActionDirectives(aggregate: AggregateHandle) {
|
|
|
393
421
|
|
|
394
422
|
const APPROVAL_EXPORT = "__nomos_owned_device_approval_7c1f5e";
|
|
395
423
|
const IDENTITY_EXPORT = "__nomos_owned_identity_relations_7c1f5e";
|
|
424
|
+
const CUSTODY_RELATIONS_EXPORT = "__nomos_owned_custody_relations_7c1f5e";
|
|
396
425
|
const WARRANT_EXPORT = "__nomos_owned_genesis_warrant_7c1f5e";
|
|
397
426
|
const SEED_EXPORT = "__nomos_owned_seed_genesis_7c1f5e";
|
|
427
|
+
const BIRTH_ATTESTATION_EXPORT = "__nomos_owned_birth_attestation_7c1f5e";
|
|
428
|
+
const BIRTH_ATTESTATION_SEED_EXPORT = "__nomos_owned_seed_birth_attestation_7c1f5e";
|
|
398
429
|
const ENROLL_EXPORT = "__nomos_owned_enroll_signer_7c1f5e";
|
|
399
430
|
const INITIALIZE_EXPORT = "__nomos_owned_initialize_identity_7c1f5e";
|
|
400
431
|
const KEY_SCOPE_EXPORT = "__nomos_owned_key_scope_7c1f5e";
|
|
@@ -411,11 +442,15 @@ const RECOVERY_FAIL_EXPORT = "__nomos_owned_recovery_fail_7c1f5e";
|
|
|
411
442
|
const RECOVERY_BLOCK_EXPORT = "__nomos_owned_recovery_block_7c1f5e";
|
|
412
443
|
const RECOVERY_DEAD_LETTER_EXPORT = "__nomos_owned_recovery_dead_letter_7c1f5e";
|
|
413
444
|
const RECOVERY_QUERY_EXPORT = "__nomos_owned_recovery_query_7c1f5e";
|
|
445
|
+
const RECOVERY_CAPABILITY_EXPORT = "__nomos_owned_recovery_capability_7c1f5e";
|
|
414
446
|
|
|
415
447
|
const ownedGenesisWarrant = hidden(GenesisWarrant);
|
|
416
448
|
const ownedSeedGenesis = hidden(seedGenesis);
|
|
449
|
+
const ownedBirthAttestation = hidden(BirthAttestation);
|
|
450
|
+
const ownedSeedBirthAttestation = hidden(seedBirthAttestation);
|
|
417
451
|
const ownedEnrollSigner = hidden(enrollSigner);
|
|
418
452
|
const ownedIdentityRelations = hidden(identityRelations);
|
|
453
|
+
const ownedCustodyRelationLaw = hidden(ownedCustodyRelations);
|
|
419
454
|
|
|
420
455
|
/**
|
|
421
456
|
* The published CLI bundles its compiler while a tenant module imports declarations from the separately
|
|
@@ -476,6 +511,7 @@ export function withOwnedCustodyExports<M extends Record<string, unknown>>(mod:
|
|
|
476
511
|
[RECOVERY_BLOCK_EXPORT, hiddenRecoveryLifecycle.block],
|
|
477
512
|
[RECOVERY_DEAD_LETTER_EXPORT, hiddenRecoveryLifecycle.deadLetter],
|
|
478
513
|
[RECOVERY_QUERY_EXPORT, hidden(ownedRecoveryOrdersByRecoveryPrincipal)],
|
|
514
|
+
[RECOVERY_CAPABILITY_EXPORT, hidden(ownedRecoveryCapability)],
|
|
479
515
|
] as const;
|
|
480
516
|
|
|
481
517
|
// These names are deliberately obscure, not magical. A tenant can still reach one accidentally or
|
|
@@ -489,6 +525,10 @@ export function withOwnedCustodyExports<M extends Record<string, unknown>>(mod:
|
|
|
489
525
|
&& !sameFrameworkDeclaration(mod[IDENTITY_EXPORT], ownedIdentityRelations)) {
|
|
490
526
|
reservedCollision(IDENTITY_EXPORT);
|
|
491
527
|
}
|
|
528
|
+
if (Object.prototype.hasOwnProperty.call(mod, CUSTODY_RELATIONS_EXPORT)
|
|
529
|
+
&& !sameFrameworkDeclaration(mod[CUSTODY_RELATIONS_EXPORT], ownedCustodyRelationLaw)) {
|
|
530
|
+
reservedCollision(CUSTODY_RELATIONS_EXPORT);
|
|
531
|
+
}
|
|
492
532
|
for (const [key, generated] of recoveryInjections) {
|
|
493
533
|
if (Object.prototype.hasOwnProperty.call(mod, key) && !sameFrameworkDeclaration(mod[key], generated)) {
|
|
494
534
|
reservedCollision(key);
|
|
@@ -497,6 +537,8 @@ export function withOwnedCustodyExports<M extends Record<string, unknown>>(mod:
|
|
|
497
537
|
for (const [key, generated] of [
|
|
498
538
|
[WARRANT_EXPORT, ownedGenesisWarrant],
|
|
499
539
|
[SEED_EXPORT, ownedSeedGenesis],
|
|
540
|
+
[BIRTH_ATTESTATION_EXPORT, ownedBirthAttestation],
|
|
541
|
+
[BIRTH_ATTESTATION_SEED_EXPORT, ownedSeedBirthAttestation],
|
|
500
542
|
[ENROLL_EXPORT, ownedEnrollSigner],
|
|
501
543
|
] as const) {
|
|
502
544
|
if (Object.prototype.hasOwnProperty.call(mod, key) && !sameFrameworkDeclaration(mod[key], generated)) {
|
|
@@ -526,6 +568,8 @@ export function withOwnedCustodyExports<M extends Record<string, unknown>>(mod:
|
|
|
526
568
|
for (const [framework, generated] of [
|
|
527
569
|
[GenesisWarrant, ownedGenesisWarrant],
|
|
528
570
|
[seedGenesis, ownedSeedGenesis],
|
|
571
|
+
[BirthAttestation, ownedBirthAttestation],
|
|
572
|
+
[seedBirthAttestation, ownedSeedBirthAttestation],
|
|
529
573
|
[enrollSigner, ownedEnrollSigner],
|
|
530
574
|
] as const) {
|
|
531
575
|
if (
|
|
@@ -546,6 +590,14 @@ export function withOwnedCustodyExports<M extends Record<string, unknown>>(mod:
|
|
|
546
590
|
) {
|
|
547
591
|
reservedCollision(`global relation type '${identityRelations.type}'`);
|
|
548
592
|
}
|
|
593
|
+
if (
|
|
594
|
+
!sameFrameworkDeclaration(value, ownedCustodyRelationLaw)
|
|
595
|
+
&& isRelationTypeDecl(value)
|
|
596
|
+
&& value.global === true
|
|
597
|
+
&& value.type === ownedCustodyRelations.type
|
|
598
|
+
) {
|
|
599
|
+
reservedCollision(`global relation type '${ownedCustodyRelations.type}'`);
|
|
600
|
+
}
|
|
549
601
|
if (value !== null && typeof value === "object" && typeof (value as { id?: unknown }).id === "string") {
|
|
550
602
|
const id = (value as { id: string }).id;
|
|
551
603
|
const generated = actions.flatMap((entry) => [entry.share, entry.unshare, entry.transfer]);
|
|
@@ -560,6 +612,7 @@ export function withOwnedCustodyExports<M extends Record<string, unknown>>(mod:
|
|
|
560
612
|
[APPROVAL_EXPORT]: approveOwnedDevice,
|
|
561
613
|
[INITIALIZE_EXPORT]: initializeOwnedIdentity,
|
|
562
614
|
[IDENTITY_EXPORT]: ownedIdentityRelations,
|
|
615
|
+
[CUSTODY_RELATIONS_EXPORT]: ownedCustodyRelationLaw,
|
|
563
616
|
[KEY_SCOPE_EXPORT]: hidden(KeyScope),
|
|
564
617
|
[WRAPPED_KEY_EXPORT]: hidden(WrappedKey),
|
|
565
618
|
[KEY_SCOPE_QUERY_EXPORT]: hidden(keyScopeByScope),
|
|
@@ -574,11 +627,14 @@ export function withOwnedCustodyExports<M extends Record<string, unknown>>(mod:
|
|
|
574
627
|
[RECOVERY_BLOCK_EXPORT]: hiddenRecoveryLifecycle.block,
|
|
575
628
|
[RECOVERY_DEAD_LETTER_EXPORT]: hiddenRecoveryLifecycle.deadLetter,
|
|
576
629
|
[RECOVERY_QUERY_EXPORT]: hidden(ownedRecoveryOrdersByRecoveryPrincipal),
|
|
630
|
+
[RECOVERY_CAPABILITY_EXPORT]: hidden(ownedRecoveryCapability),
|
|
577
631
|
// A local law test starts from an empty root, while production business state is normally born into a
|
|
578
632
|
// warranted child. Keep the same lawful bootstrap machinery available in both cases, but framework-only:
|
|
579
633
|
// the harness can seat its test identity and warrant the root without adding a tenant-facing lane.
|
|
580
634
|
[WARRANT_EXPORT]: ownedGenesisWarrant,
|
|
581
635
|
[SEED_EXPORT]: ownedSeedGenesis,
|
|
636
|
+
[BIRTH_ATTESTATION_EXPORT]: ownedBirthAttestation,
|
|
637
|
+
[BIRTH_ATTESTATION_SEED_EXPORT]: ownedSeedBirthAttestation,
|
|
582
638
|
[ENROLL_EXPORT]: ownedEnrollSigner,
|
|
583
639
|
};
|
|
584
640
|
for (const { aggregate, share, unshare, transfer } of actions) {
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import type { StableIds } from "./stable_ids_types.js";
|
|
2
|
+
import type { WireSchema } from "./wire.js";
|
|
3
|
+
import { mintGlobalAggregateSid } from "./stable_ids.js";
|
|
4
|
+
import {
|
|
5
|
+
LAW_FRAMEWORK_PARENT,
|
|
6
|
+
emptyLawIdentityRegistry,
|
|
7
|
+
lawEntityKey,
|
|
8
|
+
validateLawIdentityRegistry,
|
|
9
|
+
type LawIdentityEntity,
|
|
10
|
+
type LawIdentityRegistry,
|
|
11
|
+
} from "./law_identity.js";
|
|
12
|
+
|
|
13
|
+
export interface CompilerIdentityDomain {
|
|
14
|
+
stableIds: StableIds;
|
|
15
|
+
schemas?: Record<string, WireSchema>;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** The materialized lock shape consumed inside the compiler and authoring tools. */
|
|
19
|
+
export interface CompilerIdentityModel {
|
|
20
|
+
v: 1;
|
|
21
|
+
domains: Record<string, CompilerIdentityDomain>;
|
|
22
|
+
/** Compiler-owned entity identity, including retirements. Semantics remain exclusively in USDA. */
|
|
23
|
+
identity?: LawIdentityRegistry;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Derive the compiler entity registry from domain/aggregate/field SIDs when a first-generation
|
|
28
|
+
* identity layer has no explicit registry yet. Once present, that registry retains every retirement.
|
|
29
|
+
*/
|
|
30
|
+
export function identityRegistryFromModel(
|
|
31
|
+
model: CompilerIdentityModel | undefined,
|
|
32
|
+
): LawIdentityRegistry {
|
|
33
|
+
if (model?.identity !== undefined) {
|
|
34
|
+
validateLawIdentityRegistry(model.identity);
|
|
35
|
+
return model.identity;
|
|
36
|
+
}
|
|
37
|
+
if (model === undefined) return emptyLawIdentityRegistry();
|
|
38
|
+
|
|
39
|
+
const entities: Record<string, LawIdentityEntity> = {};
|
|
40
|
+
const aggregateOccurrences = new Map<string, number>();
|
|
41
|
+
for (const domain of Object.values(model.domains)) {
|
|
42
|
+
for (const aggregate of Object.values(domain.stableIds.aggregates)) {
|
|
43
|
+
aggregateOccurrences.set(aggregate.sid, (aggregateOccurrences.get(aggregate.sid) ?? 0) + 1);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
for (const [domainName, domain] of Object.entries(model.domains).sort(([a], [b]) =>
|
|
48
|
+
compareText(a, b),
|
|
49
|
+
)) {
|
|
50
|
+
const domainKey = importIdentityEntity(entities, {
|
|
51
|
+
kind: "domain",
|
|
52
|
+
sid: domain.stableIds.domain,
|
|
53
|
+
label: domainName,
|
|
54
|
+
generation: 0,
|
|
55
|
+
status: "active",
|
|
56
|
+
});
|
|
57
|
+
for (const [aggregateName, aggregate] of Object.entries(domain.stableIds.aggregates).sort(
|
|
58
|
+
([a], [b]) => compareText(a, b),
|
|
59
|
+
)) {
|
|
60
|
+
const parent =
|
|
61
|
+
(aggregateOccurrences.get(aggregate.sid) ?? 0) > 1 ||
|
|
62
|
+
aggregate.sid === mintGlobalAggregateSid(aggregateName)
|
|
63
|
+
? LAW_FRAMEWORK_PARENT
|
|
64
|
+
: domainKey;
|
|
65
|
+
const aggregateKey = importIdentityEntity(entities, {
|
|
66
|
+
kind: "aggregate",
|
|
67
|
+
sid: aggregate.sid,
|
|
68
|
+
parent,
|
|
69
|
+
label: aggregateName,
|
|
70
|
+
...(aggregate.was === undefined ? {} : { priorLabels: aggregate.was }),
|
|
71
|
+
generation: 0,
|
|
72
|
+
status: "active",
|
|
73
|
+
});
|
|
74
|
+
for (const [fieldName, field] of Object.entries(aggregate.fields).sort(([a], [b]) =>
|
|
75
|
+
compareText(a, b),
|
|
76
|
+
)) {
|
|
77
|
+
importIdentityEntity(entities, {
|
|
78
|
+
kind: "field",
|
|
79
|
+
sid: field.sid,
|
|
80
|
+
parent: aggregateKey,
|
|
81
|
+
label: fieldName,
|
|
82
|
+
...(field.was === undefined ? {} : { priorLabels: field.was }),
|
|
83
|
+
generation: 0,
|
|
84
|
+
status: "active",
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const registry: LawIdentityRegistry = {
|
|
91
|
+
v: 1,
|
|
92
|
+
entities: Object.fromEntries(Object.entries(entities).sort(([a], [b]) => compareText(a, b))),
|
|
93
|
+
};
|
|
94
|
+
validateLawIdentityRegistry(registry);
|
|
95
|
+
return registry;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function importIdentityEntity(
|
|
99
|
+
entities: Record<string, LawIdentityEntity>,
|
|
100
|
+
entity: LawIdentityEntity,
|
|
101
|
+
): string {
|
|
102
|
+
const key = lawEntityKey(entity.kind, entity.sid);
|
|
103
|
+
const prior = entities[key];
|
|
104
|
+
if (prior === undefined) {
|
|
105
|
+
entities[key] = entity;
|
|
106
|
+
return key;
|
|
107
|
+
}
|
|
108
|
+
if (
|
|
109
|
+
prior.kind !== entity.kind ||
|
|
110
|
+
prior.parent !== entity.parent ||
|
|
111
|
+
prior.label !== entity.label ||
|
|
112
|
+
JSON.stringify(prior.priorLabels ?? []) !== JSON.stringify(entity.priorLabels ?? [])
|
|
113
|
+
) {
|
|
114
|
+
throw new Error(
|
|
115
|
+
`NMS-ID-002 legacy SID ${key} aliases incompatible ${prior.kind} '${prior.label}' and ${entity.kind} '${entity.label}'`,
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
return key;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function compareText(a: string, b: string): number {
|
|
122
|
+
return a < b ? -1 : a > b ? 1 : 0;
|
|
123
|
+
}
|