@githolon/dsl 0.114.0 → 0.116.2
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/dart/CHANGELOG.md +55 -0
- package/dart/lib/src/application_framework_calls.dart +7 -1
- 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/nms_generated_runtime.dart +1 -0
- package/dart/lib/src/operation_receipts.dart +74 -7
- package/dart/lib/src/reception/dart_reception.dart +26 -6
- package/dart/lib/src/result_usda.dart +64 -27
- package/dart/lib/src/runner/node-entry.mjs +4733 -1534
- package/dart/lib/src/runtime_v2_transport.dart +3 -0
- package/dart/lib/src/sync_status_projection.dart +11 -0
- package/dart/pubspec.lock +1 -1
- package/dart/pubspec.yaml +2 -2
- package/dart/test/console_enrolment_test.dart +47 -0
- package/dart/test/kernel_refused_follow_test.dart +36 -0
- package/dart/test/model_adoption_test.dart +49 -0
- package/dart/test/result_usda_test.dart +67 -0
- package/dart/test/runtime_projections_test.dart +41 -0
- package/dart_types/CHANGELOG.md +30 -0
- package/dart_types/lib/src/approved_model.dart +78 -1
- package/dart_types/lib/src/proto/nomos/kernel/v1/kernel.pb.dart +528 -2
- package/dart_types/lib/src/proto/nomos/kernel/v1/kernel.pbenum.dart +24 -0
- package/dart_types/lib/src/proto/nomos/runtime/v2/correspondence_desk.pb.dart +1897 -112
- package/dart_types/lib/src/proto/nomos/runtime/v2/model_adoption_desk.pb.dart +98 -0
- package/dart_types/lib/src/proto/nomos/runtime/v2/observability_desk.pbenum.dart +11 -1
- package/dart_types/lib/src/proto/nomos/runtime/v2/records_desk.pb.dart +16 -0
- package/dart_types/lib/src/proto/nomos/runtime/v2/runtime.pb.dart +43 -2
- package/dart_types/lib/src/vo_canonical.dart +185 -0
- package/dart_types/pubspec.lock +1 -1
- package/dart_types/pubspec.yaml +3 -1
- package/dart_types/test/vo_canonical_test.dart +91 -0
- package/package.json +4 -2
- package/src/aggregate.ts +95 -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 +185 -0
- package/src/birth_key_rule.ts +472 -0
- package/src/births.ts +207 -24
- package/src/build_package.ts +47 -7
- package/src/build_provenance.ts +10 -0
- 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_call_usda.ts +6 -3
- package/src/codegen_dart.ts +114 -60
- package/src/codegen_dart_client.ts +52 -6
- package/src/codegen_dart_facade.ts +57 -35
- package/src/codegen_dart_framework_calls.ts +20 -1
- package/src/codegen_proof.ts +5 -2
- package/src/codegen_result_proto.ts +8 -6
- package/src/codegen_result_usda.ts +12 -4
- package/src/codegen_ts.ts +109 -10
- package/src/compatibility_graph.ts +41 -4
- package/src/compatibility_usda.ts +11 -1
- package/src/compile_package_main.ts +222 -52
- 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 +12 -4
- package/src/engine_javascript.ts +12 -6
- package/src/fields.ts +109 -0
- package/src/identity_usda.ts +80 -103
- package/src/index.ts +35 -1
- package/src/intent.ts +33 -3
- package/src/law_check.ts +34 -0
- package/src/law_graph.ts +142 -1
- package/src/law_graph_usda.ts +27 -144
- package/src/law_layer_prims.ts +174 -0
- package/src/law_unit_selection.ts +45 -0
- package/src/law_units.ts +7 -6
- package/src/law_version.ts +108 -0
- package/src/package_config.ts +44 -0
- package/src/package_dependencies.ts +8 -0
- package/src/point_guard.ts +5 -1
- package/src/release_inspection.ts +6 -4
- package/src/release_publication.ts +6 -5
- package/src/usda_text.ts +78 -3
- package/src/value_identity.ts +355 -0
- package/src/value_object.ts +35 -4
- package/src/wire.ts +2 -0
- package/src/wire_paths.ts +17 -0
- package/src/workspace_routing.ts +1 -1
package/src/codegen_proof.ts
CHANGED
|
@@ -32,6 +32,7 @@
|
|
|
32
32
|
* Deterministic codegen: the emitted bytes are a pure function of the modules +
|
|
33
33
|
* package name + domain hash — no timestamps or generation-time randomness.
|
|
34
34
|
*/
|
|
35
|
+
import { BUSINESS_BIRTH_KEY_PAYLOAD_FIELD } from "./births.js";
|
|
35
36
|
import type { z } from "zod";
|
|
36
37
|
|
|
37
38
|
import { BUSINESS_BIRTH_SEED_PAYLOAD_FIELD, type AggregateHandle } from "./aggregate.js";
|
|
@@ -2836,10 +2837,12 @@ function attachBusinessEncryption(
|
|
|
2836
2837
|
const directive = (mod?.directives as Directive<unknown>[] | undefined)?.find(
|
|
2837
2838
|
(candidate) => candidate.id === leg.birthDirectiveId,
|
|
2838
2839
|
) as (Directive<unknown> & {
|
|
2839
|
-
businessBirth?: { namespace?: string; keyField?: string };
|
|
2840
|
+
businessBirth?: { namespace?: string; keyField?: string; keyMode?: string };
|
|
2840
2841
|
}) | undefined;
|
|
2841
2842
|
const namespace = directive?.businessBirth?.namespace;
|
|
2842
|
-
|
|
2843
|
+
// A minted birth's key rides the hidden reserved-key field (CAP-804).
|
|
2844
|
+
const keyField = directive?.businessBirth?.keyField
|
|
2845
|
+
?? (directive?.businessBirth?.keyMode === "minted" ? BUSINESS_BIRTH_KEY_PAYLOAD_FIELD : undefined);
|
|
2843
2846
|
const offered = packageInterface[leg.parentDomain];
|
|
2844
2847
|
const offeredAggregate = offered === undefined
|
|
2845
2848
|
? undefined
|
|
@@ -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;
|
|
@@ -44,9 +45,13 @@ export const TS_RESULT_USDA_PRELUDE = [
|
|
|
44
45
|
` const c = source[i]!; if (c !== "\\\\") { out += c; continue; }`,
|
|
45
46
|
` const e = source[++i]; if (e === undefined) throw new Error("truncated USDA escape");`,
|
|
46
47
|
` if (e === "n") out += "\\n"; else if (e === "r") out += "\\r"; else if (e === "t") out += "\\t";`,
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
` else
|
|
48
|
+
// OpenUSD's escapes (TfEscapeString): named controls, \x with up to two hex digits, \NNN octal,
|
|
49
|
+
// any other escaped character is itself; plus the LEGACY \uXXXX that pre-CAP-862 kernels wrote.
|
|
50
|
+
` else if (e === "b") out += "\\b"; else if (e === "f") out += "\\f"; else if (e === "a") out += "\\x07"; else if (e === "v") out += "\\v";`,
|
|
51
|
+
` else if (e === "x") { const hex = /^[0-9a-fA-F]{0,2}/.exec(source.slice(i + 1, source.length - 1))![0]; out += String.fromCharCode(hex ? Number.parseInt(hex, 16) : 0); i += hex.length; }`,
|
|
52
|
+
` else if (e >= "0" && e <= "7") { const oct = /^[0-7]{1,3}/.exec(source.slice(i, source.length - 1))![0]; out += String.fromCharCode(Number.parseInt(oct, 8)); i += oct.length - 1; }`,
|
|
53
|
+
` else if (e === "u" && /^[0-9a-fA-F]{4}$/.test(source.slice(i + 1, i + 5)) && !/^d[89a-f]/i.test(source.slice(i + 1, i + 5))) { out += String.fromCharCode(Number.parseInt(source.slice(i + 1, i + 5), 16)); i += 4; }`,
|
|
54
|
+
` else out += e;`,
|
|
50
55
|
` } return out;`,
|
|
51
56
|
`};`,
|
|
52
57
|
`const _nmsUsdaAtom = (raw: string): string | number | boolean | readonly string[] => {`,
|
|
@@ -70,7 +75,7 @@ export const TS_RESULT_USDA_PRELUDE = [
|
|
|
70
75
|
` if (declaration) { pending = { typeName: declaration[1]!, name: _nmsUsdaUnquote(declaration[2]!), properties: [], children: [] }; continue; }`,
|
|
71
76
|
` if (line === "{") { if (!pending) throw new Error("USDA scope has no declaration"); const parent = stack.at(-1); if (parent) parent.children.push(pending); else roots.push(pending); stack.push(pending); pending = undefined; continue; }`,
|
|
72
77
|
` if (line === "}") { if (!stack.pop()) throw new Error("unbalanced USDA scope"); continue; }`,
|
|
73
|
-
` const property = /^custom(?:\\s+uniform)?\\s+(?:rel|[A-Za-z0-9_\\[\\]]+)\\s+([A-Za-z0-9_:]+)\\s*=\\s*(.+)
|
|
78
|
+
` const property = /^custom(?:\\s+uniform)?\\s+(?:rel|[A-Za-z0-9_\\[\\]]+)\\s+([A-Za-z0-9_:]+)\\s*=\\s*(.+)$/s.exec(line);`,
|
|
74
79
|
` if (property) { const owner = stack.at(-1); if (!owner) throw new Error("USDA property outside prim"); owner.properties.push({ name: property[1]!, value: _nmsUsdaAtom(property[2]!) }); continue; }`,
|
|
75
80
|
` throw new Error("unsupported canonical USDA line: " + line);`,
|
|
76
81
|
` }`,
|
|
@@ -391,6 +396,9 @@ const decode = (field: Field, expression: string): string => {
|
|
|
391
396
|
? `sha256Digest(_nmsUsdaStringValue(${expression}))`
|
|
392
397
|
: isGitObjectIdSchema(field.zod)
|
|
393
398
|
? `gitObjectId(_nmsUsdaStringValue(${expression}))`
|
|
399
|
+
: brandedIdMetaOf(field.zod as z.ZodTypeAny) !== undefined
|
|
400
|
+
// A typed id field: the wire string IS the id; the brand is nominal only.
|
|
401
|
+
? `(_nmsUsdaStringValue(${expression}) as ${brandedIdMetaOf(field.zod as z.ZodTypeAny)})`
|
|
394
402
|
: `_nmsUsdaStringValue(${expression})`;
|
|
395
403
|
case "position":
|
|
396
404
|
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
|
|
@@ -1131,6 +1158,10 @@ export interface TsClientOptions {
|
|
|
1131
1158
|
/** THE LAW this client is generated against: the unit set identity of `build/<name>.units` (CAP-773,
|
|
1132
1159
|
* law_unit_set_identity.ts). Never the package closure, and never sent to the kernel with an offer. */
|
|
1133
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[];
|
|
1134
1165
|
/** Sibling lossless USDZ transport carried by the generated client when its signed-in workspace
|
|
1135
1166
|
* still serves the predecessor model. The archive is never canonical state and is not embedded. */
|
|
1136
1167
|
readonly lawAssetName?: string;
|
|
@@ -1421,6 +1452,20 @@ function zodUsesGitObjectId(
|
|
|
1421
1452
|
export function generateTsClient(
|
|
1422
1453
|
modules: readonly DomainModule[],
|
|
1423
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>,
|
|
1424
1469
|
): string {
|
|
1425
1470
|
if (opts.callUsda === undefined) {
|
|
1426
1471
|
throw new Error(
|
|
@@ -1451,6 +1496,8 @@ export function generateTsClient(
|
|
|
1451
1496
|
)
|
|
1452
1497
|
);
|
|
1453
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>();
|
|
1454
1501
|
// A public business birth can target a deliberately hidden root aggregate. Its private binder still
|
|
1455
1502
|
// needs a nominal type, but unrelated framework records must remain absent even from generated source.
|
|
1456
1503
|
const publicBusinessAggregateIds = new Set(
|
|
@@ -1589,6 +1636,8 @@ export function generateTsClient(
|
|
|
1589
1636
|
opts.interface as unknown as Record<string, DomainInterface>
|
|
1590
1637
|
);
|
|
1591
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);
|
|
1592
1641
|
// THE E2E-SEALING MAP (the sealing client): non-null iff any field is `.encrypted()`. When null,
|
|
1593
1642
|
// NOTHING below emits — the generated client is byte-identical to a pre-sealing compile.
|
|
1594
1643
|
const encMap = buildEncryptionMap(
|
|
@@ -2216,6 +2265,20 @@ export function generateTsClient(
|
|
|
2216
2265
|
` * workspace serves a closure its own kernel composes from those units; compatibility is the kernel's verdict. */`,
|
|
2217
2266
|
`const ${hashConst} = ${JSON.stringify(opts.domainHash)};`,
|
|
2218
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
|
+
]),
|
|
2219
2282
|
...(clientInterface !== undefined
|
|
2220
2283
|
? [
|
|
2221
2284
|
`/** Canonical compiler-authored USDA compatibility matrix. The application carries`,
|
|
@@ -3729,6 +3792,7 @@ export function generateTsClient(
|
|
|
3729
3792
|
for (const [, d] of voByName)
|
|
3730
3793
|
for (const b of voBrandNames(d)) brands.add(b);
|
|
3731
3794
|
for (const b of [...brands].sort()) {
|
|
3795
|
+
emittedBrands.add(b);
|
|
3732
3796
|
out.push(
|
|
3733
3797
|
`/** Branded id \`${b}\` — a nominal string (\`brandedId("${b}")\`); wire-identical to a string. */`
|
|
3734
3798
|
);
|
|
@@ -4697,7 +4761,7 @@ export function generateTsClient(
|
|
|
4697
4761
|
publicPayloadSchema: z.ZodTypeAny;
|
|
4698
4762
|
rootId: string;
|
|
4699
4763
|
namespace: string;
|
|
4700
|
-
keyField
|
|
4764
|
+
keyField?: string;
|
|
4701
4765
|
};
|
|
4702
4766
|
}
|
|
4703
4767
|
).businessBirth;
|
|
@@ -4926,17 +4990,29 @@ export function generateTsClient(
|
|
|
4926
4990
|
businessBirth === undefined || encFields === undefined
|
|
4927
4991
|
? undefined
|
|
4928
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;
|
|
4929
4997
|
const ownedBirthPrelude =
|
|
4930
4998
|
ownedBirthDescriptor === undefined
|
|
4931
4999
|
? ""
|
|
4932
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
|
+
: "") +
|
|
4933
5007
|
` const _ownedBirthKey = await holon.__nomosOwnedBirthScopeKey({ scope: ${JSON.stringify(
|
|
4934
5008
|
ownedBirthDescriptor.scope
|
|
4935
5009
|
)}, namespace: ${JSON.stringify(
|
|
4936
5010
|
businessBirth!.namespace
|
|
4937
|
-
)}, key: ${
|
|
4938
|
-
|
|
4939
|
-
|
|
5011
|
+
)}, key: ${
|
|
5012
|
+
mintedOwnedBirth
|
|
5013
|
+
? "_birthKey"
|
|
5014
|
+
: `${inputPayload}[${JSON.stringify(businessBirth!.keyField)}]`
|
|
5015
|
+
}, record: { type: ${JSON.stringify(
|
|
4940
5016
|
ownedRuntimeType
|
|
4941
5017
|
)}, id: ${JSON.stringify(businessBirth!.rootId)} } });`;
|
|
4942
5018
|
const paramSig = `${argumentName}: ${payloadName}`;
|
|
@@ -5268,6 +5344,10 @@ export function generateTsClient(
|
|
|
5268
5344
|
type: `${payloadName} & { readonly principal: string; readonly bornAt: string${ownedSeedFields} }`,
|
|
5269
5345
|
});
|
|
5270
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
|
+
}
|
|
5271
5351
|
// The public payload remains purely application-authored. The private encoder
|
|
5272
5352
|
// type additionally admits the custody facts injected by the generated client
|
|
5273
5353
|
// after it obtains them from the framework-owned key ceremony.
|
|
@@ -5420,6 +5500,10 @@ export function generateTsClient(
|
|
|
5420
5500
|
ownedBirthDescriptor !== undefined
|
|
5421
5501
|
? `, [_OWNED_KEY_PAYLOAD_FIELD]: _ownedBirthKey.ownedKeySubject`
|
|
5422
5502
|
: ""
|
|
5503
|
+
}${
|
|
5504
|
+
ownedBirthDescriptor !== undefined && businessBirth.keyField === undefined
|
|
5505
|
+
? `, ${JSON.stringify(BUSINESS_BIRTH_KEY_PAYLOAD_FIELD)}: _birthKey`
|
|
5506
|
+
: ""
|
|
5423
5507
|
} }`
|
|
5424
5508
|
)};`,
|
|
5425
5509
|
` const result = await holon.offerCall(_call, { actor: principal, returnCreated: true });`,
|
|
@@ -7076,6 +7160,7 @@ export function generateTsClient(
|
|
|
7076
7160
|
` compatibility: {`,
|
|
7077
7161
|
` lawIdentity: sha256Digest(${hashConst}),`,
|
|
7078
7162
|
` matrixUsda: ${clientInterface === undefined ? "new Uint8Array()" : interfaceConstName(opts.packageName)},`,
|
|
7163
|
+
...(domainRequirements.length === 0 ? [] : [` domainRequirements: ${tsDomainRequirementsConstName(opts.packageName)},`]),
|
|
7079
7164
|
` readyCommands: [${rootBirths
|
|
7080
7165
|
.map(
|
|
7081
7166
|
(birth) =>
|
|
@@ -7093,6 +7178,20 @@ export function generateTsClient(
|
|
|
7093
7178
|
);
|
|
7094
7179
|
}
|
|
7095
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
|
+
|
|
7096
7195
|
// RECURSIVE PAYLOAD TYPES (`z.lazy`): emit each minted named alias once. Sorted by name for
|
|
7097
7196
|
// byte-determinism; TS hoists type aliases so referencing them above is fine.
|
|
7098
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
|
" }",
|