feat(code-review): add guided review fix workflow - #5274
Conversation
674c16d to
7d64202
Compare
Head branch was pushed to by a user without write access
6b24144 to
8ca7f6b
Compare
|
👋 Hi! This PR is currently in draft status. Workflow runs won't be auto-approved until it's marked as ready for review. When you're ready, click "Ready for review" and the workflows will be approved on the next cycle automatically. |
a8e0bd8 to
ca3fd92
Compare
UX Review (Fable 5, fork) — ⏭️ skippedRevision |
Design Review (Fable 5, fork) —
|
First Principles Review (Fable 5, fork) —
|
GPT 5.6 Review (fork) — 🔴 changes requested (blocking)Reviewed 3 of 3 blocking finding(s) are security-class and were withheld from adjudication, so the blocking verdict stands. BLOCKING -- src/kiro_crew/apps/builtins/code_review_sage/backend/fix_tasks.py:458 -- Git mutations occur before the CAS transition
BLOCKING -- src/kiro_crew/apps/builtins/code_review_sage/sage_lib/local_review.py:297 -- Output limits are enforced after unbounded capture
BLOCKING -- src/kiro_crew/review_fix.py:155 -- Soft grouping permits overlapping file ownership (origin: validation)
|
Opus 4.8 Review (fork) —
|
|
Re: FINDING — function-local imports ( Thanks for the detailed pass. We're keeping the deferred imports for now, with reasoning per site:
The rule itself is |
|
CI status on All 3 Proof it is not ours: the identical test fails identically in a clean worktree checked out at Everything attributable to this diff is green: 53/53 other checks pass — Automated Rule Check ✅, PR Hygiene ✅, Backend Lint & Type Check (3.10 + 3.12) ✅ (includes the black baseline gate), all Frontend Tests (4 shards) ✅, Frontend Coverage Merge ✅, Publish readiness ✅. The local AI-review blocker work is complete and verified; the two red-gate blocks above are main's to fix. |
Residual register — GPT 5.6 round 2 (5 BLOCKING + 1 FINDING) + Opus 4.8 round 2 (2 FINDING)All eight findings were verified in code at
Per the loop-stop rule: these are the registered residual anchors from round 1's fixes, all now closed at the root. If a re-review raises a BLOCKING against one of these anchor classes without a new root cause, the register above is the intended answer rather than a third code round; a genuinely new root cause will of course be fixed. Gates on this head: 382 pytest passed (review-fix, git-coord, sage routes/pool/local-review), 40 vitest passed, |
|
Rebased onto current Rebase: 222 commits, one conflict ( The re-review on
Gates on this head: 381 pytest passed (sage routes, review_fix, routes coverage), Per the loop-stop rule this closes every raised anchor at the root. Force-pushed to the existing branch — requesting a re-run of the review workflows on |
Open PR relationship auditThis is a consolidated, point-in-time code-level audit note. It compares complete merge-base diffs and current/merged code; it does not treat a shared topic as duplication or partial coverage as completion. Relationship findings
No PR, Issue, label, branch, or review state was changed by the relationship-note portion of this audit. |
|
Rebased onto latest
One new incompatibility surfaced after the rebase, found and fixed: upstream's new Gates on this head: 988 pytest passed (sage routes/pool/local-review, review_fix, routes coverage, security posture) · Still requesting workflow approval to re-run the review workflows on this head. |
|
Force-pushed head to |
|
Rebased onto latest What the rebase surfaced and how it was resolved:
Local verify on the rebased head: pytest |
|
Maintainer action needed: This head ( The guard blocks the PR until a maintainer applies the label; everything else on this head is green or in progress. Thank you! |
Guided fix pipeline for code-review-sage findings: immutable finding snapshots, dependency groups, a candidate worktree with per-group patch capture/validation, apply/commit gated on the validated bytes, and a review-fix UI (fix selection, task detail, model picker) in the dashboard. Hardening over the first pass, closing the AI-review blockers: - redact category/reviewer through store.redact_text before the fingerprint, so every model-written field is scrubbed at the dashboard boundary (dedup keys now derive from the redacted category) - refuse fileless fix groups at plan and capture time, and require a captured candidate_patch_id at apply (first-apply-binds removed — apply verifies the id, never overwrites it) - serialize fix tasks that share a file or a dependency group through forward-only depends_on chains; unrelated findings still run parallel - bound the AGENTS.md/CONTRIBUTING.md guidance read at 8 KiB on disk and offload prompt build + conflict-marker scan via asyncio.to_thread - fail closed when an overlapping review batch requests a different explicit model than the live pooled runtime - drop the fix action into the header overflow menu when comment and fix selections are both active (max-two-buttons-per-row) black (baselined gate) flagged in-scope files across the round-1 and round-2 edits: local_review.py, review_fix.py, test_review_fix_actions_coverage.py, review_fix_git.py, test_review_fix.py, and test_git_coord_review_fix.py. All reformatted with --target-version py310; no behavioral change (the review-fix suites re-run green). Tests: multi-group state machine (hold / advance / block, both orders), pathspec-magic capture guard, Thai and accented filename fixtures, sentinel validation failure, fake-runtime pool guard matrix, 2-request 409 + slot release, vitest action-state matrix (40 cases). Round-6 review hardening (AI-review blockers): persist the terminal fix state on every exit path, bind the run model into the review dispatch, co-locate same-file findings in one default fix group, and require a JSON true for hard dependency-group edges.
Upstream kirodotdev#7542 (3a5824d) converted dashboard/handlers/memory.py's two pip-stderr log sites to `redact_log_via_context` (via _redact_pip_stderr), so the module no longer reads a baseline redactor anywhere and the census entry only holds slack: on the merge ref, test_the_census_holds_no_slack fails with `dashboard/handlers/memory.py: 0 sites, census says 2`. Drop the entry per the census's emptied-module rule. No source change.
Problem / Motivation
Code Review Sage previously exposed review context and findings, but it did not provide a complete governed path from local review feedback to safely applying fixes. Users could inspect findings, but could not select candidate fixes, group dependent changes, validate them, apply them, commit them, and request a re-review through one coherent workflow.
Why it matters
Without a guided workflow, applying review fixes requires manual worktree and Git coordination, making it easier to mix unrelated changes, apply an unsafe candidate, lose dependency ordering, or blur the boundaries between Apply, Commit, Push, and Review again. This feature makes those transitions explicit and gives users deterministic validation and evidence for the changes they accept.
What changed (motivation → approach → change)
The implementation adds a local-review → feedback → Review Fix lifecycle while keeping candidate changes isolated and state transitions governed:
temp-screenshots/interactive-code-review/.Tests
npm run i18n:check: passed.py_compile,isort, andflake8: passed.tsc -b: could not complete locally because all available existing dependency trees lackgraphology-layout-forceatlas2andgraphology-communities-louvain; no changed-file TypeScript errors were reached.git diff --check: passed for source/test files; committed PNG evidence is valid binary image data.Manual verification
Performed against an isolated Gateway, temporary repository, and fake ACP backend (isolated evidence only; this is not production integration verification):
HEADmatched the expected commit.Screenshots / video
Related Issues
No linked issue: this feature has no tracked issue reference.
Checklist
feat|fix|docs|refactor|perf|test|chore|ci|build|revert: ...)Contribution License Agreement
N/A — repository template placeholder remains unchanged.
Update 2026-08-29 (CI fixes + rebase)
main(was behind by 10 commits; the locale catalogs,security_posture.py, andwebsite/src/api/client.tsoverlaps are pre-resolved) and squashed to this single commit.test_cli_logging.py::TestDrainBeforeHardExit::test_no_listener_is_a_silent_no_opfailed under CI's shard ordering because an earlier test can leak a live_LOG_QUEUE_LISTENER(the fixture cleared handlers but not the listener, and the short-lived-command setup path never retires one). The_pristine_loggingfixture now stops the listener in setup as well. Reproduced deterministically with a leaking-test file before the fix;test/test_cli_logging.pypasses with the leaker and alone.alli18n catalog chunk re-measures at 9815 KB with this PR's ~130 translated keys per catalog (added on top of main's own Dev Fleet increment that set the 9800 KB ceiling). The catalogs are eager by design (seesrc/i18n/index.ts's lazy-loading seam, reserved for a future catalog feat: unify artifacts library UI + session-origin Source column #13), so the growth is irreducible here —CHUNK_BUDGETS.allmoves to 10350 KB (measured + ~5% headroom, per the file's convention).test_cli_logging.py(34); focusedmypy --platform linuxon all 14 changed Python sources (clean);isort+flake8onsrc/andtest/(clean);npm run i18n:check(pass); targeted Vitest (6 files / 53 tests); bundle-size gate (734 chunks within budget).Update 2026-08-31 (review findings addressed + rebase)
main(twice: first 66 commits behind, then again 356 behind on 2026-08-31 — taskrunner's persisted-run restore gained main'sworktree_pathalongside thereview_fixmetadata; theallchunk re-measured byte-identical at 10066 KB on the merged tree). First conflict: theCHUNK_BUDGETS.allceiling — re-measured 10066 KB on the merged tree (main's own 9985 KB plus this PR's Sage Review Fix strings, now that the dead-surface catalog keys are removed), ceiling 10570 KB (~5% headroom per the file's convention).GPT 5.6 Review — all blocking findings fixed
modelkwarg — removed from therun_reviewdispatch; the model binds atpool.begin_batch(model=…)as designed. The regression test's stub carries the driver's real signature, so a reintroduced kwarg raises the sameTypeError.os.fchmodcrash —save_sessionnow delegates toatomic_write(path, content, mode=0o600); the hand-rolled temp-file/fchmod/replace block is gone.candidate_patch_idand rejects drift (re-capture and re-validate); Push recomputes the preview and rejects a stale signature (remote/branch/upstream/commits/files/diverged) before contacting the remote.execute_review_fixrolls the state back (start_rolled_back) when the post-transition start fails and the state is still RUNNING;review_againrestoresPUSHEDthe same way (fresh-read, only if unchanged).new_worktreeinert mode — removed (enum variant, setup option, catalog keys).CURRENT_BRANCHis the only target mode.asyncio.to_thread(byte-exactnewline="\n"preserved for patches).auto_approveprovenance bypass — the resume/retry action routes through the dashboard's_gate_auto_approve(deny = untrusted run, critical SEL audit), matching the core execute path.^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$at plan time, and artifact roots must be non-link and contained in the candidate worktree.work_dir— kept deliberately for now: it is the quick local-review path, while the governed candidate → validate → apply → commit lifecycle is the Review Fix task flow. Fix-run outcome (status/error/changed files) is now rendered and polled so a failed fix is no longer invisible; happy to convert Local Fix into a Review Fix task in a follow-up if maintainers prefer.Opus 4.8 Review — both blocking findings fixed
discard_candidatebricking — the CAS transition toDONEnow happens BEFOREgit worktree remove --force; RUNNING is rejected as a discard source; a failed destroy logs into the task logs without stranding the task. The finding's repro (discard fromawaiting_commit) is now the regression test.fchmod— sameatomic_writefix as above.First Principles Review (BLOCK) — inert surface deleted
APPLIED,READY_TO_REREVIEW,BLOCKED_DEPENDENCY,BLOCKED_TARGET_CHANGED,BLOCKED_CONFLICT,BLOCKED_MANUAL_RESOLUTION— plus therecheck_continueaction, thereevaluate_groupingbutton, thereplan_preview/confirm_replanlog-only actions, andReviewFixChatLink. (BLOCKED_VALIDATIONstays — validation writes it; recovery runs through retry/resume.ReviewFixGroupState.APPLIEDis a different, live enum.)task_models.py; theinclude_taskrunnerparameter (the dashboard adapter owns the taskrunner-scoped routes);ReviewFixValidationRun.output_path/error(zero writers); the hand-rolled temp write (replaced byatomic_write).Design Review (CONCERNS)
docs/system-specs/modules/taskrunner.mdnow documents the review-fix lifecycle — final state set,ReviewFixMetadatafields, the CAS contract, the transition table, model pinning, artifact-safety rules, and route ownership.resolve_pinned_modelallows an empty advertised set (repo-wide convention);autois still rejected so a fix task keeps one concrete model across retries.register_fix_task_routesregisters only the app-scoped endpoints; the taskrunner-scoped routes belong todashboard/routes/taskrunner.pyvia its adapter. Extracting the generic adapter out of the app is left as a follow-up discussion.ghdependency drop) — these deserve explicit maintainer sign-off; happy to split them into a separate PR if preferred.UX Review (CONCERNS)
_oneforms, task-panel vocabulary, model-precedence labeling, store copy.Re-validation on the merged tree
awaiting_commit, destroy-failure, failed-start rollback, group-id rejection, auto-approve denial, and empty-advertised-set allow.mypy --platform linux src/kiro_crewclean; black / isort / flake8 / subprocess-encoding / loop-bound locks / brand / harness-parity / docs-lint all clean.tsc -bclean; 154 targeted vitest tests green; fullnpm run test(jscpd + vitest + Electron) green; ESLint 0 errors on changed files.en-XAregenerated and matched;npm run i18n:checkdelta vs the previous head introduces zero new findings.Update 2026-09-01 (CI fixes: boundary gate + host-sandbox-dependent tests)
main(aa5f5a6) — resolved the conflict inagent_sdk/__init__.pyby combining main's turn-usage vocabulary (AgentTurnUsage,TURN_STOP_REASON_*) with this round's lazy model-selection re-exports.fix_tasks.py,routes.py,review_fix.py) importingkiro_crew.acp.clientdirectly, plusreview_pool.pygrowing 2→3 ACP edges.advertised_model_ids/model_is_unusable/AcpModelUnavailableare now re-exported fromkiro_crew.agent_sdk(the one layer allowed to import the ACP package) and all four files read them from the facade. The re-exports resolve lazily via module__getattr__+TYPE_CHECKING: a module-scope ACP import inagent_sdk/__init__would put the ACP client + runtime on the gateway boot path (seetest_the_boot_path_does_not_import_acp_at_module_scope).review_pool.py'sacp.clientedge is removed outright; the baseline is untouched.test_review_fix_actions_coverage.pytests at lines 263 and 415 drive real git throughgit_coord._git, whose async chokepoint (sandboxed_spawn_argv_async) probes the host sandbox and fails closed on CI runners without one (Ubuntu denies unprivileged user namespaces; Windows has none). Theunsandboxed_gitfixture inreview_fix_helpers.pynow patches the async half alongside the sync prepare half, and the coverage module imports the fixture so sharded collection registers it. Test-only change; the real git subprocess behavior stays under test."claude-sonnet-4"literal intest_backend_routes.py(TestSelectedModelGoesToThePoolOnly). Replaced with a"model-under-test"sentinel — the stub pool validates nothing and the test pins kwarg passthrough, not a real id.origin/main...HEADscope; isort + flake8 clean on all touched files;mypy src/kiro_crewclean (1,226 files); 348 targeted tests green (review-fix suites, git coordination, agent-SDK gate self-tests, Sage backend routes, review pool, fix routes).Update 2026-09-01 (AI review round 2: GPT 5.6 + Opus 4.8 blockers closed)
Rebase:
mergeable:falseresolved — branch rebased ontomain(6715282) and re-squashed to 1 commit (PR Hygiene cap 2). Thesecurity_posture.pyconflict was resolved by keeping upstream's dev-fleet module split and this PR'ssage_lib/local_review.pynon-egress entry (dropping the supersededdev_fleet/server.pyline).GPT 5.6 (round 2)
BLOCKING —
review_fix.py:120fileless groups can apply unowned/unvalidated bytes (fixed, 3 layers, fail-closed):build_review_fix_groupsraisesReviewFixPlanError(code="fileless_finding")when a finding has nofile_path(auto groups), andcode="fileless_group"when a raw group ends up with an emptyaffected_filesset.capture_group_patchrefuses a group with no owned files — an unscoped capture would have hashed a whole-worktree diff as the group's patch.fix_tasks._apply_groupnow requires a capturedcandidate_patch_idbefore the id comparison; the oldif group.candidate_patch_id and …truthy-check let an uncaptured group ("") through into the apply-vs-commit split state. The "first apply binds the id" fallback is removed — apply verifies the id, never overwrites it.BLOCKING —
review_fix.py:169all fix tasks run concurrently in one worktree (fixed):build_review_fix_tasksnow consumes itsgroupsparameter and builds serialize-per-resourcedepends_onchains — a task depends on the previous task owning the same file or belonging to the same dependency group; unrelated findings still run in parallel. Edges point strictly backwards, so the graph is acyclic by construction andtask_planner's forward-only normalization (0 < dep < index, cross-group expansion) accepts it unchanged.BLOCKING —
local_review.py:348large synchronous reads on the loop (fixed, both anchors):build_contextbounds the AGENTS.md/CONTRIBUTING.md read at disk (open("rb").read(8192)→ decode →[:4000]), so a huge guidance file is no longer pulled fully into memory; and the routes.py call site builds the prompt viaawait asyncio.to_thread(_local_prompt, diff)(mirroring the existingworking_tree_diffoffload above it).review_fix_git.apply_patch's post-apply conflict-marker scan moved into a sync helper offloaded withasyncio.to_thread. Public signatures unchanged.test_git_coord_review_fix.py(which passes unchanged).FINDING —
review_pool.py:365return rthands a live runtime to a different model (fixed, fail-closed): the fast path now raisesRuntimeError("review pool is busy with model 'X'; retry after the batch drains")when an explicitmodeldiffers from the live runtime's model. Same-model and Auto (None) requests keep pooling exactly as before, and a failed mismatch does not consume a batch count.FINDING (function-local imports) — push-back: the three deferred imports are deliberate, each inside an existing rule exception:
agent_sdk/__init__.py:128sits in the module__getattr__lazy resolver: a module-scopekiro_crew.acpimport would drag the ACP client and runtime onto the gateway boot path — exactly whattest_the_boot_path_does_not_import_acp_at_module_scopeforbids (AST-parsed at source level). This is the boot-path deferral the rule's exceptions are written for, not an accidental function-local import.fix_tasks.py:140builds the hyphenatedsage_libsys.pathentry at call time (the directory is not importable top-level); deferring keeps import cost off the route-table path.task_executor.py:887avoids ordering surprises in the suite's import graph (same pattern as the neighboringdashboard.handlers.usageimport, which carries an explicit# circular importnote). We verified review_fix_git's full top-level import closure does not reachtask_executor, so if reviewers prefer, hoisting this one import is safe and we're happy to do it in a follow-up — it just didn't belong in this hardening round. The rule isblocking:false, so this is left as a documented advisory.Opus 4.8 (round 2)
BLOCKING —
local_review.py:332category/reviewer bypassstore.redact_text(fixed):categoryandreviewernow go throughstore.redact_textlike title/message/suggestion (backend-security-controls: never trust LLM output — scrub before the dashboard). The category is redacted once, before the fingerprint is computed, and the same variable feeds both the fingerprint and the constructor — dedup keys can never diverge from stored fields.category/reviewerare absent from the stored finding; stored category equalsredact_text(raw).Local gates on this head (post-rebase, becaa06)
scripts/check_agent_sdk_boundary.py: passed (baseline unchanged — shrink-only respected).test_review_fix.py+test_review_fix_actions_coverage.py35 passed;test_local_review.py+test_review_pool.py+test_backend_routes.py259 passed; review-fix routes/git/taskrunner/dashboard suites 244 passed.CodeReviewSageReportView.test.tsx12 passed (2 new: single-selection keeps the dedicated Fix button; both-selected moves Fix into the overflow and it still fires), Review Fix UI + Local Review + Sage detail suites 82 passed.tsc -b: clean. ESLint on the touched TSX: clean. No literal model ids added to tests (model-under-testsentinels only).Update — round-2 residuals fixed (8e63ec9)
All six round-2 findings (5 BLOCKING + 1 FINDING) plus both Opus 4.8 FINDINGS are fixed on this head, disposition per anchor class:
pathspec/path-encoding —
review_fix_git.py(BLOCKING #1 + Opus O2): every consume point (candidate_patch,stage_paths,commit_group) wraps owned paths as:(literal)pathspecs — pathspec magic in a crafted filename can no longer widen a diff/stage/commit beyond the task's owned paths.git statusis now parsed from--porcelain=v1 -z --no-renames(NUL-split), so non-ASCII filenames reachdirty_overlapbyte-exact instead of C-quoted (core.quotePath).test_pathspec_magic_in_a_owned_path_captures_nothing_extra,test_non_ascii_paths_are_tracked_and_overlapped_raw.validation-evidence —
review_fix.py:455+task_executor.py(BLOCKING #2): the runner's skip output is now the exported sentinelTESTS_SKIPPED_OUTPUT;validate_grouprefuses to credit it as a pass, so a command that never ran can no longer mint a passing validation record (exit_code=0). The generic runner's skip semantics are untouched.test_validate_group_treats_missing_test_command_as_failed_validation.phase-machine —
review_fix.py:483(BLOCKING #3): the task advances out of AWAITING_VALIDATION only when every group has finished its validation phase. Completion is read per group — a PROPOSED group counts as finished only with recorded validation runs, which distinguishes "never validated" from "validated and failed" — and one failed group blocks the task even when the last group to finish passed. Revalidation from BLOCKED_VALIDATION still works.test_first_group_validation_keeps_task_awaiting_for_sibling,test_last_group_out_of_order_fail_blocks_after_sibling_passed,test_all_groups_passed_then_last_advances_ready_to_apply,test_failed_group_then_sibling_pass_blocks_validation.local-fix-concurrency —
routes.py(BLOCKING #4): direct-fix requests register the repository under_LOCAL_LOCKbeforecreate_task; a racing request gets409 fix_in_progressinstead of a second agent editing the same checkout. The slot is released in_local_fix_bg'sfinallyon every exit path (the pre-run session save moved inside the try so a raise there cannot leak the slot).test_a_second_fix_for_the_same_repo_is_rejected_409,test_the_registry_slot_is_released_after_the_run,test_the_registry_slot_is_released_after_a_failure.pool-model —
review_pool.py:371(FINDING #6 + Opus O1): the live-runtime reuse guard is strict model equality — any disagreement (pinned-vs-pinned or Auto-vs-pinned) fails closed with the busy error instead of silently running a review under the wrong model; only an exact match (including None == None) reuses.test_auto_on_pinned_runtime_fails_closed,test_pinned_on_auto_runtime_fails_closed,test_explicit_model_mismatch_on_live_runtime_fails_closed,test_same_or_auto_model_reuses_live_runtime.ui-action-cap —
ReviewFixTaskPanel.tsx(BLOCKING #5): preview is offered from the moment a group is committed (the diff is final — the action block used to vanish for the whole task-committed window), Push stays push-phase-only, and Refresh diff lives in the overflow menu — every state renders at most two controls per action row (max-two-buttons-per-row).shows preview without push while the task is committed and keeps refresh in overflow,shows exactly preview and push during awaiting_push with refresh in overflow,hides the push affordances until a group is committed.Local gates on this head (8e63ec9)
test_review_fix.pyincl. the 4 multi-group state-machine cases,test_git_coord_review_fix.py,test_review_fix_routes.py,test_review_fix_actions_coverage.py,test_review_pool.py,test_backend_routes.py,test_local_review.py).ReviewFixUi.test.tsx40 passed.tsc -b: clean.scripts/check_agent_sdk_boundary.py: passed (shrink-only baseline respected). i18n key gate: passed (more_actionspresent in all 14 locales).model-under-testsentinels only).Commit shape: history rewrite is disabled in this environment, so instead of amending into
8ed8f3db4the residuals land as a plain fast-forward third commitfix(code-review): close residual review-hardening gaps (round 2)on top of2e14007ff— same content, additive history.Update — cleanup: squashed to 2 commits, black, census drop (7c86e02)
Mechanical follow-up to the round-2 head
8e63ec9bd; content unchanged except the three items below.ab434e8e2 feat(code-review): add guided review fix workflow(round-1 feature + round-2 residuals squashed) and7c86e023f fix(ci): drop the memory.py log-site census after #7542(re-measure → drop the stale entry). Tip tree differs from8e63ec9bdonly by the two items below.--target-version py310—src/kiro_crew/review_fix.py,src/kiro_crew/review_fix_git.py,test/test_git_coord_review_fix.py,test/test_review_fix.py. Whitespace-only.dashboard/handlers/memory.py's two pip-stderr sites toredact_log_via_context, so on the merge ref the module has 0 baseline sites and the recorded 2 was pure slack (test_the_census_holds_no_slack:memory.py: 0 sites, census says 2). Thedashboard/handlers/memory.pyentry is dropped from_BASELINE_LOG_SITE_CENSUSper the emptied-module rule; no source change.Gates on this head:
black --check --target-version py310on the 4 files → clean; pytesttest_review_fix.py+test_git_coord_review_fix.py32 passed, wider review-fix suites 114 passed, backend-routes coverage 266 passed; vitestReviewFixUi+SagePrReviewDetailModel47 passed;check_agent_sdk_boundary.pypassed.test_security_posture.pyis 46/47 on this stale pre-#7542 checkout — the one local failure (test_no_new_gate_side_log_line_reads_the_baseline_redactor) counts the two now-gone sites in the local memory.py; both census tests pass against the upstream-merged tree CI tests (scanner run over upstream's converted memory.py: 0 sites).