fix(daemon): declare CHAIN_ROW_COLUMNS once, in store.rs - #424
vladimirrott merged 5 commits into
Conversation
The 17-column ChainRow SELECT list was declared twice — once per backend — kept in sync only by a 'Mirrors' doc comment. The Postgres copy's own comment records the last drift: a column addition updated one query and the mapper, and the miss surfaced only as a runtime 'no column found for name: chain_version' from the live-Postgres test while unit tests stayed green. Hoist one pub(crate) const into store.rs, the shared parent of both backends; both modules import it, the four SELECT sites keep their per-backend placeholders (?1 / $1), and the second declaration goes. The drift test ran green first, was mutated red on purpose (failure pasted in the PR), and is replaced by a pin on the exact 17-column list — the invariant both mappers depend on. Closes lacs-project#397
chain_row_columns_pinned adds one test to the workspace suite. The baseline and the three published figures that derive from it move with it, per the check the CI run called out.
vladimirrott
left a comment
There was a problem hiding this comment.
Reviewed at 52e03a90899b6bfb9f9da4f5a5d8bc3a7368beb9.
Two identical declarations of a 17-column list, one in transactions.rs and one in store/postgres.rs, collapsed into one in store.rs. The reason this is worth doing rather than tidy-for-its-own-sake is that the two copies decode differently: SQLite reads positionally and Postgres reads by name, so a drift between them would corrupt one backend silently while the other stayed correct.
I checked the only thing that actually matters here, which is whether the column list and its order survived the move byte for byte. Both removed declarations:
const CHAIN_ROW_COLUMNS: &str = "seq, key_id, transaction_id, request_id, request_hash, \
action_name, risk_level, summary, approval_id, warnings_json, \
created_at, prev_chain_hash, chain_hash, chain_version, caller_role, event_tip, \
caller_principal";and the new one in store.rs is the same string with pub(crate) added and a line break after =. Identical content, identical order. The four SELECT {CHAIN_ROW_COLUMNS} call sites in transactions.rs and store/postgres.rs are untouched by this PR, so nothing about the emitted SQL changes.
I also confirmed the positional side still lines up: chain_row_from_sqlite maps indices 0 through 16 onto those 17 names in that order, and row_to_chain_row on the Postgres side uses try_get("name"), which is order-insensitive. So the invariant your new test pins is the real one.
The test is the part I liked most:
let columns: Vec<&str> = crate::store::CHAIN_ROW_COLUMNS.split(',').map(str::trim).collect();
assert_eq!(columns, [ ... ], "CHAIN_ROW_COLUMNS changed — both ChainRow mappers read these \
columns in this order, so update them in the same change");
assert_eq!(columns.len(), 17, "column count drifted from 17");It reads the production constant and compares it against a spelled-out literal, which is an independent oracle rather than a restatement of the constant. And reverting the hoist does not leave it green, it stops the crate compiling, because the test reaches through crate::store::CHAIN_ROW_COLUMNS and that path would not exist.
The test count is handled correctly: one #[test] added, tests/evidence/workspace-tests.json 1852 to 1853, and all three prose files moved with it. I verified the delta rather than trusting the number.
One honest limitation, not a regression and not something to fix here: the pin asserts the string, and nothing asserts that chain_row_from_sqlite's indices track it. Someone reordering the constant and the mapper together would pass. That is the same coverage the repo had before your change, so you have not lost anything, but it is the next thing worth closing if you want it.
A coordination note that is not your fault: #413 and #426 also move the baseline from 1852 to 1853, and because the hunk is textually identical in all three, git merges them without a conflict. Whichever lands second carries a recorded 1853 against a real 1854. That fails closed rather than silently, since scripts/test_baseline.sh re-measures, but it does mean I regenerate the baseline in the merge rather than inheriting your figure. Mine to sequence.
Approving. Clean, well-scoped, and the constant is now at the right owning boundary.
|
Short update, and it costs you nothing. I merged #426 as That makes your I said on the review that sequencing this was mine, and it still is. Nothing to push, nothing to chase. |
…and lacs-project#421 The branch recorded 1853, which was right when it was written and is one short now that lacs-project#426 landed. The figure comes from the runner rather than from arithmetic: $ UPDATE_TEST_BASELINE=1 scripts/test_baseline.sh test_baseline: recorded 1854 rust tests in tests/evidence/workspace-tests.json The three published figures move with it, in README.md, docs/introduction.md and docs/distro-support.md, and scripts/check_evidence_claims.py agrees: Published figures match the evidence artifacts. Maintainer change on a contributor branch; the pull request's own commits are untouched.
vladimirrott
left a comment
There was a problem hiding this comment.
Re-approving at 9c815c21. The push that dismissed the earlier approval was mine, not yours, and it moved four numbers and nothing else.
Your production commit 627d875 is untouched. Against main as it stands now, the whole pull request is:
crates/sysknife-daemon/src/store.rs | 63 ++++++++++++++++++
crates/sysknife-daemon/src/store/postgres.rs | 10 ++---
crates/sysknife-daemon/src/transactions.rs | 14 ++-----
README.md, docs/introduction.md, docs/distro-support.md, tests/evidence/workspace-tests.json
The baseline needed moving because #426 landed while this sat in the queue, and your 1853 became one short. I took the figure from the runner rather than from arithmetic, since a hand-typed count is the exact thing test_baseline.sh exists to abolish and it would pass every check on its way in:
$ UPDATE_TEST_BASELINE=1 scripts/test_baseline.sh
test_baseline: recorded 1854 rust tests in tests/evidence/workspace-tests.json
$ python3 scripts/check_evidence_claims.py
Published figures match the evidence artifacts.
One failure worth naming so it does not look like yours. The pre-commit run reported a_sigterm_ignoring_child_is_escalated_to_sigkill_within_the_grace failing, on a tree that had passed it minutes earlier. It sets SYSKNIFE_ACTION_TIMEOUT_SECS as a process-global and asserts on a one-second deadline plus a five-second grace, which is #356's shape, and two cargo builds were competing for the machine at that moment. Run alone it takes 6.27s against that bound:
$ cargo nextest run -p sysknife-daemon --test execute_spec --test-threads=1
PASS [6.268s] a_sigterm_ignoring_child_is_escalated_to_sigkill_within_the_grace
Summary 21 tests run: 21 passed, 0 skipped
Nothing in your diff goes near the executor. That is #356 under load, not this pull request.
The single declaration is the right call and the comment above it is the part I would keep. Naming the miss that motivated it, a chain_version column added to the mapper and one of the two queries, showing up only as a live-Postgres no column found for name, is what makes the next person leave it alone.
|
Correcting myself on one thing in that review, because I named the wrong cause and it points at an open issue that has nothing to do with it. I attributed the That is the pre-check at the top of the test, and it fired in 0.028s, far too fast to be the one-second deadline or the twenty-second bound I quoted. The cause was mine: I had two test runs of this repository going at once on the same machine, and the marker The test was right and it said so in one line. Nothing to fix in it, nothing to fix in your pull request, and #356 is unrelated. The conclusion I drew stands; the reason I gave for it did not. |
|
Merged as The gate ran the proof rather than taking my summary of it. Moving one column name inside A change-detector test is usually a smell, and this one is not, for a reason worth writing down: the positional SQLite mapper and the by-name Postgres mapper both depend on this list and neither can tell you when it moves. The pin is the only thing that can. Your comment says as much, and naming the miss that caused it, a
Thanks for this one. It is a small diff that removes a whole failure mode. |
Problem
CHAIN_ROW_COLUMNS— the 17-column SELECT list everyChainRowread shares — was declared twice: once intransactions.rs(SQLite) and once instore/postgres.rs, kept in sync only by a "Mirrors" doc comment. The Postgres copy's own comment records the last drift: adding the caller-identity columns updated the mapper and one of the two queries, and the miss surfaced only as a runtimeno column found for name: chain_versionfrom the live-Postgres test while unit tests stayed green. A contributor adding a column still has to remember the second file, and only a running Postgres catches the miss (#397).Solution
pub(crate) const CHAIN_ROW_COLUMNSintostore.rs, the shared parent of both backends; the drift history paragraph moves with it.?1, Postgres$1— only the column list is shared). The second declaration is deleted.Tests, per the issue's sequence:
chain_row_columns_agree_across_backendsasserting the two copies are equal, and ran it green.caller_principaltocaller_principaIin the Postgres copy:chain_row_columns_pinned: asserts the exact 17-column list and count, which is the invariant both mappers (positional SQLite, by-name Postgres) actually depend on.Result
cargo nextest run -p sysknife-daemon: 1081 passed, 5 skipped.cargo fmt --checkandcargo clippy --all-targetsclean. No Postgres needed — this removes the check the live-Postgres job was uniquely positioned to catch.Closes #397