Build Log
The Approval Path Was Never the Approval Path
September 22, 2026
A stuck MedinaCHF request led to finding the Owner Loop Approve button couldn't complete a fresh deploy, a queue showing five stale items, and a Foreman findings dashboard that was 97% noise — all four traced to the same root shape and fixed at the source.
Timeline
What shipped
Fixed the Owner Loop Approve button's actual failure: owner-loop-manual-accept.yml wrote its DB credential two steps after the script that needed it, and never wrote a Vercel token at all — the accept path could not deploy, structurally, for any app.
Human decision now persists and pushes to master BEFORE any failable work runs (--record-decision-only) — a promotion/publish/finalize failure can no longer discard the approval itself.
accept-pending-candidate.mjs is now resumable from the last incomplete stage instead of blanket-refusing any non-fresh record; a REJECTED decision is hard-guarded out of the resume-to-deploy path.
base_diverged handled in-band: merges the candidate onto current master, runs the app's real tsc/predeploy checks against the INTEGRATED result (a clean merge is not verification), never force-pushes, stops for a human only on a genuine conflict.
Fixed a real exit-code bug (D5): the old expression reported success for a run that promoted code, failed to publish, and skipped finalize entirely — a stranded request exiting 0. Reproduced the old bug empirically before shipping the fix.
Evidence chain now records approved_candidate_sha, integrated_revision_sha, deployment_id, and publication_evidence_sha as four separate fields, so 'is what shipped what the human approved' stays answerable.
Concurrency: owner-loop-manual-accept.yml now serializes per app+candidate (same primitive as actor-spawn.yml's own live-verified concurrency group) so two simultaneous Approve clicks converge on one operation.
Alloy's Approve/Deny UI no longer reports 'Approved' on a bare HTTP 204 — states plainly that the run started, not that it finished, and links to the real run.
New classify-request-state.mjs + inspect-admin-intent.mjs --stuck-only: a stuck-request diagnostic whose honesty is enforced by tests, not prose — non-terminal is not stuck, block_reason=execution_failure is never reported as 'the Actor failed', ready-to-finalize requires real publication evidence.
Notification precision: intermediate refusals that are now handled in-band produce zero emails; a genuinely unfinished approved request produces exactly one actionable notice, naming what's incomplete.
reconcile-pending-acceptance.mjs: resolves a pending-acceptance record ONLY on proof (its candidate is an ancestor of master, or a sibling candidate for the same request shipped instead) — cleared all 5 stale Interventions queue items, two of which had sat since 2026-08-31 carrying a recorded approval that was never advanced.
Fixed Foreman's medinachf display: it wasn't disabled, it was the busiest app (141 records, 61 findings) — Alloy's readonly role for medinachf was actually named after the wrong Postgres role (owner_loop_resolver, not medinachf_readonly), so its own grant never reached the role that connects.
Rewrote projection_divergence (Foreman rule) to recognize 'deployed' and 'terminal' as equivalent legitimate end states instead of flagging every successfully Actor-deployed request as drift forever, and to grade severity by what actually diverged (contradiction=high, never-backfilled=low) instead of hardcoding high for everything.
Wired resolveIfInvariantRestored into the sweep for the first time ever — it existed, fully written, with zero callers, so a finding that stopped firing stayed open permanently. Scoped narrowly: only rules that examine every record with no LIMIT/lookback may be auto-resolved against, proven by a test that reads the real rule source.
Fixed a second, independent bug in the same area: recordFinding's upsert never refreshed severity or rule_version, so a rule fix could run correctly and still never reach the dashboard. Found by checking the database directly rather than trusting the sweep's own success log.