Skip to content

[rosary-44eec8] fix(store): backend guardrail parity — one BeadStore contract, both impls deliver it - #483

Merged
jamestexas merged 5 commits into
mainfrom
rosary-44eec8-backend-guardrail-parity
Sep 9, 2026
Merged

jamestexas merged 5 commits into
mainfrom
rosary-44eec8-backend-guardrail-parity

Conversation

@jamestexas

Copy link
Copy Markdown
Contributor

Problem

The two BeadStore impls each enforced a different, non-overlapping subset of guardrails — one advertised contract, two sets of guarantees. Found by the 2026-08-31 architecture audit; scoped precisely by rosary-46e7ff's 34-method parity matrix (9 contract violations, 20 identical, evidence: ~/codebase-audits/agentic-research-rosary/3274bd9/contract-parity/):

  • Transition validation (can_transition_to) fired only in SqliteBeadStore — Dolt accepted any transition, including out of terminal done.
  • Secret scrubbing fired only in Dolt — the default SQLite backend wrote secrets verbatim into the store and the git-tracked beads.jsonl. The update path scrubbed on neither.
  • get_status on Dolt skipped short-id resolution (the one method that did — its own close_bead resolves), so known beads read as None.
  • close_bead stored the alias 'closed' on both; SQLite healed it on the next connect, Dolt never — the Linear close sweep saw backend- and process-age-dependent result sets.
  • Dolt update_comment's read-then-write could corrupt the original_text audit trail under concurrent first edits; SQLite update_bead_fields could commit a partial update on mid-sequence error.

Fix — same policy, backend-appropriate mechanisms

Not a shared wrapper. Each backend delivers the guarantee via its native mechanism (the skeptic-pass constraint from the audit: SQLite's mutex pattern copied onto a MySQL pool would be a race that claims the guarantee without delivering it):

  • Dolt transitions: new update_status_checked — read-check-write inside one transaction with SELECT … FOR UPDATE, same error shape as SQLite. The raw update_status remains the verbatim primitive: set_status_verbatim continues to bypass by design (the bead_correct escape hatch from incident rosary-e0e19f), pinned by a dedicated regression test on both backends.
  • Scrubbing: scrub_and_warn on every free-text write path on both backends (create title/description, comment add/update, update_bead_fields title/description).
  • Canonical close: both backends store 'done' directly; readers still tolerate legacy 'closed' rows. Ten tests asserting the alias updated.
  • Comment race: collapsed to one atomic statement — SET original_text = COALESCE(original_text, text), text = ? (MySQL evaluates SET left-to-right) — structurally race-free, no transaction needed.
  • update_bead_fields: SQLite path wrapped in a transaction; Dolt gains scrubbing.

Deferred deliberately (own change + migration sweep): Dolt readers silently defaulting malformed timestamps — flipping read-failure semantics can brick legacy stores.

Tests

TDD: 5 cross-backend parity tests written first (4 observed red on the Dolt halves against a real sandboxed Dolt 1.86.1 server before implementation). They drive identical sequences through both impls: invalid-transition rejection, set_status_verbatim bypass survival, scrubbing across all four text paths, canonical close, short-id get_status. These are the first tests pinning update_status transition behavior on either backend. Full suite: 1720 bin tests + integration suites green; task check green.

Closes rosary-44eec8. Depends-on rosary-46e7ff (closed — parity matrices).

🤖 Generated with Claude Code

https://claude.ai/code/session_01PMJgGCoDfQ7QF4R2vcupBJ

…contract, both impls deliver it

The two BeadStore impls each enforced a different, non-overlapping
subset of guardrails (audit finding, scoped by rosary-46e7ff's
34-method parity matrix: 9 contract violations). This closes the gap
set with backend-appropriate mechanisms, not a shared wrapper:

- Transitions: new DoltClient::update_status_checked validates
  can_transition_to inside one transaction (SELECT ... FOR UPDATE) —
  the same guarantee SQLite's mutex-held read-check-write gives, via
  Dolt's native mechanism. A bare read-then-write would be a race that
  claims the guarantee without delivering it. The raw update_status
  stays as the verbatim primitive: set_status_verbatim continues to
  bypass BY DESIGN (bead_correct escape hatch, rosary-e0e19f), pinned
  by a regression test on both backends.
- get_status on Dolt resolves short ids (the one method that didn't;
  its own close_bead always did) — unresolvable ids are Ok(None),
  matching SQLite's contract.
- Secret scrubbing now fires on both backends for every free-text
  write: SQLite create title/description + comment add/update
  (previously Dolt-only — the DEFAULT backend wrote secrets verbatim
  into the store and the git-tracked JSONL), and update_bead_fields
  title/description on both (previously scrubbed nowhere).
- close_bead stores canonical 'done' on both backends. Previously the
  alias 'closed': SQLite healed it on the next connect, Dolt never,
  so the Linear close sweep saw backend- and process-age-dependent
  result sets. Readers still tolerate legacy rows; 10 tests asserting
  the alias updated to canonical.
- Dolt update_comment: read-then-conditional-write collapsed into one
  atomic statement (SET original_text = COALESCE(original_text, text),
  text = ? — MySQL evaluates SET left-to-right), closing the race
  where concurrent first edits corrupt the original_text audit trail.
- SQLite update_bead_fields wrapped in a transaction — a mid-sequence
  error no longer commits a partial update.

Deferred (own change, own migration sweep): Dolt readers silently
defaulting malformed timestamps where the trait demands fail-loud —
changing read-failure semantics can brick legacy stores.

Tests (red first — 4 Dolt-half failures observed pre-implementation):
5 cross-backend parity tests drive identical sequences through
SqliteBeadStore (pure) and DoltBeadStore (SandboxBeads, real dolt
server, self-skips without the binary) — the first tests pinning
update_status transition behavior on either backend.

Evidence: ~/codebase-audits/agentic-research-rosary/3274bd9/contract-parity/

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PMJgGCoDfQ7QF4R2vcupBJ
Copilot AI lite review requested due to automatic review settings September 2, 2026 19:00

Copilot AI 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.

🟡 Changes recommended

Dolt get_status can now silently mask real DB/SQL errors as “not found,” and the new Dolt-parity tests may still skip on CI, weakening the regression guarantee.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

This PR aligns the behavioral guardrails of the two BeadStore implementations (SQLite default backend and Dolt/MySQL-backed backend) so that the trait contract is enforced consistently across both, addressing audit-identified parity gaps.

Changes:

  • Enforce transition validation on Dolt status updates (transactional, SELECT … FOR UPDATE) while preserving the set_status_verbatim escape hatch.
  • Add secret scrubbing to SQLite write paths and to update_bead_fields on both backends; canonicalize terminal close state to "done".
  • Add cross-backend parity tests and update existing tests expecting "closed" to now expect "done".
File summaries
File Description
tests/close_jsonl_sync.rs Updates close sync test to expect canonical "done" status.
src/serve/handlers/tests.rs Updates MCP close test to expect "done".
src/serve/handlers/input_validation_tests.rs Updates forced close validation test to expect "done".
src/main.rs Updates internal tests/expectations to use "done" instead of "closed".
src/dolt/tests.rs Adds cross-backend parity tests for transitions, scrubbing, close canonicalization, and short-id status lookup.
src/dolt/query.rs Updates Dolt get_status to resolve short IDs first.
src/dolt/deps.rs Makes Dolt comment update atomic to preserve original_text under concurrency.
src/dolt/bead_crud.rs Adds transactional, transition-validated status update; scrubs secrets in field updates; closes to "done".
src/bead_sqlite/tests.rs Updates SQLite tests to expect "done" and documents canonical close behavior.
src/bead_sqlite/mod.rs Adds secret scrubbing for create/comment/update paths; makes update_bead_fields transactional; closes to "done".
src/bead_move.rs Updates move tests to expect "done" terminal status.
src/bead_dolt.rs Routes Dolt update_status through the transition-validated Dolt client path.
.beads/beads.jsonl Updates tracked bead records (including rosary-44eec8 / rosary-46e7ff status/metadata changes).
Review details
  • Files reviewed: 13/13 changed files
  • Comments generated: 4
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread src/dolt/bead_crud.rs
Comment thread src/dolt/query.rs Outdated
Comment thread src/dolt/tests.rs
Comment thread src/bead_sqlite/mod.rs Outdated
@github-actions

github-actions Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Bead changes

3274bd928a8fa0b695436b8e6334b1f1e30c9c4f:.beads/beads.jsonlbc0203241f71436fe4ee314e1dfd0ba1b63fb951:.beads/beads.jsonl

Changed (3)

  • rosary-44eec8 — Backend guardrail parity: Dolt update_status skips can_tran…
    • comments: [{"id":"1337","issue_id":"rosary-44eec8","text":"Design ref…[{"id":"1337","issue_id":"rosary-44eec8","text":"Design ref…
    • dependency_count: 10
  • rosary-451a9a — CLI dispatch::run silently discards rejected status writes …
    • comments: [{"id":"1340","issue_id":"rosary-451a9a","text":"Implementa…[{"id":"1340","issue_id":"rosary-451a9a","text":"Implementa…
    • status: opendone
  • rosary-46e7ff — Method-by-method parity diff of the two impl BeadStore bloc…
    • comments: [{"id":"1338","issue_id":"rosary-46e7ff","text":"Scope enri…[{"id":"1338","issue_id":"rosary-46e7ff","text":"Scope enri…
    • status: opendone

0 added · 3 changed · 0 removed

…_id_opt, drop shadow

Three Copilot findings verified valid and fixed:

- update_status on BOTH backends now bails when the row vanishes
  between resolve and write, instead of committing a 0-row UPDATE that
  reports success. SQLite had the identical pre-existing hole; closed
  symmetrically to preserve the parity thesis.
- Dolt get_status no longer masks real SQL/connection errors as
  Ok(None): new resolve_id_opt (shared IdResolution body with
  resolve_id, whose ambiguous-diagnostic error is preserved) maps only
  no-single-match to None and propagates genuine failures.
- Dropped the confusing String->&str rebind in SQLite update_comment;
  the String binds via ToSql.

Fourth finding (Dolt tests self-skip on CI without the dolt binary)
acknowledged as the pre-existing repo-wide posture for ALL Dolt tests,
not new to this PR — filed rosary-a95612 to install dolt in the CI
verify job and add a skip-becomes-fail marker.

Full suite 1720 green; parity suite green against sandboxed Dolt 1.86.1.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PMJgGCoDfQ7QF4R2vcupBJ
Copilot AI review requested due to automatic review settings September 2, 2026 19:17

Copilot AI 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.

🟡 Changes recommended

New tests embed PAT-shaped ghp_... string literals that may trigger secret-scanning/DLP checks and block the PR despite being fake tokens.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review details

Suppressed comments (3)

src/dolt/tests.rs:1217

  • This BeadUpdate description includes a PAT-shaped ghp_... literal and could trigger secret-scanning. If you generate the token at runtime, format the update description from that variable instead.
        store
            .update_bead_fields(
                &id,
                &BeadUpdate {
                    description: Some("upd ghp_0123456789abcdefghijABCDEFGHIJ0123456789".into()),
                    ..Default::default()
                },
            )
            .await
            .unwrap();

src/dolt/tests.rs:1186

  • This call also hardcodes a PAT-shaped ghp_... literal, which may trip secret-scanning. If you switch to a runtime-generated pat string (see earlier in this test), build the comment body from that variable instead of embedding the token literal.
        store
            .add_comment(
                &id,
                "note ghp_0123456789abcdefghijABCDEFGHIJ0123456789",
                "tester",
            )
            .await
            .unwrap();

src/dolt/tests.rs:1201

  • This update_comment test input hardcodes a PAT-shaped ghp_... literal, which can trigger secret-scanning. Prefer generating the token at runtime and formatting the edit body from that variable.
        let cid = comments.last().unwrap().id.clone();
        store
            .update_comment(
                &cid,
                "edit ghp_0123456789abcdefghijABCDEFGHIJ0123456789",
                None,
            )
            .await
            .unwrap();
  • Files reviewed: 12/13 changed files
  • Comments generated: 2
  • Review effort level: Lite

Comment thread src/dolt/tests.rs Outdated
Comment thread src/dolt/tests.rs Outdated
@github-actions

github-actions Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Bead changes

3274bd928a8fa0b695436b8e6334b1f1e30c9c4f:.beads/beads.jsonl201864b9308a3a88ed00b2fa3bf7b141ca753bbe:.beads/beads.jsonl

Added (1)

id pri type title
rosary-a95612 P2 task CI never exercises the Dolt backend — install dolt in the t…

Changed (3)

  • rosary-44eec8 — Backend guardrail parity: Dolt update_status skips can_tran…
    • comments: [{"id":"1337","issue_id":"rosary-44eec8","text":"Design ref…[{"id":"1337","issue_id":"rosary-44eec8","text":"Design ref…
    • dependency_count: 10
  • rosary-451a9a — CLI dispatch::run silently discards rejected status writes …
    • comments: [{"id":"1340","issue_id":"rosary-451a9a","text":"Implementa…[{"id":"1340","issue_id":"rosary-451a9a","text":"Implementa…
    • status: opendone
  • rosary-46e7ff — Method-by-method parity diff of the two impl BeadStore bloc…
    • comments: [{"id":"1338","issue_id":"rosary-46e7ff","text":"Scope enri…[{"id":"1338","issue_id":"rosary-46e7ff","text":"Scope enri…
    • status: opendone

1 added · 3 changed · 0 removed

…shaped literal in source

Copilot round two, both valid: the parity-scrub test embedded a full
ghp_-shaped literal that could trip secret scanning/DLP, plus a
vestigial `let _ = leak;`. Fixture now constructed at runtime
(format!("ghp_{}", "A".repeat(36)) — the same pattern secrets.rs's own
unit tests use), every write and assertion keyed to the one runtime
token. Incidentally fixes two assertions the partial refactor had left
vacuous (asserting absence of a string never written).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PMJgGCoDfQ7QF4R2vcupBJ
Copilot AI review requested due to automatic review settings September 2, 2026 19:29

Copilot AI 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.

🟡 Changes recommended

The MCP tool_bead_close response still reports "status": "closed" while the store now persists canonical "done", leaving an externally observable inconsistency.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review details
  • Files reviewed: 12/13 changed files
  • Comments generated: 1
  • Review effort level: Lite

Comment thread src/serve/handlers/tests.rs
@github-actions

github-actions Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Bead changes

3274bd928a8fa0b695436b8e6334b1f1e30c9c4f:.beads/beads.jsonl06a8ce1ad2d600a67983f842d5d87a577f3136a6:.beads/beads.jsonl

Added (1)

id pri type title
rosary-a95612 P2 task CI never exercises the Dolt backend — install dolt in the t…

Changed (3)

  • rosary-44eec8 — Backend guardrail parity: Dolt update_status skips can_tran…
    • comments: [{"id":"1337","issue_id":"rosary-44eec8","text":"Design ref…[{"id":"1337","issue_id":"rosary-44eec8","text":"Design ref…
    • dependency_count: 10
  • rosary-451a9a — CLI dispatch::run silently discards rejected status writes …
    • comments: [{"id":"1340","issue_id":"rosary-451a9a","text":"Implementa…[{"id":"1340","issue_id":"rosary-451a9a","text":"Implementa…
    • status: opendone
  • rosary-46e7ff — Method-by-method parity diff of the two impl BeadStore bloc…
    • comments: [{"id":"1338","issue_id":"rosary-46e7ff","text":"Scope enri…[{"id":"1338","issue_id":"rosary-46e7ff","text":"Scope enri…
    • status: opendone

1 added · 3 changed · 0 removed

…al status

Copilot round three, valid: the handler answered {"status": "closed"}
while close_bead now persists 'done' — API consumers observed a status
string the store never holds. Response now echoes the canonical form,
asserted in mcp_close_refreshes_only_the_published_jsonl_record.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PMJgGCoDfQ7QF4R2vcupBJ
Copilot AI review requested due to automatic review settings September 2, 2026 19:39

Copilot AI 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.

🟢 Approval recommended

The changes consistently enforce the intended backend-parity contract (including canonical "done" close semantics) with tests updated/added to pin behavior, leaving only a minor doc-comment nit.

Review details
  • Files reviewed: 13/14 changed files
  • Comments generated: 1
  • Review effort level: Lite

Comment thread src/dolt/tests.rs Outdated
@github-actions

github-actions Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Bead changes

3274bd928a8fa0b695436b8e6334b1f1e30c9c4f:.beads/beads.jsonl8fcf136bbe4a48d949f618c76088f1c31c6c18e0:.beads/beads.jsonl

Added (1)

id pri type title
rosary-a95612 P2 task CI never exercises the Dolt backend — install dolt in the t…

Changed (3)

  • rosary-44eec8 — Backend guardrail parity: Dolt update_status skips can_tran…
    • comments: [{"id":"1337","issue_id":"rosary-44eec8","text":"Design ref…[{"id":"1337","issue_id":"rosary-44eec8","text":"Design ref…
    • dependency_count: 10
  • rosary-451a9a — CLI dispatch::run silently discards rejected status writes …
    • comments: [{"id":"1340","issue_id":"rosary-451a9a","text":"Implementa…[{"id":"1340","issue_id":"rosary-451a9a","text":"Implementa…
    • status: opendone
  • rosary-46e7ff — Method-by-method parity diff of the two impl BeadStore bloc…
    • comments: [{"id":"1338","issue_id":"rosary-46e7ff","text":"Scope enri…[{"id":"1338","issue_id":"rosary-46e7ff","text":"Scope enri…
    • status: opendone

1 added · 3 changed · 0 removed

Copilot AI review requested due to automatic review settings September 2, 2026 19:49

Copilot AI 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.

🟡 Changes recommended

Both backends still query “closed linked beads” using status = 'closed', which will miss newly-closed beads now that close_bead persists done.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review details
  • Files reviewed: 13/14 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

@github-actions

github-actions Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Bead changes

3274bd928a8fa0b695436b8e6334b1f1e30c9c4f:.beads/beads.jsonl3d3fd90e5f48763cbfe3eda21273a36e0d2bba86:.beads/beads.jsonl

Added (1)

id pri type title
rosary-a95612 P2 task CI never exercises the Dolt backend — install dolt in the t…

Changed (3)

  • rosary-44eec8 — Backend guardrail parity: Dolt update_status skips can_tran…
    • comments: [{"id":"1337","issue_id":"rosary-44eec8","text":"Design ref…[{"id":"1337","issue_id":"rosary-44eec8","text":"Design ref…
    • dependency_count: 10
  • rosary-451a9a — CLI dispatch::run silently discards rejected status writes …
    • comments: [{"id":"1340","issue_id":"rosary-451a9a","text":"Implementa…[{"id":"1340","issue_id":"rosary-451a9a","text":"Implementa…
    • status: opendone
  • rosary-46e7ff — Method-by-method parity diff of the two impl BeadStore bloc…
    • comments: [{"id":"1338","issue_id":"rosary-46e7ff","text":"Scope enri…[{"id":"1338","issue_id":"rosary-46e7ff","text":"Scope enri…
    • status: opendone

1 added · 3 changed · 0 removed

@jamestexas
jamestexas merged commit 6db9162 into main Sep 9, 2026
9 checks passed
@jamestexas
jamestexas deleted the rosary-44eec8-backend-guardrail-parity branch September 9, 2026 18:25
jamestexas added a commit that referenced this pull request Sep 9, 2026
…ge train (#489)

Six audit beads closed with merge evidence (44eec8/45504f/457927/45b069/
46436d via MCP close, 46812e via the post-merge hook); rosary-b293a3
(MCP-only agents ADR design bead) published to the projection; three
alias statuses canonicalized to 'done' by the freshly installed 9abbf26
binary's export.


Claude-Session: https://claude.ai/code/session_01PMJgGCoDfQ7QF4R2vcupBJ

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
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