Repository navigation
fix(audit-trail): close the remaining scope oracles and parity gaps with Ruby [PRD-1269] - #1983
Open
Conversation
bexchauveto
added this pull request to stack #1915
October 7, 2026 14:00
6 new issues
|
|
Coverage Impact Unable to calculate total coverage change because base branch coverage was not found. Modified Files with Diff Coverage (6)
🛟 Help
|
bexchauveto
force-pushed
the
feature/prd-1269-agent-nodejs-the-search-filter-lets-a-non-admin-test-the
branch
2 times, most recently
from
October 7, 2026 15:01
8fdcc29 to
280642a
Compare
bexchauveto
force-pushed
the
feature/prd-1269-agent-nodejs-the-search-filter-lets-a-non-admin-test-the
branch
from
October 8, 2026 13:23
280642a to
153870a
Compare
bexchauveto
force-pushed
the
feature/prd-1269-agent-nodejs-the-search-filter-lets-a-non-admin-test-the
branch
from
October 8, 2026 15:22
153870a to
e13e23e
Compare
bexchauveto
force-pushed
the
feature/prd-1269-agent-nodejs-the-search-filter-lets-a-non-admin-test-the
branch
2 times, most recently
from
October 9, 2026 12:33
67f6d5d to
381a38b
Compare
…n mask, and re-read a gone record [PRD-1269] Parity with agent-ruby on two gaps. Search: the values are matched as serialized JSON, where a quote or a backslash sits escaped, so the term is now escaped the same way. That finds '15" monitor' and keeps a bare quote off the document's structure. The [redacted] mask is removed before matching, so search=redacted no longer confirms which rows hold a masked value. Same change on the served-value path. Re-read: the record is read again after the audit read even when it was already gone at the first check, and gone at either read means gone. An id taken since by a record the caller cannot read answers 404, and one taken by an in-scope record does not release the earlier life's values. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e id [PRD-1269] A delete frees the id and a later record can take it. Both reads then see a live record in scope, so the route served the whole history as the current record's, including values the caller's scope would have withheld on the record that held the id before. Rows filed at or before the id's last confirmed delete now go through the gone-record withholding: on the history route (search and fields matched against the served values, so count and authors cannot leak them), on the correlation lookups, and on /state for a reconstruction at or before that delete. A pending delete frees nothing. The history route's early path for a record gone at the first check also re-reads it now, so an id since taken by a record the caller cannot read answers 404 there too. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e earlier record's [PRD-1269] At the delete's own instant the state already reflects every row there, including a replacement create sharing that timestamp. Testing it against the scope as an earlier life withheld the replacement whenever the scope read a column the capture never kept, although the caller had just been shown to read it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
bexchauveto
force-pushed
the
feature/prd-1269-agent-nodejs-the-search-filter-lets-a-non-admin-test-the
branch
from
October 9, 2026 12:43
381a38b to
13e339b
Compare
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

Stacked on #1946 (itself stacked on #1911). Its base retargets as those merge.
Closes PRD-1269.
Item 1, the timeline
searchoracle: resolved by removing the admin gateThe oracle existed because the timeline blanked values for non-admins while
searchstill matched them. #1911 now drops the admin gate: a caller who can read a collection gets its timeline values, the same ones they read record by record. There is nothing withheld left forsearchto probe, so theidentitySearchcommit this PR used to carry is gone.Item 2, parity gap 1: search escaping and the redaction mask (commit
35375d0de)Mirrors agent-ruby's
Sql::TextSearchandsearch_matches?, in both the SQL search and the served-value matcher:15" monitor(stored as15\" monitor) is found, and a bare quote no longer matches the JSON structure;[redacted]is removed from the value text before matching, sosearch=redactedno longer confirms which rows hold a masked value. Same as Ruby, a real value containing that literal loses it for matching.Item 3, parity gap 2: re-reading a record already gone (same commit)
Aligned with Ruby:
recheckRecordVisibilityre-reads whenever a scope applies, even for a record gone at the first check, and gone at either read means gone. An id since taken by a record the caller cannot read answers 404; one taken by an in-scope record does not release the earlier values. The history,/stateand correlation routes all go through it, including the history route's early path for a gone record withsearch/fields.Item 4, a recreated id serves the earlier life's values (commits
e0b310857,e13e23e66)Rows filed at or before the id's last confirmed
deletebelong to the earlier record, so they go through the gone-record withholding even though a live record holds the id now:search/fieldsthe matching runs on served values, so count and authors cannot leak them;/state: a reconstruction strictly before thatdeleteis tested against the scope. At the delete's own instant the state already includes a replacementcreatesharing it.A
pendingdelete frees nothing. The lookup (lastDeleteOf, inaudit-trail/earlier-life.ts) runs only for a scoped caller on a live record. Ruby still needs this: PRD-1258.These are permission-scope withholdings, independent of the admin level, so they stay after the gate removal.
Tests
redactedmatch nothing; same on the served-value path./statebefore the delete withheld, after it served, and at a delete shared with a replacementcreateserved. The boundary tests fail when the check is disabled or<=is used.760 tests pass across
test/audit-trailandtest/routes; lint and typecheck clean.🤖 Generated with Claude Code
Note
Fix audit-trail id-reuse scope oracles and close search parity gaps with Ruby
lastDeleteOfandbelongsToEarlierLifein earlier-life.ts. Audit rows at or before an id's latest confirmed delete are treated as a prior record life and withheld using earlier-life permission scope, not the current record's scope. Pending deletes do not establish a boundary.recheckRecordVisibility) so an id reused by an out-of-scope record returns 404.searchConditionandmatchesServedValues: terms are JSON-escaped (jsonEscaped), so quotes and backslashes match literally, and the redaction marker is stripped from stored values before matching.Macroscope summarized 13e339b.