Skip to content

Commit 3f8097d

Browse files
committed
fix: bound candidate identity before self-check comparison
The identity-set scan in computeTargetEvidence compared an untruncated candidate identity against the 256-byte-bounded recorded identity, so a node whose own id/label exceeds the field cap could fail to match itself, corrupting the record-time self-check. Compare through the same bounding on both sides instead.
1 parent a54993b commit 3f8097d

2 files changed

Lines changed: 33 additions & 1 deletion

File tree

src/daemon/__tests__/session-target-evidence.test.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -319,3 +319,29 @@ test('computeTargetEvidence: real-capture-shaped tree (undefined hittable, anony
319319
const json = serializeTargetAnnotationV1(evidence);
320320
assert.deepEqual(parseTargetAnnotationV1Payload(json), evidence);
321321
});
322+
323+
// ---------------------------------------------------------------------------
324+
// Self-consistency: a node whose own id/label exceeds the 256-byte field cap
325+
// must still match ITSELF during the identity-set scan. The recorded
326+
// identity is truncated (writer-parser invariant); comparing an untruncated
327+
// candidate against it would spuriously exclude the winner from its own
328+
// identity set and produce a false 'unverifiable'.
329+
// ---------------------------------------------------------------------------
330+
331+
test('computeTargetEvidence: a node with an over-cap id still matches itself and is verified', () => {
332+
const overCapId = 'save-'.repeat(100); // 500 bytes, well over the 256-byte field cap
333+
const nodes = toSnapshotNodes([
334+
{
335+
index: 0,
336+
type: 'Button',
337+
identifier: overCapId,
338+
label: 'Save',
339+
rect: { x: 0, y: 0, width: 10, height: 10 },
340+
depth: 0,
341+
},
342+
]);
343+
const evidence = computeTargetEvidence({ node: nodes[0]!, nodes });
344+
assert.ok(evidence);
345+
assert.equal(evidence.id, overCapId.slice(0, TARGET_ANNOTATION_MAX_FIELD_BYTES));
346+
assert.equal(evidence.verification, 'verified');
347+
});

src/daemon/session-target-evidence.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -235,7 +235,13 @@ function computeDisambiguationDomain(params: {
235235
// Step 2: all nodes sharing the winner's local identity with a matching
236236
// leaf-anchored ancestry prefix.
237237
const identitySet = nodes.filter((candidate) => {
238-
if (!matchesLocalIdentity(computeLocalIdentity(candidate), identity)) return false;
238+
// Compare through the SAME 256-byte bounding the recorded `identity` and
239+
// ancestry entries already went through — otherwise a node whose own
240+
// id/label exceeds the field cap would spuriously fail to match ITSELF
241+
// (the recorded value is truncated; the raw candidate value is not),
242+
// corrupting both this self-check and, if the replay-time matcher ever
243+
// skipped the same bounding, a real node's identity check later.
244+
if (!matchesLocalIdentity(boundedLocalIdentity(candidate), identity)) return false;
239245
const observedAncestry = buildAncestryChain(candidate, byIndex, Math.max(ancestry.length, 1));
240246
return matchesAncestryPrefix(observedAncestry, ancestry);
241247
});

0 commit comments

Comments
 (0)