Skip to content

test(capture-kit): pin the ax1 digest wire format the daemon persists - #2941

Merged
thymikee merged 1 commit into
mainfrom
test/ax-digest-wire-format-gets-a-pin
Sep 25, 2026
Merged

thymikee merged 1 commit into
mainfrom
test/ax-digest-wire-format-gets-a-pin

Conversation

@thymikee

Copy link
Copy Markdown
Member

Summary

computeAxDigest returns a ~50-byte string that is persisted as post-action evidence and compared
across daemon restarts
(#1047). Nothing in the suite pinned the bytes it returns: 7 of its 9 tests
compared one invocation against another, so any change to the construction moved both sides of the
comparison together.

I verified that rather than assuming it. Mutants I planted and ran:

Production mutation Real-world meaning Suite before Suite after
drop the NUL separators from hashNode the tuple ('ab','','') hashes the same bytes as ('a','b','') — two different trees reporting identical evidence 9 passed 🚫 2 failed
PER_NODE_HASH_BYTES 16 → 8 per-node hashes start cancelling each other out in the XOR combiner (survives too) 1 failed (golden)

Three additions, each tied to one of those mutants:

  • field separation — ('ab','',''), ('a','b','') and ('a','','b') must all differ. This is
    the only cross-field collision guard in the function and it was invisible to the suite.
  • fixed width — /^ax1:[0-9a-f]{16}$/, replacing the startsWith('ax1:') probe, which a widened
    slice() or a dropped version prefix would survive. Strengthened in place rather than added next to
    its own weaker copy.
  • a golden for a fixed 3-node set, which makes the XOR width, the separator placement, and the
    node-count folding all wire contract instead of implementation detail.

Test-only; no production line changes. The ax1: prefix test was rewritten, not duplicated, so the
file does not grow a second assertion for the same claim.

Validation

Tested at c982439f8.

  • npx vitest run --project unit-core packages/capture-kit/src/snapshot/__tests__/ax-digest.test.ts →
    11 passed (was 9)
  • Both mutants reverted; git status clean of production changes before committing
  • pnpm check:affected --run — all runnable checks passed
  • oxlint . --deny-warnings, repo-wide oxfmt, tsc -b packages/capture-kit

Expected interaction: the golden is a deliberate wire pin. Changing the digest algorithm in the
future means updating this one literal, which is the point — the change is then visible in the diff
instead of silently invalidating evidence an earlier daemon wrote.

Note for reviewers: #2933 also touches this file. The hunks are ~55 lines apart (it deletes the
self-comparison test at the top; this edits the prefix test and appends), so they merge cleanly in
either order.

`computeAxDigest` returns a ~50-byte string that is persisted as post-action evidence and
compared across daemon restarts (#1047), yet nothing in the suite pinned the bytes. Every
test but two compared one invocation against another, so a change to the construction
moved both sides and the file stayed green.

Proven by mutant: deleting the NUL separators from `hashNode` — which makes the tuple
('ab', '', '') hash the same bytes as ('a', 'b', ''), i.e. two different trees reporting
identical evidence — leaves `ax-digest.test.ts` at 9 passed. It is the only cross-field
collision guard in the function and no test could see it go.

Three additions, each with a named mutant:
- field separation: ('ab','','') vs ('a','b','') vs ('a','','b') must differ.
  Separator removal now fails 2 tests.
- fixed width: `ax1:` + exactly 16 hex, replacing the `startsWith('ax1:')` probe, which
  a widened `slice()` or a dropped prefix would survive.
- a golden for a fixed 3-node set, so XOR width, separator placement, and the count
  folding are all contract. Narrowing `PER_NODE_HASH_BYTES` to 8 fails only this one.

Test-only: no production line changes.
@github-actions

Copy link
Copy Markdown
Contributor

Size Report

Metric Base Current Diff
Installed (including dependencies) 4.81 MB 4.81 MB 0 B
Package (unpacked) 4.81 MB 4.81 MB 0 B
Package (download) 1.44 MB 1.44 MB -4 B

Startup median (7 runs, lower is better):

Scenario Base Current Diff
CLI --version 31.5 ms 32.7 ms +1.2 ms
CLI --help 92.0 ms 93.3 ms +1.3 ms

@thymikee

Copy link
Copy Markdown
Member Author

Reviewed at c982439: this is a test-only golden table pinning the ax1 digest wire format, and it's ready for human review. All 17 checks pass, and the diff only touches packages/capture-kit/src/snapshot/tests/ax-digest.test.ts, so no production route is exercised by this change. Not blocking: the golden's comment and the PR title describe the digest as "persisted ... across daemon restarts," but every comparison found (https://github.com/callstack/agent-device/blob/c982439/src/commands/interaction/runtime/post-action-surface.ts#L66, https://github.com/callstack/agent-device/blob/c982439/src/commands/interaction/runtime/interaction-ios-tap-outcome.ts#L221-L223) computes both sides within one command execution rather than reading anything back after a restart, so it would be worth rewording that comment and the title to the field's real contract — a client-visible value in --verify evidence where a byte change is a user-visible output change — but that can wait for a follow-up.

@thymikee thymikee added the ready-for-human Valid work that needs human implementation, judgment, or maintainer merge label Sep 24, 2026
@thymikee
thymikee merged commit bfc464a into main Sep 25, 2026
17 checks passed
@thymikee
thymikee deleted the test/ax-digest-wire-format-gets-a-pin branch September 25, 2026 05:43
@github-actions

Copy link
Copy Markdown
Contributor
PR Preview Action v1.8.1
Preview removed because the pull request was closed.
2026-09-25 05:43 UTC

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ready-for-human Valid work that needs human implementation, judgment, or maintainer merge

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant