@githolon/dsl 0.113.4 → 0.115.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.
- package/assets/operation-contract.wasm +0 -0
- package/compile_package.mjs +23 -3
- package/compile_proof.d.mts +7 -0
- package/compile_proof.mjs +38 -3
- package/dart/CHANGELOG.md +60 -0
- package/dart/lib/src/application_framework_calls.dart +16 -6
- package/dart/lib/src/console_enrolment.dart +7 -0
- package/dart/lib/src/console_http_io.dart +22 -15
- package/dart/lib/src/dev_cloud.dart +62 -0
- package/dart/lib/src/generated_call.dart +14 -3
- package/dart/lib/src/nms_generated_runtime.dart +7 -0
- package/dart/lib/src/node_runner_transport.dart +77 -8
- package/dart/lib/src/nomos_trace.dart +107 -0
- package/dart/lib/src/operation_receipts.dart +74 -7
- package/dart/lib/src/reception/dart_reception.dart +174 -17
- package/dart/lib/src/rows_watch.dart +390 -0
- package/dart/lib/src/runner/node-entry.mjs +7678 -2860
- package/dart/lib/src/runtime_v2_transport.dart +3 -0
- package/dart/lib/src/sync_status_projection.dart +94 -1
- package/dart/lib/src/sync_status_reducer.dart +5 -2
- package/dart/lib/src/test_harness.dart +107 -41
- package/dart/lib/src/testing/test_peer.dart +107 -99
- package/dart/lib/src/testing/test_peer_host.dart +305 -0
- package/dart/lib/src/typed_watch_registry.dart +26 -3
- package/dart/pubspec.lock +1 -1
- package/dart/pubspec.yaml +2 -2
- package/dart/test/background_model_adoption_test.dart +1 -0
- package/dart/test/console_enrolment_test.dart +47 -0
- package/dart/test/custodian_warming_test.dart +105 -0
- package/dart/test/fixtures/offer_watch_order_peer.dart +74 -0
- package/dart/test/kernel_author_after_read_test.dart +6 -3
- package/dart/test/kernel_fence_test.dart +4 -1
- package/dart/test/model_adoption_test.dart +66 -0
- package/dart/test/nomos_trace_test.dart +91 -0
- package/dart/test/own_offer_watch_order_test.dart +90 -0
- package/dart/test/receiving_group_open_test.dart +4 -8
- package/dart/test/replica_readiness_test.dart +14 -4
- package/dart/test/rows_watch_test.dart +331 -0
- package/dart/test/runtime_projections_test.dart +41 -0
- package/dart/test/test_peer_host_selection_test.dart +49 -0
- package/dart_types/CHANGELOG.md +39 -0
- package/dart_types/lib/src/approved_model.dart +100 -6
- package/dart_types/lib/src/proto/nomos/kernel/v1/kernel.pb.dart +1063 -3
- package/dart_types/lib/src/proto/nomos/kernel/v1/kernel.pbenum.dart +89 -0
- package/dart_types/lib/src/proto/nomos/runtime/v2/application_desk.pb.dart +14 -0
- package/dart_types/lib/src/proto/nomos/runtime/v2/correspondence_desk.pb.dart +2272 -130
- package/dart_types/lib/src/proto/nomos/runtime/v2/correspondence_desk.pbenum.dart +65 -0
- package/dart_types/lib/src/proto/nomos/runtime/v2/model_adoption_desk.pb.dart +118 -2
- package/dart_types/lib/src/proto/nomos/runtime/v2/observability_desk.pb.dart +278 -9
- package/dart_types/lib/src/proto/nomos/runtime/v2/receiving_group_desk.pb.dart +7 -85
- package/dart_types/lib/src/proto/nomos/runtime/v2/records_desk.pb.dart +16 -0
- package/dart_types/lib/src/proto/nomos/runtime/v2/residency_desk.pb.dart +35 -0
- package/dart_types/lib/src/proto/nomos/runtime/v2/runtime.pb.dart +43 -2
- package/dart_types/pubspec.yaml +1 -1
- package/package.json +6 -2
- package/src/aggregate.ts +93 -0
- package/src/anonymous_value_interning.ts +205 -0
- package/src/baseline.ts +27 -0
- package/src/baseline_export.ts +432 -0
- package/src/baseline_import.ts +312 -0
- package/src/baseline_usda.ts +186 -0
- package/src/birth_key_rule.ts +472 -0
- package/src/births.ts +207 -24
- package/src/build_provenance.ts +17 -2
- package/src/business_birth.ts +50 -4
- package/src/check_package_main.ts +50 -6
- package/src/codegen_call_proto.ts +5 -18
- package/src/codegen_dart.ts +95 -3
- package/src/codegen_dart_client.ts +73 -15
- package/src/codegen_dart_facade.ts +58 -35
- package/src/codegen_dart_framework_calls.ts +32 -10
- package/src/codegen_dart_scenario.ts +7 -0
- package/src/codegen_proof.ts +5 -2
- package/src/codegen_result_proto.ts +8 -6
- package/src/codegen_result_usda.ts +4 -0
- package/src/codegen_ts.ts +135 -34
- package/src/compatibility_graph.ts +41 -4
- package/src/compatibility_usda.ts +11 -1
- package/src/compile_package_main.ts +272 -105
- package/src/composed_wire_identity.ts +24 -0
- package/src/directive.ts +6 -0
- package/src/domain_requirements.ts +75 -0
- package/src/engine_entry.ts +4 -48
- package/src/engine_javascript.ts +12 -6
- package/src/fields.ts +109 -0
- package/src/identity_usda.ts +77 -51
- package/src/index.ts +25 -1
- package/src/intent.ts +5 -0
- package/src/law_graph.ts +142 -1
- package/src/law_graph_usda.ts +25 -141
- package/src/law_layer_prims.ts +199 -0
- package/src/law_unit_selection.ts +45 -0
- package/src/law_unit_set_identity.ts +44 -0
- package/src/law_version.ts +107 -0
- package/src/package_config.ts +44 -0
- package/src/package_dependencies.ts +8 -0
- package/src/query/compile.ts +0 -0
- package/src/release_inspection.ts +24 -0
- package/src/release_publication.ts +298 -0
- package/src/value_object.ts +6 -3
- package/src/wire.ts +2 -0
- package/src/wire_paths.ts +17 -0
- package/src/workspace_routing.ts +1 -1
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { admitsNull, isGitObjectIdSchema, isSha256DigestSchema, type Field } from "./fields.js";
|
|
2
|
+
import { brandedIdMetaOf } from "./value_object.js";
|
|
2
3
|
import type {
|
|
3
4
|
LawAnonymousValueFieldNode,
|
|
4
5
|
LawAnonymousValueTypeNode,
|
|
@@ -125,12 +126,13 @@ function authoredFieldValue(
|
|
|
125
126
|
return `foreignRef<${target}>(${holon}, ${JSON.stringify(field.foreignRef.targetAggregateId)}, ${selected}, ${foreignWorkspace})`;
|
|
126
127
|
}
|
|
127
128
|
switch (field.kind) {
|
|
128
|
-
case "string":
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
129
|
+
case "string": {
|
|
130
|
+
if (isSha256DigestSchema(field.zod)) return `sha256Digest(${selected})`;
|
|
131
|
+
if (isGitObjectIdSchema(field.zod)) return `gitObjectId(${selected})`;
|
|
132
|
+
// A typed id field (`t.brandedId(Name)` / `t.uuid()`): the wire string IS the id (nominal only).
|
|
133
|
+
const brand = brandedIdMetaOf(field.zod as never);
|
|
134
|
+
return brand === undefined ? selected : `(${selected} as ${brand})`;
|
|
135
|
+
}
|
|
134
136
|
case "position": return selected;
|
|
135
137
|
case "ref":
|
|
136
138
|
return field.foreignRef === undefined &&
|
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
zodKind,
|
|
16
16
|
zodObjectShape,
|
|
17
17
|
} from "./zod_introspection.js";
|
|
18
|
+
import { brandedIdMetaOf } from "./value_object.js";
|
|
18
19
|
|
|
19
20
|
export interface TsResultField {
|
|
20
21
|
readonly name: string;
|
|
@@ -391,6 +392,9 @@ const decode = (field: Field, expression: string): string => {
|
|
|
391
392
|
? `sha256Digest(_nmsUsdaStringValue(${expression}))`
|
|
392
393
|
: isGitObjectIdSchema(field.zod)
|
|
393
394
|
? `gitObjectId(_nmsUsdaStringValue(${expression}))`
|
|
395
|
+
: brandedIdMetaOf(field.zod as z.ZodTypeAny) !== undefined
|
|
396
|
+
// A typed id field: the wire string IS the id; the brand is nominal only.
|
|
397
|
+
? `(_nmsUsdaStringValue(${expression}) as ${brandedIdMetaOf(field.zod as z.ZodTypeAny)})`
|
|
394
398
|
: `_nmsUsdaStringValue(${expression})`;
|
|
395
399
|
case "position":
|
|
396
400
|
return `_nmsUsdaStringValue(${expression})`;
|
package/src/codegen_ts.ts
CHANGED
|
@@ -35,6 +35,7 @@ import { refusalContracts, tsRefusalDecoder, tsRefusalDecoders } from "./codegen
|
|
|
35
35
|
* Like every emitter here: NO FALLBACK. An unsupported zod shape throws at compile
|
|
36
36
|
* time, never degrades to `any` silently.
|
|
37
37
|
*/
|
|
38
|
+
import { assertDomainRequirements, type DomainUnitRequirement } from "./domain_requirements.js";
|
|
38
39
|
import type { z } from "zod";
|
|
39
40
|
import { generateTsWorkspaceAccessRead } from "./codegen_workspace_access.js";
|
|
40
41
|
|
|
@@ -45,6 +46,7 @@ import {
|
|
|
45
46
|
OWNED_PREMINTED_RECORDS_PAYLOAD_FIELD,
|
|
46
47
|
type AggregateHandle,
|
|
47
48
|
} from "./aggregate.js";
|
|
49
|
+
import { BUSINESS_BIRTH_KEY_PAYLOAD_FIELD } from "./births.js";
|
|
48
50
|
import {
|
|
49
51
|
isGitObjectIdSchema,
|
|
50
52
|
isSha256DigestSchema,
|
|
@@ -86,6 +88,7 @@ import {
|
|
|
86
88
|
collectValueObjectDescriptors,
|
|
87
89
|
idOfMetaOf,
|
|
88
90
|
idOfTargetKindOf,
|
|
91
|
+
brandedIdMetaOf,
|
|
89
92
|
refOfMetaOf,
|
|
90
93
|
versionedRefOfMetaOf,
|
|
91
94
|
voMetaOf,
|
|
@@ -230,6 +233,13 @@ export const tsApplicationName = (packageName: string): string =>
|
|
|
230
233
|
export const tsHashConstName = (packageName: string): string =>
|
|
231
234
|
`${upperSnake(packageName)}_DOMAIN_HASH`;
|
|
232
235
|
|
|
236
|
+
/** `tool-library` → `TOOL_LIBRARY_DOMAIN_REQUIREMENTS` — the per-domain unit requirements const (CAP-666).
|
|
237
|
+
* Module-private: the sealed Reception exports only the receiver, the definition, the value helpers and
|
|
238
|
+
* the `<domain>Client` factories, so the table reaches tenants as `<Name>ApplicationDefinition
|
|
239
|
+
* .compatibility.domainRequirements`, mirroring Dart's `frameworkCalls.domainRequirements`. */
|
|
240
|
+
export const tsDomainRequirementsConstName = (packageName: string): string =>
|
|
241
|
+
`${upperSnake(packageName)}_DOMAIN_REQUIREMENTS`;
|
|
242
|
+
|
|
233
243
|
/** `tool-library` → `TOOL_LIBRARY_INTERFACE` — the baked structural-interface const (#72). */
|
|
234
244
|
export const interfaceConstName = (packageName: string): string =>
|
|
235
245
|
`${upperSnake(packageName)}_COMPATIBILITY_MATRIX_USDA`;
|
|
@@ -431,6 +441,18 @@ function mintLazyTypeName(reg: LazyTypeRegistry, path: string): string {
|
|
|
431
441
|
* (never `JsonValue`) — see {@link LazyTypeRegistry}. `reg` is absent only for standalone callers
|
|
432
442
|
* (e.g. a query return) that can't host a named decl; a recursive lazy there is the one place the
|
|
433
443
|
* `JsonValue` safety valve applies (query returns are read models, never recursive in practice). */
|
|
444
|
+
/** BRANDED-ID ALIASES REACHED WHILE GENERATING ONE CLIENT. {@link generateTsClient} opens the sink and
|
|
445
|
+
* emits one `export type <Brand> = string & { readonly __brand }` per name it collected, so every
|
|
446
|
+
* `brandedId(...)` / `t.brandedId(...)` / `t.uuid()` value is typed by its brand in payloads AND read models.
|
|
447
|
+
* A standalone caller with no open sink keeps the plain `string` (it has nowhere to declare an alias). */
|
|
448
|
+
let brandedIdSink: Set<string> | undefined;
|
|
449
|
+
|
|
450
|
+
function brandedIdTsType(brand: string): string {
|
|
451
|
+
if (brandedIdSink === undefined) return "string";
|
|
452
|
+
brandedIdSink.add(brand);
|
|
453
|
+
return brand;
|
|
454
|
+
}
|
|
455
|
+
|
|
434
456
|
function tsTypeOfZod(
|
|
435
457
|
zt: z.ZodTypeAny,
|
|
436
458
|
indent: string,
|
|
@@ -468,6 +490,8 @@ function tsTypeOfZod(
|
|
|
468
490
|
: aggregateType;
|
|
469
491
|
return `NomosRef<${target}>`;
|
|
470
492
|
}
|
|
493
|
+
const brand = brandedIdMetaOf(zt);
|
|
494
|
+
if (brand !== undefined && zodKind(zt) === "string") return brandedIdTsType(brand);
|
|
471
495
|
// A DECLARED VALUE OBJECT keeps its NAME (vo_first_class.md): the payload
|
|
472
496
|
// references the same exported interface the read model uses — one name, both directions —
|
|
473
497
|
// instead of an anonymous inline literal type. Checked on the BARE object only: optional/
|
|
@@ -882,12 +906,15 @@ export function tsTypeOfField(field: Field): string {
|
|
|
882
906
|
|
|
883
907
|
function tsBaseTypeOfField(field: Field): string {
|
|
884
908
|
switch (field.kind) {
|
|
885
|
-
case "string":
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
909
|
+
case "string": {
|
|
910
|
+
const schema = field.zod as z.ZodTypeAny;
|
|
911
|
+
if (isSha256DigestSchema(schema)) return "Sha256Digest";
|
|
912
|
+
if (isGitObjectIdSchema(schema)) return "GitObjectId";
|
|
913
|
+
// TYPED IDS on aggregate fields (`t.brandedId(Name)` / `t.uuid()`): the read model types them exactly as
|
|
914
|
+
// a payload of the same schema — never a bare string.
|
|
915
|
+
const brand = brandedIdMetaOf(schema);
|
|
916
|
+
return brand !== undefined ? brandedIdTsType(brand) : "string";
|
|
917
|
+
}
|
|
891
918
|
case "int":
|
|
892
919
|
return "number";
|
|
893
920
|
// STRUCTURAL CRDT scalars (read-collapsed): a counter reads as its net number, a position as its
|
|
@@ -1128,8 +1155,13 @@ function aggregatesOf(mod: Record<string, unknown>): AggregateHandle[] {
|
|
|
1128
1155
|
export interface TsClientOptions {
|
|
1129
1156
|
/** The package name (`nomos.project.ts` `name`) — used for the hash constant. */
|
|
1130
1157
|
readonly packageName: string;
|
|
1131
|
-
/**
|
|
1158
|
+
/** THE LAW this client is generated against: the unit set identity of `build/<name>.units` (CAP-773,
|
|
1159
|
+
* law_unit_set_identity.ts). Never the package closure, and never sent to the kernel with an offer. */
|
|
1132
1160
|
readonly domainHash: string;
|
|
1161
|
+
/** THE PER-DOMAIN REQUIREMENTS (CAP-666): per application domain key, its unit hash and its generated
|
|
1162
|
+
* requirement identity. Carried by the definition's `compatibility.domainRequirements` only when present
|
|
1163
|
+
* (the era rule). */
|
|
1164
|
+
readonly domainRequirements?: readonly DomainUnitRequirement[];
|
|
1133
1165
|
/** Sibling lossless USDZ transport carried by the generated client when its signed-in workspace
|
|
1134
1166
|
* still serves the predecessor model. The archive is never canonical state and is not embedded. */
|
|
1135
1167
|
readonly lawAssetName?: string;
|
|
@@ -1420,6 +1452,20 @@ function zodUsesGitObjectId(
|
|
|
1420
1452
|
export function generateTsClient(
|
|
1421
1453
|
modules: readonly DomainModule[],
|
|
1422
1454
|
opts: TsClientOptions
|
|
1455
|
+
): string {
|
|
1456
|
+
const outerSink = brandedIdSink;
|
|
1457
|
+
brandedIdSink = new Set<string>();
|
|
1458
|
+
try {
|
|
1459
|
+
return generateTsClientBody(modules, opts, brandedIdSink);
|
|
1460
|
+
} finally {
|
|
1461
|
+
brandedIdSink = outerSink;
|
|
1462
|
+
}
|
|
1463
|
+
}
|
|
1464
|
+
|
|
1465
|
+
function generateTsClientBody(
|
|
1466
|
+
modules: readonly DomainModule[],
|
|
1467
|
+
opts: TsClientOptions,
|
|
1468
|
+
brandSink: Set<string>,
|
|
1423
1469
|
): string {
|
|
1424
1470
|
if (opts.callUsda === undefined) {
|
|
1425
1471
|
throw new Error(
|
|
@@ -1450,6 +1496,8 @@ export function generateTsClient(
|
|
|
1450
1496
|
)
|
|
1451
1497
|
);
|
|
1452
1498
|
const topologyModules = modules;
|
|
1499
|
+
// Branded-id aliases already declared (by the value-object block); the rest are declared at the end.
|
|
1500
|
+
const emittedBrands = new Set<string>();
|
|
1453
1501
|
// A public business birth can target a deliberately hidden root aggregate. Its private binder still
|
|
1454
1502
|
// needs a nominal type, but unrelated framework records must remain absent even from generated source.
|
|
1455
1503
|
const publicBusinessAggregateIds = new Set(
|
|
@@ -1588,6 +1636,8 @@ export function generateTsClient(
|
|
|
1588
1636
|
opts.interface as unknown as Record<string, DomainInterface>
|
|
1589
1637
|
);
|
|
1590
1638
|
const hashConst = tsHashConstName(opts.packageName);
|
|
1639
|
+
const domainRequirements = [...(opts.domainRequirements ?? [])].sort((a, b) => a.domainKey < b.domainKey ? -1 : a.domainKey > b.domainKey ? 1 : 0);
|
|
1640
|
+
assertDomainRequirements(domainRequirements);
|
|
1591
1641
|
// THE E2E-SEALING MAP (the sealing client): non-null iff any field is `.encrypted()`. When null,
|
|
1592
1642
|
// NOTHING below emits — the generated client is byte-identical to a pre-sealing compile.
|
|
1593
1643
|
const encMap = buildEncryptionMap(
|
|
@@ -2211,9 +2261,24 @@ export function generateTsClient(
|
|
|
2211
2261
|
]
|
|
2212
2262
|
: []),
|
|
2213
2263
|
``,
|
|
2214
|
-
`/**
|
|
2264
|
+
`/** The law this client was generated against: its unit set identity (\`build/<name>.units.sha256\`). Each`,
|
|
2265
|
+
` * workspace serves a closure its own kernel composes from those units; compatibility is the kernel's verdict. */`,
|
|
2215
2266
|
`const ${hashConst} = ${JSON.stringify(opts.domainHash)};`,
|
|
2216
2267
|
``,
|
|
2268
|
+
...(domainRequirements.length === 0
|
|
2269
|
+
? []
|
|
2270
|
+
: [
|
|
2271
|
+
`/** THE PER-DOMAIN REQUIREMENTS (CAP-666) this client was generated against: for each application domain,`,
|
|
2272
|
+
` * the hash of its law unit (\`domain:<key>\` of the unit set) and the sha256 of its generated compatibility`,
|
|
2273
|
+
` * requirement. A workspace serving exactly that unit for the key is current for this build whatever else`,
|
|
2274
|
+
` * its unit set carries. Identities the adoption inventory and the support policy name; never a verdict. */`,
|
|
2275
|
+
`const ${tsDomainRequirementsConstName(opts.packageName)} = Object.freeze({`,
|
|
2276
|
+
...domainRequirements.map((requirement) =>
|
|
2277
|
+
` ${JSON.stringify(requirement.domainKey)}: Object.freeze({ domainKey: ${JSON.stringify(requirement.domainKey)}, `
|
|
2278
|
+
+ `unitSha256: ${JSON.stringify(requirement.unitSha256)}, requirementSha256: ${JSON.stringify(requirement.requirementSha256)} }),`),
|
|
2279
|
+
`} as const);`,
|
|
2280
|
+
``,
|
|
2281
|
+
]),
|
|
2217
2282
|
...(clientInterface !== undefined
|
|
2218
2283
|
? [
|
|
2219
2284
|
`/** Canonical compiler-authored USDA compatibility matrix. The application carries`,
|
|
@@ -2579,7 +2644,7 @@ export function generateTsClient(
|
|
|
2579
2644
|
`export interface NomosHolonBinding { readonly workspace: string }`,
|
|
2580
2645
|
`interface NomosHolon {`,
|
|
2581
2646
|
` /** Carry one compiler-generated nominal USDA call. Routing identity and bytes cannot be paired independently. */`,
|
|
2582
|
-
` offerCall(call: NmsGeneratedCall, opts?: {
|
|
2647
|
+
` offerCall(call: NmsGeneratedCall, opts?: { actor?: string; returnCreated?: boolean; __nomosConflictSnapshots?: readonly { aggregateType: string; aggregateId: string; field: string; targetSchema: string; token: string }[] }): Promise<string | { head: string; created?: { type: string; id: string }[]; affected?: { type: string; id: string }[]; workspace?: string; born?: unknown; birthOutcomes?: unknown }>;`,
|
|
2583
2648
|
...(sourceTrustPresent
|
|
2584
2649
|
? [
|
|
2585
2650
|
` /** FRAMEWORK-INTERNAL: inspect a sibling's immutable custody identity before authoring`,
|
|
@@ -2590,7 +2655,7 @@ export function generateTsClient(
|
|
|
2590
2655
|
...(hasCorrections
|
|
2591
2656
|
? [
|
|
2592
2657
|
` /** Framework-private seam used by generated \`corrections\`; apps never supply an intent id. */`,
|
|
2593
|
-
` correctMostRecent?(opts: { kind: "undo" | "redo"; domain: string; actions: string[]; reason: string;
|
|
2658
|
+
` correctMostRecent?(opts: { kind: "undo" | "redo"; domain: string; actions: string[]; reason: string; actor?: string }): Promise<NomosCorrectionOutcome>;`,
|
|
2594
2659
|
]
|
|
2595
2660
|
: []),
|
|
2596
2661
|
...(hasBirths
|
|
@@ -2598,7 +2663,7 @@ export function generateTsClient(
|
|
|
2598
2663
|
` /** BIRTH A CHILD WORKSPACE (a signed CROSS-WORKSPACE governance offer to \`parent\`) — OPTIONAL:`,
|
|
2599
2664
|
` * @githolon/runtime's connect() attaches it. The generated \`.births()\` methods call this, binding`,
|
|
2600
2665
|
` * the declaring domain/directiveId/law hash so a frontend dev never hand-writes those raw strings. */`,
|
|
2601
|
-
` createWorkspace?(opts: { domain: string; directiveId: string;
|
|
2666
|
+
` createWorkspace?(opts: { domain: string; directiveId: string; parent: string; actor: string; authorSecret?: string; payload?: unknown; name?: string; authToken?: string }): Promise<{ ok?: boolean; status?: number; body?: unknown }>;`,
|
|
2602
2667
|
]
|
|
2603
2668
|
: []),
|
|
2604
2669
|
` /** The workspace this holon is bound to — the \`workspace\` of every [NomosRef] this client composes. */`,
|
|
@@ -2769,7 +2834,7 @@ export function generateTsClient(
|
|
|
2769
2834
|
? [
|
|
2770
2835
|
`/** Framework-private merge lane used only by generated conflict capabilities. The snapshot`,
|
|
2771
2836
|
` * proves the exact observation set being merged; application code never supplies it. */`,
|
|
2772
|
-
`type __NomosConflictOffer = (call: NmsGeneratedCall, opts: {
|
|
2837
|
+
`type __NomosConflictOffer = (call: NmsGeneratedCall, opts: { actor?: string; returnCreated: true; __nomosConflictSnapshots: readonly { aggregateType: string; aggregateId: string; field: string; targetSchema: string; token: string }[] }) => ReturnType<NomosHolon["offerCall"]>;`,
|
|
2773
2838
|
``,
|
|
2774
2839
|
]
|
|
2775
2840
|
: []),
|
|
@@ -3001,8 +3066,8 @@ export function generateTsClient(
|
|
|
3001
3066
|
`}`,
|
|
3002
3067
|
``,
|
|
3003
3068
|
`/** Build the owner ceremony for one domain (thin: all mechanism lives here; the factory just wires it). */`,
|
|
3004
|
-
`function _ownerEncryption(holon: NomosHolon, cfg: { domain: string;
|
|
3005
|
-
` const { domain,
|
|
3069
|
+
`function _ownerEncryption(holon: NomosHolon, cfg: { domain: string; actor?: string; scopes: readonly string[] }): NomosEncryption {`,
|
|
3070
|
+
` const { domain, actor, scopes } = cfg;`,
|
|
3006
3071
|
` const defScope = (scope?: string): string => {`,
|
|
3007
3072
|
` if (scope) return scope;`,
|
|
3008
3073
|
` if (scopes.length === 1) return scopes[0]!;`,
|
|
@@ -3076,7 +3141,7 @@ export function generateTsClient(
|
|
|
3076
3141
|
` wraps.push({ recipientPrincipal: principal, recipientPubKey: pub, hpkeEpk: w.hpkeEpk, ct: w.ct });`,
|
|
3077
3142
|
` }`,
|
|
3078
3143
|
` const epoch = (await currentEpoch(scope)) + 1; // forward-only: strictly increments`,
|
|
3079
|
-
' const head = await holon.offerDirective(domain, "advanceKeyEpoch", { scope, epoch, openedAt: opts.openedAt ?? new Date().toISOString(), wraps }, {
|
|
3144
|
+
' const head = await holon.offerDirective(domain, "advanceKeyEpoch", { scope, epoch, openedAt: opts.openedAt ?? new Date().toISOString(), wraps }, { actor });',
|
|
3080
3145
|
` return { head, epoch, roster: members };`,
|
|
3081
3146
|
` };`,
|
|
3082
3147
|
` return {`,
|
|
@@ -3084,12 +3149,12 @@ export function generateTsClient(
|
|
|
3084
3149
|
` roster: (scope) => readRoster(defScope(scope)),`,
|
|
3085
3150
|
` async grantAndWrap(args) {`,
|
|
3086
3151
|
` const scope = defScope(args.scope);`,
|
|
3087
|
-
` await holon.offerDirective(domain, args.grantDirective, args.grantPayload, {
|
|
3152
|
+
` await holon.offerDirective(domain, args.grantDirective, args.grantPayload, { actor });`,
|
|
3088
3153
|
` return advance(scope, { add: { principal: args.principal, publicKey: args.publicKey }, scopeKey: args.scopeKey, memberKeys: args.memberKeys, expectRoster: args.expectRoster, openedAt: args.openedAt });`,
|
|
3089
3154
|
` },`,
|
|
3090
3155
|
` async revokeAndRotate(args) {`,
|
|
3091
3156
|
` const scope = defScope(args.scope);`,
|
|
3092
|
-
` await holon.offerDirective(domain, args.revokeDirective, args.revokePayload, {
|
|
3157
|
+
` await holon.offerDirective(domain, args.revokeDirective, args.revokePayload, { actor });`,
|
|
3093
3158
|
` return advance(scope, { remove: [args.principal], scopeKey: args.scopeKey, memberKeys: args.memberKeys, expectRoster: args.expectRoster, openedAt: args.openedAt });`,
|
|
3094
3159
|
` },`,
|
|
3095
3160
|
` async rotate(args) {`,
|
|
@@ -3727,6 +3792,7 @@ export function generateTsClient(
|
|
|
3727
3792
|
for (const [, d] of voByName)
|
|
3728
3793
|
for (const b of voBrandNames(d)) brands.add(b);
|
|
3729
3794
|
for (const b of [...brands].sort()) {
|
|
3795
|
+
emittedBrands.add(b);
|
|
3730
3796
|
out.push(
|
|
3731
3797
|
`/** Branded id \`${b}\` — a nominal string (\`brandedId("${b}")\`); wire-identical to a string. */`
|
|
3732
3798
|
);
|
|
@@ -4668,7 +4734,7 @@ export function generateTsClient(
|
|
|
4668
4734
|
domain
|
|
4669
4735
|
)}, actions: ${JSON.stringify(
|
|
4670
4736
|
correctionActions.map((d) => d.id)
|
|
4671
|
-
)}, reason,
|
|
4737
|
+
)}, reason, actor: actor ?? holon.principal });`,
|
|
4672
4738
|
` },`,
|
|
4673
4739
|
` redo: async (reason: string): Promise<NomosCorrectionOutcome> => {`,
|
|
4674
4740
|
` if (!reason.trim()) throw new Error("corrections.redo() needs a reason");`,
|
|
@@ -4677,7 +4743,7 @@ export function generateTsClient(
|
|
|
4677
4743
|
domain
|
|
4678
4744
|
)}, actions: ${JSON.stringify(
|
|
4679
4745
|
correctionActions.map((d) => d.id)
|
|
4680
|
-
)}, reason,
|
|
4746
|
+
)}, reason, actor: actor ?? holon.principal });`,
|
|
4681
4747
|
` },`,
|
|
4682
4748
|
` },`
|
|
4683
4749
|
);
|
|
@@ -4695,7 +4761,7 @@ export function generateTsClient(
|
|
|
4695
4761
|
publicPayloadSchema: z.ZodTypeAny;
|
|
4696
4762
|
rootId: string;
|
|
4697
4763
|
namespace: string;
|
|
4698
|
-
keyField
|
|
4764
|
+
keyField?: string;
|
|
4699
4765
|
};
|
|
4700
4766
|
}
|
|
4701
4767
|
).businessBirth;
|
|
@@ -4924,17 +4990,29 @@ export function generateTsClient(
|
|
|
4924
4990
|
businessBirth === undefined || encFields === undefined
|
|
4925
4991
|
? undefined
|
|
4926
4992
|
: Object.values(encFields).find((field) => field.owned === true);
|
|
4993
|
+
// A minted birth (the default) has no business key, but an owned birth seals to the child's address
|
|
4994
|
+
// before dispatch: reserve the key from the kernel's own minter and hand it to the law (CAP-804).
|
|
4995
|
+
const mintedOwnedBirth =
|
|
4996
|
+
ownedBirthDescriptor !== undefined && businessBirth!.keyField === undefined;
|
|
4927
4997
|
const ownedBirthPrelude =
|
|
4928
4998
|
ownedBirthDescriptor === undefined
|
|
4929
4999
|
? ""
|
|
4930
5000
|
: ` if (!holon.__nomosOwnedBirthScopeKey) throw new Error("this holon predates automatic .owned() birth custody — upgrade @githolon/runtime");\n` +
|
|
5001
|
+
(mintedOwnedBirth
|
|
5002
|
+
? ` if (!holon.mint) throw new Error("this holon predates minted birth keys — upgrade @githolon/runtime");\n` +
|
|
5003
|
+
` const _birthKey = await holon.mint(${JSON.stringify(
|
|
5004
|
+
businessBirth!.aggregateId
|
|
5005
|
+
)});\n`
|
|
5006
|
+
: "") +
|
|
4931
5007
|
` const _ownedBirthKey = await holon.__nomosOwnedBirthScopeKey({ scope: ${JSON.stringify(
|
|
4932
5008
|
ownedBirthDescriptor.scope
|
|
4933
5009
|
)}, namespace: ${JSON.stringify(
|
|
4934
5010
|
businessBirth!.namespace
|
|
4935
|
-
)}, key: ${
|
|
4936
|
-
|
|
4937
|
-
|
|
5011
|
+
)}, key: ${
|
|
5012
|
+
mintedOwnedBirth
|
|
5013
|
+
? "_birthKey"
|
|
5014
|
+
: `${inputPayload}[${JSON.stringify(businessBirth!.keyField)}]`
|
|
5015
|
+
}, record: { type: ${JSON.stringify(
|
|
4938
5016
|
ownedRuntimeType
|
|
4939
5017
|
)}, id: ${JSON.stringify(businessBirth!.rootId)} } });`;
|
|
4940
5018
|
const paramSig = `${argumentName}: ${payloadName}`;
|
|
@@ -5044,7 +5122,7 @@ export function generateTsClient(
|
|
|
5044
5122
|
)}: _sourceGenesis,`,
|
|
5045
5123
|
`${indent} ${JSON.stringify(sourceTrust.keyHashesField)}: [],`,
|
|
5046
5124
|
`${indent}} satisfies ${payloadType};`,
|
|
5047
|
-
`${indent}await _commit(holon.offerCall(${encoder}(_sourceTrustCall, holon.workspace), {
|
|
5125
|
+
`${indent}await _commit(holon.offerCall(${encoder}(_sourceTrustCall, holon.workspace), { actor, returnCreated: true }) as Promise<_NomosRawWriteResult>);`,
|
|
5048
5126
|
...(satisfactionQuery === undefined ? [] : [" }"]),
|
|
5049
5127
|
];
|
|
5050
5128
|
})();
|
|
@@ -5266,6 +5344,10 @@ export function generateTsClient(
|
|
|
5266
5344
|
type: `${payloadName} & { readonly principal: string; readonly bornAt: string${ownedSeedFields} }`,
|
|
5267
5345
|
});
|
|
5268
5346
|
}
|
|
5347
|
+
// A minted birth's law admits a key the client reserved ahead of dispatch (CAP-804).
|
|
5348
|
+
if (businessBirth !== undefined && businessBirth.keyField === undefined) {
|
|
5349
|
+
callExtraFields.set(BUSINESS_BIRTH_KEY_PAYLOAD_FIELD, { type: "string", optional: true });
|
|
5350
|
+
}
|
|
5269
5351
|
// The public payload remains purely application-authored. The private encoder
|
|
5270
5352
|
// type additionally admits the custody facts injected by the generated client
|
|
5271
5353
|
// after it obtains them from the framework-owned key ceremony.
|
|
@@ -5418,9 +5500,13 @@ export function generateTsClient(
|
|
|
5418
5500
|
ownedBirthDescriptor !== undefined
|
|
5419
5501
|
? `, [_OWNED_KEY_PAYLOAD_FIELD]: _ownedBirthKey.ownedKeySubject`
|
|
5420
5502
|
: ""
|
|
5503
|
+
}${
|
|
5504
|
+
ownedBirthDescriptor !== undefined && businessBirth.keyField === undefined
|
|
5505
|
+
? `, ${JSON.stringify(BUSINESS_BIRTH_KEY_PAYLOAD_FIELD)}: _birthKey`
|
|
5506
|
+
: ""
|
|
5421
5507
|
} }`
|
|
5422
5508
|
)};`,
|
|
5423
|
-
` const result = await holon.offerCall(_call, {
|
|
5509
|
+
` const result = await holon.offerCall(_call, { actor: principal, returnCreated: true });`,
|
|
5424
5510
|
` const born = typeof result === "object" && result !== null && Array.isArray(result.born) ? result.born[0] : undefined;`,
|
|
5425
5511
|
` if (typeof born !== "object" || born === null || typeof born.workspace !== "string" || !born.workspace) throw new Error("birth() committed without returning the born workspace");`,
|
|
5426
5512
|
...(ownedBirthDescriptor !== undefined
|
|
@@ -5453,7 +5539,7 @@ export function generateTsClient(
|
|
|
5453
5539
|
...(gateGuard ? [gateGuard.replace(/\n$/, "")] : []),
|
|
5454
5540
|
` return _commit(holon.offerCall(${encodeCall(
|
|
5455
5541
|
birthPayload
|
|
5456
|
-
)}, {
|
|
5542
|
+
)}, { actor, returnCreated: true }) as Promise<_NomosRawWriteResult>);`,
|
|
5457
5543
|
` },`
|
|
5458
5544
|
);
|
|
5459
5545
|
continue;
|
|
@@ -5488,10 +5574,10 @@ export function generateTsClient(
|
|
|
5488
5574
|
const mintedCall = hasBusinessResult
|
|
5489
5575
|
? `_businessResult<${resultName}>(holon.workspace, ${resultSpec}, holon.offerCall(${encodeCall(
|
|
5490
5576
|
body
|
|
5491
|
-
)}, {
|
|
5577
|
+
)}, { actor, returnCreated: true }) as Promise<_NomosRawWriteResult>)`
|
|
5492
5578
|
: `_commit(holon.offerCall(${encodeCall(
|
|
5493
5579
|
body
|
|
5494
|
-
)}, {
|
|
5580
|
+
)}, { actor, returnCreated: true }) as Promise<_NomosRawWriteResult>)`;
|
|
5495
5581
|
methods.push(
|
|
5496
5582
|
` /** Author \`${domain}/${d.id}\`: the generated client MINTS \`${mint.field}\` internally`,
|
|
5497
5583
|
` * — ids are not application input — ${
|
|
@@ -5541,10 +5627,10 @@ export function generateTsClient(
|
|
|
5541
5627
|
const commitCall = hasBusinessResult
|
|
5542
5628
|
? `_businessResult<${resultName}>(holon.workspace, ${resultSpec}, holon.offerCall(${encodeCall(
|
|
5543
5629
|
offerPayload
|
|
5544
|
-
)}, {
|
|
5630
|
+
)}, { actor, returnCreated: true }) as Promise<_NomosRawWriteResult>)`
|
|
5545
5631
|
: `_commit(holon.offerCall(${encodeCall(
|
|
5546
5632
|
offerPayload
|
|
5547
|
-
)}, {
|
|
5633
|
+
)}, { actor, returnCreated: true }) as Promise<_NomosRawWriteResult>)`;
|
|
5548
5634
|
const outcomeDoc =
|
|
5549
5635
|
d.marker === "creates"
|
|
5550
5636
|
? `Author \`${domain}/${d.id}\`: the LAW mints the ${
|
|
@@ -6181,7 +6267,7 @@ export function generateTsClient(
|
|
|
6181
6267
|
` merge: (reconcile: (versions: readonly SurfacedObservation<${valueType}>[]) => ${valueType}, rest?: ${restType}): Promise<void> => {`,
|
|
6182
6268
|
` const merged = reconcile(versions);`,
|
|
6183
6269
|
` const resolution = ${resolutionLiteral} satisfies ${payloadName};`,
|
|
6184
|
-
` return _commit((holon.offerCall as __NomosConflictOffer)(${resolverCallEncoder}(resolution, holon.workspace), {
|
|
6270
|
+
` return _commit((holon.offerCall as __NomosConflictOffer)(${resolverCallEncoder}(resolution, holon.workspace), { actor, returnCreated: true, __nomosConflictSnapshots: [{ aggregateType: ${JSON.stringify(
|
|
6185
6271
|
runtimeAggregateType
|
|
6186
6272
|
)}, aggregateId, field: ${JSON.stringify(
|
|
6187
6273
|
b.field
|
|
@@ -6617,7 +6703,7 @@ export function generateTsClient(
|
|
|
6617
6703
|
` const payload = await holon.__nomosPrepareOwnedRecordAction({ action: ${JSON.stringify(
|
|
6618
6704
|
descriptor.action
|
|
6619
6705
|
)}, scope: ${JSON.stringify(ownedScope)}, record, peer });`,
|
|
6620
|
-
` return _commit(holon.offerCall(${descriptor.functionName}(payload, holon.workspace), {
|
|
6706
|
+
` return _commit(holon.offerCall(${descriptor.functionName}(payload, holon.workspace), { actor, returnCreated: true }) as Promise<_NomosRawWriteResult>);`,
|
|
6621
6707
|
` }`,
|
|
6622
6708
|
]),
|
|
6623
6709
|
` default: throw new Error(\`${"${action}()"} has no generated canonical offer encoder\`);`,
|
|
@@ -6666,7 +6752,7 @@ export function generateTsClient(
|
|
|
6666
6752
|
` const payload = await holon.__nomosPrepareOwnedDeviceApproval({ request, scope: ${JSON.stringify(
|
|
6667
6753
|
ownedScope
|
|
6668
6754
|
)} });`,
|
|
6669
|
-
` return _commit(holon.offerCall(${ownedDeviceApprovalCallEncoder!}(payload, holon.workspace), {
|
|
6755
|
+
` return _commit(holon.offerCall(${ownedDeviceApprovalCallEncoder!}(payload, holon.workspace), { actor: principal, returnCreated: true }) as Promise<_NomosRawWriteResult>);`,
|
|
6670
6756
|
` },`,
|
|
6671
6757
|
` },`,
|
|
6672
6758
|
]
|
|
@@ -6677,7 +6763,7 @@ export function generateTsClient(
|
|
|
6677
6763
|
` /** Owner policy: grant, revoke and rotate under the signed-in identity. */`,
|
|
6678
6764
|
` encryption: _ownerEncryption(holon, { domain: ${JSON.stringify(
|
|
6679
6765
|
domain
|
|
6680
|
-
)},
|
|
6766
|
+
)}, actor, scopes: ${JSON.stringify(
|
|
6681
6767
|
domScopes
|
|
6682
6768
|
)} }) as NomosEncryption,`,
|
|
6683
6769
|
]
|
|
@@ -7074,6 +7160,7 @@ export function generateTsClient(
|
|
|
7074
7160
|
` compatibility: {`,
|
|
7075
7161
|
` lawIdentity: sha256Digest(${hashConst}),`,
|
|
7076
7162
|
` matrixUsda: ${clientInterface === undefined ? "new Uint8Array()" : interfaceConstName(opts.packageName)},`,
|
|
7163
|
+
...(domainRequirements.length === 0 ? [] : [` domainRequirements: ${tsDomainRequirementsConstName(opts.packageName)},`]),
|
|
7077
7164
|
` readyCommands: [${rootBirths
|
|
7078
7165
|
.map(
|
|
7079
7166
|
(birth) =>
|
|
@@ -7091,6 +7178,20 @@ export function generateTsClient(
|
|
|
7091
7178
|
);
|
|
7092
7179
|
}
|
|
7093
7180
|
|
|
7181
|
+
// TYPED-ID ALIASES reached by payloads and read models (beyond those the value objects declared above).
|
|
7182
|
+
// TS hoists type aliases, so declaring them last is fine; sorted for byte-determinism.
|
|
7183
|
+
const remainingBrands = [...brandSink].filter((brand) => !emittedBrands.has(brand)).sort();
|
|
7184
|
+
if (remainingBrands.length > 0) {
|
|
7185
|
+
out.push(`// ── typed ids (brandedId / t.brandedId / t.uuid) — nominal strings, wire-identical to a string ──`);
|
|
7186
|
+
for (const b of remainingBrands) {
|
|
7187
|
+
out.push(
|
|
7188
|
+
`/** Branded id \`${b}\` — a nominal string (\`brandedId("${b}")\`); wire-identical to a string. */`,
|
|
7189
|
+
`export type ${b} = string & { readonly __brand: "${b}" };`,
|
|
7190
|
+
``
|
|
7191
|
+
);
|
|
7192
|
+
}
|
|
7193
|
+
}
|
|
7194
|
+
|
|
7094
7195
|
// RECURSIVE PAYLOAD TYPES (`z.lazy`): emit each minted named alias once. Sorted by name for
|
|
7095
7196
|
// byte-determinism; TS hoists type aliases so referencing them above is fine.
|
|
7096
7197
|
if (lazyReg.decls.size > 0) {
|
|
@@ -18,6 +18,7 @@ import type { Field } from "./fields.js";
|
|
|
18
18
|
import type { GeometryKind } from "./value_object.js";
|
|
19
19
|
import { isBusinessIntent } from "./intent.js";
|
|
20
20
|
import { isFrameworkAggregate } from "./framework/framework_aggregates.js";
|
|
21
|
+
import { composedWireIdentity } from "./composed_wire_identity.js";
|
|
21
22
|
import type {
|
|
22
23
|
LawAnonymousValueTypeNode,
|
|
23
24
|
LawAggregateNode,
|
|
@@ -42,6 +43,21 @@ export interface PayloadFieldShape {
|
|
|
42
43
|
readonly type?: string;
|
|
43
44
|
}
|
|
44
45
|
|
|
46
|
+
/**
|
|
47
|
+
* One member of a nested record value. Two laws can agree on every nested name and kind while
|
|
48
|
+
* their generated codecs disagree on the Protobuf field numbers (the 1 October 2026
|
|
49
|
+
* `EstateAsset.canvasRequirements{}.isRequired` incident), so the member carries the allocation
|
|
50
|
+
* the generated codec actually encodes and decodes it under, plus any retained aliases.
|
|
51
|
+
*/
|
|
52
|
+
export interface CompatibilityRecordField {
|
|
53
|
+
readonly required: boolean;
|
|
54
|
+
readonly type: string;
|
|
55
|
+
/** Canonical Protobuf allocation the generated codec uses for this member. */
|
|
56
|
+
readonly wireKey?: string;
|
|
57
|
+
/** Previously shipped allocations this codec still decodes during an overlap window. */
|
|
58
|
+
readonly wireAliases?: readonly string[];
|
|
59
|
+
}
|
|
60
|
+
|
|
45
61
|
/**
|
|
46
62
|
* The compatibility vocabulary for domain values. This is a graph rather than
|
|
47
63
|
* an inline tree so recursive values and shared nested value objects remain
|
|
@@ -68,9 +84,7 @@ export type CompatibilityValueShape =
|
|
|
68
84
|
| { readonly kind: "map"; readonly value: string }
|
|
69
85
|
| {
|
|
70
86
|
readonly kind: "record";
|
|
71
|
-
readonly fields: Readonly<
|
|
72
|
-
Record<string, { readonly required: boolean; readonly type: string }>
|
|
73
|
-
>;
|
|
87
|
+
readonly fields: Readonly<Record<string, CompatibilityRecordField>>;
|
|
74
88
|
}
|
|
75
89
|
| { readonly kind: "sum"; readonly variants: readonly string[] }
|
|
76
90
|
| { readonly kind: "recursive"; readonly body: string };
|
|
@@ -352,10 +366,31 @@ export function domainInterface(
|
|
|
352
366
|
* workspace offers are now two projections of the same graph/USDA, rather than
|
|
353
367
|
* independently interpreting DomainModule and hoping the shapes coincide.
|
|
354
368
|
*/
|
|
355
|
-
export function domainInterfaceFromLawGraph(
|
|
369
|
+
export function domainInterfaceFromLawGraph(
|
|
370
|
+
graph: LawGraph,
|
|
371
|
+
/** The composed law layer's path index. Nested members of a record that OpenUSD composition
|
|
372
|
+
* shares with another contract take the canonical prim's allocation, exactly as the generated
|
|
373
|
+
* codecs do through `composedWireIdentity`; without the index each member's own identity is
|
|
374
|
+
* its allocation. */
|
|
375
|
+
paths: ReadonlyMap<string, string> = new Map()
|
|
376
|
+
): DomainInterface {
|
|
356
377
|
const nodes = new Map<string, LawGraph["nodes"][number]>(
|
|
357
378
|
graph.nodes.map((node) => [node.entity.id, node])
|
|
358
379
|
);
|
|
380
|
+
// The nested allocation is the one fact the generated Dart/TS codecs and installed law must
|
|
381
|
+
// agree on. It is derived here from the same identity the codecs derive it from, never from
|
|
382
|
+
// the member's name.
|
|
383
|
+
const nestedWire = (entity: {
|
|
384
|
+
readonly id: string;
|
|
385
|
+
readonly sid: string;
|
|
386
|
+
readonly wireKey?: string;
|
|
387
|
+
readonly wireAliases?: readonly string[];
|
|
388
|
+
}): Pick<CompatibilityRecordField, "wireKey" | "wireAliases"> => ({
|
|
389
|
+
wireKey: composedWireIdentity(paths, entity),
|
|
390
|
+
...(entity.wireAliases === undefined || entity.wireAliases.length === 0
|
|
391
|
+
? {}
|
|
392
|
+
: { wireAliases: entity.wireAliases }),
|
|
393
|
+
});
|
|
359
394
|
const sorted = <T>(
|
|
360
395
|
entries: readonly (readonly [string, T])[]
|
|
361
396
|
): Record<string, T> =>
|
|
@@ -587,6 +622,7 @@ export function domainInterfaceFromLawGraph(graph: LawGraph): DomainInterface {
|
|
|
587
622
|
{
|
|
588
623
|
required: field.required,
|
|
589
624
|
type: resolvedTypeId(field.value.id),
|
|
625
|
+
...nestedWire(field.entity),
|
|
590
626
|
},
|
|
591
627
|
] as const,
|
|
592
628
|
];
|
|
@@ -653,6 +689,7 @@ export function domainInterfaceFromLawGraph(graph: LawGraph): DomainInterface {
|
|
|
653
689
|
{
|
|
654
690
|
required: field.required,
|
|
655
691
|
type: resolvedTypeId(field.value.id),
|
|
692
|
+
...nestedWire(field.entity),
|
|
656
693
|
},
|
|
657
694
|
] as const,
|
|
658
695
|
];
|
|
@@ -60,10 +60,14 @@ function canonicalCompatibilityTypes(
|
|
|
60
60
|
value = `sum(${type.variants.map(ref).sort().map(fingerprintAtom).join("")})`;
|
|
61
61
|
break;
|
|
62
62
|
case "record":
|
|
63
|
+
// Two records with identical names and kinds but different nested allocations are
|
|
64
|
+
// different contracts; interning them as one would erase the very evidence the kernel
|
|
65
|
+
// judges, so the allocation and its aliases are part of the fingerprint.
|
|
63
66
|
value = `record(${Object.entries(type.fields)
|
|
64
67
|
.sort(([left], [right]) => left.localeCompare(right))
|
|
65
68
|
.map(([name, field]) =>
|
|
66
|
-
`${fingerprintAtom(name)}${field.required ? "1" : "0"}${fingerprintAtom(ref(field.type))}`
|
|
69
|
+
`${fingerprintAtom(name)}${field.required ? "1" : "0"}${fingerprintAtom(ref(field.type))}` +
|
|
70
|
+
`${fingerprintAtom(field.wireKey ?? "")}${[...(field.wireAliases ?? [])].sort().map(fingerprintAtom).join("")}`)
|
|
67
71
|
.join("")})`;
|
|
68
72
|
break;
|
|
69
73
|
}
|
|
@@ -146,6 +150,12 @@ function emitType(lines: string[], type: CompatibilityValueShape, typeRef: (id:
|
|
|
146
150
|
` def NomosCompatibilityValueField "${numbered("f", index)}"`,
|
|
147
151
|
" {",
|
|
148
152
|
` custom uniform string nomos:name = ${usdaString(name)}`,
|
|
153
|
+
...(field.wireKey === undefined ? [] : [
|
|
154
|
+
` custom uniform string nomos:wireKey = ${usdaString(field.wireKey)}`,
|
|
155
|
+
]),
|
|
156
|
+
...(field.wireAliases === undefined || field.wireAliases.length === 0 ? [] : [
|
|
157
|
+
` custom uniform string[] nomos:wireAliases = [${field.wireAliases.map(usdaString).join(", ")}]`,
|
|
158
|
+
]),
|
|
149
159
|
` custom uniform bool nomos:required = ${field.required}`,
|
|
150
160
|
` custom rel nomos:type = <${typeRef(field.type)}>`,
|
|
151
161
|
" }",
|