@githolon/dsl 0.32.0 → 0.34.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/dart/lib/src/errors.dart +105 -1
- package/dart/lib/src/webview_bridge.dart +6 -4
- package/dart/test/webview_bridge_test.dart +65 -0
- package/package.json +1 -1
- package/src/aggregate.ts +12 -1
- package/src/authoring.ts +4 -1
- package/src/birth_meter.ts +300 -0
- package/src/birth_meter_ensure.ts +67 -0
- package/src/build_package.ts +59 -0
- package/src/codegen_dart.ts +347 -3
- package/src/codegen_ts.ts +74 -1
- package/src/compile_engine.ts +13 -2
- package/src/compile_package_main.ts +42 -2
- package/src/directive.ts +10 -1
- package/src/engine_entry.ts +56 -9
- package/src/fields.ts +84 -0
- package/src/framework/birth_meter.ts +125 -0
- package/src/framework/impure_capability.ts +5 -2
- package/src/framework/platform_admin.ts +4 -1
- package/src/framework/relations.ts +19 -0
- package/src/framework/workspaces.ts +42 -1
- package/src/index.ts +53 -0
- package/src/read.ts +22 -0
- package/src/read_projection_composition.ts +117 -0
- package/src/read_projection_manifest.ts +94 -0
- package/src/read_projection_namespace.ts +152 -0
- package/src/usd.ts +141 -10
- package/src/usd_layers.ts +82 -0
- package/src/value_object.ts +456 -0
- package/src/wire_encode.ts +34 -3
|
@@ -0,0 +1,456 @@
|
|
|
1
|
+
// NOMOS — Nomos Sovereign: participants act · verify · remember LOCALLY; hosted
|
|
2
|
+
// remotes are replaceable custody/transport, not truth. ⇒ ONE Nomos GitHolon
|
|
3
|
+
// wasm32-wasip1 artifact {kernel · projection · embedded
|
|
4
|
+
// QuickJS engine} on V8 + WASI-shim, byte-identical everywhere. V8 = portability; the one
|
|
5
|
+
// wasm = determinism. No native, no wasmtime, no 2nd artifact, no domain-JS on bare V8.
|
|
6
|
+
// If a file isn't this / hosting this / authoring for this / proving this — it's gone.
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* FIRST-CLASS COMPOSABLE VALUE OBJECTS — `valueObject(name, shape)` + the `geo.*`
|
|
10
|
+
* geometry VOs.
|
|
11
|
+
*
|
|
12
|
+
* THE TENANT GAP THIS CLOSES: a structured value on an aggregate was a hand-rolled
|
|
13
|
+
* `t.jsonObject()` plus a privately-kept zod schema per site — untyped in the generated
|
|
14
|
+
* clients, invisible in the package IR. A value object is the NAMED, REUSABLE,
|
|
15
|
+
* COMPOSABLE version of that same leaf:
|
|
16
|
+
*
|
|
17
|
+
* const PostalAddress = valueObject("PostalAddress", {
|
|
18
|
+
* streetAddress: z.string(), city: z.string(), countryCode: z.string(),
|
|
19
|
+
* });
|
|
20
|
+
* const GeoPoint = geo.point("GeoPoint"); // RFC 7946 Point
|
|
21
|
+
* const SiteLocation = valueObject("SiteLocation", {
|
|
22
|
+
* address: PostalAddress.optional(),
|
|
23
|
+
* point: GeoPoint.optional(),
|
|
24
|
+
* });
|
|
25
|
+
* // on an aggregate:
|
|
26
|
+
* location: SiteLocation.field().optional(),
|
|
27
|
+
*
|
|
28
|
+
* ONE definition produces: the zod schema (the SAME zod the engine validates — embed
|
|
29
|
+
* `SiteLocation.zod` in a directive payload), the inferred TS type (`SiteLocation.field()`
|
|
30
|
+
* types `set()` and the generated `.client.ts` names the interface), the generated Dart
|
|
31
|
+
* class (typed decode from the projection's json-object value), and a canonical
|
|
32
|
+
* descriptor that rides the package IR (the additive `nomosValueObjects` USD key) so
|
|
33
|
+
* tooling/visualizers can SEE the structure.
|
|
34
|
+
*
|
|
35
|
+
* STORAGE / MERGE SEMANTICS (the v1 boundary — state it, don't blur it): a VO field IS
|
|
36
|
+
* the existing `t.jsonObject()` value-leaf lane — ONE Lww-class leaf, replaced WHOLESALE
|
|
37
|
+
* on write (exactly the encrypted-envelope rule). There is NO per-subfield merge: two
|
|
38
|
+
* concurrent writes to different subfields of one VO converge to ONE writer's whole
|
|
39
|
+
* value. When subfields must merge independently, model them as aggregate fields or a
|
|
40
|
+
* `t.map(...)` — that is what aggregates/maps are for.
|
|
41
|
+
*
|
|
42
|
+
* ERA SAFETY: the VO metadata never enters the kernel wire schema or the canonical
|
|
43
|
+
* manifest — a domain's domainHash with a VO field equals the same domain with a plain
|
|
44
|
+
* `t.jsonObject()` field. The `nomosValueObjects` USD key is emitted IFF a domain's
|
|
45
|
+
* aggregates carry ≥1 VO field; a VO-free domain's package bytes are untouched.
|
|
46
|
+
*
|
|
47
|
+
* THE v1 CONSTRAINED FIELD SET (no fallback — anything else throws at compile):
|
|
48
|
+
* `z.string()` · `z.number()` (int or float) · `z.boolean()` · `z.enum([...])` ·
|
|
49
|
+
* `z.array(<any of these>)` · a nested `ValueObject` · and `.optional()` on any of
|
|
50
|
+
* them (arrays of optionals excluded). Unions, records, maps, tuples, dates etc. are
|
|
51
|
+
* deliberately OUT — this surface is forever; grow it deliberately.
|
|
52
|
+
*/
|
|
53
|
+
import { z } from "zod";
|
|
54
|
+
|
|
55
|
+
import {
|
|
56
|
+
valueObjectField,
|
|
57
|
+
type Field,
|
|
58
|
+
type GeometryKind,
|
|
59
|
+
type ValueObjectDescriptor,
|
|
60
|
+
type ValueObjectFieldDescriptor,
|
|
61
|
+
type ValueObjectFieldMeta,
|
|
62
|
+
} from "./fields.js";
|
|
63
|
+
// LOCAL zod introspection (the same `_def ?? def` convention as codegen_ts.ts) —
|
|
64
|
+
// deliberately NOT imported from codegen_ts.js: this module rides the RUNTIME barrel
|
|
65
|
+
// (the engine lump bundles it, `--platform=neutral`), and codegen_ts transitively
|
|
66
|
+
// imports `node:crypto` via workspace_routing. Keep this file node-builtin-free.
|
|
67
|
+
type ZodInternalDef = {
|
|
68
|
+
type?: string | z.ZodTypeAny;
|
|
69
|
+
innerType?: z.ZodTypeAny;
|
|
70
|
+
element?: z.ZodTypeAny;
|
|
71
|
+
entries?: Record<string, string | number>;
|
|
72
|
+
};
|
|
73
|
+
function zodDef(zt: z.ZodTypeAny): ZodInternalDef {
|
|
74
|
+
const raw = zt as unknown as { _def?: ZodInternalDef; def?: ZodInternalDef };
|
|
75
|
+
return raw._def ?? raw.def ?? {};
|
|
76
|
+
}
|
|
77
|
+
function zodKind(zt: z.ZodTypeAny): string {
|
|
78
|
+
const t = zodDef(zt).type;
|
|
79
|
+
return typeof t === "string" ? t : "unknown";
|
|
80
|
+
}
|
|
81
|
+
function zodEnumValues(zt: z.ZodTypeAny): string[] {
|
|
82
|
+
const raw = zt as unknown as { options?: readonly unknown[] };
|
|
83
|
+
if (raw.options !== undefined) return raw.options.map(String);
|
|
84
|
+
const entries = zodDef(zt).entries;
|
|
85
|
+
if (entries !== undefined) return Object.values(entries).map(String);
|
|
86
|
+
throw new Error("valueObject: ZodEnum has no values");
|
|
87
|
+
}
|
|
88
|
+
function zodArrayElement(zt: z.ZodTypeAny): z.ZodTypeAny {
|
|
89
|
+
const def = zodDef(zt);
|
|
90
|
+
if (def.element !== undefined) return def.element;
|
|
91
|
+
if (typeof def.type !== "string" && def.type !== undefined) return def.type;
|
|
92
|
+
throw new Error("valueObject: ZodArray has no element type");
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export type { GeometryKind, ValueObjectDescriptor, ValueObjectFieldDescriptor } from "./fields.js";
|
|
96
|
+
|
|
97
|
+
/** An `.optional()`-wrapped value object — valid ONLY inside a `valueObject(...)` shape. */
|
|
98
|
+
export interface OptionalValueObject<T = unknown> {
|
|
99
|
+
readonly __isOptionalValueObject: true;
|
|
100
|
+
readonly valueObject: ValueObject<T>;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* A NAMED, COMPOSABLE value object — produced by {@link valueObject} / {@link geo}.
|
|
105
|
+
* Carries the zod (validation + TS inference), the canonical descriptor (the IR/codegen
|
|
106
|
+
* shape), and lowers onto an aggregate via {@link ValueObject.field}.
|
|
107
|
+
*/
|
|
108
|
+
export interface ValueObject<T = unknown> {
|
|
109
|
+
readonly __isValueObject: true;
|
|
110
|
+
/** The declared name — the generated TS interface / Dart class name. */
|
|
111
|
+
readonly name: string;
|
|
112
|
+
/** The zod schema — the single source of truth (embed it in directive payloads). */
|
|
113
|
+
readonly zod: z.ZodType<T>;
|
|
114
|
+
/** This VO's own canonical descriptor (field names sorted; byte-deterministic). */
|
|
115
|
+
readonly descriptor: ValueObjectDescriptor;
|
|
116
|
+
/** Every transitively nested VO's descriptor (self excluded), sorted by name. */
|
|
117
|
+
readonly dependencies: readonly ValueObjectDescriptor[];
|
|
118
|
+
/** Present iff this is a `geo.*` geometry VO (the spatial-index pairing). */
|
|
119
|
+
readonly geometry?: GeometryKind;
|
|
120
|
+
/** Mark this VO optional INSIDE another `valueObject(...)` shape (nesting only —
|
|
121
|
+
* for an optional aggregate FIELD use `.field().optional()`). */
|
|
122
|
+
optional(): OptionalValueObject<T>;
|
|
123
|
+
/**
|
|
124
|
+
* Lower this VO to an aggregate FIELD: one `t.jsonObject()`-class value leaf
|
|
125
|
+
* (kind `json`, `jsonShape:"object"`, Lww) whose whole value is this VO —
|
|
126
|
+
* WHOLESALE-REPLACE merge, no per-subfield merge (v1). Chain the usual field
|
|
127
|
+
* modifiers: `.merge(Lww/LwwCausal/LwwBy)` (Lww-class only — the value is replaced
|
|
128
|
+
* whole), `.optional()`, `.encrypted()` (under the existing `requires(cap)` rules).
|
|
129
|
+
*/
|
|
130
|
+
field(): Field<T, "json">;
|
|
131
|
+
/** Phantom to keep T load-bearing for inference. */
|
|
132
|
+
readonly _t?: T;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/** Infer the authored TS type of a ValueObject. */
|
|
136
|
+
export type ValueObjectValue<V> = V extends ValueObject<infer T> ? T : never;
|
|
137
|
+
|
|
138
|
+
/** A `valueObject(...)` shape entry: a constrained zod schema, a nested VO, or an optional VO. */
|
|
139
|
+
export type ValueObjectShapeEntry = z.ZodTypeAny | ValueObject<unknown> | OptionalValueObject<unknown>;
|
|
140
|
+
|
|
141
|
+
type EntryValue<E> =
|
|
142
|
+
E extends OptionalValueObject<infer T> ? T | undefined
|
|
143
|
+
: E extends ValueObject<infer T> ? T
|
|
144
|
+
: E extends z.ZodType<infer T> ? T
|
|
145
|
+
: never;
|
|
146
|
+
|
|
147
|
+
type OptionalKeys<S> = {
|
|
148
|
+
[K in keyof S]: undefined extends EntryValue<S[K]> ? K : never;
|
|
149
|
+
}[keyof S];
|
|
150
|
+
|
|
151
|
+
/** The inferred value type of a shape: optional entries become optional properties. */
|
|
152
|
+
export type ShapeValue<S> = { [K in Exclude<keyof S, OptionalKeys<S>>]: EntryValue<S[K]> } & {
|
|
153
|
+
[K in OptionalKeys<S>]?: EntryValue<S[K]>;
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
const isValueObject = (v: unknown): v is ValueObject<unknown> =>
|
|
157
|
+
typeof v === "object" && v !== null && (v as { __isValueObject?: boolean }).__isValueObject === true;
|
|
158
|
+
|
|
159
|
+
const isOptionalValueObject = (v: unknown): v is OptionalValueObject<unknown> =>
|
|
160
|
+
typeof v === "object" &&
|
|
161
|
+
v !== null &&
|
|
162
|
+
(v as { __isOptionalValueObject?: boolean }).__isOptionalValueObject === true;
|
|
163
|
+
|
|
164
|
+
/** Collect a VO's descriptor + its transitive dependencies into `into` (name-keyed). */
|
|
165
|
+
function collectDescriptors(vo: ValueObject<unknown>, into: Map<string, ValueObjectDescriptor>): void {
|
|
166
|
+
const existing = into.get(vo.name);
|
|
167
|
+
if (existing !== undefined) {
|
|
168
|
+
if (JSON.stringify(existing) !== JSON.stringify(vo.descriptor)) {
|
|
169
|
+
throw new Error(
|
|
170
|
+
`valueObject: two DIVERGENT value objects share the name '${vo.name}' — one name, one shape. ` +
|
|
171
|
+
`Rename one of them (the descriptor is identity-bearing).`,
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
into.set(vo.name, vo.descriptor);
|
|
177
|
+
for (const dep of vo.dependencies) {
|
|
178
|
+
const prior = into.get(dep.name);
|
|
179
|
+
if (prior !== undefined && JSON.stringify(prior) !== JSON.stringify(dep)) {
|
|
180
|
+
throw new Error(
|
|
181
|
+
`valueObject: two DIVERGENT value objects share the name '${dep.name}' — one name, one shape. ` +
|
|
182
|
+
`Rename one of them (the descriptor is identity-bearing).`,
|
|
183
|
+
);
|
|
184
|
+
}
|
|
185
|
+
into.set(dep.name, dep);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Describe one CONSTRAINED zod schema as a descriptor field (NO FALLBACK — an
|
|
191
|
+
* out-of-set shape throws with the field's path so the author fixes the declaration,
|
|
192
|
+
* never ships an undescribed structure).
|
|
193
|
+
*/
|
|
194
|
+
function describeZod(voName: string, path: string, schema: z.ZodTypeAny): ValueObjectFieldDescriptor {
|
|
195
|
+
switch (zodKind(schema)) {
|
|
196
|
+
case "string":
|
|
197
|
+
return { type: "string" };
|
|
198
|
+
case "number":
|
|
199
|
+
return { type: "number" };
|
|
200
|
+
case "boolean":
|
|
201
|
+
return { type: "boolean" };
|
|
202
|
+
case "enum":
|
|
203
|
+
return { type: "enum", values: zodEnumValues(schema) };
|
|
204
|
+
case "array": {
|
|
205
|
+
const items = describeZod(voName, `${path}[]`, zodArrayElement(schema));
|
|
206
|
+
if (items.optional === true) {
|
|
207
|
+
throw new Error(
|
|
208
|
+
`valueObject '${voName}': field '${path}' is an array of optionals — not expressible in the v1 set.`,
|
|
209
|
+
);
|
|
210
|
+
}
|
|
211
|
+
return { type: "array", items };
|
|
212
|
+
}
|
|
213
|
+
case "optional":
|
|
214
|
+
case "default": {
|
|
215
|
+
const inner = describeZod(voName, path, zodDef(schema).innerType!);
|
|
216
|
+
return { ...inner, optional: true };
|
|
217
|
+
}
|
|
218
|
+
default:
|
|
219
|
+
throw new Error(
|
|
220
|
+
`valueObject '${voName}': field '${path}' uses zod shape '${zodKind(schema)}', which is outside ` +
|
|
221
|
+
`the v1 value-object field set (string | number | boolean | enum | array | nested valueObject, ` +
|
|
222
|
+
`each optionally .optional()). Model richer shapes as aggregates/maps, or nest a valueObject.`,
|
|
223
|
+
);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const VO_NAME_RE = /^[A-Za-z][A-Za-z0-9_]*$/;
|
|
228
|
+
|
|
229
|
+
function makeValueObject<T>(
|
|
230
|
+
name: string,
|
|
231
|
+
zodSchema: z.ZodType<T>,
|
|
232
|
+
descriptor: ValueObjectDescriptor,
|
|
233
|
+
dependencies: readonly ValueObjectDescriptor[],
|
|
234
|
+
geometry?: GeometryKind,
|
|
235
|
+
): ValueObject<T> {
|
|
236
|
+
if (!VO_NAME_RE.test(name)) {
|
|
237
|
+
throw new Error(
|
|
238
|
+
`valueObject: name '${name}' is not a valid identifier ([A-Za-z][A-Za-z0-9_]*) — it becomes the ` +
|
|
239
|
+
`generated TS interface / Dart class name.`,
|
|
240
|
+
);
|
|
241
|
+
}
|
|
242
|
+
const meta: ValueObjectFieldMeta = {
|
|
243
|
+
name,
|
|
244
|
+
descriptor,
|
|
245
|
+
dependencies,
|
|
246
|
+
...(geometry !== undefined ? { geometry } : {}),
|
|
247
|
+
};
|
|
248
|
+
const vo: ValueObject<T> = {
|
|
249
|
+
__isValueObject: true,
|
|
250
|
+
name,
|
|
251
|
+
zod: zodSchema,
|
|
252
|
+
descriptor,
|
|
253
|
+
dependencies,
|
|
254
|
+
...(geometry !== undefined ? { geometry } : {}),
|
|
255
|
+
optional(): OptionalValueObject<T> {
|
|
256
|
+
return { __isOptionalValueObject: true, valueObject: vo };
|
|
257
|
+
},
|
|
258
|
+
field(): Field<T, "json"> {
|
|
259
|
+
return valueObjectField(zodSchema, meta);
|
|
260
|
+
},
|
|
261
|
+
};
|
|
262
|
+
return vo;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Declare a NAMED, COMPOSABLE value object. `shape` entries come from the v1
|
|
267
|
+
* constrained set (see the module banner): plain zod scalars/enums/arrays, nested
|
|
268
|
+
* `ValueObject`s, or `SomeVo.optional()`. Returns a {@link ValueObject} carrying the
|
|
269
|
+
* zod schema, the inferred TS type, the canonical descriptor (+ transitive nested-VO
|
|
270
|
+
* descriptors), and `.field()` to place it on an aggregate.
|
|
271
|
+
*/
|
|
272
|
+
export function valueObject<const Name extends string, S extends Record<string, ValueObjectShapeEntry>>(
|
|
273
|
+
name: Name,
|
|
274
|
+
shape: S,
|
|
275
|
+
): ValueObject<ShapeValue<S>> {
|
|
276
|
+
const zodShape: Record<string, z.ZodTypeAny> = {};
|
|
277
|
+
const fields: Record<string, ValueObjectFieldDescriptor> = {};
|
|
278
|
+
const deps = new Map<string, ValueObjectDescriptor>();
|
|
279
|
+
for (const key of Object.keys(shape).sort()) {
|
|
280
|
+
const entry = shape[key]!;
|
|
281
|
+
if (isOptionalValueObject(entry)) {
|
|
282
|
+
const inner = entry.valueObject;
|
|
283
|
+
zodShape[key] = (inner.zod as z.ZodTypeAny).optional();
|
|
284
|
+
fields[key] =
|
|
285
|
+
inner.geometry !== undefined
|
|
286
|
+
? { type: "geometry", ref: inner.name, geometry: inner.geometry, optional: true }
|
|
287
|
+
: { type: "object", ref: inner.name, optional: true };
|
|
288
|
+
collectDescriptors(inner, deps);
|
|
289
|
+
} else if (isValueObject(entry)) {
|
|
290
|
+
zodShape[key] = entry.zod as z.ZodTypeAny;
|
|
291
|
+
fields[key] =
|
|
292
|
+
entry.geometry !== undefined
|
|
293
|
+
? { type: "geometry", ref: entry.name, geometry: entry.geometry }
|
|
294
|
+
: { type: "object", ref: entry.name };
|
|
295
|
+
collectDescriptors(entry, deps);
|
|
296
|
+
} else {
|
|
297
|
+
fields[key] = describeZod(name, key, entry);
|
|
298
|
+
zodShape[key] = entry;
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
if (deps.has(name)) {
|
|
302
|
+
throw new Error(`valueObject '${name}': a value object cannot nest itself (directly or via a dependency).`);
|
|
303
|
+
}
|
|
304
|
+
const descriptor: ValueObjectDescriptor = { name, fields };
|
|
305
|
+
const dependencies = [...deps.values()].sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0));
|
|
306
|
+
return makeValueObject(
|
|
307
|
+
name,
|
|
308
|
+
z.object(zodShape) as unknown as z.ZodType<ShapeValue<S>>,
|
|
309
|
+
descriptor,
|
|
310
|
+
dependencies,
|
|
311
|
+
);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// ── geo.* — the RFC 7946 geometry value objects ──────────────────────────────────────
|
|
315
|
+
|
|
316
|
+
/** An RFC 7946 position: `[lng, lat]` or `[lng, lat, altitude]`. */
|
|
317
|
+
const positionZod = z.array(z.number()).min(2).max(3);
|
|
318
|
+
|
|
319
|
+
/** The authored TS shape of a `geo.point(...)` value (RFC 7946 Point). */
|
|
320
|
+
export interface GeoPointValue {
|
|
321
|
+
type: "Point";
|
|
322
|
+
/** `[lng, lat]` or `[lng, lat, altitude]` (RFC 7946 position order). */
|
|
323
|
+
coordinates: number[];
|
|
324
|
+
}
|
|
325
|
+
/** The authored TS shape of a `geo.lineString(...)` value (RFC 7946 LineString). */
|
|
326
|
+
export interface GeoLineStringValue {
|
|
327
|
+
type: "LineString";
|
|
328
|
+
coordinates: number[][];
|
|
329
|
+
}
|
|
330
|
+
/** The authored TS shape of a `geo.polygon(...)` value (RFC 7946 Polygon). */
|
|
331
|
+
export interface GeoPolygonValue {
|
|
332
|
+
type: "Polygon";
|
|
333
|
+
coordinates: number[][][];
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
function geoZod(kind: GeometryKind, coordinates: z.ZodTypeAny): z.ZodTypeAny {
|
|
337
|
+
return z.object({ type: z.literal(kind), coordinates });
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* The GEO value objects — RFC 7946 geometries as named VOs, and THE BLESSED WAY to
|
|
342
|
+
* declare a spatial-indexable field: give the geometry its OWN aggregate field
|
|
343
|
+
* (`point: GeoPoint.field()`) and pair it with `spatial(id).of(Agg).on("point")` —
|
|
344
|
+
* the R*Tree indexes the field's GeoJSON geometry directly. (v1 boundary: the spatial
|
|
345
|
+
* lane indexes a TOP-LEVEL field; a geometry nested inside another VO — e.g.
|
|
346
|
+
* `location.point` — is data, not an indexable path.)
|
|
347
|
+
*/
|
|
348
|
+
export const geo = {
|
|
349
|
+
/** An RFC 7946 **Point** (`{type:"Point", coordinates:[lng,lat(,alt)?]}`) — the v1
|
|
350
|
+
* spatial-index geometry. */
|
|
351
|
+
point(name = "GeoPoint"): ValueObject<GeoPointValue> {
|
|
352
|
+
return makeValueObject<GeoPointValue>(
|
|
353
|
+
name,
|
|
354
|
+
geoZod("Point", positionZod) as z.ZodType<GeoPointValue>,
|
|
355
|
+
{ name, geometry: "Point" },
|
|
356
|
+
[],
|
|
357
|
+
"Point",
|
|
358
|
+
);
|
|
359
|
+
},
|
|
360
|
+
/** An RFC 7946 **LineString** (≥2 positions). */
|
|
361
|
+
lineString(name = "GeoLineString"): ValueObject<GeoLineStringValue> {
|
|
362
|
+
return makeValueObject<GeoLineStringValue>(
|
|
363
|
+
name,
|
|
364
|
+
geoZod("LineString", z.array(positionZod).min(2)) as z.ZodType<GeoLineStringValue>,
|
|
365
|
+
{ name, geometry: "LineString" },
|
|
366
|
+
[],
|
|
367
|
+
"LineString",
|
|
368
|
+
);
|
|
369
|
+
},
|
|
370
|
+
/** An RFC 7946 **Polygon** (linear rings — ≥4 positions each, first == last by RFC;
|
|
371
|
+
* ring closure is the author's contract, not re-validated here). */
|
|
372
|
+
polygon(name = "GeoPolygon"): ValueObject<GeoPolygonValue> {
|
|
373
|
+
return makeValueObject<GeoPolygonValue>(
|
|
374
|
+
name,
|
|
375
|
+
geoZod("Polygon", z.array(z.array(positionZod).min(4))) as z.ZodType<GeoPolygonValue>,
|
|
376
|
+
{ name, geometry: "Polygon" },
|
|
377
|
+
[],
|
|
378
|
+
"Polygon",
|
|
379
|
+
);
|
|
380
|
+
},
|
|
381
|
+
};
|
|
382
|
+
|
|
383
|
+
// ── module-plane collection (composeDomainModule / codegen) ──────────────────────────
|
|
384
|
+
|
|
385
|
+
/**
|
|
386
|
+
* Collect the DEDUPED, SORTED value-object descriptors used by a set of aggregates
|
|
387
|
+
* (each VO field's own descriptor + its transitive dependencies). Divergent same-name
|
|
388
|
+
* descriptors throw (one name, one shape). Returns `[]` when no field carries a VO —
|
|
389
|
+
* the omit-when-empty input to the `nomosValueObjects` era key.
|
|
390
|
+
*/
|
|
391
|
+
export function collectValueObjectDescriptors(
|
|
392
|
+
aggregates: readonly { id: string; fields: Record<string, Field> }[],
|
|
393
|
+
): ValueObjectDescriptor[] {
|
|
394
|
+
const byName = new Map<string, ValueObjectDescriptor>();
|
|
395
|
+
for (const agg of aggregates) {
|
|
396
|
+
for (const field of Object.values(agg.fields)) {
|
|
397
|
+
const meta = field.valueObject;
|
|
398
|
+
if (meta === undefined) continue;
|
|
399
|
+
const all = [meta.descriptor, ...meta.dependencies];
|
|
400
|
+
for (const d of all) {
|
|
401
|
+
const existing = byName.get(d.name);
|
|
402
|
+
if (existing !== undefined && JSON.stringify(existing) !== JSON.stringify(d)) {
|
|
403
|
+
throw new Error(
|
|
404
|
+
`valueObject: aggregate '${agg.id}' uses a value object named '${d.name}' whose shape DIVERGES ` +
|
|
405
|
+
`from another use of the same name — one name, one shape. Rename one of them.`,
|
|
406
|
+
);
|
|
407
|
+
}
|
|
408
|
+
byName.set(d.name, d);
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
return [...byName.values()].sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0));
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
/**
|
|
416
|
+
* ONE VO↔FIELD ATTACHMENT (the IR pointer): aggregate `aggregate`'s field `field`
|
|
417
|
+
* stores value object `vo`. Rides the additive `nomosValueObjects` USD key as
|
|
418
|
+
* `usages` so tooling (the scene projector, the explorer glyphs) can attach a VO
|
|
419
|
+
* molecule to the exact field that carries it. NEVER in the canonical
|
|
420
|
+
* manifest/domainHash — a VO field's wire schema stays a plain `jsonObject` leaf.
|
|
421
|
+
*/
|
|
422
|
+
export interface ValueObjectUsage {
|
|
423
|
+
readonly aggregate: string;
|
|
424
|
+
readonly field: string;
|
|
425
|
+
readonly vo: string;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
/**
|
|
429
|
+
* Collect the sorted VO↔field usage pointers of a set of aggregates (one entry per
|
|
430
|
+
* VO-typed field; sorted by aggregate then field — byte-deterministic). Returns `[]`
|
|
431
|
+
* when no field carries a VO — the omit-when-empty input to the `usages` entry of
|
|
432
|
+
* the `nomosValueObjects` era key.
|
|
433
|
+
*/
|
|
434
|
+
export function collectValueObjectUsages(
|
|
435
|
+
aggregates: readonly { id: string; fields: Record<string, Field> }[],
|
|
436
|
+
): ValueObjectUsage[] {
|
|
437
|
+
const usages: ValueObjectUsage[] = [];
|
|
438
|
+
for (const agg of aggregates) {
|
|
439
|
+
for (const fieldName of Object.keys(agg.fields)) {
|
|
440
|
+
const meta = agg.fields[fieldName]!.valueObject;
|
|
441
|
+
if (meta === undefined) continue;
|
|
442
|
+
usages.push({ aggregate: agg.id, field: fieldName, vo: meta.name });
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
return usages.sort((a, b) =>
|
|
446
|
+
a.aggregate !== b.aggregate
|
|
447
|
+
? a.aggregate < b.aggregate
|
|
448
|
+
? -1
|
|
449
|
+
: 1
|
|
450
|
+
: a.field < b.field
|
|
451
|
+
? -1
|
|
452
|
+
: a.field > b.field
|
|
453
|
+
? 1
|
|
454
|
+
: 0,
|
|
455
|
+
);
|
|
456
|
+
}
|
package/src/wire_encode.ts
CHANGED
|
@@ -20,9 +20,16 @@
|
|
|
20
20
|
* LATER coordinated migration — we target the current serde_json shape now.
|
|
21
21
|
*/
|
|
22
22
|
import { encodeDriver } from "./drivers.js";
|
|
23
|
+
import { __qualifyAggregateType } from "./framework/relations.js";
|
|
23
24
|
import { __resetAuthoring, __drainAuthoring } from "./authoring.js";
|
|
24
25
|
import { __resetReads, __drainReads } from "./read.js";
|
|
25
26
|
import { __resetBirths, __drainBirths } from "./births.js";
|
|
27
|
+
import {
|
|
28
|
+
birthMeterSubject,
|
|
29
|
+
birthMeterBornAt,
|
|
30
|
+
captureBirthMeterReads,
|
|
31
|
+
planBirthRecordOps,
|
|
32
|
+
} from "./birth_meter.js";
|
|
26
33
|
import type { AggregateHandle } from "./aggregate.js";
|
|
27
34
|
import type { Field, EvidenceRefValue } from "./fields.js";
|
|
28
35
|
import type { Directive, ReferentialMarker } from "./directive.js";
|
|
@@ -203,13 +210,33 @@ export function executeDirectiveToIntent<P>(
|
|
|
203
210
|
__resetReads();
|
|
204
211
|
__resetBirths();
|
|
205
212
|
const returned = directive.plan(parsed, ctx);
|
|
206
|
-
const planned = [...__drainAuthoring(), ...returned];
|
|
207
|
-
const reads = __drainReads();
|
|
208
213
|
// THE BIRTH EFFECT (G3 — birth is a declared kernel primitive): a `.births()` directive's plan records
|
|
209
214
|
// the child's genesis recipe via `birth(recipe)`; it rides the intent as a DECLARED output (like a
|
|
210
215
|
// captured read), drained here. Empty for every non-birth directive — OMITTED below so a birth-free
|
|
211
216
|
// intent is byte-identical (the era rule).
|
|
212
217
|
const births = __drainBirths();
|
|
218
|
+
// METERED BIRTH (slice A — architecture/metered_birth_and_attested_reads.md). For every PLANNED birth,
|
|
219
|
+
// the library — HERE, in the ONE plan-output assembly both lanes (client authoring + the sealed engine's
|
|
220
|
+
// `plan`) funnel through, so the halves are byte-identical by construction — does two things the tenant
|
|
221
|
+
// never writes:
|
|
222
|
+
// A1 THE AUTO-RECORD: append the `NomosBirthRecord` field ops (deterministic id per child; Lww upsert)
|
|
223
|
+
// to the SAME intent that carries the birth effect. The kernel's `nomosBirthRecordGate` era arm
|
|
224
|
+
// refuses a new-era birth whose intent carries no record — this is what satisfies it.
|
|
225
|
+
// A2 THE AUTO-CAPTURE: capture the three meter reads (records-by-subject + grant + policy — the #58
|
|
226
|
+
// lane `.births()` auto-declares) so the synthesized birth-quota invariant judges over COMMITTED,
|
|
227
|
+
// CAS-re-derived pre-apply state on every lane.
|
|
228
|
+
// A `.births()` dispatch that planned NO birth (e.g. birthChild without a genesis) appends/captures
|
|
229
|
+
// nothing — byte-identical to before (the era rule); pre-era bundles ship an older lump and are untouched.
|
|
230
|
+
const meterOps: PlannedOp[] = [];
|
|
231
|
+
if (births.length > 0) {
|
|
232
|
+
const payloadRecord = parsed as unknown as Record<string, unknown>;
|
|
233
|
+
const subject = birthMeterSubject(payloadRecord);
|
|
234
|
+
captureBirthMeterReads(subject);
|
|
235
|
+
const bornAt = birthMeterBornAt(payloadRecord);
|
|
236
|
+
for (const b of births) meterOps.push(...planBirthRecordOps(b, subject, bornAt));
|
|
237
|
+
}
|
|
238
|
+
const planned = [...__drainAuthoring(), ...returned, ...meterOps];
|
|
239
|
+
const reads = __drainReads();
|
|
213
240
|
|
|
214
241
|
// Partition the plan output: STRIKE ops route onto the intent's `strikes` channel
|
|
215
242
|
// (the kernel's strikeout / revert facet, folded by parity); FIELD ops group into
|
|
@@ -293,7 +320,11 @@ export function executeDirectiveToIntent<P>(
|
|
|
293
320
|
aggregate,
|
|
294
321
|
marker: eventMarker,
|
|
295
322
|
ops: [
|
|
296
|
-
|
|
323
|
+
// READ-PROJECTION NAMESPACING: the stamped TYPE is qualified by the dispatching domain iff
|
|
324
|
+
// the domain declares it non-global — so the folded row's `__type` equals the qualified
|
|
325
|
+
// schema key the SAME compile wrote (rows and read manifest agree by construction). Outside
|
|
326
|
+
// a plan dispatch (codegen probes) the type passes through bare.
|
|
327
|
+
{ field: "__type", op: { Set: { Str: __qualifyAggregateType(ty) } } },
|
|
297
328
|
{ field: "__id", op: { Set: { Str: aggregate } } },
|
|
298
329
|
...ops,
|
|
299
330
|
],
|