@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
package/src/births.ts
CHANGED
|
@@ -4,10 +4,19 @@
|
|
|
4
4
|
// QuickJS engine}. If a file isn't this / hosting this / authoring for this / proving this — it's gone.
|
|
5
5
|
|
|
6
6
|
import { z } from "zod";
|
|
7
|
-
import type
|
|
7
|
+
import { __declareBirthKeyWriteOnce, type AggregateHandle } from "./aggregate.js";
|
|
8
8
|
import type { AggregateRef } from "./authoring.js";
|
|
9
9
|
import type { Ports } from "./ctx.js";
|
|
10
|
-
import
|
|
10
|
+
import {
|
|
11
|
+
CANONICAL_UUID_RE,
|
|
12
|
+
isUuidIdSchema,
|
|
13
|
+
typedIdShapeRefusal,
|
|
14
|
+
type Field,
|
|
15
|
+
type FieldValue,
|
|
16
|
+
type NomosRefValue,
|
|
17
|
+
type TypedId,
|
|
18
|
+
} from "./fields.js";
|
|
19
|
+
import { brandedIdMetaOf } from "./value_object.js";
|
|
11
20
|
import {
|
|
12
21
|
object,
|
|
13
22
|
relationType,
|
|
@@ -42,6 +51,10 @@ export interface BirthRecipe {
|
|
|
42
51
|
/** Target-owned roles granted at genesis to the cryptographically verified immediate parent workspace.
|
|
43
52
|
* Values are law constants; the kernel supplies the parent workspace id after verifying the birth. */
|
|
44
53
|
readonly parentWorkspaceRoles?: readonly WorkspaceRole[];
|
|
54
|
+
/** The typed key the {@link workspace} name hashes (`workspaceNameForKey(namespace, birthKey)`). Carried
|
|
55
|
+
* on the recipe (`nomos:birthKey`) by a business birth whose law declares its key type, so the kernel can
|
|
56
|
+
* check the key's shape and re-derive the name. Absent on every other recipe (historical law included). */
|
|
57
|
+
readonly birthKey?: string;
|
|
45
58
|
}
|
|
46
59
|
|
|
47
60
|
declare const WORKSPACE_ROLE: unique symbol;
|
|
@@ -129,13 +142,20 @@ export interface BusinessBirthDecl<
|
|
|
129
142
|
readonly __isBusinessBirth: true;
|
|
130
143
|
readonly aggregate: AggregateHandle<string, F>;
|
|
131
144
|
readonly payloadSchema: z.ZodObject<z.ZodRawShape> & z.ZodType<P>;
|
|
132
|
-
/**
|
|
145
|
+
/** How this birth's workspace name is keyed (see `architecture/choosing_a_birth_key.md`). `"minted"` —
|
|
146
|
+
* the default — keys the name by a Nomos-minted id drawn inside the birth itself; `"field"` keys it by one
|
|
147
|
+
* declared, birth-only TYPED ID fact. */
|
|
148
|
+
readonly keyMode: "minted" | "field";
|
|
149
|
+
/** The key's typed-id family and brand (the kernel's era-ruled `nomos:birthKeyType` declaration). */
|
|
150
|
+
readonly keyType: Omit<BirthKeyDeclaration, "namespace">;
|
|
151
|
+
/** The declared business key of a `"field"`-keyed birth. A `"minted"` birth has no business key:
|
|
152
|
+
* calling this throws, because its name is drawn by Nomos when the birth runs. */
|
|
133
153
|
readonly key: (payload: P) => string;
|
|
134
|
-
/** The complete custody address
|
|
135
|
-
*
|
|
154
|
+
/** The complete custody address a `"field"`-keyed birth gives the born root. A `"minted"` birth's
|
|
155
|
+
* address is the born workspace its birth returns (carry that `NomosRef`); calling this throws. */
|
|
136
156
|
readonly reference: (payload: P) => NomosRefValue;
|
|
137
|
-
/** The declared business fact used by {@link key}
|
|
138
|
-
readonly keyField: string;
|
|
157
|
+
/** The declared business fact used by {@link key} (`"field"` births only; `undefined` when minted). */
|
|
158
|
+
readonly keyField: string | undefined;
|
|
139
159
|
/** Framework-derived initial authoring. Tenant code marks aggregate fields `.fromBirth()` instead. */
|
|
140
160
|
readonly plan: (root: AggregateRef<F>, payload: P, ctx: Ports) => unknown;
|
|
141
161
|
/** Business roles the founder holds from the first valid state. Nomos seats them inside genesis. */
|
|
@@ -160,17 +180,100 @@ export type BirthFactKeys<F extends Record<string, Field>> = {
|
|
|
160
180
|
[K in keyof F]-?: F[K] extends { readonly isFromBirth: true } ? K : never;
|
|
161
181
|
}[keyof F] & string;
|
|
162
182
|
|
|
163
|
-
|
|
164
|
-
|
|
183
|
+
/**
|
|
184
|
+
* The `.fromBirth()` facts that may key a birth: TYPED IDS ONLY — a `t.brandedId(Name)`, `t.uuid()` or
|
|
185
|
+
* field (value type {@link TypedId}), or a string reference (`t.ref` / `t.foreignRef`, the field form of `idOf`).
|
|
186
|
+
* A plain `t.string()`, an enum, a number, or anything else is NOT a key, and naming it in `key:` is a
|
|
187
|
+
* compile error. There is no name heuristic: `estateId: t.string()` is a plain string however it is named.
|
|
188
|
+
*/
|
|
189
|
+
export type BirthKeyFactKeys<F extends Record<string, Field>> = {
|
|
190
|
+
[K in BirthFactKeys<F>]: F[K] extends { readonly kind: "ref" }
|
|
191
|
+
? (FieldValue<F[K]> extends string ? K : never)
|
|
192
|
+
: FieldValue<F[K]> extends TypedId<string> ? K : never;
|
|
165
193
|
}[BirthFactKeys<F>] & string;
|
|
166
194
|
|
|
167
195
|
export type BusinessBirthPayload<F extends Record<string, Field>> = {
|
|
168
196
|
readonly [K in BirthFactKeys<F>]: FieldValue<F[K]>;
|
|
169
197
|
};
|
|
170
198
|
|
|
199
|
+
/** Where every birth-key compile error points. */
|
|
200
|
+
export const CHOOSING_A_BIRTH_KEY_DOC = "architecture/choosing_a_birth_key.md";
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* The typed-id families a birth key may have. The kernel reads this off the law (the
|
|
204
|
+
* `nomos:birthKeyType` era key on the birthing intent) and checks every carried birth's key against it.
|
|
205
|
+
* - `minted` — the default: `<Aggregate>_<uuidv7>`, drawn by Nomos inside the birth;
|
|
206
|
+
* - `brandedId` — `t.brandedId(Name)`;
|
|
207
|
+
* - `uuid` — `t.uuid()`, canonical lowercase;
|
|
208
|
+
* - `ref` — a string `t.ref(Aggregate)` / `t.foreignRef` (the field form of `idOf`): an `Aggregate` id.
|
|
209
|
+
*/
|
|
210
|
+
export type BirthKeyType = "minted" | "brandedId" | "uuid" | "ref";
|
|
211
|
+
|
|
212
|
+
/** The law's declaration of a birth's key: its typed-id family, the brand (the `brandedId` name, `Uuid`,
|
|
213
|
+
* the referenced aggregate, or for `minted` the born aggregate), and the name's namespace. */
|
|
214
|
+
export interface BirthKeyDeclaration {
|
|
215
|
+
readonly type: BirthKeyType;
|
|
216
|
+
readonly brand: string;
|
|
217
|
+
readonly namespace: string;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/** The typed-id family of a `.fromBirth()` field, or `undefined` when it is not a typed id. Reads the
|
|
221
|
+
* field's TYPE (its schema brand / reference kind) — never its name. */
|
|
222
|
+
export function birthKeyTypeOfField(field: Field): Omit<BirthKeyDeclaration, "namespace"> | undefined {
|
|
223
|
+
if (field.kind === "ref") {
|
|
224
|
+
if (field.nomosRef !== undefined || field.versionedForeignRef !== undefined) return undefined;
|
|
225
|
+
const target = field.refAggregateId;
|
|
226
|
+
return target === undefined ? undefined : { type: "ref", brand: target };
|
|
227
|
+
}
|
|
228
|
+
if (field.kind !== "string") return undefined;
|
|
229
|
+
const schema = field.zod as z.ZodTypeAny;
|
|
230
|
+
if (isUuidIdSchema(schema)) return { type: "uuid", brand: "Uuid" };
|
|
231
|
+
const brand = brandedIdMetaOf(schema);
|
|
232
|
+
if (brand !== undefined) return { type: "brandedId", brand };
|
|
233
|
+
return undefined;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
const MINTED_KEY_UUIDV7 = /^[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/;
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Why `key` is not a well-formed key for `declaration` (`undefined` when it is). The ONE rule set, mirrored
|
|
240
|
+
* byte-for-byte by the kernel (`kernel/src/birth_stage.rs` `typed_birth_key_refusal`), which re-checks every
|
|
241
|
+
* carried birth whose law declares a typed key:
|
|
242
|
+
* - every key is a typed id value ({@link typedIdShapeRefusal}: non-empty, ≤ 256 UTF-8 bytes, no control
|
|
243
|
+
* character — so the `(namespace, key)` unit separator can never appear inside a key);
|
|
244
|
+
* - `minted` is exactly `<brand>_<uuidv7>`; `uuid` is the canonical lowercase uuid.
|
|
245
|
+
*/
|
|
246
|
+
export function birthKeyShapeRefusal(declaration: BirthKeyDeclaration, key: unknown): string | undefined {
|
|
247
|
+
const shape = typedIdShapeRefusal(key);
|
|
248
|
+
if (shape !== undefined) return shape;
|
|
249
|
+
const text = key as string;
|
|
250
|
+
switch (declaration.type) {
|
|
251
|
+
case "minted": {
|
|
252
|
+
const prefix = `${declaration.brand}_`;
|
|
253
|
+
return text.startsWith(prefix) && MINTED_KEY_UUIDV7.test(text.slice(prefix.length))
|
|
254
|
+
? undefined
|
|
255
|
+
: `a minted key is ${declaration.brand}_<uuidv7>`;
|
|
256
|
+
}
|
|
257
|
+
case "uuid":
|
|
258
|
+
return CANONICAL_UUID_RE.test(text) ? undefined : "a uuid key is a canonical lowercase uuid";
|
|
259
|
+
case "brandedId":
|
|
260
|
+
case "ref":
|
|
261
|
+
return undefined;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
171
265
|
export interface BusinessBirthSpec<F extends Record<string, Field>> {
|
|
172
|
-
/**
|
|
173
|
-
|
|
266
|
+
/**
|
|
267
|
+
* How the born workspace is named.
|
|
268
|
+
* - OMIT IT (the default): Nomos mints the key inside the birth (`<Aggregate>_<uuidv7>`), so two births
|
|
269
|
+
* never share a name.
|
|
270
|
+
* - `key: "estateId"` — a `.fromBirth()` TYPED ID field: `t.brandedId(Name)`, `t.uuid()` or a string
|
|
271
|
+
* `t.ref(Aggregate)` (the field form of `idOf`). A plain `t.string()` is a compile error (see {@link BirthKeyFactKeys}).
|
|
272
|
+
* A keyed name is `workspaceNameForKey(namespace, key)` — global to the namespace and fixed at birth: Nomos
|
|
273
|
+
* refuses any later write that changes the key, and the kernel re-checks the key's shape and the name it
|
|
274
|
+
* derives on every birth. See `architecture/choosing_a_birth_key.md`.
|
|
275
|
+
*/
|
|
276
|
+
readonly key?: BirthKeyFactKeys<F>;
|
|
174
277
|
readonly namespace?: string;
|
|
175
278
|
/** Business policy: roles held by the founder from the aggregate's first valid state. */
|
|
176
279
|
readonly founder?: {
|
|
@@ -221,15 +324,39 @@ export function birth<F extends Record<string, Field>>(
|
|
|
221
324
|
`birth(${aggregate.id}): no birth facts are declared — mark every required initial business field with .fromBirth()`,
|
|
222
325
|
);
|
|
223
326
|
}
|
|
224
|
-
const
|
|
225
|
-
if (
|
|
226
|
-
throw new Error(`birth(${aggregate.id}): key '${String(spec.key)}' must be a declared .fromBirth() field`);
|
|
227
|
-
}
|
|
228
|
-
if (keyField.kind !== "string" && keyField.kind !== "enum" && keyField.kind !== "ref") {
|
|
327
|
+
const rawKey = (spec as { key?: unknown }).key;
|
|
328
|
+
if (rawKey !== undefined && typeof rawKey !== "string") {
|
|
229
329
|
throw new Error(
|
|
230
|
-
`birth(${aggregate.id}): key
|
|
330
|
+
`birth(${aggregate.id}): a birth key is \`key: "<typed-id fromBirth field>"\`; omit \`key\` for the ` +
|
|
331
|
+
`default Nomos-minted key. See ${CHOOSING_A_BIRTH_KEY_DOC}.`,
|
|
231
332
|
);
|
|
232
333
|
}
|
|
334
|
+
const keyFieldName: string | undefined = rawKey;
|
|
335
|
+
let keyType: Omit<BirthKeyDeclaration, "namespace"> = { type: "minted", brand: aggregate.id };
|
|
336
|
+
if (keyFieldName !== undefined) {
|
|
337
|
+
const keyField = (aggregate.fields as Record<string, Field>)[keyFieldName];
|
|
338
|
+
if (keyField === undefined || keyField.isFromBirth !== true) {
|
|
339
|
+
throw new Error(
|
|
340
|
+
`birth(${aggregate.id}): key field '${keyFieldName}' must be a declared .fromBirth() field — a birth key ` +
|
|
341
|
+
`is a fact the birth itself supplies, fixed for the life of the workspace. See ${CHOOSING_A_BIRTH_KEY_DOC}.`,
|
|
342
|
+
);
|
|
343
|
+
}
|
|
344
|
+
// The compile-time type (BirthKeyFactKeys) already refuses this; the same judgement by TYPE refuses an
|
|
345
|
+
// untyped (JS / cast) caller. Never by the field's name.
|
|
346
|
+
const typed = birthKeyTypeOfField(keyField);
|
|
347
|
+
if (typed === undefined) {
|
|
348
|
+
throw new Error(
|
|
349
|
+
`birth(${aggregate.id}): key field '${keyFieldName}' is a '${keyField.kind}' field, not a typed id, so it ` +
|
|
350
|
+
`cannot name a workspace. A birth key is t.brandedId(Name), t.uuid() or a string t.ref(Aggregate) ` +
|
|
351
|
+
`— or omit \`key\` for the Nomos-minted default. See ${CHOOSING_A_BIRTH_KEY_DOC}.`,
|
|
352
|
+
);
|
|
353
|
+
}
|
|
354
|
+
keyType = typed;
|
|
355
|
+
// The name is fixed at birth, so the key is write-once — enforced by the framework at the kernel
|
|
356
|
+
// (the aggregate's composed invariant), not by tenant code. An encrypted key (an `.owned()` content
|
|
357
|
+
// field) is ciphertext in the snapshot and cannot be compared, so it is not registered.
|
|
358
|
+
if (keyField.isEncrypted !== true) __declareBirthKeyWriteOnce(aggregate as AggregateHandle, keyFieldName);
|
|
359
|
+
}
|
|
233
360
|
const payloadSchema = z.object(Object.fromEntries(facts.map(([name, field]) => [name, field.zod])));
|
|
234
361
|
const founderRoles = [...new Set(spec.founder?.roles ?? [])];
|
|
235
362
|
for (const role of founderRoles) {
|
|
@@ -272,13 +399,31 @@ export function birth<F extends Record<string, Field>>(
|
|
|
272
399
|
__isBusinessBirth: true,
|
|
273
400
|
aggregate,
|
|
274
401
|
payloadSchema,
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
402
|
+
keyMode: keyFieldName === undefined ? "minted" as const : "field" as const,
|
|
403
|
+
keyType,
|
|
404
|
+
key: (payload: Payload) => {
|
|
405
|
+
if (keyFieldName === undefined) {
|
|
406
|
+
throw new Error(
|
|
407
|
+
`birth(${aggregate.id}) is keyed by a Nomos-minted id drawn when the birth runs; it has no business key. ` +
|
|
408
|
+
`Carry the born workspace's NomosRef instead. See ${CHOOSING_A_BIRTH_KEY_DOC}.`,
|
|
409
|
+
);
|
|
410
|
+
}
|
|
411
|
+
return String(payload[keyFieldName as keyof Payload]);
|
|
412
|
+
},
|
|
413
|
+
reference: (payload: Payload): NomosRefValue => {
|
|
414
|
+
if (keyFieldName === undefined) {
|
|
415
|
+
throw new Error(
|
|
416
|
+
`birth(${aggregate.id}).reference(facts): a Nomos-minted birth's address is not derivable from its facts — ` +
|
|
417
|
+
`it is the born workspace the birth returns. Carry that NomosRef. See ${CHOOSING_A_BIRTH_KEY_DOC}.`,
|
|
418
|
+
);
|
|
419
|
+
}
|
|
420
|
+
return {
|
|
421
|
+
workspace: workspaceNameForKey(namespace, String(payload[keyFieldName as keyof Payload])),
|
|
422
|
+
type: aggregate.id,
|
|
423
|
+
id: rootId,
|
|
424
|
+
};
|
|
425
|
+
},
|
|
426
|
+
keyField: keyFieldName,
|
|
282
427
|
plan: (root: AggregateRef<F>, payload: Payload, ctx: Ports) => {
|
|
283
428
|
const writable = root as unknown as { set(field: string, value: unknown): unknown };
|
|
284
429
|
for (const [name] of facts) writable.set(name, payload[name as keyof Payload]);
|
|
@@ -325,12 +470,50 @@ declare const nomos: { sha256(input: string): string };
|
|
|
325
470
|
* ("estate", "estate-abc123") -> estate-83662e729ba18cd04bde52c7a8d854594833d70e
|
|
326
471
|
* ("home", "user:ada") -> home-561af44c94b5b80824f1a9e8cfa1095105f3cb34
|
|
327
472
|
* ("catalogue", "cat-42") -> catalogue-f50bf3b9732dc36961b05b8e6503d96df5f3e88e
|
|
473
|
+
*
|
|
474
|
+
* NAMES ARE FIXED AT BIRTH. A business child (`birth(Aggregate, { key })`) is named exactly this way too —
|
|
475
|
+
* the birthing parent is NOT part of the name. CAP-788 briefly folded the parent into the hash; CAP-803
|
|
476
|
+
* withdrew that before release, because every child born before it is addressed by the name it was born
|
|
477
|
+
* with and a recomputation under a rule it was not born with addresses a workspace that does not exist.
|
|
478
|
+
* One parent per node is enforced where custody is, by the kernel's typed `[birth-child-already-parented]`
|
|
479
|
+
* refusal: a second parent's birth of an existing child name is refused, never re-routed.
|
|
480
|
+
*
|
|
481
|
+
* THE KEY IS MINTED BY DEFAULT (CAP-804). `birth(Aggregate, spec)` with no `key` hashes a Nomos-minted id
|
|
482
|
+
* ({@link mintBirthKey}) drawn inside the birth plan, so names cannot collide by construction; the address
|
|
483
|
+
* the app keeps is the born workspace's NomosRef. An explicit `key: { field, scope: "global" }` hashes a
|
|
484
|
+
* birth-only business fact exactly as above — see `architecture/choosing_a_birth_key.md`.
|
|
328
485
|
*/
|
|
329
486
|
export const WORKSPACE_NAME_SEP = "\u001f"; // unit separator disambiguates (namespace,key)
|
|
330
487
|
export function workspaceNameForKey(namespace: string, key: string): string {
|
|
331
488
|
return namespace + "-" + nomos.sha256(String(namespace) + WORKSPACE_NAME_SEP + String(key)).slice(0, 40);
|
|
332
489
|
}
|
|
333
490
|
|
|
491
|
+
/**
|
|
492
|
+
* @internal THE DEFAULT BIRTH KEY: a typed `<Aggregate>_<uuidv7>` drawn with `nomos.mint` INSIDE the birth
|
|
493
|
+
* plan. The sealed engine derives it from the intent's captured clock and captured entropy seed, so the
|
|
494
|
+
* author draws a fresh, unguessable id and every replay of that intent reproduces it byte-for-byte; the
|
|
495
|
+
* born name is then `workspaceNameForKey(namespace, mintedKey)` like any other birth.
|
|
496
|
+
*/
|
|
497
|
+
/** @internal The hidden payload field through which a generated client that must know a minted birth's
|
|
498
|
+
* address BEFORE dispatch (an encrypted `.owned()` birth seals to it) supplies a key it reserved from the
|
|
499
|
+
* kernel's own minter (`mintId`). Absent, the birth plan mints. Never part of the application payload. */
|
|
500
|
+
export const BUSINESS_BIRTH_KEY_PAYLOAD_FIELD = "__nomos_birth_key_4b9e2d";
|
|
501
|
+
|
|
502
|
+
/** @internal A key Nomos minted for `aggregateId`: the typed `<Aggregate>_<uuidv7>` shape, nothing else. */
|
|
503
|
+
export function isMintedBirthKey(aggregateId: string, key: string): boolean {
|
|
504
|
+
return birthKeyShapeRefusal({ type: "minted", brand: aggregateId, namespace: "" }, key) === undefined;
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
export function mintBirthKey(aggregateId: string): string {
|
|
508
|
+
const host = (globalThis as { nomos?: { mint?: (typeTag: string) => string } }).nomos;
|
|
509
|
+
if (typeof host?.mint !== "function") {
|
|
510
|
+
throw new Error(
|
|
511
|
+
`birth(${aggregateId}): Nomos mints this birth's key inside the sealed engine, but no nomos.mint capability is bound here`,
|
|
512
|
+
);
|
|
513
|
+
}
|
|
514
|
+
return host.mint(aggregateId);
|
|
515
|
+
}
|
|
516
|
+
|
|
334
517
|
/** A genesis chain, or a builder that receives the resolved child workspace name (so a seed step can embed
|
|
335
518
|
* it — e.g. `seedEstate` needs to write its own `estateName`). Passing a builder is the common case. */
|
|
336
519
|
// An array of genesis steps, OR a builder that receives the resolved child name. Typed as `unknown[]`
|
package/src/build_provenance.ts
CHANGED
|
@@ -43,8 +43,10 @@ export interface ProvenanceSubject {
|
|
|
43
43
|
export interface BuildProvenanceInput {
|
|
44
44
|
/** Package name from the project config. */
|
|
45
45
|
readonly packageName: string;
|
|
46
|
-
/**
|
|
46
|
+
/** The PACKAGE identity: this compiler's closure of `<name>.law.usdz`. Not what a unit-lane target serves. */
|
|
47
47
|
readonly domainHash: string;
|
|
48
|
+
/** THE LAW IDENTITY: the unit set identity of `<name>.units` (law_unit_set_identity.ts, CAP-773). */
|
|
49
|
+
readonly unitSetIdentity: string;
|
|
48
50
|
/** The transports the domainHash addresses (law archive, package asset, …). */
|
|
49
51
|
readonly artifacts: readonly { readonly name: string; readonly sha256?: string }[];
|
|
50
52
|
/** Per-domain compiled surface, already computed for the summary. */
|
|
@@ -61,6 +63,13 @@ export interface BuildProvenanceInput {
|
|
|
61
63
|
readonly builder: { readonly id: string; readonly version: string };
|
|
62
64
|
/** Source law modules, by feature — the external parameters of the build. */
|
|
63
65
|
readonly sourceModules?: readonly string[];
|
|
66
|
+
/** The released baseline the candidate extends (CAP-660): pinned by content address, naming its source. */
|
|
67
|
+
readonly baseline?: {
|
|
68
|
+
readonly sha256: string;
|
|
69
|
+
readonly source: "release" | "installed" | "units";
|
|
70
|
+
readonly release: string;
|
|
71
|
+
readonly unitSet: string;
|
|
72
|
+
};
|
|
64
73
|
}
|
|
65
74
|
|
|
66
75
|
/**
|
|
@@ -71,8 +80,11 @@ export interface BuildProvenanceInput {
|
|
|
71
80
|
*/
|
|
72
81
|
export function buildProvenanceStatement(input: BuildProvenanceInput): Record<string, unknown> {
|
|
73
82
|
const subject: ProvenanceSubject[] = [
|
|
74
|
-
// The law closure
|
|
83
|
+
// The package: `<name>.law` is the compiler's closure of the `.law.usdz` bytes (the package identity).
|
|
75
84
|
{ name: `${input.packageName}.law`, digest: { sha256: input.domainHash } },
|
|
85
|
+
// The law: `<name>.units` names the unit set identity, what releases, clients and qualification name.
|
|
86
|
+
// A target serves a closure its own kernel composes from these units, which neither digest names.
|
|
87
|
+
{ name: `${input.packageName}.units`, digest: { sha256: input.unitSetIdentity } },
|
|
76
88
|
...input.artifacts
|
|
77
89
|
.filter((a): a is { name: string; sha256: string } => typeof a.sha256 === "string")
|
|
78
90
|
.map((a) => ({ name: a.name, digest: { sha256: a.sha256 } })),
|
|
@@ -90,6 +102,9 @@ export function buildProvenanceStatement(input: BuildProvenanceInput): Record<st
|
|
|
90
102
|
...(input.sourceModules === undefined
|
|
91
103
|
? {}
|
|
92
104
|
: { sourceModules: [...input.sourceModules].sort() }),
|
|
105
|
+
// The baseline is an external parameter: the same source compiled against another
|
|
106
|
+
// baseline is a different build, and a publisher can gate on which release it extends.
|
|
107
|
+
...(input.baseline === undefined ? {} : { baseline: { ...input.baseline } }),
|
|
93
108
|
},
|
|
94
109
|
// What the compile RESOLVED — the surface each domain actually exposes. A
|
|
95
110
|
// consumer diffing two provenances sees the law's shape change without
|
package/src/business_birth.ts
CHANGED
|
@@ -13,6 +13,11 @@ import { ensure } from "./authoring.js";
|
|
|
13
13
|
import { directive, type Directive } from "./directive.js";
|
|
14
14
|
import {
|
|
15
15
|
birth,
|
|
16
|
+
BUSINESS_BIRTH_KEY_PAYLOAD_FIELD,
|
|
17
|
+
birthKeyShapeRefusal,
|
|
18
|
+
isMintedBirthKey,
|
|
19
|
+
type BirthKeyDeclaration,
|
|
20
|
+
mintBirthKey,
|
|
16
21
|
requireParentAttestedBirth,
|
|
17
22
|
workspaceNameForKey,
|
|
18
23
|
type BusinessBirthDecl,
|
|
@@ -164,7 +169,13 @@ export interface BusinessBirthMetadata {
|
|
|
164
169
|
readonly publicPayloadSchema: z.ZodObject<z.ZodRawShape>;
|
|
165
170
|
readonly rootId: string;
|
|
166
171
|
readonly namespace: string;
|
|
167
|
-
|
|
172
|
+
/** `"minted"` (default): the name hashes a Nomos-minted id; `"field"`: it hashes {@link keyField}. */
|
|
173
|
+
readonly keyMode: "minted" | "field";
|
|
174
|
+
/** The declared business key field of a `"field"`-keyed birth; absent for a minted birth. */
|
|
175
|
+
readonly keyField?: string;
|
|
176
|
+
/** The key's typed-id family and brand: generated clients type the key by it (a `brandedId`/`uuid` key is
|
|
177
|
+
* its branded type, never a bare string). */
|
|
178
|
+
readonly keyType: { readonly type: import("./births.js").BirthKeyType; readonly brand: string };
|
|
168
179
|
/** Exact child-genesis offer route prepared before the parent birth dispatch. */
|
|
169
180
|
readonly seedDirectiveId: string;
|
|
170
181
|
/** Compiler-owned child-genesis routes which may be appended after the business seed. */
|
|
@@ -198,6 +209,7 @@ const INTERNAL_FIELDS = [
|
|
|
198
209
|
"principal",
|
|
199
210
|
"bornAt",
|
|
200
211
|
BUSINESS_BIRTH_SEED_PAYLOAD_FIELD,
|
|
212
|
+
BUSINESS_BIRTH_KEY_PAYLOAD_FIELD,
|
|
201
213
|
] as const;
|
|
202
214
|
|
|
203
215
|
/**
|
|
@@ -271,8 +283,16 @@ function lowerOne<P extends Record<string, unknown>, F extends Record<string, Fi
|
|
|
271
283
|
});
|
|
272
284
|
const parentSchema = seedSchema.extend({
|
|
273
285
|
...(encryptedBirth ? { [BUSINESS_BIRTH_SEED_PAYLOAD_FIELD]: seedCarrierSchema } : {}),
|
|
286
|
+
// A minted birth's client may reserve its key ahead of dispatch (an encrypted .owned() birth seals to
|
|
287
|
+
// the child's address). A field-keyed birth's law is unchanged: it declares no such field.
|
|
288
|
+
...(decl.keyMode === "minted" ? { [BUSINESS_BIRTH_KEY_PAYLOAD_FIELD]: z.string().min(1).optional() } : {}),
|
|
274
289
|
});
|
|
275
290
|
|
|
291
|
+
// THE TYPED BIRTH KEY (era key `nomos:birthKeyType`/`nomos:birthKeyBrand`/`nomos:birthKeyNamespace` on the
|
|
292
|
+
// birthing intent): the kernel reads it off the law and checks every carried recipe's `nomos:birthKey`
|
|
293
|
+
// against it — its shape, and that the child's name is `workspaceNameForKey(namespace, key)`.
|
|
294
|
+
const birthKeyDeclaration: BirthKeyDeclaration = { ...decl.keyType, namespace };
|
|
295
|
+
|
|
276
296
|
const seedBase = directive(seedId)
|
|
277
297
|
.ensures(decl.aggregate)
|
|
278
298
|
.payload(seedSchema)
|
|
@@ -327,7 +347,28 @@ function lowerOne<P extends Record<string, unknown>, F extends Record<string, Fi
|
|
|
327
347
|
.payload(parentSchema)
|
|
328
348
|
.plan((p, ctx) => {
|
|
329
349
|
const founder = p.principal as string;
|
|
330
|
-
|
|
350
|
+
// A name is fixed at birth: a minted birth (the default) hashes an id Nomos draws here, inside the
|
|
351
|
+
// plan, from the intent's captured entropy (replay reproduces it); a field-keyed birth hashes its
|
|
352
|
+
// declared birth-only fact. Either way the name is `workspaceNameForKey(namespace, key)`.
|
|
353
|
+
const reserved = (p as Record<string, unknown>)[BUSINESS_BIRTH_KEY_PAYLOAD_FIELD];
|
|
354
|
+
if (reserved !== undefined && !isMintedBirthKey(decl.aggregate.id, String(reserved))) {
|
|
355
|
+
throw new Error(
|
|
356
|
+
`birth(${decl.aggregate.id}): a reserved birth key must be one Nomos minted for ${decl.aggregate.id}; ` +
|
|
357
|
+
"an application cannot choose a workspace name",
|
|
358
|
+
);
|
|
359
|
+
}
|
|
360
|
+
const birthKey = decl.keyMode === "minted"
|
|
361
|
+
? (reserved !== undefined ? String(reserved) : mintBirthKey(decl.aggregate.id))
|
|
362
|
+
: decl.key(p as P);
|
|
363
|
+
// The same shape rule the kernel re-checks on the carried recipe (`nomos:birthKey`): refuse here, as
|
|
364
|
+
// the author, before a name is derived from a key the kernel would refuse.
|
|
365
|
+
const keyRefusal = birthKeyShapeRefusal(birthKeyDeclaration, birthKey);
|
|
366
|
+
if (keyRefusal !== undefined) {
|
|
367
|
+
throw new Error(
|
|
368
|
+
`[birth-key-shape] birth(${decl.aggregate.id}): the ${birthKeyDeclaration.type} key is malformed: ${keyRefusal}`,
|
|
369
|
+
);
|
|
370
|
+
}
|
|
371
|
+
const child = workspaceNameForKey(namespace, birthKey);
|
|
331
372
|
const foundingKeyHash = foundedBirthKeyHash(founder);
|
|
332
373
|
const founded = foundingKeyHash !== undefined;
|
|
333
374
|
if (founded) {
|
|
@@ -358,6 +399,7 @@ function lowerOne<P extends Record<string, unknown>, F extends Record<string, Fi
|
|
|
358
399
|
Object.assign(childPayload, sealedSeed as Record<string, unknown>);
|
|
359
400
|
}
|
|
360
401
|
delete childPayload[BUSINESS_BIRTH_SEED_PAYLOAD_FIELD];
|
|
402
|
+
delete childPayload[BUSINESS_BIRTH_KEY_PAYLOAD_FIELD];
|
|
361
403
|
const chain: Array<Record<string, unknown>> = [
|
|
362
404
|
domainInstallGenesisOffer(
|
|
363
405
|
DomainInstallExecutor.StageZero,
|
|
@@ -417,7 +459,7 @@ function lowerOne<P extends Record<string, unknown>, F extends Record<string, Fi
|
|
|
417
459
|
if (founded) {
|
|
418
460
|
// No provider verifier, inherited model authority (operators ride the founding step instead),
|
|
419
461
|
// parent workspace role or epoch edge: those belong to the certificate lane's attestation.
|
|
420
|
-
birth({ workspace: child, genesisChain: chain });
|
|
462
|
+
birth({ workspace: child, genesisChain: chain, birthKey });
|
|
421
463
|
return [];
|
|
422
464
|
}
|
|
423
465
|
|
|
@@ -443,6 +485,7 @@ function lowerOne<P extends Record<string, unknown>, F extends Record<string, Fi
|
|
|
443
485
|
birth({
|
|
444
486
|
workspace: child,
|
|
445
487
|
genesisChain: chain,
|
|
488
|
+
birthKey,
|
|
446
489
|
...(decl.parentWorkspaceRoles.length > 0
|
|
447
490
|
? { parentWorkspaceRoles: decl.parentWorkspaceRoles }
|
|
448
491
|
: {}),
|
|
@@ -475,7 +518,9 @@ function lowerOne<P extends Record<string, unknown>, F extends Record<string, Fi
|
|
|
475
518
|
publicPayloadSchema: publicSchema,
|
|
476
519
|
rootId,
|
|
477
520
|
namespace,
|
|
478
|
-
|
|
521
|
+
keyMode: decl.keyMode,
|
|
522
|
+
...(decl.keyField !== undefined ? { keyField: decl.keyField } : {}),
|
|
523
|
+
keyType: decl.keyType,
|
|
479
524
|
seedDirectiveId: seedId,
|
|
480
525
|
genesisDirectiveIds: [
|
|
481
526
|
"enrollSigner",
|
|
@@ -493,6 +538,7 @@ function lowerOne<P extends Record<string, unknown>, F extends Record<string, Fi
|
|
|
493
538
|
};
|
|
494
539
|
const parent = {
|
|
495
540
|
...birthBase,
|
|
541
|
+
birthKey: birthKeyDeclaration,
|
|
496
542
|
businessBirth: metadata,
|
|
497
543
|
[BUSINESS_BIRTH_POLICY_BINDING]: (domains: readonly string[]) => {
|
|
498
544
|
decl.__nomosBirthPolicyDomains = domains;
|
|
@@ -32,6 +32,9 @@ import {
|
|
|
32
32
|
NOMOS_IDENTITY_FILENAME,
|
|
33
33
|
parseIdentityUsda,
|
|
34
34
|
} from "./identity_usda.js";
|
|
35
|
+
import { identityRegistryFromModel } from "./identity_model.js";
|
|
36
|
+
import { NOMOS_BASELINE_FILENAME, isBaselineUsda, parseBaselineUsda, type ReleasedBaseline } from "./baseline_usda.js";
|
|
37
|
+
import { BaselineRefusal, importBaseline } from "./baseline_import.js";
|
|
35
38
|
import {
|
|
36
39
|
validateNomosPackageConfig,
|
|
37
40
|
type NormalizedNomosPackageConfig,
|
|
@@ -150,11 +153,13 @@ export async function loadComposedModulesWithSources(
|
|
|
150
153
|
}
|
|
151
154
|
|
|
152
155
|
/**
|
|
153
|
-
* Load the PRIOR identity surface for `--against <path>` (the evolve preview's baseline).
|
|
154
|
-
* supported
|
|
155
|
-
*
|
|
156
|
+
* Load the PRIOR identity surface for `--against <path>` (the evolve preview's baseline). Two
|
|
157
|
+
* artifacts are supported, directly or via a package dir: the committed `nomos.identity.usda`
|
|
158
|
+
* layer, and a released `nomos.baseline.usda` (CAP-660) — the continuity exported from custody,
|
|
159
|
+
* which also pre-fires the compile's baseline verification as findings. There is no
|
|
160
|
+
* interface/JSON sidecar lane: prior executable-law inspection belongs to the holon.
|
|
156
161
|
*/
|
|
157
|
-
function loadPriorEvolveLaw(arg: string): { law: EvolveLaw; source: string; note?: string } {
|
|
162
|
+
function loadPriorEvolveLaw(arg: string): { law: EvolveLaw; source: string; note?: string; baseline?: ReleasedBaseline } {
|
|
158
163
|
if (/^https?:\/\//.test(arg)) {
|
|
159
164
|
fail(
|
|
160
165
|
`--against does not support a live URL (${arg}) — the installed package's #58 stable-ids are ` +
|
|
@@ -167,12 +172,29 @@ function loadPriorEvolveLaw(arg: string): { law: EvolveLaw; source: string; note
|
|
|
167
172
|
let file = abs;
|
|
168
173
|
if (statSync(abs).isDirectory()) {
|
|
169
174
|
const identity = path.join(abs, NOMOS_IDENTITY_FILENAME);
|
|
175
|
+
const baseline = path.join(abs, NOMOS_BASELINE_FILENAME);
|
|
170
176
|
if (existsSync(identity)) file = identity;
|
|
171
|
-
else
|
|
177
|
+
else if (existsSync(baseline)) file = baseline;
|
|
178
|
+
else fail(`--against: no ${NOMOS_IDENTITY_FILENAME} or ${NOMOS_BASELINE_FILENAME} under ${abs}`);
|
|
179
|
+
}
|
|
180
|
+
const text = readFileSync(file, "utf8");
|
|
181
|
+
if (isBaselineUsda(text)) {
|
|
182
|
+
let baseline: ReleasedBaseline;
|
|
183
|
+
try {
|
|
184
|
+
baseline = parseBaselineUsda(text);
|
|
185
|
+
} catch (e) {
|
|
186
|
+
fail(`--against: could not parse ${file}: ${(e as Error).message}`);
|
|
187
|
+
}
|
|
188
|
+
return {
|
|
189
|
+
law: evolveLawFromLockfile(baseline.continuity),
|
|
190
|
+
source: file,
|
|
191
|
+
baseline,
|
|
192
|
+
note: `released baseline (${baseline.release === "none" ? "unreleased units" : `release ${baseline.release}`}, law ${baseline.unitSet}): identities and wire allocations are verified against the ledger; value contracts are not carried by released law.`,
|
|
193
|
+
};
|
|
172
194
|
}
|
|
173
195
|
let lock: CompilerIdentityModel;
|
|
174
196
|
try {
|
|
175
|
-
lock = parseIdentityUsda(
|
|
197
|
+
lock = parseIdentityUsda(text);
|
|
176
198
|
} catch (e) {
|
|
177
199
|
fail(`--against: could not parse ${file}: ${(e as Error).message}`);
|
|
178
200
|
}
|
|
@@ -229,6 +251,28 @@ async function main(): Promise<void> {
|
|
|
229
251
|
const current = currentEvolveLaw(modules, lock);
|
|
230
252
|
const diff = evolveDiff(prior.law, current);
|
|
231
253
|
findings.push(...diff.findings);
|
|
254
|
+
if (prior.baseline !== undefined) {
|
|
255
|
+
// The compile's own baseline verification, pre-fired here with the same typed reasons
|
|
256
|
+
// (baseline_import.ts): a diverged ledger or wire allocation is named before any compile.
|
|
257
|
+
try {
|
|
258
|
+
importBaseline(
|
|
259
|
+
identityRegistryFromModel(lock),
|
|
260
|
+
prior.baseline,
|
|
261
|
+
modules.flatMap((mod) => [mod.domain ?? mod.name, mod.name]),
|
|
262
|
+
);
|
|
263
|
+
} catch (error) {
|
|
264
|
+
if (!(error instanceof BaselineRefusal)) throw error;
|
|
265
|
+
const details = error.details.length === 0 ? [error.message] : error.details;
|
|
266
|
+
for (const detail of details) {
|
|
267
|
+
findings.push({
|
|
268
|
+
domain: modules[0]?.domain ?? modules[0]?.name ?? "package",
|
|
269
|
+
level: "refuse",
|
|
270
|
+
what: `[${error.code}] ${detail}`,
|
|
271
|
+
fix: error.message.split("\n")[0] ?? error.message,
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
}
|
|
232
276
|
if (!asJson) console.log(`githolon check: evolve preview against ${prior.source}`);
|
|
233
277
|
}
|
|
234
278
|
|
|
@@ -14,9 +14,14 @@ import type {
|
|
|
14
14
|
import type { GeometryKind } from "./fields.js";
|
|
15
15
|
import { dartMemberName, unionArmClassName } from "./codegen_dart.js";
|
|
16
16
|
import { mintStableId } from "./stable_ids.js";
|
|
17
|
+
import { composedWireIdentity } from "./composed_wire_identity.js";
|
|
17
18
|
import { protobufFieldNumber } from "./protobuf_field_number.js";
|
|
18
19
|
import { provenanceRowIdentity, provenanceFieldIdentity, provenanceAuthorIdentity, provenanceContestedIdentity } from "./provenance_wire.js";
|
|
19
20
|
|
|
21
|
+
// The composed wire identity lives in its own node-free module so the compatibility graph can reach
|
|
22
|
+
// it from the law bundle; the generated codecs keep importing it from here.
|
|
23
|
+
export { composedWireIdentity };
|
|
24
|
+
|
|
20
25
|
export interface TsCallProtoFieldAccess {
|
|
21
26
|
readonly value: string;
|
|
22
27
|
readonly present?: string;
|
|
@@ -87,24 +92,6 @@ function requireUniqueFieldNumbers(
|
|
|
87
92
|
}
|
|
88
93
|
}
|
|
89
94
|
|
|
90
|
-
/**
|
|
91
|
-
* OpenUSD composition may share structurally identical anonymous records between
|
|
92
|
-
* operation contracts. `compileSparseLawGraphUsda` records that sharing by
|
|
93
|
-
* redirecting each duplicate field path to the canonical field prim. The wire
|
|
94
|
-
* identity must follow that composed path too; using the duplicate graph-local
|
|
95
|
-
* SID makes generated clients speak a different Protobuf schema from the law the
|
|
96
|
-
* kernel actually installs.
|
|
97
|
-
*/
|
|
98
|
-
export function composedWireIdentity(
|
|
99
|
-
paths: ReadonlyMap<string, string>,
|
|
100
|
-
entity: { readonly id: string; readonly sid: string; readonly wireKey?: string },
|
|
101
|
-
): string {
|
|
102
|
-
if (entity.wireKey !== undefined) return entity.wireKey;
|
|
103
|
-
const path = paths.get(entity.id);
|
|
104
|
-
const canonical = path?.match(/\/(?:Fields\/f_|Variants\/v_)([0-9a-f]{16})$/i)?.[1];
|
|
105
|
-
return canonical ?? entity.sid;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
95
|
interface NominalSumArm {
|
|
109
96
|
readonly discriminator: string;
|
|
110
97
|
readonly variant: LawAnonymousValueVariantNode;
|