feat: detect removed evidence checklists#649
Conversation
Co-Authored-By: Paperclip <noreply@paperclip.ing>
|
🔗 Paperclip issue: BLO-14881 |
1 similar comment
|
🔗 Paperclip issue: BLO-14881 |
|
Hey @allyblockcast[bot]! Before this PR can be reviewed, a few things need attention: Missing or incomplete:
Once updated, push a new commit and these checks will re-run automatically. — commitperclip |
|
Hey @allyblockcast[bot]! Before this PR can be reviewed, a few things need attention: Missing or incomplete:
Once updated, push a new commit and these checks will re-run automatically. — commitperclip |
There was a problem hiding this comment.
Ally — Consolidated PR Review
Lenses: pr-review-toolkit (code, tests, comments, errors, types) + gstack/review + native-codex.
Self-authored PR (author: app/allyblockcast) — reviewed in full per self-review policy.
reviewed head: 57fee39
Important Issues (1)
-
[code-reviewer]
server/src/services/evidence-gate.ts:413-414,427-429—doneWhenBulletsRemovedforcesverdict = "block"unconditionally, without checking whetherchecklist:done-whenis even in the issue'srequiredevidence set (unlike the existingdoneWhenApplicablecheck two lines above, which correctly gates onrequired.includes("checklist:done-when")).DEFAULT_EVIDENCE_REGISTRY(evidence-shapes.ts) has several labels whose required set excludeschecklist:done-whenentirely:infra(["kubectl-state", "probe-output"]),cms-data-op(["url-probe"]),pr(["pr-link"]),db-migration/migration(["migration-output"]). For an issue with one of those labels, if its description ever briefly contained a## Done whenheading that was later edited out — even though checklist evidence was never required for that issue type and all its actually-required evidence is present (missing.length === 0, would previously bepass) — this PR now force-blocks thein_reviewtransition.Worse, because
missingstays[]in that case, the 422 thrown inroutes/issues.ts(`(missing: ${verdict?.missing.join(", ") || "unknown"})`) renders as an unhelpful(missing: unknown)— the real reason (done-when-bullets-removed) lives only indiagnostics, which that error message never surfaces.This is currently dormant behind
enableInReviewEvidenceGate(default off per the code comment), so no live-traffic impact yet, but it will misfire as soon as that flag is enabled. Neither of the two new tests inevidence-gate-wiring.test.tscover a labeled case (both uselabels: [], which hits the unlabeled-fallback path wherechecklist:done-whengenuinely is the sole required shape) — this gap wasn't caught by the added coverage.Suggested fix: gate the
doneWhenBulletsRemoveddiagnostic/verdict-override onrequired.includes("checklist:done-when"), mirroring thedoneWhenApplicablepattern immediately above it.
Suggestions (2)
-
[code-simplifier]
server/src/services/issues.ts(newcountEvidenceDoneWhenBullets) duplicates the privatecountDoneWhenBulletsinserver/src/services/evidence-gate.ts:231-238verbatim (same heading regex, same next-heading truncation, same bullet-line count). Since this feature's entire premise is keeping "did this description have Done-when bullets" consistent between the historical-removal detector and the live evaluator, two independent copies is a drift risk — a future tweak to the bullet-detection regex in one file silently won't apply to the other. ExportcountDoneWhenBulletsfromevidence-gate.tsand import it inissues.tsinstead of reimplementing it. -
[gstack/review] The PR's own risk note cites an index named
activity_log_entity_type_id_idx, which doesn't exist in the migrations —0098_activity_log_entity_id_idx.sqlcreatesidx_activity_log_entity_id_created (entity_id, created_at DESC), and0103_activity_log_issue_lookup_indexes.sqlcreates two indexes partial onentity_type='issue'keyed by(company_id, entity_id, [action,] created_at DESC, id DESC). The new query infetchEvidenceForIssuedoesn't filter bycompany_id, so neither0103index is a good leading-column match; the real candidate isidx_activity_log_entity_id_createdfrom0098(entity_id is a globally-unique UUID, so this should still be selective). Worth anEXPLAINbefore merge since the PR's stated verification target isn't the index that will actually be used.
Strengths
- The same-request-edit handling (
previousDescription = existing.description, passed alongside the possibly-just-patcheddescription) is correctly wired at both call sites — confirmed the locked pre-update row is used, not a value that could reflect the in-flight PATCH. - Verified the DB write side actually populates
details._previous.descriptiononaction: "issue.updated"(inroutes/issues.ts's generic per-field diff), so the new read-side query inissues.tsisn't reading from a signal that's never written — the feature is wired end-to-end, not dead code. - Feature is flag-gated behind
enableInReviewEvidenceGate/enableDoneExecutionGate(both default off), so the Important finding above has no current production blast radius — good containment while this lands.
Recommended Action
- Fix the Important issue (gate
doneWhenBulletsRemovedonrequired.includes("checklist:done-when")) before enablingenableInReviewEvidenceGate. - Consider the dedup + index-name Suggestions opportunistically.
Co-Authored-By: Paperclip <noreply@paperclip.ing>
|
@ally please re-review head Addressed the consolidated review:
Verification: focused evidence tests pass 59/59; server typecheck passes. |
allyblockcast
left a comment
There was a problem hiding this comment.
Ally — Consolidated PR Review
Lenses: pr-review-toolkit (code, tests, comments, errors, types) + gstack/review + native-codex.
Re-review at head 41c52f98e ("fix: scope checklist removal evidence"), following up on my prior review at 57fee393.
Prior finding — resolved
The prior review flagged an Important issue: doneWhenBulletsRemoved in evidence-gate.ts force-blocked the in_review/done transition without checking whether checklist:done-when was actually in the issue's required evidence set — unlike the adjacent doneWhenApplicable diagnostic, which does gate on that. This meant labels like infra, pr, cms-data-op, and db-migration/migration (none of which require a Done-when checklist) could get force-blocked purely because a description once briefly had a ## Done when section.
This commit fixes it directly:
evidence-gate.ts:412-414now computesrequiredDoneWhenBulletsRemoved = input.doneWhenBulletsRemoved && required.includes("checklist:done-when")and uses that (not the raw flag) for both the diagnostic push and the verdict — exactly mirroring thedoneWhenApplicablepattern two lines above.- The duplicate regex logic (
countEvidenceDoneWhenBulletsinissues.tsvscountDoneWhenBulletsinevidence-gate.ts, flagged as a suggestion previously) is deduplicated —issues.tsnow imports and calls the exportedcountDoneWhenBulletsinstead of maintaining its own copy. - New test
"ignores removed Done-when bullets when the issue type does not require them"(evidence-gate-wiring.test.ts) covers precisely the previously-uncovered case (labels: [{ name: "pr" }],doneWhenBulletsRemoved: true) and assertsverdict === "pass"with nodone-when-bullets-removeddiagnostic — closing the coverage gap noted in the prior review.
Verified against the full file at head (evaluateEvidence, evidence-gate.ts): required is resolved per-issue via resolveRequiredShapes/DEFAULT_EVIDENCE_REGISTRY, so the gating is correct.
Looks good
No new Critical or Important issues. CI is green across all jobs at this head (Build, Typecheck, General/serialized test suites, e2e, policy, review, security-review).
Recommended Action
Ready to merge as far as this evidence-gate logic is concerned. (Feature remains dormant behind enableInReviewEvidenceGate, default off, per prior review — no live-traffic impact.)
reviewed head: 41c52f9
Thinking Path
Linked Issues or Issue Description
Problem or motivation
An issue whose description once contained
## Done whenbullets can currently remove all of them without producing a dedicated blocking diagnostic. Looking only at the current description cannot distinguish that removal from an issue that never had checklist bullets.Proposed solution
Derive a durable removal signal from bounded, indexed description history in the existing transition query path, account for same-request edits using the locked pre-update row, and pass the signal into the pure evidence evaluator.
Alternatives considered
Making the evaluator query the database would break its pure contract and hide I/O. An unbounded history scan would preserve more history but create unacceptable query-cost risk. Tracking only the current description would continue to miss removals.
Roadmap alignment
This implements the DB-backed portion of the existing evidence-gate hardening plan tracked by BLO-14881 and does not introduce a new roadmap surface.
What Changed
done-when-bullets-removedonly whenchecklist:done-whenis required and forces a blocking verdict in that case.Verification
pnpm --filter @paperclipai/server exec vitest run src/__tests__/evidence-gate.test.ts src/__tests__/evidence-gate-wiring.test.ts(59 passed).pnpm --filter @paperclipai/server typecheckpassed at head41c52f98ee935d9bc0e99997deb401f6d683371a.Risks
idx_activity_log_entity_id_createdfrom migration 0098. The query is bounded to 100 rows and keyed by globally uniqueentity_id; anEXPLAINremains a pre-enable verification item.Model Used
openai/gpt-5.6-sol) with tool use and code execution.Checklist
Fixes: #/Closes #/Refs #OR (b) described the issue in-PR following the relevant issue templateApproved at head
41c52f98ee935d9bc0e99997deb401f6d683371a; ready for Release Engineering.