Skip to content

feat(tools): verify-time twin-equivalence gate keyed on twin_of (LAB-3967) - #65

Open
27Bslash6 wants to merge 3 commits into
agent/winston/0f053e16from
agent/winston/29eba07be647
Open

27Bslash6 wants to merge 3 commits into
agent/winston/0f053e16from
agent/winston/29eba07be647

Conversation

@27Bslash6

@27Bslash6 27Bslash6 commented Sep 19, 2026

Copy link
Copy Markdown
Contributor

Summary

This PR promotes the twin-equivalence check for frame test vectors from a generate-time-only stderr warning into a hard verify gate, driven by a new operator-owned twin_of declaration (LAB-3967, follow-up to LAB-1203).

Problem

The _bin vector in test-vectors/python-frame.json claims to be an "encoding-only twin" of the legacy default_saas_write_msgpack_bytestorage vector — differing only in envelope encoding (msgpack bin vs. array-of-integers). Previously that claim was:

  • Made only in prose (the vector's description), and
  • Enforced only at generate time as a stderr warning that CI never reads.

The check could not be made a hard failure at generate time: the legacy array-of-ints wheel no longer ships in any installable release, so a legitimate future write-path change would permanently deadlock generate (the twin can never re-match a frozen legacy vector).

Changes

New twin_of field. The _bin vector now carries "twin_of": "default_saas_write_msgpack_bytestorage" — an explicit, operator-owned declaration living in the fixture rather than in code. From the bytes alone, "the wheel drifted" and "the protocol legitimately moved" are indistinguishable, so the claim must be declared rather than inferred.

verify becomes the CI gate. A shared _twin_divergence() helper hard-fails any declared twin that differs from its base beyond envelope encoding, checking value_json, the frame prefix (compared as raw bytes, so a header key reorder is caught), and envelope fields compressed_data_hex, checksum_hex, original_size, format, and inner_msgpack_hex. It also fails vacuous declarations — self-reference, same-encoding copies, envelope-less bases, unknown vector names, and duplicate vector names.

generate keeps warning, never raising. The old _require_twin_equivalence becomes _warn_twin_divergence, sharing the same divergence logic as verify so the two can't disagree on what "twin" means. The warning now names the two legitimate exits: fix the wheel/codec, or drop twin_of in the same commit as the regenerated bytes.

_upsert carries twin_of across rebuilds. The field is preserved on rewrite (and ignored when comparing for no-op churn), and is never added or dropped automatically — that stays the operator's reviewable move. The _bin description is moved to a BIN_DESCRIPTION constant that no longer makes the twin claim in prose (the field is the claim), so the gate survives the next generate.

Tests. tools/test_python_frame_reference.py is rewritten as a mutation suite over the committed fixture, proving verify fails on each covered field, that dropping twin_of stays green without loosening any byte compare, that generate warns but never raises (the LAB-1203 deadlock pin), and that _upsert carries the declaration correctly.

Impact

The fixture sha256 changes because of JSON metadata only (twin_of added, _bin description reworded); every frame_hex, expected_payload_hex, expected_header, and payload_envelope byte is unchanged. No SDK vendored this fixture at the time, so nothing downstream needs to re-pin. Documentation in spec/wire-format.md and CHANGELOG.md is updated accordingly.


Summary

This PR hardens the twin-equivalence verification gate to fail gracefully on malformed or incomplete twin declarations, ensuring these produce clean FAIL output lines instead of unhandled tracebacks.

Changes

Robustness of the _twin_divergence check (tools/python-frame-reference.py):

  • Validates that twin_of is a string vector-name before use; a non-string value now yields a descriptive failure reason rather than crashing.
  • Broadens the "missing field" detection to treat both absent keys and null values as missing, and adds frame_hex to the list of required fields.
  • Extends validation into the payload_envelope sub-fields, so a partial envelope (missing or null sub-field like inner_msgpack_hex) is reported by name.

Test coverage (tools/test_python_frame_reference.py):

  • Adds cases confirming that a partial envelope, a null envelope sub-field on both sides, and a non-string twin_of each cause verify to exit 1 with a named FAIL line rather than a traceback.
  • Adds a generate-path case where the base vector lacks frame_hex, confirming it warns without raising.

Documentation (CHANGELOG.md):

  • Updates the changelog to reflect that partial-envelope vectors are now caught, clarifying that a missing envelope field produces a FAIL line rather than a traceback.

Purpose

The core intent is to ensure the twin-equivalence gate can never crash on malformed input — every invalid twin declaration produces a diagnostic failure message naming the specific problem, and null values are prevented from vacuously satisfying the equivalence claim.

…laration (LAB-3967)

verify() now hard-fails any python-frame vector declaring `twin_of` that
differs from its base beyond envelope encoding (value_json, frame-prefix
bytes, compressed_data_hex, checksum_hex, original_size, format,
inner_msgpack_hex), or whose declaration cannot hold: unknown base,
self-reference, same-encoding copy, envelope-less base, duplicate vector
names. Until now that check ran only at generate time, as a warning.

Keyed on the declaration, not the bytes: from the bytes alone "the wheel
drifted" and "the protocol legitimately moved while the legacy vector
stayed frozen" are indistinguishable, so mirroring the byte compare into
CI would move the generator deadlock one level up. The exit for a
legitimate protocol evolution is dropping `twin_of` in the same commit as
the regenerated bytes — a reviewable fixture diff — with every byte
comparison intact and both encodings still observed.

- generate keeps warning (never raising) and names both exits
- _upsert carries `twin_of` across rebuilds; generate never adds/drops it
- the generator's `_bin` description no longer claims twin-ness in prose,
  so the exit survives the next generate
- fixture: `twin_of` added to the `_bin` vector, description reworded;
  every frame/payload/envelope byte unchanged (sha256 d8a3756a… →
  f43eb733…); no repo in the org vendors this fixture
- test_python_frame_reference.py rewritten as a stdlib mutation suite
  that runs verify against a mutated copy of the committed fixture
- dead `import msgpack` in generate(); bare `assert` on module import in
  the test → explicit ImportError
…(LAB-3967)

The diverged generate-time case built its envelope from the legacy vector,
so it inherited envelope_encoding "int-array" and the new "a twin must
differ from its base in encoding" rule fired before the field compare;
the case then asserted the wrong warning text.
@coderabbitai

coderabbitai Bot commented Sep 19, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository: cachekit-io/protocol/.coderabbit.yaml

Review profile: ASSERTIVE

Plan: Team

Run ID: 198b4658-56e9-4332-9635-824e49a3bbe7

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Comment @coderabbitai help to get the list of available commands.

@kodus-27b

This comment has been minimized.

Comment thread tools/python-frame-reference.py
Comment thread tools/python-frame-reference.py

@kodus-27b kodus-27b Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Found critical issues please review the requested changes

…FAIL, not a traceback (LAB-3967)

The presence guard covered value_json, expected_payload_hex and
payload_envelope, then strictly indexed frame_hex and the five envelope
fields the twin compare reads. A vector whose payload_envelope lacked
one of them made verify() die with a KeyError traceback instead of the
`FAIL {name}: ...` line the gate promises, and the generate-time warning
path raised where its contract is "warn, never raise".

The guard now covers every field the compare reads, and tests the value
rather than the key: a JSON null on both sides would otherwise compare
equal and pass the twin claim vacuously. A non-string `twin_of` (an
unhashable list, say) is reported the same way instead of a TypeError.
The comparison itself stays strict indexing after the precondition.

The mutation suite pins each case against the pre-fix behaviour; the
CHANGELOG sentence names partial-envelope vectors alongside envelope-less
ones.
@kodus-27b

kodus-27b Bot commented Sep 19, 2026

Copy link
Copy Markdown

Kody Review Complete

Great news! 🎉
No issues were found that match your current review configurations.

Keep up the excellent work! 🚀

Kody Guide: Usage and Configuration
Interacting with Kody
  • Request a Review: Ask Kody to review your PR manually by adding a comment with the @kody start-review command at the root of your PR.

  • Validate Business Logic: Ask Kody to validate your code against business rules by adding a comment with the @kody -v business-logic command.

  • Provide Feedback: Help Kody learn and improve by reacting to its comments with a 👍 for helpful suggestions or a 👎 if improvements are needed.

Current Kody Configuration
Review Options

The following review options are enabled or disabled:

Options Enabled
Bug
Performance
Security
Business Logic

Access your configuration settings here.

@27Bslash6

Copy link
Copy Markdown
Contributor Author

@kody start-review

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant