Skip to content

Release: develop -> main - #4485

Merged
davidleomay merged 2 commits into
mainfrom
develop
Jul 30, 2026
Merged

Release: develop -> main#4485
davidleomay merged 2 commits into
mainfrom
develop

Conversation

@github-actions

Copy link
Copy Markdown

Automatic Release PR

This PR was automatically created after changes were pushed to develop.

Commits: 1 new commit(s)

Checklist

  • Review all changes
  • Verify CI passes
  • Approve and merge when ready for production

…4482)

* fix(auth): scope the login sign message per environment

A wallet signature proves ownership of the exact text that was signed. That
text was identical in every environment, so a signature stored in a lower
environment verified on PRD as well.

Non-PRD environments now prefix the sign message with their environment
marker, which changes the signed payload and therefore the resulting
signature. PRD keeps the historical text byte-for-byte, so existing PRD
signatures stay valid. An unset or unknown ENVIRONMENT is deliberately
treated as non-PRD: it must never accidentally yield a PRD-valid signature.

Covered by two specs: one pinning the PRD text byte-for-byte and the
fail-closed behaviour, one proving cryptographically that a signature made
over the PRD text does not verify under the DEV text and vice versa.

* feat(migration): rotate the DEV wallet signatures with an audited fingerprint

The stored user.signature values on DEV were produced with the previous
environment-independent sign message and would therefore still verify on
PRD. This clears them.

CONTRIBUTING.md requires an overwritten value to stay recoverable from the
database. That cannot be satisfied literally here, and the conflict is
inherent: the values are login credentials, so retaining them recoverably
would preserve exactly what this rotation removes. Instead the migration
writes an audit row to log before the update, holding the affected user id
and an md5 fingerprint of the previous signature rather than its plaintext,
and couples the update fail-closed to that insert via
EXISTS (SELECT 1 FROM "audit") - following the precedent in
1784600000011-FixFiatOutputValutaDateSerials.js. A known candidate value can
be checked against the fingerprint; the credential itself cannot be
recovered. md5 acts purely as an audit fingerprint here.

down() deliberately restores nothing: the prior value is not reconstructible
by design.

* test(auth): verify the signature rotation against a real database

The migration spec only matched substrings against a mocked query runner, so
an invalid CTE or a broken audit coupling would have passed unnoticed. Adds a
MIGRATION_TEST_PG-gated Postgres block, following the pattern already used by
most migration specs in this repo.

Four scenarios: the rotation writes exactly one audit row whose fingerprints
match independently computed md5 values; an empty candidate set writes no
audit row and changes nothing; a failing audit insert (log table dropped)
rejects and leaves the signature intact, which is the fail-closed property;
and the environment gate holds against a real database.

Also switches both sign-message specs to absolute imports as required by
CONTRIBUTING.md, and states the reason for the require-import lint exception.

* test(auth): isolate the audit coupling and pin the fingerprint-only guarantee

The previous commit claimed its dropped-log-table test proved the fail-closed
property. It did not: because the audit insert and the update are one
statement, Postgres rolls the statement back on any insert error, so that test
passes even without the EXISTS coupling. Verified by mutation - removing the
coupling left it green. Its title now says what it actually shows, statement
atomicity.

The coupling is now isolated by a test where the insert SUCCEEDS but yields no
row: a BEFORE INSERT trigger on log discards it without raising. up() then
completes, no audit row exists, and the signature must stay intact. Removing
the coupling turns exactly this test red.

Also pins the property the audit exception rests on - no plaintext signature
reaches the log, asserted negatively against both fixtures and against a
before field - narrows the env-scope titles to the text difference they
actually assert, since the cryptographic proof lives in the replay spec, and
covers an unknown ENVIRONMENT value.

* test(auth): make the down() assertion non-tautological

The mock was created but never passed to down(), which takes no query runner,
so the assertion observed an object the call could not reach. It would have
stayed green through any regression.

The unit test now asserts the actual property - down() takes zero parameters
and resolves - and the behavioural guarantee is pinned against a live database
instead: after up() followed by down(), the signature stays NULL and the single
audit row remains. Passing the mock in would have meant giving the migration a
parameter it does not need, changing production code to satisfy a test.

* test(auth): assert the environment gate for every non-dev value

Two titles claimed coverage for any non-dev ENVIRONMENT while their bodies set
only 'prd'. A regression that executed on loc, on an unset value or on an
unknown one would have left both green.

Both are now table-driven over prd, loc, staging and unset - at the unit level
and against a live database - so the fail-closed guarantee of the strict
!== 'dev' check is pinned across the whole value space instead of being
inferred from a single case.
…the completed files (#4483)

* test(coverage): close the last uncovered branches on three files

listKeys was never called without a prefix, the S3 WORM guard's
non-Error fallbacks were never hit, and the scorechain screening
entity's JSON getters were never read with a value set. Add the
missing cases and pin all three files in the coverage ratchet.

The unreachable `?? name` in the mock storage's getBlob is replaced
by a slice: split always yields at least one element, so pop can
never be undefined there and no test could ever cover that fallback.

* test(coverage): pin four more completed files and refresh the numbers

The gate run for the previous commit surfaced four files that already
hold 100% on all four metrics but were never pinned; they arrived with
recently merged PRs. Pin them and bring docs/coverage-gate.md to the
measured state.

Also records what the run made visible: part of the remaining branches
are defensive fallbacks that cannot fire at runtime, so they are closed
by removing the fallback, not by inventing a mock for them.

* test(coverage): pin the last completed file and rebase the measurement

Rebasing onto current develop moved the totals slightly and surfaced one
more file already at 100%. Pin it and restate the numbers against the
commit they were actually measured on.

* docs(coverage): correct the Frick gate size and the importer counts

The gate table still described the Frick gate as covering 7 files run by
7 specs; both the script and jest.frick.config.js have listed 10 for a
while. The test-scaffolding note counted 60 and 28 importers, now 62 and
29 - all of them still specs, so the surrounding claim is unchanged.

* docs(coverage): finish correcting the Frick gate size

Two more places still said the Frick gate runs seven specs: the prose in
the measurement section and the header comment of the ratchet config. A
sweep over the three files confirms these were the last ones.
@davidleomay
davidleomay merged commit 6cbf615 into main Jul 30, 2026
17 checks passed
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.

2 participants