@githolon/dsl 0.119.34 → 0.119.35

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/CHANGELOG.md CHANGED
@@ -1,3 +1,7 @@
1
+ ## 0.70.33
2
+
3
+ - Restage the bundled runtime with hosted REST parity fixes: foreign references serialise as plain references (CAP-1107), and payload shape errors name the field path and the expected shape (CAP-1108).
4
+
1
5
  ## 0.70.32
2
6
 
3
7
  - Restage the bundled runtime with the projection fix that keeps a founding record's owner contract, so reading it on an upgraded Home no longer reports `evolutionRequired` (CAP-1012).
@@ -26258,7 +26258,7 @@ function delayUntilDue(follow, nowUnixMillis) {
26258
26258
 
26259
26259
  // ../src/runtime/runtime_identity.ts
26260
26260
  init_define_NOMOS_KERNEL_RELEASE();
26261
- var PEER_RUNTIME_IDENTITY = "0.119.34" ? `@githolon/runtime@${"0.119.34"}` : "@githolon/runtime@unbundled";
26261
+ var PEER_RUNTIME_IDENTITY = "0.119.35" ? `@githolon/runtime@${"0.119.35"}` : "@githolon/runtime@unbundled";
26262
26262
 
26263
26263
  // ../src/runtime/replica_follow_desk.ts
26264
26264
  init_define_NOMOS_KERNEL_RELEASE();
package/dart/pubspec.yaml CHANGED
@@ -7,7 +7,7 @@ repository: https://github.com/Captain-App/nomos2
7
7
  description: >-
8
8
  Nomos 2 Dart client — generated domain values, intent payloads and application
9
9
  lifecycle types over the typed Nomos kernel protocol and USDA custody model.
10
- version: 0.70.32
10
+ version: 0.70.33
11
11
 
12
12
  environment:
13
13
  sdk: ^3.5.0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@githolon/dsl",
3
- "version": "0.119.34",
3
+ "version": "0.119.35",
4
4
  "type": "module",
5
5
  "sideEffects": false,
6
6
  "description": "Nomos 2 domain-authoring DSL: aggregates + directives in TS, executed and encoded to the Rust kernel's wire shapes.",
@@ -82,7 +82,7 @@
82
82
  "test": "vitest run"
83
83
  },
84
84
  "dependencies": {
85
- "@githolon/runtime": "0.119.34",
85
+ "@githolon/runtime": "0.119.35",
86
86
  "@bufbuild/protobuf": "2.13.0",
87
87
  "@bufbuild/protoc-gen-es": "2.13.0",
88
88
  "esbuild": "0.24.2",
@@ -827,12 +827,39 @@ export const TS_CALL_PROTO_PRELUDE = [
827
827
  ` routingCandidates.push(value);`,
828
828
  ` return value;`,
829
829
  `};`,
830
- `const _nmsProtoInt = (value: number): bigint => {`,
831
- ` if (!Number.isSafeInteger(value)) throw new TypeError("Nomos integers must be safe integers");`,
830
+ // Shape checks name the payload field path and its expected shape: hosted REST and other
831
+ // untyped callers reach this encoder with raw JSON, where a wrong shape must say which field.
832
+ `const _nmsShapeOf = (value: unknown): string => {`,
833
+ ` if (value === undefined) return "nothing (the field is missing)";`,
834
+ ` if (value === null) return "null";`,
835
+ ` if (Array.isArray(value)) return "a list";`,
836
+ ` if (typeof value === "object") { const keys = Object.keys(value); return "an object { " + keys.slice(0, 6).join(", ") + (keys.length > 6 ? ", …" : "") + " }"; }`,
837
+ ` if (typeof value === "string") return "a string";`,
838
+ ` if (typeof value === "number") return "the number " + String(value);`,
839
+ ` return typeof value;`,
840
+ `};`,
841
+ `const _nmsShapeError = (path: string, expected: string, value: unknown): never => {`,
842
+ ` throw new TypeError(path + ": expected " + expected + ", got " + _nmsShapeOf(value));`,
843
+ `};`,
844
+ `const _nmsCheck = <T>(value: T, kind: "string" | "number" | "boolean" | "record" | "list", path: string, expected: string): T => {`,
845
+ ` const valid = kind === "record" ? typeof value === "object" && value !== null && !Array.isArray(value)`,
846
+ ` : kind === "list" ? Array.isArray(value) : typeof value === kind;`,
847
+ ` return valid ? value : _nmsShapeError(path, expected, value);`,
848
+ `};`,
849
+ `const _nmsCheckOneOf = <T>(value: T, allowed: readonly unknown[], path: string, expected: string): T =>`,
850
+ ` allowed.includes(value) ? value : _nmsShapeError(path, expected, value);`,
851
+ `const _nmsProtoInt = (value: number, path?: string): bigint => {`,
852
+ ` if (!Number.isSafeInteger(value)) {`,
853
+ ` if (path !== undefined) _nmsShapeError(path, "a safe integer", value);`,
854
+ ` throw new TypeError("Nomos integers must be safe integers");`,
855
+ ` }`,
832
856
  ` return BigInt(value);`,
833
857
  `};`,
834
- `const _nmsProtoNumber = (value: number): number => {`,
835
- ` if (!Number.isFinite(value)) throw new TypeError("Nomos numbers must be finite");`,
858
+ `const _nmsProtoNumber = (value: number, path?: string): number => {`,
859
+ ` if (!Number.isFinite(value)) {`,
860
+ ` if (path !== undefined) _nmsShapeError(path, "a finite number", value);`,
861
+ ` throw new TypeError("Nomos numbers must be finite");`,
862
+ ` }`,
836
863
  ` return value;`,
837
864
  `};`,
838
865
  ``,
@@ -909,6 +936,41 @@ export function emitTsCallProtoEncoder(
909
936
  let local = 0;
910
937
  const fresh = (prefix: string): string => `_${prefix}${local++}`;
911
938
 
939
+ /** The field path a shape error names, e.g. `catalogue/createListing facts.rows[].site`. */
940
+ const pathOf = (where: string): string => JSON.stringify(`${domain}/${spec.publicDirective} facts.${where}`);
941
+ /** The expected JSON shape a shape error names; nested records list their field names only. */
942
+ const describe = (typeId: string, depth = 0): string => {
943
+ const node = typeNode(spec.nodes, typeId);
944
+ const semantic = node.expression.semantic;
945
+ if (semantic?.kind === "id-reference" || semantic?.kind === "address-reference")
946
+ return `a NomosRef { workspace, type, id }${semantic.target ? ` to ${semantic.target}` : ""}`;
947
+ const geometry = structuralGeometryKind(spec.nodes, node);
948
+ if (geometry !== undefined) return `a GeoJSON ${geometry}`;
949
+ const expression = node.expression;
950
+ switch (expression.kind) {
951
+ case "default": return describe(expression.value.id, depth);
952
+ case "nullable": return `${describe(expression.value.id, depth)} or null`;
953
+ case "string": return "a string";
954
+ case "enum": return `one of ${expression.values.map((value) => JSON.stringify(value)).join(" | ")}`;
955
+ case "integer": return "an integer";
956
+ case "number": return "a number";
957
+ case "boolean": return "true or false";
958
+ case "null": return "null";
959
+ case "literal": return JSON.stringify(expression.value);
960
+ case "record": {
961
+ const names = expression.fields.map((ref) => fieldNode(spec.nodes, ref))
962
+ .map((item) => `${item.name}${item.required ? "" : "?"}`);
963
+ return `an object { ${names.join(", ")} }`;
964
+ }
965
+ case "list": return depth > 1 ? "a list" : `a list of ${describe(expression.element.id, depth + 1)}`;
966
+ case "map": return depth > 1 ? "an object map" : `an object map of ${describe(expression.value.id, depth + 1)}`;
967
+ case "sum": return `one of ${nominalSumArms(spec.nodes, node, `${domain}/${directive} union`)
968
+ .map((arm) => `{ type: ${JSON.stringify(arm.discriminator)}, … }`).join(" | ")}`;
969
+ default: return "a value";
970
+ }
971
+ };
972
+ const expected = (typeId: string): string => JSON.stringify(describe(typeId));
973
+
912
974
  /** Build a MessageInitShape expression for protoc-gen-es. Nested records,
913
975
  * lists, maps and unions stay typed objects until the maintained Protobuf
914
976
  * runtime serializes the one top-level generated message. */
@@ -932,6 +994,9 @@ export function emitTsCallProtoEncoder(
932
994
  const position = fresh("position");
933
995
  return `(() => { const ${selected} = ${value}; const ${coordinates}: number[] = []; const ${positions}: number[] = []; for (const ${position} of ${selected}.coordinates) { ${coordinates}.push(...${position}); ${positions}.push(${coordinates}.length); } return { coordinates: ${coordinates}, positionEnds: ${positions} }; })()`;
934
996
  }
997
+ // Reference semantics lowered `value` through their own field-naming helpers above.
998
+ const checked = (kind: "string" | "number" | "boolean" | "record" | "list"): string =>
999
+ value !== raw ? value : `_nmsCheck(${value}, ${JSON.stringify(kind)}, ${pathOf(where)}, ${expected(typeId)})`;
935
1000
  switch (node.expression.kind) {
936
1001
  case "default": return emitValue(value, node.expression.value.id, where);
937
1002
  case "recursive": throw new Error(`call Protobuf codegen: '${domain}/${directive}' contains recursive values`);
@@ -940,11 +1005,12 @@ export function emitTsCallProtoEncoder(
940
1005
  return `(() => { const ${selected} = ${value}; return ${selected} === null ? { selected: { case: "isNull" as const, value: true } } : { selected: { case: "value" as const, value: ${emitValue(selected, node.expression.value.id, where)} } }; })()`;
941
1006
  }
942
1007
  case "string":
1008
+ return `_nmsProtoString(${checked("string")}, _nmsRoutingCandidates)`;
943
1009
  case "enum":
944
- return `_nmsProtoString(${value}, _nmsRoutingCandidates)`;
945
- case "integer": return `_nmsProtoInt(${value})`;
946
- case "number": return `_nmsProtoNumber(${value})`;
947
- case "boolean": return value;
1010
+ return `_nmsProtoString(_nmsCheckOneOf(${value}, ${JSON.stringify(node.expression.values)}, ${pathOf(where)}, ${expected(typeId)}), _nmsRoutingCandidates)`;
1011
+ case "integer": return `_nmsProtoInt(${checked("number")}, ${pathOf(where)})`;
1012
+ case "number": return `_nmsProtoNumber(${checked("number")}, ${pathOf(where)})`;
1013
+ case "boolean": return checked("boolean");
948
1014
  case "null": return `{}`;
949
1015
  case "literal": {
950
1016
  const literal = JSON.stringify(node.expression.value);
@@ -967,7 +1033,7 @@ export function emitTsCallProtoEncoder(
967
1033
  const encoded = emitValue(child.required ? childRaw : `(${childRaw})!`, child.value.id, `${where}.${child.name}`);
968
1034
  return `${fieldName}: ${child.required ? encoded : `${childRaw} === undefined ? undefined : ${encoded}`}`;
969
1035
  });
970
- return `(() => { const ${selected} = (${value})!; return { ${properties.join(", ")} }; })()`;
1036
+ return `(() => { const ${selected} = ${checked("record")}!; return { ${properties.join(", ")} }; })()`;
971
1037
  }
972
1038
  case "list": {
973
1039
  const item = fresh("item");
@@ -975,13 +1041,13 @@ export function emitTsCallProtoEncoder(
975
1041
  // a generated message initializer (`{ items: ... }`), an unwrapped
976
1042
  // arrow body is parsed as a statement block and silently becomes
977
1043
  // `void[]` instead of the declared nested value.
978
- return `{ items: ${value}.map((${item}) => (${emitValue(item, node.expression.element.id, `${where}[]`)})) }`;
1044
+ return `{ items: ${checked("list")}.map((${item}) => (${emitValue(item, node.expression.element.id, `${where}[]`)})) }`;
979
1045
  }
980
1046
  case "map": {
981
1047
  const entries = fresh("entries");
982
1048
  const key = fresh("key");
983
1049
  const item = fresh("item");
984
- return `(() => { const ${entries} = Object.entries(${value}).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0); return { entries: ${entries}.map(([${key}, ${item}]) => ({ key: _nmsProtoString(${key}, _nmsRoutingCandidates), value: ${emitValue(item, node.expression.value.id, `${where}.*`)} })) }; })()`;
1050
+ return `(() => { const ${entries} = Object.entries(${checked("record")}).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0); return { entries: ${entries}.map(([${key}, ${item}]) => ({ key: _nmsProtoString(${key}, _nmsRoutingCandidates), value: ${emitValue(item, node.expression.value.id, `${where}.*`)} })) }; })()`;
985
1051
  }
986
1052
  case "sum": {
987
1053
  const arms = nominalSumArms(spec.nodes, node, `${domain}/${directive} ${where}`);
@@ -991,7 +1057,7 @@ export function emitTsCallProtoEncoder(
991
1057
  const fieldName = protoFieldName(composedWireIdentity(paths, arm.variant.entity));
992
1058
  cases.push(`case ${JSON.stringify(arm.discriminator)}: return { choice: { case: ${JSON.stringify(fieldName)} as const, value: ${emitValue(selected, arm.value.entity.id, `${where}.${arm.discriminator}`)} } };`);
993
1059
  }
994
- return `(() => { const ${selected} = ${value}; switch (${selected}.type) { ${cases.join(" ")} default: throw new TypeError("Unknown nominal union arm: " + String((${selected} as unknown as { readonly type?: unknown }).type)); } })()`;
1060
+ return `(() => { const ${selected} = ${checked("record")}; switch (${selected}.type) { ${cases.join(" ")} default: return _nmsShapeError(${pathOf(where)}, ${expected(typeId)}, ${selected}); } })()`;
995
1061
  }
996
1062
  case "unknown": throw new Error(`call Protobuf codegen: '${domain}/${directive}' contains an unstructured value at ${where}`);
997
1063
  }
@@ -2593,8 +2593,8 @@ const NOMOS_TYPES_DEP = "^0.16.0";
2593
2593
  // 0.48.x: session-bound clients (XClient.bind/NomosClientInterface/DomainClientSessionMismatch/
2594
2594
  // DomainNotInstalledForSession, engine.follow, pendingConflicts lanes) — the generated client
2595
2595
  // REQUIRES this surface. 0.48.1 fixes NomosSession.watchPendingConflicts delegation.
2596
- const NOMOS_CLIENT_DEP = "^0.70.32";
2597
- const NOMOS_FLUTTER_DEP = "^0.69.24";
2596
+ const NOMOS_CLIENT_DEP = "^0.70.33";
2597
+ const NOMOS_FLUTTER_DEP = "^0.69.25";
2598
2598
  // DRIFT TRIPWIRE: pubspec_pins.test.ts asserts these equal the CURRENT versions in the Dart and Flutter
2599
2599
  // package pubspecs — a release without this bump fails the suite, so tenants never
2600
2600
  // land back in dependency_overrides (the exact co2 report this closes, third occurrence).
@@ -8,6 +8,42 @@ import {
8
8
  } from "./reception_artifact_contract.js";
9
9
  export * from "./reception_artifact_contract.js";
10
10
 
11
+ /** The generated module's one JSON boundary, shared by the closure and per-unit Receptions. A
12
+ * foreign reference (`t.foreignRef`) carries resolver functions for in-process navigation; across
13
+ * hosted JSON it is its plain locator, the same `{workspace,type,id,address}` shape as any other
14
+ * reference, so hosted REST answers the values the app reads. Anything else carrying a function still
15
+ * refuses. Changing these lines changes served modules: bump both generator identities. */
16
+ export const RECEPTION_RESULT_SERIALISER: readonly string[] = [
17
+ `const FOREIGN_REFERENCE_MEMBERS = "exists,id,ref,resolve,resolveResult,type,watch,workspace";`,
18
+ `function foreignReference(descriptors) {`,
19
+ ` if (Object.keys(descriptors).sort().join(",") !== FOREIGN_REFERENCE_MEMBERS) return undefined;`,
20
+ ` const value = key => descriptors[key].value;`,
21
+ ` if (["exists", "resolve", "resolveResult", "watch"].some(key => typeof value(key) !== "function")) return undefined;`,
22
+ ` if (["workspace", "type", "id"].some(key => typeof value(key) !== "string")) return undefined;`,
23
+ ` const ref = value("ref"); if (!ref || typeof ref !== "object" || Object.getPrototypeOf(ref) !== Object.prototype) return undefined;`,
24
+ ` const own = Object.getOwnPropertyDescriptors(ref); if (Object.keys(own).sort().join(",") !== "address,id,type,workspace" || Object.values(own).some(descriptor => descriptor.get || descriptor.set)) return undefined;`,
25
+ ` return ["workspace", "type", "id"].every(key => own[key].value === value(key)) ? ref : undefined;`,
26
+ `}`,
27
+ `function serialiseResult(value, depth = 0, seen = new Set()) {`,
28
+ ` const unavailable = () => { const error = new Error("Generated result cannot cross the hosted JSON contract"); error.code = "nomos.reception.result.unavailable"; throw error; };`,
29
+ ` if (depth > 64) return unavailable();`,
30
+ ` if (value === undefined) return { $nomos: "absent" };`,
31
+ ` if (value === null || typeof value === "string" || typeof value === "boolean") return value;`,
32
+ ` if (typeof value === "number") { if (!Number.isFinite(value)) return unavailable(); return value; }`,
33
+ ` if (typeof value === "bigint") return { $nomos: "integer", decimal: value.toString() };`,
34
+ ` if (value instanceof Uint8Array) { let binary = ""; for (const byte of value) binary += String.fromCharCode(byte); return { $nomos: "bytes", base64: btoa(binary) }; }`,
35
+ ` if (value instanceof Date) { if (!Number.isFinite(value.getTime())) return unavailable(); return { $nomos: "timestamp", iso: value.toISOString() }; }`,
36
+ ` if (typeof value !== "object" || seen.has(value)) return unavailable();`,
37
+ ` seen.add(value); try {`,
38
+ ` if (Array.isArray(value)) return value.map(item => serialiseResult(item, depth + 1, seen));`,
39
+ ` const prototype = Object.getPrototypeOf(value); if (prototype !== Object.prototype && prototype !== null) return unavailable();`,
40
+ ` const descriptors = Object.getOwnPropertyDescriptors(value); if (Object.values(descriptors).some(descriptor => descriptor.get || descriptor.set)) return unavailable();`,
41
+ ` const reference = foreignReference(descriptors); if (reference !== undefined) return serialiseResult(reference, depth + 1, seen);`,
42
+ ` const output = {}; for (const [key, descriptor] of Object.entries(descriptors)) Object.defineProperty(output, key, { value: serialiseResult(descriptor.value, depth + 1, seen), enumerable: true }); return output;`,
43
+ ` } finally { seen.delete(value); }`,
44
+ `}`,
45
+ ];
46
+
11
47
  export async function generateReceptionArtifact(input: ReceptionGenerationInput): Promise<ClosureReceptionArtifact> {
12
48
  const templates = await validateReceptionGenerationInput(input);
13
49
  const artifactId = await artifactIdentity(input.closure, input.generator, input.runtimeContract);
@@ -17,22 +53,7 @@ export async function generateReceptionArtifact(input: ReceptionGenerationInput)
17
53
  ...templates.map((template, index) => `const factory${index} = (() => { ${template.factorySource.replaceAll(JSON.stringify(RECEPTION_CLOSURE_TOKEN), JSON.stringify(input.closure))} return __NOMOS_RECEPTION_FACET__.${template.factory}; })();`),
18
54
  `const domains = Object.freeze(Object.assign(Object.create(null), { ${templates.map((template, index) => `${JSON.stringify(template.domain)}: factory${index}`).join(", ")} }));`,
19
55
  `const description = ${JSON.stringify({ artifactId, closure: input.closure, generator: input.generator, runtimeContract: input.runtimeContract, domains: inventory })};`,
20
- `function serialiseResult(value, depth = 0, seen = new Set()) {`,
21
- ` const unavailable = () => { const error = new Error("Generated result cannot cross the hosted JSON contract"); error.code = "nomos.reception.result.unavailable"; throw error; };`,
22
- ` if (depth > 64) return unavailable();`,
23
- ` if (value === undefined) return { $nomos: "absent" };`,
24
- ` if (value === null || typeof value === "string" || typeof value === "boolean") return value;`,
25
- ` if (typeof value === "number") { if (!Number.isFinite(value)) return unavailable(); return value; }`,
26
- ` if (typeof value === "bigint") return { $nomos: "integer", decimal: value.toString() };`,
27
- ` if (value instanceof Uint8Array) { let binary = ""; for (const byte of value) binary += String.fromCharCode(byte); return { $nomos: "bytes", base64: btoa(binary) }; }`,
28
- ` if (value instanceof Date) { if (!Number.isFinite(value.getTime())) return unavailable(); return { $nomos: "timestamp", iso: value.toISOString() }; }`,
29
- ` if (typeof value !== "object" || seen.has(value)) return unavailable();`,
30
- ` seen.add(value); try {`,
31
- ` if (Array.isArray(value)) return value.map(item => serialiseResult(item, depth + 1, seen));`,
32
- ` const prototype = Object.getPrototypeOf(value); if (prototype !== Object.prototype && prototype !== null) return unavailable();`,
33
- ` const output = {}; for (const [key, descriptor] of Object.entries(Object.getOwnPropertyDescriptors(value))) { if (descriptor.get || descriptor.set) return unavailable(); Object.defineProperty(output, key, { value: serialiseResult(descriptor.value, depth + 1, seen), enumerable: true }); } return output;`,
34
- ` } finally { seen.delete(value); }`,
35
- `}`,
56
+ ...RECEPTION_RESULT_SERIALISER,
36
57
  `export default class Reception extends WorkerEntrypoint {`,
37
58
  ` describe() { return description; }`,
38
59
  ` async invoke(call, office) {`,
@@ -1,7 +1,7 @@
1
1
  /** Derived Reception identity is independent of admitted law and request authority. */
2
2
  import { usdaString, usdaUnquote } from "./usda_text.js";
3
3
 
4
- export const RECEPTION_GENERATOR = "nomos-typescript-reception-v1";
4
+ export const RECEPTION_GENERATOR = "nomos-typescript-reception-v2";
5
5
  export const RECEPTION_RUNTIME_CONTRACT = "nomos-peer-office-reception-v1";
6
6
  export const RECEPTION_TEMPLATE_FORMAT = "nomos.reception-template.v1";
7
7
  export const RECEPTION_COMPRESSED_TEMPLATE_FORMAT = "nomos.reception-template.v2";
@@ -2,6 +2,7 @@
2
2
  * Unit associations must be authenticated upstream. A cached module receives closure context
3
3
  * only through a fresh host-verified binding; the Office still judges the requested operation. */
4
4
  import { RECEPTION_CLOSURE_TOKEN, RECEPTION_SOURCE_BYTES_LIMIT } from "./reception_artifact_contract.js";
5
+ import { RECEPTION_RESULT_SERIALISER } from "./reception_artifact.js";
5
6
  import {
6
7
  assertUnitReceptionRecord, snapshotUnitReceptionValue, canonicalReceptionOperations, closureReceptionOperations, closureReceptionManifestIdentity,
7
8
  receptionSha256, unitReceptionArtifactIdentity, unitReceptionUnavailable, validateUnitReceptionIdentity,
@@ -40,22 +41,7 @@ export async function generateUnitReceptionArtifact(input: UnitReceptionInput):
40
41
  `description.operations = Object.freeze(description.operations.map(operation => Object.freeze(operation))); Object.freeze(description);`,
41
42
  `const operations = Object.freeze(Object.fromEntries(description.operations.map(operation => [operation.member, Object.freeze(operation)])));`,
42
43
  `function bindFactory(__nomosBoundClosure) { ${template.factorySource.replaceAll(literal, "__nomosBoundClosure")} return __NOMOS_RECEPTION_FACET__.${template.factory}; }`,
43
- `function serialiseResult(value, depth = 0, seen = new Set()) {`,
44
- ` const unavailable = () => { const error = new Error("Generated result cannot cross the hosted JSON contract"); error.code = "nomos.reception.result.unavailable"; throw error; };`,
45
- ` if (depth > 64) return unavailable();`,
46
- ` if (value === undefined) return { $nomos: "absent" };`,
47
- ` if (value === null || typeof value === "string" || typeof value === "boolean") return value;`,
48
- ` if (typeof value === "number") { if (!Number.isFinite(value)) return unavailable(); return value; }`,
49
- ` if (typeof value === "bigint") return { $nomos: "integer", decimal: value.toString() };`,
50
- ` if (value instanceof Uint8Array) { let binary = ""; for (const byte of value) binary += String.fromCharCode(byte); return { $nomos: "bytes", base64: btoa(binary) }; }`,
51
- ` if (value instanceof Date) { if (!Number.isFinite(value.getTime())) return unavailable(); return { $nomos: "timestamp", iso: value.toISOString() }; }`,
52
- ` if (typeof value !== "object" || seen.has(value)) return unavailable();`,
53
- ` seen.add(value); try {`,
54
- ` if (Array.isArray(value)) return value.map(item => serialiseResult(item, depth + 1, seen));`,
55
- ` const prototype = Object.getPrototypeOf(value); if (prototype !== Object.prototype && prototype !== null) return unavailable();`,
56
- ` const output = {}; for (const [key, descriptor] of Object.entries(Object.getOwnPropertyDescriptors(value))) { if (descriptor.get || descriptor.set) return unavailable(); Object.defineProperty(output, key, { value: serialiseResult(descriptor.value, depth + 1, seen), enumerable: true }); } return output;`,
57
- ` } finally { seen.delete(value); }`,
58
- `}`,
44
+ ...RECEPTION_RESULT_SERIALISER,
59
45
  `export default class UnitReception extends WorkerEntrypoint {`,
60
46
  ` describe() { return description; }`,
61
47
  ` async invoke(call, office, binding) {`,
@@ -2,7 +2,7 @@
2
2
  * the caller must obtain this exact unit/template association from installed law. */
3
3
  import { receptionSha256, RECEPTION_RUNTIME_CONTRACT, type ReceptionOperation, type ReceptionTemplate } from "./reception_artifact_contract.js";
4
4
  export { receptionSha256, RECEPTION_RUNTIME_CONTRACT };
5
- export const RECEPTION_UNIT_GENERATOR = "nomos-typescript-unit-reception-v1";
5
+ export const RECEPTION_UNIT_GENERATOR = "nomos-typescript-unit-reception-v2";
6
6
  export const UNIT_RECEPTION_ARTIFACT_FORMAT = "nomos.reception-unit-artifact.v1";
7
7
  export const CLOSURE_RECEPTION_MANIFEST_FORMAT = "nomos.reception-closure-manifest.v1";
8
8
  export const UNIT_RECEPTION_SOURCE_LIMIT = 16 * 1024 * 1024;