Repository navigation
feat(db): the remediation and remediation_finding tables - #117
Conversation
Code Review by Qodo
1.
|
032835f to
75825fc
Compare
|
Code review by qodo was updated up to the latest commit 75825fc |
|
@qodo relative to your first finding, the problem with that model is that it doesn't' give consumers the option to use a mirror that simply isn't tied to the repository. E.g. they're handling the fix in total isolation of the repo so the commit ref and the repository ref aren't in alignment. I think this model gives more flexibility, that the the fk constraint here is that it has match back to a finding, then it gives room for more unique workflows. |
Your mirror point is a fair one, but I don't think it applies to
The fork/mirror isolation you describe already lives in Your "match back to a finding" idea is the right constraint. Two ways to get it without giving up the mirror model:
Either way, the test in |
Yes, you can keep The current schema checks that
The The one design question is whether you ever need to track a finding under a repository other than its owner. If so, the composite FK would block that, and the alternative is to drop |
Yes, Those two goals conflict only if the table really should record repositories other than the finding's owner:
Either way, Which model do you want: repository as effort scope, or repository as the finding's owner? |
Your point holds for The finding is right that
If you agree, |
|
@qodo counter point - let's imagine if you will (or can?), someone wanted to run it twice to check on various implementations, yet maybe each on their own accord are able to successfully remediate, but they want to look at both and choose one. Maybe they do this n times, it doesn't matter. ... in that case what then? |
Under the current schema, nothing stops this. Running the remediation n times is allowed, and the schema has no notion of a chosen attempt.
If you want "pick one of n" enforced rather than left to services, options include:
I can draft any of these for the SQL and tests, or look at how the status-sync code is expected to handle losing attempts. |
The schema already supports running remediation several times against the same finding. Choosing a winner is not modelled yet.
|
Code Review by Qodo 1. Upgraded orders can cross repositories
|
|
Code review by qodo was updated up to the latest commit aef70bb |
|
traust-core#377 (b8038c45) now pins this branch at aef70bb and uses |
|
Code review by qodo was updated up to the latest commit 1145c0c |
|
1145c0c addresses the aef70bb review:
Changed tests (new on this branch): Suite: 1,342 passed on SQLite and Postgres. |
|
Code review by qodo was updated up to the latest commit 15fd2a5 |
|
15fd2a5 addresses Moved findings appear in the wrong repo and Orders retain a moved work item. A finding under remediation can no longer change repository. A |
|
40a9e66 addresses the 15fd2a5 findings:
Suite: 1,346 passed (SQLite and Postgres). |
|
Code review by qodo was updated up to the latest commit 40a9e66 |
|
@smith-xyz — catching up on your design questions from last night; here's where they landed: Can we still leverage repository_id as an FK? Yes, and it's in: The mirror workflow you described (fix handled in total isolation, commit ref and repository ref out of alignment) lives in Running it n times and choosing one: the schema allows exactly that — Everything Qodo raised on this branch is resolved as of 40a9e66 (its review now reads 0 bugs), and traust-core#377 is re-pinned to it. |
|
triple checking the assessment table, we might already have a way to accomplish this - verifying against our use cases here |
|
@smith-xyz a few schema facts for that verification, since I just walked them:
Happy to prototype whichever shape the use cases point at. |
There was a problem hiding this comment.
Reviewing this against the settled data model (attempt = assessment, status = decision, fork/patch/checks = artifact) — keeping the run_order work, dropping the two tables, shrinking the contracts change to work_item_id + the fork columns on run_order. Details inline. Happy to pair on the #377 reshape.
| -- only guards against a re-created database. | ||
| CREATE TABLE IF NOT EXISTS traust.remediation ( | ||
| id UUID NOT NULL DEFAULT gen_random_uuid(), | ||
| fork_url TEXT NOT NULL CHECK (fork_url <> ''), |
There was a problem hiding this comment.
I'd like to drop these two tables and keep the contracts change minimal — run_order.work_item_id is exactly the right shape, but the rest duplicates what already shipped:
remediation.schema.jsonis already the system of record for the out-of-band world — fork url, fix_branch, checks, revalidation, PR, andsummary.statusare all in it.- The candidate decisions #377 writes on the work item already link findings to each effort.
So we already have the pathway for tracking fixes that live outside the repo, and the artifact gives the flexibility for free — a document can describe any mirror or workflow without the DB's invariants needing to know it exists. The judgment trail stays on the real repo's rails (decision.fixed_repository_ref_id for landing), which covers the three cases we talked about: out-of-band mirror verification (comparison assessment, resolution closes the audited line), track-until-it-lands (candidate → fixed → landed decisions), and n-attempts-choose-one (one work item per attempt, the choosing decision names the winner).
What the runner still needs at claim is the fork facts — nothing else carries them between scheduling and the run's own artifact. I'd put them on run_order beside work_item_id:
work_item_id UUID REFERENCES traust.assessment(id) ON DELETE RESTRICT,
fork_url TEXT, -- the mirror this remediation runs on (remediate_finding only)
fork_branch TEXT, -- its fix branchWrite-once at scheduling, read-once at claim, no cross-row query — column-shaped, and it keeps the whole contracts change to one table: three columns, one index, the pairing guards you already built.
| id UUID NOT NULL DEFAULT gen_random_uuid(), | ||
| remediation_id UUID NOT NULL REFERENCES traust.remediation(id) ON DELETE RESTRICT, | ||
| finding_id UUID NOT NULL REFERENCES traust.finding(id) ON DELETE RESTRICT, | ||
| repository_id UUID NOT NULL REFERENCES traust.repository(id) ON DELETE RESTRICT, |
There was a problem hiding this comment.
Beyond the duplication with the candidate decisions, repository_id here can't carry information: remediation_finding_repository_of_finding enforces it always equals finding.repository_id, so every query on it is a join on finding in disguise.
It's also what buys the reverse guard on finding (a finding under remediation can never move repositories) and the FOR SHARE machinery serializing the two — most of the diff's complexity exists to maintain a column that is by construction always equal to another table's column. Dropping the column (or the table) deletes that machinery with it.
| finished_at TIMESTAMPTZ, | ||
| created_at TIMESTAMPTZ NOT NULL DEFAULT statement_timestamp(), | ||
| updated_at TIMESTAMPTZ NOT NULL DEFAULT statement_timestamp(), | ||
| work_item_id UUID REFERENCES traust.assessment(id) ON DELETE RESTRICT, |
There was a problem hiding this comment.
This part I'd keep in full — the column, the partial index, the pairing guards, and the additive upgrade with its never-invent-a-work-item test. It's the one thing the model genuinely lacked: the runner's link from an order to the work item it runs.
Only addition: since the remediation table goes away (see the comment there), add fork_url / fork_branch here as nullable columns so the order carries the mirror facts to the runner at claim.
There was a problem hiding this comment.
One thing while you're in here: the dashboard's in-flight query (orders → work item → decisions) has every leg indexed except decision(assessment_id) — decisions are only indexed by finding_id and occurred_at, so fetching a work item's decisions scans. Worth adding idx_decision_assessment ON traust.decision (assessment_id) WHERE assessment_id IS NOT NULL (both dialects) to this PR, with an EXPLAIN (ANALYZE) at seeded volume in perf/queries/ to show it earns its keep — the house rule is measure first, and it'd serve the status fold too, not just dashboards.
|
Taking this one off your plate @gurnben — reworking the branch per the review: dropping the two |
…n gains its assessment index Rework of #117 per its review: the remediation / remediation_finding tables are dropped - remediation.schema.json is the system of record for the out-of-band world (fork url, branch, checks, status), and the candidate decisions on the work item already link findings to each effort. The contracts change shrinks to run_order and one index: - work_item_id on run_order (Gurney's work, kept in substance): the remediation assessment an order runs, with its partial index, the repository-pairing guards (an order runs a work item of its own repository; a ref holding an ordered work item keeps its repository; FOR SHARE on Postgres serializes concurrent moves), the additive upgrade, and the never-invent-a-work-item test. - fork_url / fork_branch on run_order: the mirror an effort runs on and its fix branch - operational facts, write-once at scheduling, read-once at claim; not a repository FK, because mirrors are out of band and never inventory, and the durable record lives in the run's remediation artifact. - idx_decision_assessment on decision (assessment_id) WHERE assessment_id IS NOT NULL: a work item's decisions had no index (only finding_id and occurred_at), so the remediation fold and the in-flight queries scanned the fastest-growing table. Measured at 100k decisions on Postgres (5k work items, 20k findings): one work item's 20 decisions 0.018ms with the index vs 3.858ms without; the in-flight dashboard query (500 open orders joined to findings through work_item_id) 5.3ms for 10k rows, the decision leg an index scan.
620b838 to
b176d64
Compare
|
Code review by qodo was updated up to the latest commit b176d64 |
|
bnote the upgrade script , we need a more formal upgrade path so that needs removed for now and handled manually. once stable we can add that feature |
…n gains its assessment index Rework of #117 per its review: the remediation / remediation_finding tables are dropped - remediation.schema.json is the system of record for the out-of-band world (fork url, branch, checks, status), and the candidate decisions on the work item already link findings to each effort. The contracts change shrinks to run_order and one index: - work_item_id on run_order (Gurney's work, kept in substance): the remediation assessment an order runs, with its partial index, the repository-pairing guards (an order runs a work item of its own repository; a ref holding an ordered work item keeps its repository; FOR SHARE on Postgres serializes concurrent moves), the additive upgrade, and the never-invent-a-work-item test. - fork_url / fork_branch on run_order: the mirror an effort runs on and its fix branch - operational facts, write-once at scheduling, read-once at claim; not a repository FK, because mirrors are out of band and never inventory, and the durable record lives in the run's remediation artifact. - idx_decision_assessment on decision (assessment_id) WHERE assessment_id IS NOT NULL: a work item's decisions had no index (only finding_id and occurred_at), so the remediation fold and the in-flight queries scanned the fastest-growing table. Measured at 100k decisions on Postgres (5k work items, 20k findings): one work item's 20 decisions 0.018ms with the index vs 3.858ms without; the in-flight dashboard query (500 open orders joined to findings through work_item_id) 5.3ms for 10k rows, the decision leg an index scan.
…er by hand The upgrades/ channel is an undesigned mechanism (two ad-hoc instances now: manifest_sbom_binding, then this) - a caller-inspect-then-apply export that implies a migration system we do not have. Until there is an official setup for tracking upgrades, an operator with an existing run_order table applies the additive statements by hand; they are documented in the PR. traust-core's rework drops its call site with the table code.
7c4c57c to
7c9554c
Compare
|
Code review by qodo was updated up to the latest commit 7c9554c |
|
[APPROVALNOTIFIER] This PR is APPROVED This pull-request has been approved by: gurnben, smith-xyz The full list of commands accepted by this bot can be found here. The pull request process is described here DetailsNeeds approval from an approver in each of these files:
Approvers can indicate their approval by writing |
Development landed the reworked remediation-scheduling slice (#117, #121, #123): run_order gains work_item_id plus fork_url/fork_branch, the remediation / remediation_finding tables are retired (remediation.schema.json is the system of record), and the upgrades/ channel is dropped (7c9554c). This merge takes development's decisions wholesale for the DB surface and keeps this branch's sole addition: the benchmark-scorecards view contract, its tests and README section, layered after the compliance-precision section.
What
The remediation scheduling contracts change, reworked per the review to the minimal shape: no new tables —
remediation.schema.jsonis the system of record for the out-of-band world (fork url, branch, checks, status), and the candidate decisions on the work item already link findings to each effort. What the model genuinely lacked, in one commit:run_order.work_item_id(Gurney's work, kept in substance): the remediation assessment an order runs, with its partial index, the repository-pairing guards (an order runs a work item of its own repository; a ref holding an ordered work item keeps its repository;FOR SHAREon Postgres serializes concurrent moves).run_order.fork_url/.fork_branch: the mirror an effort runs on and its fix branch — operational facts, write-once at scheduling, read-once at claim. Deliberately not a repository FK: mirrors are out of band and never inventory, and the durable record lives in the run's remediation artifact.idx_decision_assessmentondecision (assessment_id) WHERE assessment_id IS NOT NULL(both dialects): a work item's decisions had no index, so the remediation fold and the in-flight queries scanned the fastest-growing table.Status tracking stays where G11 put it: judgment checkpoints are decision rows (candidate → fixed → landed), run outcome is
assessment.status, in-flight isrun_order.state. Theremediation/remediation_findingtables from the original branch are dropped, as is the upgrade file — existing databases alter by hand (below) until there is an official setup for tracking upgrades.Measured
100k decisions, 5k work items, 20k findings on Postgres (
make db-up):idx_decision_assessmentwork_item_id, 10k rows)Upgrading an existing database (by hand)
Databases are recreated, not migrated; an operator whose
run_orderpredates these columns applies the additive statements themselves — existing orders name no work item and carry no fork; none is inferred:The pairing-guard triggers come with a fresh bootstrap; a hand-upgraded database gets them on its next recreate, or the operator creates them from
run_order.sql.Down / rollback
No down-migration machinery; for an operator who must roll back by hand —
test_the_documented_rollback_leaves_findings_refs_and_orders_workingruns exactly these statements:Tests
test_db_behavior.py: an order names a real work item of its own repository or none (and carries its fork facts); concurrent ref moves and orders never both commit (Postgres); the documented rollback leaves findings, refs and orders working. Both dialects — Postgres runs in CI (make db-up).After merge
traust-core re-pins to the merge commit and reworks the merged scheduling service (
RemediationSchedulingService) onto this shape: the order carries the fork facts, reads fold from decisions.💘 Generated with Crush