feat(runtime-verifier): script-driven proof with video, gitignored + uploaded to per-stage PRs - #376
Conversation
…uploaded to per-stage PRs The runtime-verifier used to drive the app through the haiku-playwright MCP and write screenshots into .haiku/.../proof/ — which was NOT gitignored, so every verification run committed regenerated binary churn into history, and the MCP gave screenshots only (no video). This reshapes proof end to end: - Driver: the web/GUI verifier now writes a SELF-INSTALLED Playwright script (own scratch dir, never the project's deps) that records video + step screenshots. haiku_view boot still returns the URL; the haiku-playwright MCP becomes the fallback when @playwright/test can't be installed. Doctrine + all nine runtime-verifier mandates updated; browser_* tool prose softened to capability language. - Proof is gitignored: ensureWorktreesGitignored → ensureHaikuGitignored seeds the worktree pool + both proof globs idempotently; root .gitignore matches. Engine git-add paths honor .gitignore, so proof stays out of commits and out of dirty_tree_blocking_tick. The SPA still serves it live off disk. - Proof is uploaded to the PR: runtime-verifier joins PR_INTERACTION_ROLES (now in BOTH role classes). Subagent templates grant ADDITIVE scopes (proof-write AND PR-upload). The dispatch builders inject a mode-aware proofTargetPrUrl. GitLab uploads API / GitHub release-asset doctrine in git.md. - Per-stage delivery PR: in discrete / discrete-hybrid mode the engine opens a DRAFT stage PR at stage start (openStageDraftPullRequest, base = intent main), records it in a stage_prs map on intent.md FM, and flips it draft→ready at the gate (instead of opening a second PR). That stage PR is where the stage's proof lands. continuous / autopilot / quick keep the single intent-main draft PR. stage_prs is NOT a tamper-checksummed INTENT_FIELD (like draft_pr_*). Sync: CLAUDE.md concept rows, ARCHITECTURE.md §2, the website architecture map (actors.ts + payload-for.ts). Tests: new stage-pr-and-proof-gitignore.test.mjs (10) + updated verification-doctrine.test.mjs to lock the script/video/upload + additive-scope contract. Full suite 2080 passed / 0 failed; website tsc + build clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Claude finished @jwaldrip's task in 4m 47s —— View job PR Review Complete
Overall AssessmentThis is a well-structured PR with a clear problem statement and a coherent solution. The four changes (Playwright-script driver, proof gitignored, proof uploaded to PR, per-stage delivery PR) are all tightly coupled and ship together correctly. The 2080-test suite passing is a strong signal. Here's my detailed read: What works well
Role-class additive scopes. Adding
Mandate updates. All nine Issues found1. 2. Double-banner on partial upgrade (inline comment at 3. 4. GitHub release-asset privacy risk (inline comment at 5. Minor: 6. Test helper serializes nested objects as JSON-in-YAML (inline comment at What's scoped out correctlyThe PR notes that The paper is intentionally left untouched — proof mechanics are below the abstraction level of the paper's runtime/delivery section. Agreed. The architecture map entries ( VerdictThe core feature is correct and well-tested. Issues 1 and 2 are the only ones I'd call pre-merge candidates — everything else is follow-up or editorial. The test suite coverage (role classification, FM round-trip, gitignore idempotency, mode-aware URL selection) is solid and directly locks the contracts introduced here. |
Follow-up to the per-stage delivery PR. In `discrete-hybrid` mode the engine no longer opens a draft PR for every stage — only for stages whose `review:` gate is or includes `external` (`stageRequiresExternalReview`). The continuous stages run inline and keep their work + proof on the intent-main PR. Plain `discrete` still opens one per stage. The dispatch builder's mode-aware `proofTargetPrUrl` needs no change: a non-external hybrid stage has no `stage_prs[stage]` entry, so it already falls back to the intent-main `draft_pr_url`. Sync: CLAUDE.md, ARCHITECTURE.md, git.md, the architecture map. Test: stageRequiresExternalReview against real software STAGE.md gates (development/design = [external, ask] → external; inception = ask, operations = auto → not). Full suite 2081 passed / 0 failed; website tsc clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Claude finished @jwaldrip's task in 3m 55s —— View job Re-review:
|
| # | Issue | Status |
|---|---|---|
| 1 | PER_STAGE_PR_MODES defined in two places (side-effects.ts:110 + dispatch_approval/index.ts:47) |
Still open — inline comment re-filed |
| 2 | Double-banner on partial upgrade (ensureHaikuGitignored appends new banner after old one) |
Still open |
| 3 | status: "failed" comment in haiku_await_gate.ts misleading ("not a second PR" only true when status=draft) |
Still open |
| 4 | GitHub release-asset privacy risk callout (flagged in git.md but easy to skim) |
Addressed in docs — editorial only |
| 5 | if (stage && PER_STAGE_PR_MODES.has(mode)) redundant stage && guard |
Still open — inline comment re-filed |
| 6 | seedIntent test helper serializes stage_prs as JSON-in-YAML rather than YAML object syntax |
Still open |
Issues 1 and 5 are the only pre-merge candidates from this round; 2, 3, and 6 remain low-risk follow-up candidates.
Overall verdict
The new commit ships the correct narrowing for discrete-hybrid with minimal surface area — one new function, one added guard, tests against real STAGE.md files, and complete documentation sync. No regressions introduced. The fallback behavior in haiku_await_gate and resolveProofTargetPrUrl handles all edge cases correctly without changes.
The one structural concern that carries across both commits is the duplicated PER_STAGE_PR_MODES constant. It controls two independent behaviors (whether to open a stage PR, and which PR to target for proof upload) and now also implicitly shapes the stageRequiresExternalReview call site — a new mode added to one side won't propagate to the other. Worth extracting before additional delivery modes appear.
- Extract `PER_STAGE_PR_MODES` to a shared leaf module (orchestrator/workflow/delivery-modes.ts) and import it from both side-effects.ts (opens the stage PR) and dispatch_approval/index.ts (picks the proof-upload target). Was duplicated inline in both — a new mode would have had to be added in two places (review #1). - ensureHaikuGitignored: emit a concise one-line note instead of a second full banner when a repo already carries the old worktree-only banner (partial upgrade no longer stacks two banners) (review #2). - Clarify the haiku_await_gate external-review comment: the else-if fallback covers BOTH no-per-stage-PR modes AND a failed stage-start open (no recorded URL) — not just "second PR" (review #3). - Biome: fixed import-sort/format in the touched src files (`npx biome check packages/haiku/src` was red on the import order). Kept as-is with rationale on the threads: the `stage &&` guard in resolveProofTargetPrUrl (defensive on a param), the release-asset privacy note (already documented in git.md), and the test helper's JSON-in-YAML seed (valid YAML, test-only). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Claude finished @jwaldrip's task in 2m 58s —— View job Re-review:
|
| # | Issue | Status |
|---|---|---|
| 1 | PER_STAGE_PR_MODES defined in two places |
✅ Fixed — extracted to delivery-modes.ts, imported from both sites |
| 2 | Double-banner on partial upgrade | ✅ Fixed — hasManagedLine check: emits a short one-line note instead of a second full banner |
| 3 | haiku_await_gate comment misleading |
✅ Fixed — comment now says "covers both no-per-stage-PR modes AND the case where the stage-start open failed" |
| 4 | GitHub release-asset privacy callout | Kept — already documented in git.md, author's rationale accepted |
| 5 | stage && guard in resolveProofTargetPrUrl |
Kept — defensive on a param (string type still admits empty strings); harmless |
| 6 | seedIntent JSON-in-YAML test seed |
Kept — valid YAML, test-only, explicitly documented in commit message |
What the fix commit does well
delivery-modes.ts is a clean, neutral leaf module — 19 lines, no heavy deps, well-commented. The documentation correctly distinguishes discrete (every stage gets a PR) from discrete-hybrid (only external-review stages), and the note "membership here means 'this mode CAN open per-stage PRs'" is exactly the right framing given the narrowing that stageRequiresExternalReview applies on top.
hasManagedLine heuristic uses lines.some((l) => l.startsWith(".haiku/")) to detect partial upgrades. Edge cases are all handled correctly: old worktree-only entry (.haiku/worktrees/ starts with .haiku/) → short note; fresh repo (no .haiku/ lines) → full banner; proof globs already present (nothing in missing) → early return before the check even fires. Solid.
openStageDraftPrIfDelivery adds a detectPrTool() === null guard before calling into the PR-open path. This is an improvement over the openStageDraftPullRequest function itself's best-effort handling — it short-circuits cleanly when no provider CLI is present, avoiding a push attempt that would fail anyway.
Biome fix is included — the import sort was red on npx biome check packages/haiku/src before this commit. Clean CI path now.
Test coverage is thorough: 11 tests across role classification, stageRequiresExternalReview against real STAGE.md files, setStagePrField/readStagePr FM round-trips, ensureHaikuGitignored idempotency (including the partial-upgrade case), and mode-aware proof-upload target selection. These lock exactly the contracts introduced in this PR.
Overall verdict
All three pre-merge blockers from prior reviews are resolved. No new issues found in the fix commit. The PR is clean and ready to merge.
|
Claude finished @jwaldrip's task in 3m 13s —— View job PR #376 Final Review
This is a comprehensive review of the full PR diff across all three commits. The PR has already gone through two prior rounds of review; this pass covers the complete merged state. What the PR doesFour tightly-coupled changes shipped together:
Architecture assessment
Test coverageThe new
Prior review issues — final status
VerdictAll three pre-merge blockers from prior reviews are resolved. The implementation is correct, well-tested (2081 tests, 0 failed), and the architecture is clean. The |
What & why
The runtime-verifier drove the app through the
haiku-playwrightMCP and wrote screenshots into.haiku/.../proof/— which was not gitignored, so every verification run committed regenerated binary churn into history, and the MCP gave screenshots only (no video). This reshapes proof end to end and adds the per-stage delivery PR for discrete mode.Changes
haiku_viewboot still returns the URL; thehaiku-playwrightMCP is the documented fallback. Doctrine + all nineruntime-verifier.mdmandates updated;browser_*tool prose softened to capability language (doctrine owns how to drive, mandates own what to verify).ensureWorktreesGitignored→ensureHaikuGitignoredseeds the worktree pool + both proof globs idempotently; root.gitignorematches. Enginegit addpaths honor.gitignore, so proof stays out of commits and out ofdirty_tree_blocking_tick. The SPA still serves it live off disk.runtime-verifierjoinsPR_INTERACTION_ROLES(now in BOTH role classes). Subagent templates grant additive scopes (proof-write AND PR-upload). Dispatch builders inject a mode-awareproofTargetPrUrl. GitLab uploads-API / GitHub release-asset doctrine ingit.md.discrete/discrete-hybridthe engine opens a DRAFT stage PR at stage start (openStageDraftPullRequest, base =haiku/<slug>/main), records it in astage_prsmap on intent FM, and flips it draft→ready at the gate — not a second PR. That stage PR is where the stage's proof lands.continuous/autopilot/quickkeep the single intent-main draft PR.stage_prsis not a tamper-checksummedINTENT_FIELD(likedraft_pr_*).Sync surface
CLAUDE.md concept rows,
ARCHITECTURE.md§2, the website architecture map (actors.ts+payload-for.ts).Verification
stage-pr-and-proof-gitignore.test.mjs(10, incl. mode-aware URL selection) + updatedverification-doctrine.test.mjsto lock the script/video/upload + additive-scope contract.tsc+next build: clean. MCP bundle rebuilt.Scoped out (follow-up)
external_reviewis a runtime human decision today).🤖 Generated with Claude Code