@githolon/dsl 0.102.4 → 0.103.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/compile_package.mjs +43 -2
- package/compile_proof.d.mts +47 -0
- package/compile_proof.mjs +343 -0
- package/dart/CHANGELOG.md +7 -0
- package/dart/lib/src/dev_cloud.dart +8 -8
- package/dart/lib/src/nms_generated_runtime.dart +2 -0
- package/dart/lib/src/runner/node-entry.mjs +5504 -1236
- package/dart/lib/src/runner/offline-install.mjs +4578 -528
- package/dart/lib/src/test_harness.dart +206 -152
- package/dart/lib/src/webview_bridge.dart +196 -89
- package/dart/pubspec.lock +1 -1
- package/dart/pubspec.yaml +2 -2
- package/dart/test/acquire_session_credentials_test.dart +4 -3
- package/dart/test/adv_realm_interference_test.dart +7 -6
- package/dart/test/cloud_bound_topology_test.dart +3 -3
- package/dart/test/cluster_births_test.dart +4 -3
- package/dart/test/cluster_device_keyed_acceptance_test.dart +10 -16
- package/dart/test/cluster_local_autosync_test.dart +3 -2
- package/dart/test/cluster_scale_test.dart +3 -2
- package/dart/test/dlq_session_surface_test.dart +7 -6
- package/dart/test/first_app_gauntlet_test.dart +14 -21
- package/dart/test/fixtures/wedge/fake-install.mjs +1 -1
- package/dart/test/four_layer_nested_topology_test.dart +4 -4
- package/dart/test/harness_phase_timeout_test.dart +2 -2
- package/dart/test/local_sync_inert_test.dart +1 -1
- package/dart/test/parent_signer_recovery_test.dart +2 -2
- package/dart/test/sealing_client_test.dart +4 -3
- package/dart/test/session_bound_test.dart +8 -7
- package/dart/test/test_harness_test.dart +13 -12
- package/dart/test/vo_read_typing_test.dart +4 -3
- package/dart/test/watch_after_offer_stall_test.dart +4 -3
- package/dart/test/webview_bridge_test.dart +102 -5
- package/dart_types/CHANGELOG.md +5 -0
- package/dart_types/lib/nomos_types.dart +9 -0
- package/dart_types/lib/src/nomos_ref.dart +20 -0
- package/dart_types/lib/src/optional_ref.dart +85 -0
- package/dart_types/lib/src/sha256_digest.dart +41 -0
- package/dart_types/lib/src/versioned_nomos_ref.dart +81 -0
- package/dart_types/pubspec.yaml +1 -1
- package/dart_types/test/optional_ref_test.dart +44 -0
- package/dart_types/test/sha256_digest_test.dart +18 -0
- package/package.json +9 -5
- package/runtime/generated-application-host.js +5365 -1105
- package/src/aggregate.ts +18 -0
- package/src/application_composition.ts +6 -32
- package/src/attested_core.ts +6 -3
- package/src/birth_meter.ts +1 -0
- package/src/birth_meter_ensure.ts +28 -2
- package/src/births.ts +1 -1
- package/src/build_package.ts +162 -538
- package/src/business_birth.ts +19 -3
- package/src/canonical_json.ts +21 -0
- package/src/check_package_main.ts +76 -35
- package/src/clear_directive.ts +19 -8
- package/src/codegen_dart.ts +305 -22
- package/src/codegen_dart_application.ts +844 -292
- package/src/codegen_dart_client.ts +1084 -407
- package/src/codegen_dart_scenario.ts +12 -12
- package/src/codegen_proof.ts +169 -39
- package/src/codegen_provider_dart.ts +17 -1
- package/src/codegen_ts.ts +298 -106
- package/src/compat_manifest.ts +2 -2
- package/src/compat_package_main.ts +8 -12
- package/src/compile_package_main.ts +1866 -597
- package/src/compose.ts +1 -1
- package/src/directive.ts +208 -15
- package/src/engine_entry.ts +165 -111
- package/src/evolve_preview.ts +43 -81
- package/src/extremum.ts +1 -1
- package/src/fields.ts +183 -4
- package/src/flow_gate.ts +75 -2
- package/src/framework/application_home.ts +2 -0
- package/src/framework/auth_provider_law.ts +2 -2
- package/src/framework/birth_meter.ts +7 -19
- package/src/framework/bootstrap.ts +0 -2
- package/src/framework/business_corrections.ts +9 -5
- package/src/framework/domain_lifecycle.ts +15 -29
- package/src/framework/encryption_scope.ts +2 -1
- package/src/framework/framework_aggregates.ts +2 -0
- package/src/framework/genesis.ts +16 -14
- package/src/framework/home.ts +20 -14
- package/src/framework/owned_custody.ts +9 -3
- package/src/framework/platform_admin.ts +3 -3
- package/src/framework/relations.ts +21 -13
- package/src/framework/signing_identity.ts +45 -40
- package/src/framework/workspace_invariant.ts +245 -0
- package/src/framework/workspaces.ts +23 -29
- package/src/index.ts +36 -16
- package/src/intent.ts +178 -6
- package/src/law_check.ts +32 -11
- package/src/law_graph.ts +2902 -0
- package/src/law_graph_usda.ts +1799 -0
- package/src/law_identity.ts +435 -0
- package/src/law_value_type.ts +230 -0
- package/src/lsp_server.ts +61 -31
- package/src/ops.ts +7 -0
- package/src/package_config.ts +141 -0
- package/src/package_dependencies.ts +108 -0
- package/src/package_dependency_usage.ts +150 -0
- package/src/package_source_imports.ts +71 -0
- package/src/plan_source_slice.ts +335 -0
- package/src/point_guard.ts +192 -0
- package/src/precondition.ts +44 -9
- package/src/read_projection_namespace.ts +1 -1
- package/src/runtime_zod.ts +44 -0
- package/src/secondary_ensure.ts +67 -14
- package/src/shape_compat.ts +1 -1
- package/src/stable_id_identity_bridge.ts +149 -0
- package/src/stable_id_lockfile.ts +278 -0
- package/src/stable_ids.ts +7 -2
- package/src/stable_ids_for_module.ts +348 -0
- package/src/stable_ids_types.ts +1 -1
- package/src/sum.ts +1 -1
- package/src/usd_state.ts +1 -1
- package/src/usda_relationship_integrity.ts +66 -0
- package/src/usda_text.ts +18 -0
- package/src/usdz.ts +78 -0
- package/src/value_object.ts +95 -0
- package/src/wire.ts +19 -0
- package/src/wire_encode.ts +22 -0
- package/src/workspace_sharding.ts +52 -36
- package/src/workspace_sharing.ts +28 -4
- package/src/zod_introspection.ts +1 -1
- package/dart/test/cluster_attested_test.dart +0 -201
- package/src/compile_engine.ts +0 -595
- package/src/manifest.ts +0 -1476
- package/src/read_projection_composition.ts +0 -117
- package/src/read_projection_manifest.ts +0 -115
- package/src/relation_composition.ts +0 -286
- package/src/usd.ts +0 -1967
- package/src/usd_layers.ts +0 -1776
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import type { DomainModule } from "./codegen_dart.js";
|
|
4
|
+
import { canonicalJson } from "./canonical_json.js";
|
|
5
|
+
import { encodeDriver } from "./drivers.js";
|
|
6
|
+
import type { Field } from "./fields.js";
|
|
7
|
+
import {
|
|
8
|
+
mintAggregateSid,
|
|
9
|
+
mintDomainSid,
|
|
10
|
+
mintFieldSid,
|
|
11
|
+
type StableAggregateId,
|
|
12
|
+
type StableFieldEvolution,
|
|
13
|
+
type StableFieldId,
|
|
14
|
+
type StableIds,
|
|
15
|
+
} from "./stable_ids.js";
|
|
16
|
+
import { encodeKernelSchema } from "./wire_encode.js";
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* The domain's STABLE-ID surface (#58): continuity-carried when the compiler attached
|
|
21
|
+
* `mod.stableIdContinuity` (renames resolved, sids + lineage carried), deterministic
|
|
22
|
+
* first-appearance minting otherwise — so the manifest stays a pure function of the
|
|
23
|
+
* module (+ its attached continuity). Any aggregate/field the continuity does not
|
|
24
|
+
* cover (a genuinely new appearance) mints fresh.
|
|
25
|
+
*/
|
|
26
|
+
export function stableIdsForModule(mod: DomainModule): StableIds {
|
|
27
|
+
const continuity = mod.stableIdContinuity;
|
|
28
|
+
const domainSid = continuity?.domain ?? mintDomainSid(mod.name);
|
|
29
|
+
// The CURRENT field KINDS off the live handles (the retype arm's identity input —
|
|
30
|
+
// a `t.string()` → `t.int()` retype moves `kind` while the driver stays Lww).
|
|
31
|
+
const handlesById = new Map(mod.aggregates.map((a) => [a.id, a]));
|
|
32
|
+
const out: Record<string, StableAggregateId> = {};
|
|
33
|
+
for (const agg of [...mod.aggregates].sort((a, b) => (a.id < b.id ? -1 : a.id > b.id ? 1 : 0))) {
|
|
34
|
+
const prior = continuity?.aggregates[agg.id];
|
|
35
|
+
const aggSid = prior?.sid ?? mintAggregateSid(domainSid, agg.id);
|
|
36
|
+
const handleFields = (handlesById.get(agg.id)?.fields ?? {}) as Record<string, Field | undefined>;
|
|
37
|
+
const fields: Record<string, StableFieldId> = {};
|
|
38
|
+
// `encodeKernelSchema` is the typed storage palette and omits virtual relations; stable field
|
|
39
|
+
// identity therefore follows the exact stored field set without constructing a JSON law manifest.
|
|
40
|
+
for (const fieldName of Object.keys(encodeKernelSchema(agg)).sort()) {
|
|
41
|
+
const pf = prior?.fields[fieldName];
|
|
42
|
+
const kind = handleFields[fieldName]?.kind;
|
|
43
|
+
const handle = handleFields[fieldName];
|
|
44
|
+
const details = handle === undefined ? undefined : fieldValueContractDetails(handle);
|
|
45
|
+
const driver = handle === undefined ? undefined : encodeDriver(handle.driver);
|
|
46
|
+
const schema =
|
|
47
|
+
handle === undefined || kind === undefined || details === undefined || driver === undefined
|
|
48
|
+
? undefined
|
|
49
|
+
: fieldSchemaIdentity(aggSid, pf?.sid ?? mintFieldSid(aggSid, fieldName), kind, details.contract, driver);
|
|
50
|
+
const evolutions =
|
|
51
|
+
handle === undefined || schema === undefined || driver === undefined || details === undefined
|
|
52
|
+
? []
|
|
53
|
+
: fieldEvolutionRules(handle, pf, aggSid, pf?.sid ?? mintFieldSid(aggSid, fieldName), details, driver, schema);
|
|
54
|
+
fields[fieldName] = {
|
|
55
|
+
sid: pf?.sid ?? mintFieldSid(aggSid, fieldName),
|
|
56
|
+
...(pf?.was !== undefined && pf.was.length > 0 ? { was: pf.was } : {}),
|
|
57
|
+
// The CURRENT kind, always from the live module (continuity never supplies it).
|
|
58
|
+
...(kind !== undefined ? { kind } : {}),
|
|
59
|
+
...(details !== undefined ? { contract: details.contract, shape: details.shape } : {}),
|
|
60
|
+
...(schema !== undefined ? { schema } : {}),
|
|
61
|
+
...(driver !== undefined ? { driver } : {}),
|
|
62
|
+
...(evolutions.length > 0 ? { evolutions } : {}),
|
|
63
|
+
// Birth-presence law rides identity: the evolve guard reads it (fromBirth may not be
|
|
64
|
+
// NEWLY declared on a sid that existed without it).
|
|
65
|
+
...(handleFields[fieldName]?.isFromBirth === true ? { fromBirth: true as const } : {}),
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
out[agg.id] = {
|
|
69
|
+
sid: aggSid,
|
|
70
|
+
...(prior?.was !== undefined && prior.was.length > 0 ? { was: prior.was } : {}),
|
|
71
|
+
fields,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
return { v: 1, domain: domainSid, aggregates: out };
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Identity of the value a generated TS/Dart conflict resolver is allowed to call `T`.
|
|
78
|
+
* This is deliberately narrower than the domain hash (unrelated law may roll out without
|
|
79
|
+
* invalidating a conflict) and richer than `Field.kind` (enum arms, value-object members,
|
|
80
|
+
* nullability, references and encrypted/plain decoding all matter to the public type).
|
|
81
|
+
*/
|
|
82
|
+
function fieldValueContractDetails(field: Field): { contract: string; shape: unknown } {
|
|
83
|
+
const valueObjectContract = (meta: NonNullable<Field["valueObject"]>) => ({
|
|
84
|
+
descriptor: meta.descriptor,
|
|
85
|
+
dependencies: meta.dependencies,
|
|
86
|
+
...(meta.geometry !== undefined ? { geometry: meta.geometry } : {}),
|
|
87
|
+
});
|
|
88
|
+
const contractShape = normalizeFieldContract({
|
|
89
|
+
v: "nomos-field-value-contract-v1",
|
|
90
|
+
kind: field.kind,
|
|
91
|
+
jsonSchema: z.toJSONSchema(field.zod as z.ZodTypeAny),
|
|
92
|
+
...(field.mapValueKind !== undefined ? { mapValueKind: field.mapValueKind } : {}),
|
|
93
|
+
...(field.jsonShape !== undefined ? { jsonShape: field.jsonShape } : {}),
|
|
94
|
+
...(field.valueObject !== undefined
|
|
95
|
+
? { valueObject: valueObjectContract(field.valueObject) }
|
|
96
|
+
: {}),
|
|
97
|
+
...(field.mapValueVo !== undefined
|
|
98
|
+
? { mapValueVo: valueObjectContract(field.mapValueVo) }
|
|
99
|
+
: {}),
|
|
100
|
+
...(field.refAggregateId !== undefined ? { refAggregateId: field.refAggregateId } : {}),
|
|
101
|
+
...(field.foreignRef !== undefined ? { foreignRef: field.foreignRef } : {}),
|
|
102
|
+
...(field.nomosRef !== undefined ? { nomosRef: field.nomosRef } : {}),
|
|
103
|
+
...(field.versionedForeignRef !== undefined
|
|
104
|
+
? { versionedForeignRef: field.versionedForeignRef }
|
|
105
|
+
: {}),
|
|
106
|
+
...(field.nullSemantic !== undefined ? { nullSemantic: field.nullSemantic } : {}),
|
|
107
|
+
...(field.isClearable === true ? { clearable: true } : {}),
|
|
108
|
+
...(field.isEncrypted === true ? { encrypted: true } : {}),
|
|
109
|
+
});
|
|
110
|
+
const contract = createHash("sha256")
|
|
111
|
+
.update(
|
|
112
|
+
`nomos-field-value-contract:v1:${canonicalJson(contractShape)}`,
|
|
113
|
+
"utf8",
|
|
114
|
+
)
|
|
115
|
+
.digest("hex");
|
|
116
|
+
return { contract, shape: contractShape };
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function fieldSchemaIdentity(
|
|
120
|
+
aggregateSid: string,
|
|
121
|
+
fieldSid: string,
|
|
122
|
+
kind: string,
|
|
123
|
+
valueContract: string,
|
|
124
|
+
driver: unknown,
|
|
125
|
+
): string {
|
|
126
|
+
return createHash("sha256")
|
|
127
|
+
.update(
|
|
128
|
+
canonicalJson({
|
|
129
|
+
v: "nomos-field-schema-v2",
|
|
130
|
+
aggregate: aggregateSid,
|
|
131
|
+
field: fieldSid,
|
|
132
|
+
kind,
|
|
133
|
+
valueContract,
|
|
134
|
+
driver,
|
|
135
|
+
}),
|
|
136
|
+
"utf8",
|
|
137
|
+
)
|
|
138
|
+
.digest("hex");
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function fieldEvolutionRules(
|
|
142
|
+
field: Field,
|
|
143
|
+
prior: StableFieldId | undefined,
|
|
144
|
+
aggregateSid: string,
|
|
145
|
+
fieldSid: string,
|
|
146
|
+
target: { contract: string; shape: unknown },
|
|
147
|
+
driver: unknown,
|
|
148
|
+
targetSchema: string,
|
|
149
|
+
): StableFieldEvolution[] {
|
|
150
|
+
const bySource = new Map<string, StableFieldEvolution>();
|
|
151
|
+
const add = (rule: StableFieldEvolution, origin: string) => {
|
|
152
|
+
if (rule.from === rule.to) return;
|
|
153
|
+
const existing = bySource.get(rule.from);
|
|
154
|
+
if (existing !== undefined && canonicalJson(existing) !== canonicalJson(rule)) {
|
|
155
|
+
throw new Error(
|
|
156
|
+
`field evolution collision for source contract ${rule.from}: ${origin} disagrees with an existing route. ` +
|
|
157
|
+
`One source contract must have exactly one deterministic destination.`,
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
bySource.set(rule.from, rule);
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
for (const rule of prior?.evolutions ?? []) add(rule, "the prior release");
|
|
164
|
+
|
|
165
|
+
for (const declaration of field.evolutions ?? []) {
|
|
166
|
+
const source = fieldValueContractDetails(declaration.source);
|
|
167
|
+
const sourceSchema = fieldSchemaIdentity(
|
|
168
|
+
aggregateSid,
|
|
169
|
+
fieldSid,
|
|
170
|
+
declaration.source.kind,
|
|
171
|
+
source.contract,
|
|
172
|
+
driver,
|
|
173
|
+
);
|
|
174
|
+
if (declaration.mode === "identity" && !safeIdentityProjection(source.shape, target.shape)) {
|
|
175
|
+
throw new Error(
|
|
176
|
+
`.evolvesFrom(source) is not a safe identity widening: some source values are not valid ` +
|
|
177
|
+
`under the target field. Supply a finite business transform as the second argument.`,
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
add(
|
|
181
|
+
{
|
|
182
|
+
from: sourceSchema,
|
|
183
|
+
to: targetSchema,
|
|
184
|
+
source: {
|
|
185
|
+
kind: declaration.source.kind,
|
|
186
|
+
...(declaration.source.mapValueKind !== undefined
|
|
187
|
+
? { mapValueKind: declaration.source.mapValueKind }
|
|
188
|
+
: {}),
|
|
189
|
+
},
|
|
190
|
+
mode: declaration.mode,
|
|
191
|
+
...(declaration.cases !== undefined ? { cases: declaration.cases } : {}),
|
|
192
|
+
},
|
|
193
|
+
".evolvesFrom()",
|
|
194
|
+
);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// The compiler silently owns genuinely compatible widening between consecutive releases.
|
|
198
|
+
// The prior lock carries the full value descriptor, so this is a proof, not a cast.
|
|
199
|
+
if (
|
|
200
|
+
prior?.schema !== undefined &&
|
|
201
|
+
prior.schema !== targetSchema &&
|
|
202
|
+
prior.shape !== undefined &&
|
|
203
|
+
canonicalJson(prior.driver) === canonicalJson(driver) &&
|
|
204
|
+
safeAutomaticWidening(prior.shape, target.shape) &&
|
|
205
|
+
!bySource.has(prior.schema)
|
|
206
|
+
) {
|
|
207
|
+
add(
|
|
208
|
+
{
|
|
209
|
+
from: prior.schema,
|
|
210
|
+
to: targetSchema,
|
|
211
|
+
source: {
|
|
212
|
+
kind: prior.kind ?? field.kind,
|
|
213
|
+
...((prior.shape as { mapValueKind?: string } | undefined)?.mapValueKind !== undefined
|
|
214
|
+
? { mapValueKind: (prior.shape as { mapValueKind: string }).mapValueKind }
|
|
215
|
+
: {}),
|
|
216
|
+
},
|
|
217
|
+
mode: "identity",
|
|
218
|
+
},
|
|
219
|
+
"the compiler's compatible-widening proof",
|
|
220
|
+
);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// Every retained source route must reach the one current target. This is the compile-time
|
|
224
|
+
// completeness gate for all still-supported eras, not merely the immediately prior law.
|
|
225
|
+
for (const source of bySource.keys()) {
|
|
226
|
+
const seen = new Set<string>();
|
|
227
|
+
let cursor = source;
|
|
228
|
+
while (cursor !== targetSchema) {
|
|
229
|
+
if (seen.has(cursor)) {
|
|
230
|
+
throw new Error(`field evolution graph cycles at ${cursor}; every source must terminate at ${targetSchema}`);
|
|
231
|
+
}
|
|
232
|
+
seen.add(cursor);
|
|
233
|
+
const next = bySource.get(cursor);
|
|
234
|
+
if (next === undefined) {
|
|
235
|
+
throw new Error(
|
|
236
|
+
`field evolution route from ${source} stops at ${cursor}, not the current contract ${targetSchema}. ` +
|
|
237
|
+
`Carry the intermediate source with .evolvesFrom(...) before this rollout.`,
|
|
238
|
+
);
|
|
239
|
+
}
|
|
240
|
+
cursor = next.to;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
return [...bySource.values()].sort((a, b) => (a.from < b.from ? -1 : a.from > b.from ? 1 : 0));
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/** Explicit identity projection: the tenant declares the business meaning is continuous;
|
|
248
|
+
* the compiler still proves every old value is accepted and its physical decoding is unchanged. */
|
|
249
|
+
function safeIdentityProjection(sourceShape: unknown, targetShape: unknown): boolean {
|
|
250
|
+
if (sourceShape === null || targetShape === null || typeof sourceShape !== "object" || typeof targetShape !== "object") {
|
|
251
|
+
return canonicalJson(sourceShape) === canonicalJson(targetShape);
|
|
252
|
+
}
|
|
253
|
+
const source = sourceShape as Record<string, unknown>;
|
|
254
|
+
const target = targetShape as Record<string, unknown>;
|
|
255
|
+
const physical = (shape: Record<string, unknown>) => {
|
|
256
|
+
const { kind, mapValueKind, jsonShape, encrypted } = shape;
|
|
257
|
+
return { kind, mapValueKind, jsonShape, encrypted };
|
|
258
|
+
};
|
|
259
|
+
return canonicalJson(physical(source)) === canonicalJson(physical(target))
|
|
260
|
+
&& jsonSchemaSubset(source.jsonSchema, target.jsonSchema);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/** Automatic widening is stricter: every non-schema business descriptor must remain
|
|
264
|
+
* identical. Brands, units, references and null meaning require an explicit declaration. */
|
|
265
|
+
function safeAutomaticWidening(sourceShape: unknown, targetShape: unknown): boolean {
|
|
266
|
+
if (sourceShape === null || targetShape === null || typeof sourceShape !== "object" || typeof targetShape !== "object") {
|
|
267
|
+
return canonicalJson(sourceShape) === canonicalJson(targetShape);
|
|
268
|
+
}
|
|
269
|
+
const source = sourceShape as Record<string, unknown>;
|
|
270
|
+
const target = targetShape as Record<string, unknown>;
|
|
271
|
+
const metadata = (shape: Record<string, unknown>) => {
|
|
272
|
+
const { jsonSchema: _jsonSchema, ...rest } = shape;
|
|
273
|
+
return rest;
|
|
274
|
+
};
|
|
275
|
+
return canonicalJson(metadata(source)) === canonicalJson(metadata(target))
|
|
276
|
+
&& jsonSchemaSubset(source.jsonSchema, target.jsonSchema);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
function jsonSchemaSubset(source: unknown, target: unknown): boolean {
|
|
280
|
+
if (canonicalJson(source) === canonicalJson(target)) return true;
|
|
281
|
+
if (source === null || target === null || typeof source !== "object" || typeof target !== "object") return false;
|
|
282
|
+
const s = source as Record<string, unknown>;
|
|
283
|
+
const t = target as Record<string, unknown>;
|
|
284
|
+
if (Array.isArray(s.enum)) return s.enum.every((value) => jsonSchemaAcceptsLiteral(t, value));
|
|
285
|
+
if (Object.prototype.hasOwnProperty.call(s, "const")) return jsonSchemaAcceptsLiteral(t, s.const);
|
|
286
|
+
const sUnion = (Array.isArray(s.anyOf) ? s.anyOf : Array.isArray(s.oneOf) ? s.oneOf : undefined);
|
|
287
|
+
if (sUnion !== undefined) return sUnion.every((arm) => jsonSchemaSubset(arm, target));
|
|
288
|
+
const tUnion = (Array.isArray(t.anyOf) ? t.anyOf : Array.isArray(t.oneOf) ? t.oneOf : undefined);
|
|
289
|
+
if (tUnion !== undefined) return tUnion.some((arm) => jsonSchemaSubset(source, arm));
|
|
290
|
+
const sourceTypes = new Set(Array.isArray(s.type) ? s.type : s.type === undefined ? [] : [s.type]);
|
|
291
|
+
const targetTypes = new Set(Array.isArray(t.type) ? t.type : t.type === undefined ? [] : [t.type]);
|
|
292
|
+
if ([...sourceTypes].some((type) => !targetTypes.has(type))) return false;
|
|
293
|
+
if (sourceTypes.has("object")) {
|
|
294
|
+
const sp = (s.properties ?? {}) as Record<string, unknown>;
|
|
295
|
+
const tp = (t.properties ?? {}) as Record<string, unknown>;
|
|
296
|
+
const sr = new Set(Array.isArray(s.required) ? s.required as string[] : []);
|
|
297
|
+
const tr = new Set(Array.isArray(t.required) ? t.required as string[] : []);
|
|
298
|
+
if ([...tr].some((name) => !sr.has(name))) return false;
|
|
299
|
+
for (const [name, schema] of Object.entries(sp)) {
|
|
300
|
+
if (tp[name] === undefined) {
|
|
301
|
+
if (t.additionalProperties === false) return false;
|
|
302
|
+
} else if (!jsonSchemaSubset(schema, tp[name])) {
|
|
303
|
+
return false;
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
if (s.additionalProperties !== false && t.additionalProperties === false) return false;
|
|
307
|
+
}
|
|
308
|
+
if (sourceTypes.has("array") && s.items !== undefined && t.items !== undefined) {
|
|
309
|
+
if (!jsonSchemaSubset(s.items, t.items)) return false;
|
|
310
|
+
}
|
|
311
|
+
return sourceTypes.size > 0 && [...sourceTypes].every((type) => targetTypes.has(type));
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
function jsonSchemaAcceptsLiteral(schema: Record<string, unknown>, value: unknown): boolean {
|
|
315
|
+
if (Array.isArray(schema.enum)) return schema.enum.some((candidate) => canonicalJson(candidate) === canonicalJson(value));
|
|
316
|
+
if (Object.prototype.hasOwnProperty.call(schema, "const")) return canonicalJson(schema.const) === canonicalJson(value);
|
|
317
|
+
const union = Array.isArray(schema.anyOf) ? schema.anyOf : Array.isArray(schema.oneOf) ? schema.oneOf : undefined;
|
|
318
|
+
if (union !== undefined) return union.some((arm) => arm !== null && typeof arm === "object" && jsonSchemaAcceptsLiteral(arm as Record<string, unknown>, value));
|
|
319
|
+
const actual = value === null ? "null" : Array.isArray(value) ? "array" : typeof value === "number" ? "number" : typeof value;
|
|
320
|
+
const types = new Set(Array.isArray(schema.type) ? schema.type : schema.type === undefined ? [] : [schema.type]);
|
|
321
|
+
return types.has(actual) || (actual === "number" && types.has("integer") && Number.isInteger(value));
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/** JSON Schema contains a handful of set-valued arrays whose authored order is not part
|
|
325
|
+
* of the accepted value language. Normalize those (and VO enum `values`) so a cosmetic
|
|
326
|
+
* enum/union reorder does not manufacture an N/N+1 incompatibility. */
|
|
327
|
+
function normalizeFieldContract(value: unknown, parentKey = ""): unknown {
|
|
328
|
+
if (Array.isArray(value)) {
|
|
329
|
+
const items = value.map((item) => normalizeFieldContract(item));
|
|
330
|
+
if (["enum", "required", "type", "values", "oneOf", "anyOf", "allOf"].includes(parentKey)) {
|
|
331
|
+
return items.sort((a, b) => {
|
|
332
|
+
const left = canonicalJson(a);
|
|
333
|
+
const right = canonicalJson(b);
|
|
334
|
+
return left < right ? -1 : left > right ? 1 : 0;
|
|
335
|
+
});
|
|
336
|
+
}
|
|
337
|
+
return items;
|
|
338
|
+
}
|
|
339
|
+
if (value !== null && typeof value === "object") {
|
|
340
|
+
return Object.fromEntries(
|
|
341
|
+
Object.entries(value as Record<string, unknown>).map(([key, child]) => [
|
|
342
|
+
key,
|
|
343
|
+
normalizeFieldContract(child, key),
|
|
344
|
+
]),
|
|
345
|
+
);
|
|
346
|
+
}
|
|
347
|
+
return value;
|
|
348
|
+
}
|
package/src/stable_ids_types.ts
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* RUNTIME type graph (DomainModule's optional `stableIdContinuity`) never drags
|
|
9
9
|
* `node:crypto` into a tenant scaffold's typecheck (scaffolds compile without
|
|
10
10
|
* `@types/node`). The minting/derivation machinery lives in `stable_ids.ts`
|
|
11
|
-
* (build-time only — `nomos-compile` +
|
|
11
|
+
* (build-time only — `nomos-compile` + `stable_ids_for_module.ts`).
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
14
|
/** The read decoder needed to interpret one evolution rule's source value. */
|
package/src/sum.ts
CHANGED
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
* VALIDATION RULE (manifest-load, not type-level): the `sumField` parameter MUST be
|
|
27
27
|
* an `int`-kind field of the `of`-aggregate. The DSL type system cannot enforce this
|
|
28
28
|
* at the `sum(id, sumField)` call site (the aggregate is not yet known), but
|
|
29
|
-
*
|
|
29
|
+
* typed USDA projection-law admission performs a no-fallback reject
|
|
30
30
|
* if the field is absent or non-int on the declared aggregate.
|
|
31
31
|
*
|
|
32
32
|
* ORDER-SENSITIVE GUARDRAIL: sum exposes NO `.first`/`.take`/`.orderBy`. Same
|
package/src/usd_state.ts
CHANGED
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
* never the runtime barrel. The deployable package bytes and the domainHash do not
|
|
47
47
|
* move — state emission reads the PROJECTION, it never enters the law.
|
|
48
48
|
*/
|
|
49
|
-
import { primName, usdaString, usdaStringArray } from "./
|
|
49
|
+
import { primName, usdaString, usdaStringArray } from "./usda_text.js";
|
|
50
50
|
|
|
51
51
|
/** The state-layer format marker (customLayerData `nomos:format`). */
|
|
52
52
|
export const NOMOS_USD_STATE_LAYER_FORMAT = "nomos.usd-state-layer.v1";
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
// NOMOS USDA law is stricter than a general OpenUSD scene: every internal typed relationship must
|
|
2
|
+
// resolve inside the authenticated closure. OpenUSD intentionally permits dangling relationships,
|
|
3
|
+
// so stock usdcat cannot provide this Nomos-specific compile-validity guarantee.
|
|
4
|
+
|
|
5
|
+
export interface UsdaRelationshipAsset {
|
|
6
|
+
readonly logicalPath: string;
|
|
7
|
+
readonly utf8: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
interface PrimFrame {
|
|
11
|
+
readonly indent: number;
|
|
12
|
+
readonly path: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
interface RelationshipTarget {
|
|
16
|
+
readonly asset: string;
|
|
17
|
+
readonly line: number;
|
|
18
|
+
readonly path: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const PRIM_DECLARATION = /^(\s*)(?:def|over)(?:\s+[A-Za-z_][A-Za-z0-9_:]*)?\s+"([^"]+)"\s*$/;
|
|
22
|
+
const RELATIONSHIP_TARGET = /<([^>]+)>/g;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Refuse internal relationship targets which do not name a prim authored anywhere in the closure.
|
|
26
|
+
*
|
|
27
|
+
* This intentionally checks only absolute `/Nomos` targets. Asset paths, external scene references
|
|
28
|
+
* and ordinary USD relationships retain normal OpenUSD semantics; Nomos law identities do not.
|
|
29
|
+
*/
|
|
30
|
+
export function assertUsdaRelationshipIntegrity(assets: readonly UsdaRelationshipAsset[]): void {
|
|
31
|
+
const prims = new Set<string>();
|
|
32
|
+
const targets: RelationshipTarget[] = [];
|
|
33
|
+
|
|
34
|
+
for (const asset of assets) {
|
|
35
|
+
const stack: PrimFrame[] = [];
|
|
36
|
+
const lines = asset.utf8.split(/\r?\n/);
|
|
37
|
+
for (const [offset, line] of lines.entries()) {
|
|
38
|
+
const declaration = PRIM_DECLARATION.exec(line);
|
|
39
|
+
if (declaration !== null) {
|
|
40
|
+
const indent = declaration[1]!.length;
|
|
41
|
+
while (stack.length > 0 && stack[stack.length - 1]!.indent >= indent) stack.pop();
|
|
42
|
+
const parent = stack[stack.length - 1]?.path ?? "";
|
|
43
|
+
const path = `${parent}/${declaration[2]!}`;
|
|
44
|
+
stack.push({ indent, path });
|
|
45
|
+
prims.add(path);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
for (const match of line.matchAll(RELATIONSHIP_TARGET)) {
|
|
49
|
+
const path = match[1]!;
|
|
50
|
+
if (path === "/Nomos" || path.startsWith("/Nomos/")) {
|
|
51
|
+
targets.push({ asset: asset.logicalPath, line: offset + 1, path });
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const missing = targets.filter((target) => !prims.has(target.path));
|
|
58
|
+
if (missing.length === 0) return;
|
|
59
|
+
const examples = missing.slice(0, 8).map((target) =>
|
|
60
|
+
`${target.asset}:${target.line} targets missing prim <${target.path}>`).join("\n ");
|
|
61
|
+
const remainder = missing.length > 8 ? `\n ... and ${missing.length - 8} more` : "";
|
|
62
|
+
throw new Error(
|
|
63
|
+
`typed USDA relationship integrity failed (${missing.length} dangling internal target${missing.length === 1 ? "" : "s"}):\n ` +
|
|
64
|
+
`${examples}${remainder}`,
|
|
65
|
+
);
|
|
66
|
+
}
|
package/src/usda_text.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// NOMOS — minimal deterministic USDA text literals for build-time emitters.
|
|
2
|
+
// This module deliberately has no parser, generic value tree, flattening model or law vocabulary.
|
|
3
|
+
|
|
4
|
+
/** Escape a string for a double-quoted USDA string literal. */
|
|
5
|
+
export function usdaString(value: string): string {
|
|
6
|
+
return JSON.stringify(value);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/** Emit a USDA `string[]` literal. */
|
|
10
|
+
export function usdaStringArray(items: readonly string[]): string {
|
|
11
|
+
return `[${items.map(usdaString).join(", ")}]`;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/** A valid USD prim-name identifier for an arbitrary authored label. */
|
|
15
|
+
export function primName(id: string): string {
|
|
16
|
+
const sanitized = id.replace(/[^A-Za-z0-9_]/g, "_");
|
|
17
|
+
return /^[A-Za-z_]/.test(sanitized) ? sanitized : `_${sanitized}`;
|
|
18
|
+
}
|
package/src/usdz.ts
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
// Deterministic USDZ transport for canonical Nomos law. This is deliberately the
|
|
2
|
+
// uncompressed OpenUSD package profile: USDA first, 64-byte-aligned file payloads,
|
|
3
|
+
// fixed timestamps, and no host-specific metadata.
|
|
4
|
+
|
|
5
|
+
const CRC_TABLE = (() => {
|
|
6
|
+
const table = new Uint32Array(256);
|
|
7
|
+
for (let n = 0; n < 256; n += 1) {
|
|
8
|
+
let value = n;
|
|
9
|
+
for (let bit = 0; bit < 8; bit += 1) {
|
|
10
|
+
value = value & 1 ? 0xedb88320 ^ (value >>> 1) : value >>> 1;
|
|
11
|
+
}
|
|
12
|
+
table[n] = value >>> 0;
|
|
13
|
+
}
|
|
14
|
+
return table;
|
|
15
|
+
})();
|
|
16
|
+
|
|
17
|
+
export function crc32(bytes: Uint8Array): number {
|
|
18
|
+
let value = 0xffffffff;
|
|
19
|
+
for (const byte of bytes) value = CRC_TABLE[(value ^ byte) & 0xff]! ^ (value >>> 8);
|
|
20
|
+
return (value ^ 0xffffffff) >>> 0;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function usdzPack(entries: ReadonlyArray<readonly [string, Uint8Array]>): Uint8Array {
|
|
24
|
+
if (entries.length === 0 || !entries[0]![0].endsWith(".usda")) {
|
|
25
|
+
throw new Error("USDZ law package must place its composed USDA root layer first");
|
|
26
|
+
}
|
|
27
|
+
const jsonEntry = entries.find(([name]) => name.toLowerCase().endsWith(".json"));
|
|
28
|
+
if (jsonEntry !== undefined) {
|
|
29
|
+
throw new Error(
|
|
30
|
+
`Nomos law USDZ cannot contain JSON ('${jsonEntry[0]}'); ` +
|
|
31
|
+
"express law and package metadata as typed USDA",
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
const chunks: Uint8Array[] = [];
|
|
35
|
+
const central: Uint8Array[] = [];
|
|
36
|
+
let offset = 0;
|
|
37
|
+
const w16 = (v: number): number[] => [v & 0xff, (v >>> 8) & 0xff];
|
|
38
|
+
const w32 = (v: number): number[] => [v & 0xff, (v >>> 8) & 0xff, (v >>> 16) & 0xff, (v >>> 24) & 0xff];
|
|
39
|
+
for (const [name, data] of entries) {
|
|
40
|
+
const nameBytes = new TextEncoder().encode(name);
|
|
41
|
+
const crc = crc32(data);
|
|
42
|
+
const headerLen = 30 + nameBytes.length;
|
|
43
|
+
const misalignment = (offset + headerLen) % 64;
|
|
44
|
+
let extraLen = 0;
|
|
45
|
+
if (misalignment !== 0) {
|
|
46
|
+
extraLen = 64 - misalignment;
|
|
47
|
+
if (extraLen < 4) extraLen += 64;
|
|
48
|
+
}
|
|
49
|
+
const padding = extraLen === 0 ? 0 : extraLen - 4;
|
|
50
|
+
const local = new Uint8Array([
|
|
51
|
+
...w32(0x04034b50), ...w16(20), ...w16(0), ...w16(0), ...w16(0), ...w16(0x21),
|
|
52
|
+
...w32(crc), ...w32(data.length), ...w32(data.length), ...w16(nameBytes.length),
|
|
53
|
+
...w16(extraLen), ...nameBytes,
|
|
54
|
+
...(extraLen > 0 ? [...w16(0x1986), ...w16(padding), ...new Array<number>(padding).fill(0)] : []),
|
|
55
|
+
]);
|
|
56
|
+
chunks.push(local, data);
|
|
57
|
+
central.push(new Uint8Array([
|
|
58
|
+
...w32(0x02014b50), ...w16(20), ...w16(20), ...w16(0), ...w16(0), ...w16(0),
|
|
59
|
+
...w16(0x21), ...w32(crc), ...w32(data.length), ...w32(data.length),
|
|
60
|
+
...w16(nameBytes.length), ...w16(0), ...w16(0), ...w16(0), ...w16(0),
|
|
61
|
+
...w32(0), ...w32(offset), ...nameBytes,
|
|
62
|
+
]));
|
|
63
|
+
offset += local.length + data.length;
|
|
64
|
+
}
|
|
65
|
+
const centralStart = offset;
|
|
66
|
+
const centralLen = central.reduce((sum, entry) => sum + entry.length, 0);
|
|
67
|
+
const eocd = new Uint8Array([
|
|
68
|
+
...w32(0x06054b50), ...w16(0), ...w16(0), ...w16(entries.length), ...w16(entries.length),
|
|
69
|
+
...w32(centralLen), ...w32(centralStart), ...w16(0),
|
|
70
|
+
]);
|
|
71
|
+
const output = new Uint8Array(offset + centralLen + eocd.length);
|
|
72
|
+
let at = 0;
|
|
73
|
+
for (const chunk of [...chunks, ...central, eocd]) {
|
|
74
|
+
output.set(chunk, at);
|
|
75
|
+
at += chunk.length;
|
|
76
|
+
}
|
|
77
|
+
return output;
|
|
78
|
+
}
|
package/src/value_object.ts
CHANGED
|
@@ -58,6 +58,7 @@ import { z } from "zod";
|
|
|
58
58
|
import { isRuntimeLaw } from "./authoring_context.js";
|
|
59
59
|
import type { Driver } from "./drivers.js";
|
|
60
60
|
import {
|
|
61
|
+
isSha256DigestSchema,
|
|
61
62
|
valueObjectField,
|
|
62
63
|
type Field,
|
|
63
64
|
type GeometryKind,
|
|
@@ -281,6 +282,99 @@ export function idOf(aggregate: { id: string; __isContract?: true } | string): z
|
|
|
281
282
|
return schema;
|
|
282
283
|
}
|
|
283
284
|
|
|
285
|
+
/**
|
|
286
|
+
* A directive-payload publication pin. Like `idOf(...)`, this is a compile-time
|
|
287
|
+
* annotation over an ordinary JSON wire value; generated clients expose one
|
|
288
|
+
* `VersionedNomosRef<TVersion, TSeries>` so callers cannot mismatch workspace,
|
|
289
|
+
* type, publication id, series id, or current-pointer law.
|
|
290
|
+
*/
|
|
291
|
+
export const NOMOS_VERSIONED_REF_BRAND: unique symbol = Symbol.for("nomos.versionedRefOf");
|
|
292
|
+
|
|
293
|
+
export interface VersionedRefOfMeta {
|
|
294
|
+
readonly targetAggregateType: string;
|
|
295
|
+
readonly targetKind: "aggregate" | "contract";
|
|
296
|
+
readonly seriesAggregateType: string;
|
|
297
|
+
readonly seriesKind: "aggregate" | "contract";
|
|
298
|
+
readonly currentField: string;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
type RefTarget = {
|
|
302
|
+
id: string;
|
|
303
|
+
__isContract?: true;
|
|
304
|
+
fields: Record<string, { nomosRef?: { targetAggregateId: string } }>;
|
|
305
|
+
};
|
|
306
|
+
|
|
307
|
+
const refTargetMeta = (target: RefTarget, helper: string): { type: string; kind: "aggregate" | "contract" } => {
|
|
308
|
+
const type = target.id;
|
|
309
|
+
if (typeof type !== "string" || type === "") {
|
|
310
|
+
throw new Error(`${helper}: pass an aggregate or contract handle with a stable id.`);
|
|
311
|
+
}
|
|
312
|
+
return {
|
|
313
|
+
type,
|
|
314
|
+
kind: target.__isContract === true ? "contract" : "aggregate",
|
|
315
|
+
};
|
|
316
|
+
};
|
|
317
|
+
|
|
318
|
+
const nomosAddressSchema = z.object({
|
|
319
|
+
workspace: z.string().min(1),
|
|
320
|
+
type: z.string().min(1),
|
|
321
|
+
id: z.string().min(1),
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* A payload field holding an immutable publication and its stable current-version
|
|
326
|
+
* series. The kernel receives `{pinned, series}`; `current` is compiled law and
|
|
327
|
+
* never caller-controlled.
|
|
328
|
+
*/
|
|
329
|
+
export function versionedRefOf(opts: {
|
|
330
|
+
target: RefTarget;
|
|
331
|
+
series: RefTarget;
|
|
332
|
+
current: string;
|
|
333
|
+
}): z.ZodType<{ pinned: z.infer<typeof nomosAddressSchema>; series: z.infer<typeof nomosAddressSchema> }> {
|
|
334
|
+
const target = refTargetMeta(opts.target, "versionedRefOf");
|
|
335
|
+
const series = refTargetMeta(opts.series, "versionedRefOf");
|
|
336
|
+
if (typeof opts.current !== "string" || opts.current === "") {
|
|
337
|
+
throw new Error("versionedRefOf: current must name the stable series current-pointer field.");
|
|
338
|
+
}
|
|
339
|
+
const current = opts.series.fields[opts.current];
|
|
340
|
+
if (current === undefined) {
|
|
341
|
+
throw new Error(
|
|
342
|
+
`versionedRefOf: series '${series.type}' has no current field '${opts.current}'.`,
|
|
343
|
+
);
|
|
344
|
+
}
|
|
345
|
+
if (current.nomosRef?.targetAggregateId !== target.type) {
|
|
346
|
+
throw new Error(
|
|
347
|
+
`versionedRefOf: '${series.type}.${opts.current}' must be t.nomosRef(${target.type}); ` +
|
|
348
|
+
"it is the O(1) current pointer used for freshness.",
|
|
349
|
+
);
|
|
350
|
+
}
|
|
351
|
+
const schema = z.object({ pinned: nomosAddressSchema, series: nomosAddressSchema });
|
|
352
|
+
Object.defineProperty(schema, NOMOS_VERSIONED_REF_BRAND, {
|
|
353
|
+
value: {
|
|
354
|
+
targetAggregateType: target.type,
|
|
355
|
+
targetKind: target.kind,
|
|
356
|
+
seriesAggregateType: series.type,
|
|
357
|
+
seriesKind: series.kind,
|
|
358
|
+
currentField: opts.current,
|
|
359
|
+
} satisfies VersionedRefOfMeta,
|
|
360
|
+
enumerable: false,
|
|
361
|
+
});
|
|
362
|
+
return schema;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
/** Resolve `versionedRefOf(...)` metadata through omission/null wrappers. */
|
|
366
|
+
export function versionedRefOfMetaOf(zt: z.ZodTypeAny): VersionedRefOfMeta | undefined {
|
|
367
|
+
let cur: z.ZodTypeAny | undefined = zt;
|
|
368
|
+
for (let hops = 0; cur !== undefined && hops < 16; hops++) {
|
|
369
|
+
const branded = (cur as unknown as Record<symbol, unknown>)[NOMOS_VERSIONED_REF_BRAND];
|
|
370
|
+
if (typeof branded === "object" && branded !== null) return branded as VersionedRefOfMeta;
|
|
371
|
+
const kind = zodKind(cur);
|
|
372
|
+
if (kind !== "optional" && kind !== "default" && kind !== "nullable") return undefined;
|
|
373
|
+
cur = zodDef(cur).innerType;
|
|
374
|
+
}
|
|
375
|
+
return undefined;
|
|
376
|
+
}
|
|
377
|
+
|
|
284
378
|
/**
|
|
285
379
|
* THE BRANDED-ID BRAND (dart_codegen_parity.md P2 — deliberately NOT an aggregate reference) —
|
|
286
380
|
* `brandedId("SiteId")` is a NOMINAL string: wire-identical to `z.string()` but the generated TS is a branded alias
|
|
@@ -408,6 +502,7 @@ function describeZod(
|
|
|
408
502
|
...(idOfTargetKindOf(schema) === "contract" ? { idOfContract: true as const } : {}),
|
|
409
503
|
};
|
|
410
504
|
}
|
|
505
|
+
if (isSha256DigestSchema(schema)) return { type: "string", digest: "sha256" };
|
|
411
506
|
return typeof brandedBrand === "string" ? { type: "string", brand: brandedBrand } : { type: "string" };
|
|
412
507
|
}
|
|
413
508
|
case "record": {
|