diff --git a/docs/architecture/rfcs/typescript-control-plane-migration-v0.md b/docs/architecture/rfcs/typescript-control-plane-migration-v0.md index e896e74ba8..ad6e478010 100644 --- a/docs/architecture/rfcs/typescript-control-plane-migration-v0.md +++ b/docs/architecture/rfcs/typescript-control-plane-migration-v0.md @@ -1757,6 +1757,16 @@ domain. Transport checks such as framing, authentication, and size limits stay separate from schema validation and semantic invariants. An unchecked `JSON.parse(...) as T` must not establish control-plane authority. +For shipped Todo contracts, the coordination record codec reuses its first +strict-JSON normalization after checking allowed and required fields. That tree +already owns its containers and orders keys by Unicode code point; rebuilding +the same fields and cloning them again adds no authority or isolation. Generic +contracts retain legacy inherited-field selection when reconstruction is needed. +Keep malformed-JSON rejection, schema-error order, canonical bytes and +input/output independence unchanged. +This is a bounded shared-codec cost repair, not a provider-format migration or +qualification of sustained operation, CI capacity or release admission. + `as unknown as T` is permitted only as a named migration seam: its exact call site, upstream validator, negative boundary coverage, and removal owner must be visible in the cutover PR. A migrated domain cannot pass its promotion gate diff --git a/loopx/control_plane/coordination/coordination_state_contract.ts b/loopx/control_plane/coordination/coordination_state_contract.ts index ec302043f4..5deb8e8908 100644 --- a/loopx/control_plane/coordination/coordination_state_contract.ts +++ b/loopx/control_plane/coordination/coordination_state_contract.ts @@ -99,9 +99,15 @@ export function canonicalCoordinationRecord( `${label} omits required fields: ${missing.join(", ")}`, ); } - return canonicalAuthorityObject(Object.fromEntries( - contract.fields.flatMap((field) => field in record ? [[field, record[field]]] : []), - ), label); + // Preserve the generic contract's legacy `in` selection for inherited fields. + // Shipped Todo contracts need no reconstruction: normalization already owns + // every allowed field and nested container in canonical order. + if (contract.fields.some((field) => !Object.hasOwn(record, field) && field in record)) { + return canonicalAuthorityObject(Object.fromEntries( + contract.fields.flatMap((field) => field in record ? [[field, record[field]]] : []), + ), label); + } + return record; } export function canonicalCoordinationTodoRecord( diff --git a/tests/control_plane_ts/coordination_state_contract.test.ts b/tests/control_plane_ts/coordination_state_contract.test.ts index 774aa61001..69a51a68c2 100644 --- a/tests/control_plane_ts/coordination_state_contract.test.ts +++ b/tests/control_plane_ts/coordination_state_contract.test.ts @@ -197,3 +197,67 @@ test("record validation rejects a required field outside the declared schema", ( /required fields are absent from fields: role/, ); }); + +test("canonical records own nested containers and sort keys by Unicode code point", () => { + const nested = {z: [{b: 2, a: 1}], a: null}; + const input = Object.assign(Object.create(null), { + "\u{10000}": 4, "\ue000": 3, nested, + }); + Object.defineProperty(input, "__proto__", { + value: {tag: "owned"}, enumerable: true, writable: true, + }); + const contract = { + fields: ["\u{10000}", "\ue000", "nested", "__proto__"], required_fields: ["nested"], + }; + const result = canonicalCoordinationRecord(input, contract, "record"); + assert.equal(JSON.stringify(result), + '{"__proto__":{"tag":"owned"},"nested":{"a":null,"z":[{"a":1,"b":2}]},"\ue000":3,"\u{10000}":4}'); + assert.equal(Object.getPrototypeOf(result), Object.prototype); + assert.ok(Object.hasOwn(result, "__proto__")); + assert.notEqual(result.nested, nested); + const owned = result.nested as {z: {a: number; b: number}[]}; + assert.notEqual(owned.z, nested.z); + assert.notEqual(owned.z[0], nested.z[0]); + owned.z[0]!.a = 99; + assert.equal(nested.z[0]!.a, 1); + const retry = canonicalCoordinationRecord(input, contract, "record"); + assert.notEqual(retry, result); + assert.notEqual(retry.nested, result.nested); + assert.equal((retry.nested as typeof nested).z[0]!.a, 1); +}); + +test("strict JSON and schema errors retain their rejection order", () => { + const contract = {fields: ["payload"], required_fields: ["payload"]}; + const invalidContract = {fields: ["payload"], required_fields: ["absent"]}; + assert.throws(() => canonicalCoordinationRecord( + {unversioned: Infinity}, invalidContract, "record"), /JSON numbers must be finite/); + assert.throws(() => canonicalCoordinationRecord( + {unversioned: true}, invalidContract, "record"), + /record required fields are absent from fields: absent/); + assert.throws(() => canonicalCoordinationRecord( + {z_extra: true, a_extra: true}, contract, "record"), + /record has unversioned fields: a_extra, z_extra/); + assert.throws(() => canonicalCoordinationRecord({}, contract, "record"), + /record omits required fields: payload/); + const cycle: Record = {}; + cycle.self = cycle; + for (const value of [undefined, 1n, cycle, new Date(0)]) { + assert.throws(() => canonicalCoordinationRecord( + {payload: value, extra: true}, contract, "record"), + /value must be strict JSON|JSON value must be acyclic|JSON objects must be plain objects/); + } +}); + +test("generic contracts retain inherited-field selection and strict JSON rejection", () => { + for (const required_fields of [[], ["__proto__"]]) { + assert.deepEqual(canonicalCoordinationRecord({}, { + fields: ["__proto__"], required_fields, + }, "record"), JSON.parse('{"__proto__":{}}')); + } + assert.throws(() => canonicalCoordinationRecord({}, { + fields: ["constructor"], required_fields: [], + }, "record"), /value must be strict JSON/); + assert.deepEqual(canonicalCoordinationRecord({constructor: null}, { + fields: ["constructor"], required_fields: [], + }, "record"), {constructor: null}); +});