From 67335c498c2c52c6ba86dea6f825b5171ca1fe83 Mon Sep 17 00:00:00 2001 From: sim Date: Tue, 25 Aug 2026 08:53:49 -0400 Subject: [PATCH] fix(#3841): express the anchor semantically so the pretty payload still verifies The remote matrix caught a regression I introduced in the previous commit. The anchor check was implemented as a byte-prefix match against IDENTITY_RAW_PREFIX, which describes the `--raw` wire format -- but `cmdRuntimeIdentity` without that flag pretty-prints at indent 2, and the classifier is handed BOTH serializations. Only the shell is restricted to `--raw`. The pre-existing test that runs the real verb with no flag went red: `+ 'unparseable' - 'ok'`. No local gate caught it. build:lib, eslint and lint:ci were green throughout, because none of them execute tests. The anchor now reproduces its two properties semantically instead of byte-wise, and both hold for either serialization: the payload begins at the first byte of stdout, and `packageName` serializes first. IDENTITY_RAW_PREFIX stays exported with its own tests -- it is the wire contract for the shell, not a general classifier predicate, and conflating those was the error. Adds the two rows the matrix was missing: the default pretty serialization verifies, and a pretty payload with `packageName` not first does not. The design and matrix now record that they enumerated only the inputs the SHELL produces and assumed the classifier's input set was the same. Refs #3841 Co-Authored-By: Claude Opus 5 --- src/runtime-identity.cts | 26 ++++++++++++++++---------- tests/runtime-identity.test.cjs | 13 +++++++++++++ 2 files changed, 29 insertions(+), 10 deletions(-) diff --git a/src/runtime-identity.cts b/src/runtime-identity.cts index 5abea8bb5..984fc470e 100644 --- a/src/runtime-identity.cts +++ b/src/runtime-identity.cts @@ -190,16 +190,22 @@ export function classifyIdentityProbe( const version = typeof record.version === 'string' ? record.version : undefined; if (actual !== expected) return { reason: 'identity_mismatch', expected, actual, version }; - // ANCHOR PARITY (#3841). The shell preamble cannot parse JSON; it matches - // a `case` pattern anchored at the START of stdout, so `packageName` must - // serialize first. A structural parse alone would accept - // `{"note":"x","packageName":""}` — a shape this package never emits — - // while the shell rejected it, so the two surfaces would disagree in the - // FAIL-OPEN direction. Honoring the anchor here is also what the module - // already claims to do: IDENTITY_RAW_PREFIX is documented as "ANCHORED, - // never a substring search", and buildIdentityPayload inserts packageName - // first precisely so a genuine payload always satisfies it. - if (!probe.stdout.startsWith(IDENTITY_RAW_PREFIX)) { + // ANCHOR PARITY (#3841). The shell preamble cannot parse JSON: it matches + // a `case` pattern anchored at the START of stdout, so a payload only + // verifies there when it begins at the first byte and serializes + // `packageName` first. A purely structural parse would accept + // `{"note":"x","packageName":""}` and a leading-whitespace payload + // that the shell rejects -- a FAIL-OPEN disagreement between the two + // surfaces. + // + // Reproduce those two properties SEMANTICALLY rather than byte-matching + // IDENTITY_RAW_PREFIX. The prefix describes the `--raw` wire format, but + // this classifier is also handed the DEFAULT pretty serialization + // (`runtime-identity` with no `--raw`, which is two-space indented) -- + // byte-matching the compact prefix rejected that, which the remote matrix + // caught. `startsWith('{')` and a first-key check hold for both. + const firstKey = Object.keys(record)[0]; + if (!probe.stdout.startsWith('{') || firstKey !== 'packageName') { return { reason: 'unparseable', expected, diff --git a/tests/runtime-identity.test.cjs b/tests/runtime-identity.test.cjs index d1f56452b..495b6303f 100644 --- a/tests/runtime-identity.test.cjs +++ b/tests/runtime-identity.test.cjs @@ -190,6 +190,19 @@ describe('classifyIdentityProbe', () => { assert.equal(v.reason, 'unparseable'); }); + test('the default PRETTY serialization still verifies (not just --raw)', () => { + // The classifier is handed both serializations: the shell only ever sees + // `--raw`, but `runtime-identity` with no flag emits two-space-indented + // JSON. An anchor expressed as a compact byte prefix rejected this. + const pretty = JSON.stringify({ packageName: EXPECTED_PACKAGE_NAME, version: '9.9.9' }, null, 2); + assert.equal(classifyIdentityProbe({ stdout: `${pretty}\n`, exitCode: 0 }).reason, 'ok'); + }); + + test('a pretty payload with packageName not first does not verify', () => { + const pretty = JSON.stringify({ note: 'x', packageName: EXPECTED_PACKAGE_NAME }, null, 2); + assert.equal(classifyIdentityProbe({ stdout: `${pretty}\n`, exitCode: 0 }).reason, 'unparseable'); + }); + test('a foreign packageName is a mismatch wherever it appears in the object', () => { const stdout = '{"note":"x","packageName":"get-shit-done-cc"}'; const v = classifyIdentityProbe({ stdout, exitCode: 0 });