Skip to content

Requirement delivery: agrippa/v2 slots, checkpoints & loops, Codex reviewer, platform PRs - #5

Merged
hutusi merged 22 commits into
mainfrom
feat/requirement-delivery-workflow
Jul 23, 2026
Merged

hutusi merged 22 commits into
mainfrom
feat/requirement-delivery-workflow

Conversation

@hutusi

@hutusi hutusi commented Jul 23, 2026 •

Copy link
Copy Markdown
Contributor

Takes a natural-language requirement all the way to a reviewed pull request with two cooperating agents — an implementer (Claude Code / Forge) and a reviewer (OpenAI Codex / the new Arbiter faber) — with the user in the loop for clarification, plan confirmation, and per-finding review decisions, and the whole team watching and commenting live.

What's in here

agrippa/v2 template format (ADR-0010 — the explicit v2 decision ADR-0006 reserved)

  • Agent slots: templates declare named roles (implementer, reviewer) binding a faber + executor; the submitter can swap either on overridable slots. Bindings freeze onto the run at submit with capability checks against a static executor catalog in core and per-slot, provider-filtered model resolution (a Codex slot without a granted OpenAI model fails at submit, not mid-run).
  • Checkpoint steps replace phase-level approvals: approval (incl. request changes inside loops, with the comment fed back into the revision), input (structured Q&A from an agent-produced questions artifact, with recommended answers), and review-gate (fix-selected vs accept-remaining per finding). Empty sources auto-pass. Decisions store a structured response that re-enters the run as the checkpoints.<id> expression root.
  • Bounded loops (maxIterations 1–10, until conditions) power clarify-Q&A, plan revision, and review-fix uniformly. Iteration is a column on steps/checkpoints/artifacts; resume derives its round from persisted rows, so crash recovery needs no new state.
  • v1 templates upgrade to the v2 IR purely at load — no data migration, and the entire pre-existing engine compliance suite passes through the upgrade path.

OpenAI Codex executor (ADR-0011)

  • packages/executor-codex wraps codex exec --json (event shapes pinned against codex-cli 0.145.0 by live probes; samples in the package README): normalized event mapping, cache-aware usage splitting, same-step resume via thread ids, SIGTERM-on-abort.
  • Containment through the existing seams where they reach — env via the shared allow-list scrubber — and Codex's native OS sandbox where they don't (read-only/workspace-write, network off, approval_policy=never); the enforcement matrix is documented in the ADR. Read-only reviewer steps synthesize their json artifact from the final message's fenced block.
  • Registered only when the CLI probe and auth succeed; every registered executor is asserted against the core catalog at worker boot.

Platform-side git write-path

  • New system actions git.branch / git.push / pr.open run through an engine SCM seam (worker: credential-injected push that never touches .git/config, PR/MR via the GitHub/GitLab REST API). The PR link is a contract-required artifact, so it no longer depends on an optional MCP server or agent behavior.
  • Accepted review findings are never silent: they land in the PR body as explicit waivers with who accepted them, accumulated across rounds (a later fix decision supersedes an earlier acceptance).

API + collaboration

  • POST /runs/:id/checkpoints/:checkpointId/respond (kind-discriminated, validated against the pending row's snapshot; CAS + event + audit in one transaction), a generalized GET /checkpoints/pending "waiting on you" inbox, and run comments that commit together with their comment.added event so the live timeline and the thread can't disagree. Legacy /approvals routes removed.

UX

  • The run page's default tab is a conversational timeline built from the replayed SSE stream: phase headers with loop-round chips, streaming agent turns tagged with persona + engine, inline checkpoint cards (question forms with one-click recommendations; findings tables with severity badges and an explicit accept-remaining confirmation), teammate comments with a pinned composer, and a PR card at the end. Submit gains agent-slot pickers; the phases rail and inbox are round- and kind-aware.

Flagship template — swdev.requirement-delivery: clarify loop → plan loop (onMaxIterations: fail — an unapproved plan is never implemented) → implement on a platform-named branch → review-fix loop → publish, with an extra sign-off only when the loop exhausts right after a fix that was never re-reviewed.

Schema — approvals → checkpoints (data-preserving rename; + kind/iteration/response), iteration on run_steps/artifacts, agent_bindings/work_branch on runs, new run_comments (migration 0005_requirement-delivery).

Verification

  • bun run check, bun test (160 tests incl. Postgres integration suites), bun run templates:validate, bun run build — all green at every commit.
  • Engine compliance suite extended: two-slot routing, checkpoint pause/answer/interpolate, auto-pass, findings fix/accept paths, loop resume mid-iteration, exhaustion fail/continue, SCM retry + PR-link contract — and the full v1 suite through the upgrade path.
  • Executor tests drive the Codex adapter against a fixture CLI replaying pinned JSONL: argv/sandbox/resume construction, event/usage mapping, prompt assembly, read-only artifact synthesis, error normalization, abort, env scrubbing.
  • End-to-end: the real template driven through the engine with the demo executor (Q&A → plan → review-fix → PR with correct title/body/waivers), plus the repaired Playwright screenshot harness capturing every page — including the new timeline, findings table, comments, and agent pickers — with zero browser console errors.

Notes for review

  • Seeded OpenAI model ids/pricing (gpt-5.1-codex, gpt-5.1-codex-mini) should be verified against the current price list at rollout; registry rows are admin-editable.
  • Reviewer steps currently run with the workspace's readWrite access (access is per-workspace, not per-step); the reviewer is instructed not to modify code — per-step access modes are noted as future hardening in ADR-0011.
  • Migrating bug-localize-fix's MCP-based open-pr step to platform pr.open is a noted follow-up.
  • With AGRIPPA_EXECUTOR=fake, every slot binds to the demo executor (a demo install must never silently route to a key-consuming engine); AGRIPPA_SCM=fake fabricates branch/push/PR, so the whole workflow demos token-free.

Summary by CodeRabbit

  • New Features
    • Added the agrippa/v2 “requirement-delivery” flow (bounded loops + human checkpoints) with agent slots and automated PR publishing.
    • Introduced Codex execution as a supported executor option (with sandboxed execution and capability limits).
    • Added a live run timeline and a checkpoints inbox/UI for approval, questions, and review-gate interactions, including per-slot agent overrides.
  • Changed
    • Replaced the approvals inbox/UI with a “Waiting on you” checkpoints inbox; run details now center on timeline + checkpoint interaction cards.
  • Bug Fixes
    • Strengthened checkpoint decision validation, evidence/artifact handling, snapshot publishing integrity, idempotent PR/branch behavior, executor availability checks, and workspace/resume safety.

hutusi added 11 commits July 23, 2026 05:46
The requirement-delivery workflow (ADR-0010) needs three contracts shared
across the API, SPA, engine, and executors without breaking dependency
direction: a static executor catalog (the API may never import executor
packages, so capability/provider metadata lives in core and the worker
asserts its registrations against it at boot), zod schemas for the two
structured interaction artifacts (agent questions and review reports)
that drive the new input/review-gate checkpoints, and the checkpoint
kind/severity vocabulary plus respond/comment request schemas.

StepExecutionRequest gains optional iteration/agentSlot so executors and
scripted fakes can distinguish loop rounds and agent slots; optional
fields keep every existing executor source-compatible.
Schema groundwork for the requirement-delivery workflow (ADR-0010):

- approvals becomes checkpoints (data-preserving RENAME) and grows kind
  (approval|input|review-gate), iteration, and a response jsonb column so
  a decision can carry structured data (answers, findings dispositions)
  back into the run's expression context — the old table could only say
  approved/rejected.
- run_steps and artifacts gain iteration (default 1) so loop rounds get
  distinct idempotency/attribution rows without changing any non-loop
  behavior; run_steps_uq now includes it.
- runs gains agent_bindings (per-slot faber+executor) and work_branch
  (set by the upcoming git.branch system action). faber_id/executor_id
  stay as primary-slot denormalization for list/usage queries.
- new run_comments table for the team discussion thread on a run.

decideApproval/findStrandedApprovalRuns are renamed to their checkpoint
equivalents across engine/api/worker (behavior unchanged); route paths
keep their /approvals shape until the API slice lands.
…ded loops, SCM actions

ADR-0006 deferred loops and richer interaction to 'an explicit agrippa/v2
decision'; the requirement-delivery workflow is that decision (ADR-0010).
The format needs three constructs the linear v1 graph cannot express: a
review→fix cycle, per-step agent routing (Claude implements, Codex
reviews), and human decisions that carry data back into the run.

Template format: v2 documents declare spec.agents (slots binding a faber
+ executor, submit-overridable), checkpoint *steps* (approval | input |
review-gate — the latter two auto-pass on an empty json source artifact),
bounded kind:loop nodes (static maxIterations keeps compiler validation
total), and git.branch/git.push/pr.open system actions with interpolable
'with' config. New expression roots: checkpoints.<id> (decided response;
same-loop forward refs resolve to the previous iteration) and
artifacts.<key> (latest inline content). v1 stays a supported authoring
format: a pure upgradeV1ToV2 maps it onto the same IR (one 'main' slot,
phase approvals become checkpoint steps), applied at compile time and
when loading stored compiled rows — no data migration, no behavior
change for existing templates (the whole compliance suite passes through
the upgrade path).

Engine: per-slot executor/faber bindings resolved at pickup (empty
run.agent_bindings falls back to the run's primary faber/executor);
checkpoint steps reuse the pause mechanics (pending row, CAS to
waiting_approval, job completes) but can now sit mid-phase and store a
structured response that re-enters the expression context; loops derive
their resume iteration from persisted (stepId, iteration) rows so crash
recovery needs no new state; SCM actions run through a new optional
EngineDeps.scm seam, with pr.open composing the PR body plus an explicit
waiver section for review findings the team accepted.

Events gain iteration and the approval.required/decided pair becomes
checkpoint.required/decided; loop.iteration.started/completed/exhausted,
branch.created/pushed, and pr.opened are new.
…s, scm

The v2 constructs need the same executable-contract treatment ADR-0005
gave the executor boundary: a fixture template exercising the whole
requirement-delivery spine (Q&A loop with auto-pass, plan approval,
review-fix loop with fix-selected vs accept-remaining, exhaustion-
continue publish gate, exhaustion-fail, platform branch/push/PR with
retry) so any future engine change is pinned against it.

FakeExecutor learns iteration-scoped script keys (stepId@iteration) so
loop rounds can behave differently — findings in round 1, clean in
round 2 — without changing existing scripts.

Two engine fixes fell out of writing these tests: PR-body waivers now
accumulate across review rounds (latest human decision per finding id
wins) instead of reading only the final gate row, which — being the
auto-pass — always carried zero accepted findings; and loop lifecycle
events consult the event log so resumes through a finished loop don't
re-emit loop.completed. Engine-side patch generation is now keyed to
(step, iteration) so a loop's fix round re-diffs the workspace instead
of being skipped because the key existed from the implement phase.
The requirement-delivery workflow's reviewer slot needs a second real
agent engine, and ADR-0005's promise — a new executor implements one
interface and passes the existing compliance semantics — gets its first
outside test. The adapter wraps codex exec --json (JSONL event shapes
pinned against codex-cli 0.145.0 by live probes; samples in the README)
and maps thread/item/turn events onto the normalized ExecutorEvent
stream: exactly one terminal event, usage split so the cached-inclusive
input_tokens never double-charges cache reads, thread_id as the
same-step resume handle.

Isolation goes through the existing seams rather than around them
(ADR-0009): the subprocess env uses the shared allow-list scrubber
(OPENAI_API_KEY/CODEX_API_KEY join the provider-auth allow-list and the
redaction values), and containment maps toolPolicy.access onto Codex's
native OS sandbox (read-only / workspace-write, network off,
approval_policy=never) because codex exec has no per-tool-call hook for
evaluateToolCall — the enforcement matrix is documented for ADR-0011.

Read-only steps (the reviewer) cannot write artifact files, so a json
artifact is synthesized from the final message's fenced json block and
validated by the shared interaction schema downstream. The file-based
artifact convention moves from executor-claude into executor-core
(artifacts.ts) and both adapters share it. Tests drive the executor
against a fixture binary replaying canned JSONL — argv/sandbox/resume
construction, event and usage mapping, prompt assembly, read-only
synthesis, error normalization, SIGTERM-on-abort, and env scrubbing.
…emo executor

GitScmService implements the engine's new scm seam: branch creation with
checkout -B (idempotent across retries/crash-resume), push against a
credential-injected URL that never lands in .git/config, and PR/MR
creation via the GitHub or GitLab REST API from the project-scoped repo
connection — the PR link is contract-required, so it cannot depend on an
optional MCP server or agent behavior. The connection/credential loading
moves out of GitWorkspaceManager into shared helpers both use.
AGRIPPA_SCM=fake wires the in-memory fake for token-free demos.

The codex executor registers only when the CLI probe succeeds and auth
is configured, and every registered executor is asserted against
EXECUTOR_CATALOG at boot — the catalog is what the API and compiler
trust for capability checks, so drift must fail fast, not surface as
runtime template errors. (The catalog's fake entry stops over-promising
resume, which the demo executor never supported.)

DemoExecutor becomes round-aware: question/review json artifacts report
findings in round 1 and come back clean in round 2, so the checkpoint
loops of the requirement-delivery template demo realistically.
…errides

One respond path serves every checkpoint kind: the payload is validated
against the pending row's kind and snapshot (required answers against
the question list, finding ids against the report), and the stored
response carries full finding objects — templates interpolate
checkpoints.<id>.selectedFindings directly, so the expression language
never needs filtering. request_changes is refused outside loops (there
is nothing to send the agent back to; the outcome would silently read
as a pass — the engine now stamps loopId into checkpoint payloads for
exactly this guard). The decision CAS, checkpoint.decided event, and
audit row commit in one transaction before the run re-enqueues; the
legacy /approvals decide route and inbox stay as thin delegates until
the SPA switches over.

Run comments insert with their comment.added timeline event in the same
transaction, so the SSE stream and the thread can never disagree;
members write, viewers read.

Task submit resolves every agent slot to a concrete faber + executor
(resolveAgentBindings): template defaults — the v1-upgrade sentinel
maps to the deployment default, preserving pre-slot behavior exactly —
then user overrides on overridable slots, capability checks against the
executor catalog, and per-slot provider-filtered model resolution (a
codex reviewer without a granted openai model fails at submit with
model_unresolvable, not mid-run). Bindings and slot-keyed model
resolution freeze onto the run; retry copies them. Run detail embeds
checkpoints with decider names and per-slot faber/executor metadata;
the task-type detail exposes slot pickers' data.
…ai model seeds

The flagship agrippa/v2 template: a requirement goes from natural
language to a reviewed PR through two cooperating agent slots — the
implementer (Forge on Claude Code) and the reviewer (the new Arbiter
faber on Codex, an exacting persona that must defend every severity and
treats an empty findings list as a valid verdict).

Flow: platform checkout + deterministic work branch; a bounded clarify
loop (agent asks structured questions with recommended answers, empty
list auto-passes); a plan loop whose approval gate supports
request-changes with the comment fed back into the revision (exhaustion
fails the run — implementing an unapproved plan is never acceptable);
implementation on the pre-created branch; a review-fix loop where the
user picks fix-selected vs accept-remaining per round and zero findings
auto-pass; and a publish phase whose extra sign-off appears only when
the loop exhausted right after an un-re-reviewed fix, before the
platform pushes and opens the PR with the plan and accepted-findings
waivers in the body.

Seeds: arbiter faber, the requirement-delivery template head + task
type, and openai codex model rows (ids/pricing to verify at rollout —
registry rows are admin-editable). The API fixture now grants
anthropic-only models so the no-openai-model submit failure stays
testable.
…ers, inbox

The SPA catches up with the interaction model. Submitting a task with an
agent-slot template shows per-slot pickers (faber persona + executor
engine from the core catalog, prefilled with template defaults, sparse
overrides only). The run detail page gains a conversational Timeline
tab built entirely from the replayed SSE stream — phase headers with
loop-round chips, streaming agent turns tagged with the slot's faber
and executor, inline interaction cards, teammate comments, and a PR
card at the end — with the raw activity feed kept as its own tab.

CheckpointPanel replaces ApprovalPanel with kind-specific bodies: the
approval card adds Request changes (loop checkpoints only, comment
required — it feeds the revision); input checkpoints render the agent's
question form with one-click 'use recommendation' fills; review gates
render a findings table with severity badges where checked findings go
to the fix round and unchecked ones are explicitly accepted (the
confirm dialog spells out what is being waived). Decided checkpoints
show who decided them and when. Comments post from a composer pinned
under the timeline; they arrive back through the event stream, so every
watcher sees them live.

The approvals inbox becomes a kind-aware 'waiting on you' list wired to
/checkpoints/pending, and the legacy /approvals routes and schema are
removed now that nothing consumes them. Model-resolution display and
the phase rail understand slot-keyed resolutions and loop iterations.

resolveAgentBindings gains demo mode: when the deployment default
executor is 'fake' (the documented token-free demo switch), every slot
binds to it — a demo install must never silently route a slot to a
real, key-consuming engine. Verified end-to-end by driving the real
requirement-delivery template through the engine with the demo executor:
Q&A round, plan gate, review-fix loop, and platform PR all behave.
…elivery

ADR-0010 records the agrippa/v2 decision ADR-0006 explicitly deferred —
agent slots, checkpoint steps, and bounded loops, including the
deliberate loosening that lets v2 templates read checkpoint responses —
and ADR-0011 records the Codex executor (with its enforcement matrix:
what the native OS sandbox covers and where evaluateToolCall cannot
reach) plus the platform-side git write-path and PR waiver policy.

The living design docs move with the implementation: 02 documents the
v2 format and the compiled-IR/upgrade story, 03 the Codex CLI mapping
and multi-executor catalog, 04 the checkpoint/loop runtime semantics,
05 the respond/comments/inbox API surface and its error vocabulary,
06 the run-timeline UX, checkpoint cards, and agent pickers.
ARCHITECTURE.md, the bilingual manual (concepts + running tasks, both
locales in this commit per the i18n rule), the changelog, and the
README front door are updated to match.
…kpoints

The render-level smoke was broken twice over: it still called the
sign-up endpoint that invite-only onboarding closed (403 on boot), and
it drove approvals through the removed legacy routes. It now bootstraps
the first admin via the CLI + signs in, responds through the checkpoint
endpoints, and — new coverage — seeds a requirement-delivery run
against a file:// connection to this working tree (AGRIPPA_SCM=fake),
answering the Q&A round and approving the plan so the capture lands on
the round-1 review gate: the timeline, findings table, comment thread,
and agent pickers all render under the console-error gate. Waiting for
the next checkpoint polls the checkpoint list rather than the run
status, which stays waiting_approval across a resume and made the old
status-poll return before the worker had moved on.

The timeline drops the duplicate phase header a resume re-announces for
the round it paused in — spotted in the captures.

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hutusi has reached the 50-credit limit for trial accounts. To continue receiving code reviews, upgrade your plan.

@coderabbitai

coderabbitai Bot commented Jul 23, 2026 •

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This PR adds agrippa/v2 requirement-delivery orchestration with bounded loops, typed checkpoints, per-slot agent bindings, Codex execution, platform SCM operations, checkpoint APIs, and a timeline-centered web interface.

Changes

Requirement delivery workflow

Layer / File(s) Summary
V2 contracts and compilation
packages/core/*, packages/orchestration/src/*, templates/swdev/requirement-delivery.yaml, packages/db/src/schema/*
Adds v2 schemas, checkpoint contracts, loop and agent-slot support, compilation upgrades, binding resolution, persistence changes, and the requirement-delivery template.
Iteration-aware engine and checkpoint lifecycle
packages/orchestration/src/engine/*, packages/db/drizzle/*
Executes iteration-keyed loops and checkpoints, persists structured responses, resumes runs, validates artifacts, and performs SCM system steps.
Codex executor and SCM wiring
packages/executor-codex/*, packages/executor-core/*, apps/worker/src/*
Adds Codex JSONL execution, sandboxing, artifact handling, executor registration, workspace base diffs, and GitHub/GitLab branch, push, and PR operations.
Checkpoint API and submission flow
apps/api/src/routes/*, apps/api/src/lib/*
Adds agent metadata, executor availability, checkpoint inbox and response endpoints, transactional decisions, run projections, comments, and retry binding preservation.
Run timeline and checkpoint UI
apps/web/src/*
Adds agent-slot selection, kind-specific checkpoint panels, question and finding forms, checkpoint inboxes, event-driven timelines, comments, and iteration-aware phase rendering.
Validation, fixtures, and documentation
**/*.test.ts, scripts/*, docs/*, README.md, CHANGELOG.md, packages/i18n/*
Adds compiler, engine, API, executor, SCM, demo, seed, screenshot, localization, and documentation coverage for the new workflow.

Estimated code review effort: 5 (Critical) | ~120 minutes

Sequence Diagram(s)

sequenceDiagram
  participant SubmitTaskPage
  participant ExecutionAPI
  participant RunEngine
  participant CodexExecutor
  participant CheckpointAPI
  participant GitScmService

  SubmitTaskPage->>ExecutionAPI: submit task with agent overrides
  ExecutionAPI->>RunEngine: enqueue resolved run
  RunEngine->>CodexExecutor: execute bound reviewer step
  CodexExecutor-->>RunEngine: stream events and artifacts
  RunEngine->>CheckpointAPI: persist pending checkpoint
  CheckpointAPI->>RunEngine: validate and decide response
  RunEngine->>GitScmService: push branch and open pull request
Loading

Possibly related PRs

  • ainaive/agrippa#2: Continues the run-lifecycle CAS and event-allocation work while extending it to checkpoints and loop iterations.
  • ainaive/agrippa#4: Overlaps with the approvals/run web surfaces migrated here from approvals to checkpoints.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 58.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main agrippa/v2 workflow changes, including slots, checkpoints/loops, Codex review, and platform PRs.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/requirement-delivery-workflow

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 18

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/web/src/features/useRunEvents.ts (1)

40-55: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

branch.* events don't trigger a run-query refresh, despite being newly subscribed.

branch.created/branch.pushed are added to the SSE subscription list (L86-87) but are missing from the refresh-trigger condition here. Any run-detail UI relying on the invalidated ["run", runId] query (e.g. branch/PR metadata) will not update when a branch is created or pushed — only on a subsequent checkpoint.*/loop.*/pr.opened/comment.added event. The AI summary for this diff states the refresh conditions were updated to include branch.created/branch.pushed, which the code here does not show.

🐛 Proposed fix
         if (
           parsed.type.startsWith("run.") ||
           parsed.type.startsWith("step.") ||
           parsed.type.startsWith("phase.") ||
           parsed.type.startsWith("checkpoint.") ||
           parsed.type.startsWith("loop.") ||
+          parsed.type.startsWith("branch.") ||
           parsed.type === "comment.added" ||
           parsed.type === "pr.opened" ||
           parsed.type === "artifact"
         ) {
           refresh();
         }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/src/features/useRunEvents.ts` around lines 40 - 55, Update the
refresh-trigger condition in onAnyEvent to include the branch.created and
branch.pushed event types alongside the existing run, step, phase, checkpoint,
loop, comment, PR, and artifact events, while preserving the current
deduplication and refresh behavior.
🧹 Nitpick comments (2)
README.md (1)

43-43: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Document the real-run SCM configuration.

This paragraph explains AGRIPPA_SCM=fake but does not tell users what provider credentials or repository connection setup is required for real branch/push/PR operations. Add the required configuration or link to the SCM deployment documentation.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@README.md` at line 43, Update the README setup paragraph around AGRIPPA_SCM
to document the required real-run SCM provider configuration, including
credentials and repository connection setup needed for branch, push, and PR
operations. Preserve the existing fake-SCM explanation and link to the SCM
deployment documentation if the full configuration is documented elsewhere.
apps/api/src/routes/catalog.ts (1)

76-103: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Redundant fabri query — fetch active fabers once and derive fabriOptions.

activeFabri (Line 76) and fabriOptions (Lines 100-103) issue two separate round-trips to fabri with the identical status = 'active' predicate. Hoist the query above the if block and derive fabriOptions from it to drop one DB round-trip per task-type detail load.

♻️ Proposed consolidation
     let agents: Record<string, unknown> | null = null;
+    // selectable fabri for overridable slots (members submit; registry CRUD is admin-only)
+    const activeFabri = await db.select().from(fabri).where(eq(fabri.status, "active"));
+    const bySlug = new Map(activeFabri.map((f) => [f.slug, f]));
     if (template?.latestPublishedVersionId) {
       ...
         inputs = compiled.spec.inputs;
         budgets = compiled.spec.budgets;
-        const activeFabri = await db.select().from(fabri).where(eq(fabri.status, "active"));
-        const bySlug = new Map(activeFabri.map((f) => [f.slug, f]));
         agents = Object.fromEntries(
-    // selectable fabri for overridable slots (members submit; registry CRUD is admin-only)
-    const fabriOptions = await db
-      .select({ id: fabri.id, slug: fabri.slug, nameI18n: fabri.nameI18n, avatar: fabri.avatar })
-      .from(fabri)
-      .where(eq(fabri.status, "active"));
+    const fabriOptions = activeFabri.map((f) => ({
+      id: f.id,
+      slug: f.slug,
+      nameI18n: f.nameI18n,
+      avatar: f.avatar,
+    }));
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/api/src/routes/catalog.ts` around lines 76 - 103, Hoist the active fabri
query before the conditional block so it runs once, retaining the full records
needed by the existing bySlug map. Replace the separate fabriOptions database
query with a projection of that shared activeFabri result containing only id,
slug, nameI18n, and avatar, while preserving the existing status-filtered
options behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/web/src/features/runs/CheckpointPanel.tsx`:
- Line 1: The review-gate checkpoint can deadlock because evidence failure
disables both fix and accept actions. Update FindingsTable to retain disabled
for fix gating and add acceptDisabled for accept gating, then in
CheckpointPanel’s review-gate branch pass busy-only disabled for fix and
busy-or-evidenceFailed acceptDisabled, keeping accept blocked while fix remains
available.
- Around line 121-138: Update the review-gate branch in CheckpointPanel so
evidenceFailed disables only the blind accept action while keeping the fix
action available, analogous to the approval rejection behavior. Adjust the
FindingsTable action-specific disabled handling or props around onFix and
onAccept, preserving busy-state disabling and ensuring Accept remains blocked
when evidence loading fails.

In `@apps/web/src/features/runs/FindingsTable.tsx`:
- Around line 32-44: Split FindingsTable’s single disabled prop into separate
disabled states for the onFix and onAccept actions. Update the button usages in
FindingsTable and all callers such as CheckpointPanel so evidenceFailed disables
only the fix action while the accept/waiver action remains available; preserve
existing behavior for other disable conditions.

In `@apps/web/src/features/runs/QuestionsForm.tsx`:
- Around line 39-42: Update the Accept All Recommendations flow around
recommendable and its fill loop so required questions without a recommended
value remain missing. After applying recommendations, recompute or otherwise
validate the remaining required unanswered questions, and disable or prevent
submission when any remain, matching the primary Submit button’s missing-field
behavior.

In `@apps/web/src/features/runs/RunTimeline.tsx`:
- Around line 330-333: Update the auto-scroll useEffect in RunTimeline to
include itemCount in its dependency array so endRef scrolls into view whenever
streamed timeline items are appended; preserve the existing nearest-block scroll
behavior.

In `@apps/worker/src/deps/scm.ts`:
- Around line 29-80: Make openPullRequest resume-safe by checking for an
existing open head-to-base PR/MR before creating one, reusing its URL when
found. Add provider-specific lookup logic alongside openGithubPr and
openGitlabMr, and have both lookup paths use the same credentials, repository,
head, and base from PullRequestSpec; only POST when no matching open change
exists.

In `@ARCHITECTURE.md`:
- Line 30: Update the apps/api architecture entry to replace the stale
“approvals” terminology with “checkpoints,” including comments if that is part
of the exposed API surface, while preserving the rest of the listed
responsibilities.
- Line 7: Update the adjacent executor/system diagram in ARCHITECTURE.md so its
engine-path label matches the overview and visibly includes the OpenAI Codex CLI
alongside the Claude SDK; change only the diagram label or relevant diagram
node, preserving the existing architecture relationships.

In `@docs/adr/0011-codex-executor-and-platform-scm.md`:
- Around line 11-12: Update the Codex containment description around the
read-only reviewer claim to match the current reviewer behavior, which uses
workspace readWrite access. State the actual enforcement guarantee and remove or
qualify any assertion that reviewer-style steps cannot write artifacts, keeping
the existing unsupported per-tool-call enforcement caveat intact.

In `@docs/design/02-orchestration-template.md`:
- Around line 206-208: Enforce a non-empty comment for loop request_changes in
the shared approval validation/schema, then align the documented contract at
docs/design/02-orchestration-template.md:206-208,
docs/design/06-frontend.md:59-59, and docs/manual/en/02-concepts.md:21-21 so
each describes the enforced requirement and corresponding UI/API behavior.

In `@docs/design/05-api-and-auth.md`:
- Line 25: Update the “member” project-role capability description to qualify
request-changes as available only on loop checkpoints, while preserving the
other checkpoint actions and member permissions unchanged.

In `@docs/design/06-frontend.md`:
- Around line 73-75: Update the remaining frontend terminology in the design
document from “Approvals” to the checkpoint inbox wording, including sidebar and
dashboard navigation labels and route names. Keep the existing “Waiting on you”
terminology and checkpoint behavior unchanged.

In `@docs/manual/en/02-concepts.md`:
- Line 13: Update the “Agent slot & executor” documentation sentence to qualify
that slots are swappable only when the selected executor supports the step’s
required resources and the model is resolvable. Replace the unqualified “roles
are interchangeable by design” wording, and mention that Codex does not support
subagents, skills, or MCP.

In `@docs/manual/zh-CN/02-concepts.md`:
- Line 15: Update the “编排模板” definition to replace “人工审批节点” with “人工检查点,” and
explicitly mention the three supported checkpoint types: question-form,
review-gate, and approval. Keep the surrounding template definition unchanged.

In `@packages/db/src/seed/index.ts`:
- Around line 364-384: Remove the gpt-5.1-codex and gpt-5.1-codex-mini entries
from the seed model registry, or replace them with currently supported OpenAI
model IDs and verified pricing. Update the affected rows in the model seed
collection while preserving valid tier and context metadata for any
replacements.

In `@packages/executor-codex/src/executor.test.ts`:
- Around line 11-14: Update makeWorkspace to track each mkdtempSync directory,
then extend the existing afterEach hook to remove all tracked workspace paths
recursively and reset the tracking collection so every test cleans up its
temporary workspace.

In `@packages/executor-codex/src/executor.ts`:
- Around line 30-39: Update the read-only artifact synthesis in
collectStepArtifacts to reject multiple non-patch artifacts sharing the same
kind before assigning fallback content from finalMessage. Preserve the existing
single-artifact behavior, and return a clear error identifying the duplicated
artifact kind or keys instead of silently applying identical content.

In `@packages/orchestration/src/resolve.ts`:
- Around line 262-268: Update the slot resolution flow around resolveModelRoles
so each slot resolves only the roles used by its bound agent steps, rather than
the full compiled.spec.models set. Build the per-slot role set from the slot’s
bound agent steps, then pass that scoped role collection to resolveModelRoles
while preserving the existing providers constraint.

---

Outside diff comments:
In `@apps/web/src/features/useRunEvents.ts`:
- Around line 40-55: Update the refresh-trigger condition in onAnyEvent to
include the branch.created and branch.pushed event types alongside the existing
run, step, phase, checkpoint, loop, comment, PR, and artifact events, while
preserving the current deduplication and refresh behavior.

---

Nitpick comments:
In `@apps/api/src/routes/catalog.ts`:
- Around line 76-103: Hoist the active fabri query before the conditional block
so it runs once, retaining the full records needed by the existing bySlug map.
Replace the separate fabriOptions database query with a projection of that
shared activeFabri result containing only id, slug, nameI18n, and avatar, while
preserving the existing status-filtered options behavior.

In `@README.md`:
- Line 43: Update the README setup paragraph around AGRIPPA_SCM to document the
required real-run SCM provider configuration, including credentials and
repository connection setup needed for branch, push, and PR operations. Preserve
the existing fake-SCM explanation and link to the SCM deployment documentation
if the full configuration is documented elsewhere.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 6166beae-b1ed-4e96-8b26-dc70262806fb

📥 Commits

Reviewing files that changed from the base of the PR and between 7b434db and 8432022.

⛔ Files ignored due to path filters (1)
  • bun.lock is excluded by !**/*.lock
📒 Files selected for processing (83)
  • ARCHITECTURE.md
  • CHANGELOG.md
  • README.md
  • apps/api/src/routes/catalog.ts
  • apps/api/src/routes/execution.ts
  • apps/api/src/test/checkpoints.integration.test.ts
  • apps/api/src/test/execution.integration.test.ts
  • apps/web/src/components/shell/AppSidebar.tsx
  • apps/web/src/components/submit/AgentSlotPicker.tsx
  • apps/web/src/features/runs/ApprovalPanel.tsx
  • apps/web/src/features/runs/CheckpointPanel.tsx
  • apps/web/src/features/runs/FindingsTable.tsx
  • apps/web/src/features/runs/PhaseTimeline.tsx
  • apps/web/src/features/runs/QuestionsForm.tsx
  • apps/web/src/features/runs/RunMetaCard.tsx
  • apps/web/src/features/runs/RunTimeline.tsx
  • apps/web/src/features/usePendingApprovals.ts
  • apps/web/src/features/usePendingCheckpoints.ts
  • apps/web/src/features/useRunEvents.ts
  • apps/web/src/lib/types.ts
  • apps/web/src/pages/ApprovalsPage.tsx
  • apps/web/src/pages/DashboardPage.tsx
  • apps/web/src/pages/RunDetailPage.tsx
  • apps/web/src/pages/SubmitTaskPage.tsx
  • apps/worker/package.json
  • apps/worker/src/deps/demo-executor.ts
  • apps/worker/src/deps/scm.ts
  • apps/worker/src/deps/workspace.ts
  • apps/worker/src/index.ts
  • apps/worker/tsconfig.json
  • docs/adr/0010-agrippa-v2-slots-checkpoints-loops.md
  • docs/adr/0011-codex-executor-and-platform-scm.md
  • docs/design/02-orchestration-template.md
  • docs/design/03-executor-abstraction.md
  • docs/design/04-execution-runtime.md
  • docs/design/05-api-and-auth.md
  • docs/design/06-frontend.md
  • docs/manual/en/02-concepts.md
  • docs/manual/en/03-running-tasks.md
  • docs/manual/zh-CN/02-concepts.md
  • docs/manual/zh-CN/03-running-tasks.md
  • packages/core/src/domain.ts
  • packages/core/src/executors.ts
  • packages/core/src/index.ts
  • packages/core/src/interaction-schemas.ts
  • packages/core/src/schemas.ts
  • packages/db/drizzle/0005_requirement-delivery.sql
  • packages/db/drizzle/meta/0005_snapshot.json
  • packages/db/drizzle/meta/_journal.json
  • packages/db/src/schema/runs.ts
  • packages/db/src/seed/index.ts
  • packages/executor-claude/src/executor.ts
  • packages/executor-codex/README.md
  • packages/executor-codex/package.json
  • packages/executor-codex/src/cli.ts
  • packages/executor-codex/src/events.ts
  • packages/executor-codex/src/executor.test.ts
  • packages/executor-codex/src/executor.ts
  • packages/executor-codex/src/index.ts
  • packages/executor-codex/test/fixtures/fake-codex.ts
  • packages/executor-codex/tsconfig.json
  • packages/executor-core/src/artifacts.ts
  • packages/executor-core/src/fake-executor.ts
  • packages/executor-core/src/index.ts
  • packages/executor-core/src/isolation.ts
  • packages/executor-core/src/types.ts
  • packages/i18n/locales/en/catalog.json
  • packages/i18n/locales/en/runs.json
  • packages/i18n/locales/zh-CN/catalog.json
  • packages/i18n/locales/zh-CN/runs.json
  • packages/orchestration/src/compile.test.ts
  • packages/orchestration/src/compile.ts
  • packages/orchestration/src/engine/deps.ts
  • packages/orchestration/src/engine/engine.integration.test.ts
  • packages/orchestration/src/engine/engine.ts
  • packages/orchestration/src/engine/fakes.ts
  • packages/orchestration/src/engine/run-lifecycle.ts
  • packages/orchestration/src/resolve.ts
  • packages/orchestration/src/template-schema.ts
  • scripts/check-deps.ts
  • scripts/screenshot.ts
  • templates/swdev/requirement-delivery.yaml
  • tsconfig.json
💤 Files with no reviewable changes (2)
  • apps/web/src/features/runs/ApprovalPanel.tsx
  • apps/web/src/features/usePendingApprovals.ts

Comment thread apps/web/src/features/runs/CheckpointPanel.tsx
Comment thread apps/web/src/features/runs/CheckpointPanel.tsx
Comment thread apps/web/src/features/runs/FindingsTable.tsx
Comment thread apps/web/src/features/runs/QuestionsForm.tsx
Comment thread apps/web/src/features/runs/RunTimeline.tsx Outdated
Comment thread docs/manual/zh-CN/02-concepts.md Outdated
Comment thread packages/db/src/seed/index.ts
Comment thread packages/executor-codex/src/executor.test.ts
Comment thread packages/executor-codex/src/executor.ts
Comment on lines +262 to +268
const providers = entry?.providers ?? "*";
modelResolution[slotId] = await resolveModelRoles(
db,
projectId,
compiled.spec.models,
providers,
);

@coderabbitai coderabbitai Bot Jul 23, 2026 •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Which roles does each agent slot's steps actually use vs. all declared roles?
rg -nP -C3 'resolveModelRoles\(' packages/orchestration/src/resolve.ts
rg -nP -C2 '\brole\b' packages/orchestration/src/resolve.ts
# Inspect template role usage per slot
sed -n '50,209p' templates/swdev/requirement-delivery.yaml

Repository: ainaive/agrippa

Length of output: 8200


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== resolve.ts relevant ranges =="
sed -n '60,150p' packages/orchestration/src/resolve.ts
echo
sed -n '220,290p' packages/orchestration/src/resolve.ts

echo
echo "== templates files and reviewer binding usage =="
rg -n -C2 'slotId|resolveAgentBindings|agent_bindings|codex-cli|providers:\s*\[' templates swagger templates packages/orchestration -g '*.yaml' -g '*.yml' -g '*.ts' || true

echo
echo "== parse template roles and role references =="
python3 - <<'PY'
from pathlib import Path
import re

p = Path('templates/swdev/requirement-delivery.yaml')
text = p.read_text()
roles = []
for m in re.finditer(r'^( {6,}\S+):\s*$|^      (planning|coding|review):\s*{.*\n(?:        .*|$)', text):
    pass
for m in re.finditer(r'^ {6}(\w+):\s*{(.*)\n(?: {8}.*(?:$|\n)){1,2}', text):
    role = m.group(1)
    print(f"decl_role={role} first_line={m.group(0).replace(chr(10), ' '[:50])!r}")
refs = []
for i,line in enumerate(text.splitlines(),1):
    m = re.search(r'model:\s*\{\s*role:\s*(\w+)', line)
    if m:
        refs.append((i, m.group(1), line.strip()))
for r in refs:
    print(f"ref_line={r[0]} role={r[1]} line={r[2]!r}")
print('declared=', {m.group(1) for m in re.finditer(r'^ \s{6}(\w+):\s*{', text)})
print('references=', sorted(set(r[1] for r in refs)))

echo
echo "== resolveModelRoles implementation =="
rg -n -C8 "function resolveModelRoles|resolveModelRoles" packages/orchestration/src/resolve.ts
PY

Repository: ainaive/agrippa

Length of output: 13634


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== all roles declarations and role references =="
python3 - <<'PY'
import re
from pathlib import Path

p = Path('templates/swdev/requirement-delivery.yaml')
text = p.read_text()
decl = {m.group(1): m.group(0).splitlines()[0] for m in re.finditer(r'^ {6}(\w+):\s*{(.*?)\n(?: {8}.*\n|\})', text)}
refs = []
for i, line in enumerate(text.splitlines(), 1):
    m = re.search(r'model:\s*\{\s*role:\s*(\w+)', line)
    if m:
        refs.append((i, m.group(1), line.strip()))
print("declared_roles:")
for role in sorted(decl):
    print(f"  {role}: {decl[role]}")
print("template_role_refs:")
for line, role, txt in refs:
    print(f"  line {line} role={role}: {txt[:160]}")
print("declared_minus_refs:", sorted(set(decl) - set(r[1] for r in refs)))
print("refs_minus_decl": sorted(set(r[1] for r in refs) - set(decl)))
PY

echo
echo "== executor catalog codex-cli entry =="
rg -n -C4 'codex-cli' packages/orchestration -g '*.ts'

echo
echo "== model resolution tests related to roles/providers =="
rg -n -C4 'resolveModelRoles|model_resolution|planning|coding|review|provider' packages/orchestration/src packages/orchestration/test -g '*.ts' || true

echo
echo "== compile test for reviewer slot and executor =="
sed -n '160,230p' packages/orchestration/src/compile.test.ts

Repository: ainaive/agrippa

Length of output: 359


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
import re
from pathlib import Path

p = Path('templates/swdev/requirement-delivery.yaml')
text = p.read_text()
decl = {m.group(1): m.group(0).splitlines()[0] for m in re.finditer(r'^ {6}(\w+):\s*{(.*?)\n(?: {8}.*\n|\})', text, re.S)}
refs = []
for i, line in enumerate(text.splitlines(), 1):
    m = re.search(r'model:\s*\{\s*role:\s*(\w+)', line)
    if m:
        refs.append((i, m.group(1), line.strip()))
print("declared_roles:")
for role in sorted(decl):
    print(f"  {role}: {decl[role]}")
print("template_role_refs:")
for line, role, txt in refs:
    print(f"  line {line} role={role}: {txt[:160]}")
print("declared_minus_refs:", sorted(set(decl) - set(r[1] for r in refs)))
print("refs_minus_decl:", sorted(set(r[1] for r in refs) - set(decl)))
PY

echo
echo "== executor catalog codex-cli entry =="
rg -n -C6 'codex-cli' packages/orchestration -g '*.ts'

echo
echo "== model resolution tests related to roles/providers =="
rg -n -C4 'resolveModelRoles|model_resolution|planning|coding|review|provider|model_unresolvable' packages/orchestration/src packages/orchestration/test -g '*.ts' || true

echo
echo "== compile test for reviewer slot and executor =="
sed -n '160,230p' packages/orchestration/src/compile.test.ts

Repository: ainaive/agrippa

Length of output: 50371


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
import re
from pathlib import Path

p = Path('templates/swdev/requirement-delivery.yaml')
text = p.read_text()
decl = {m.group(1): m.group(0).splitlines()[0] for m in re.finditer(r'^ {6}(\w+):\s*{(.*?)\n(?: {8}.*\n|\})', text, re.S)}
refs = []
for i, line in enumerate(text.splitlines(), 1):
    m = re.search(r'model:\s*\{\s*role:\s*(\w+)', line)
    if m:
        refs.append((i, m.group(1), line.strip()))
print("declared_roles:")
for role in sorted(decl):
    print(f"  {role}: {decl[role]}")
print("template_role_refs:")
for line, role, txt in refs:
    print(f"  line {line} role={role}: {txt[:160]}")
print("declared_minus_refs:", sorted(set(decl) - set(r[1] for r in refs)))
print("refs_minus_decl:", sorted(set(r[1] for r in refs) - set(decl)))
PY

echo
echo "== executor catalog codex-cli entry =="
rg -n -C6 'codex-cli' packages/orchestration -g '*.ts'

echo
echo "== model resolution tests related to roles/providers =="
rg -n -C4 'resolveModelRoles|model_resolution|planning|coding|review|provider|model_unresolvable' packages/orchestration/src packages/orchestration/test -g '*.ts' || true

echo
echo "== compile test for reviewer.slot =="
sed -n '160,230p' packages/orchestration/src/compile.test.ts

Repository: ainaive/agrippa

Length of output: 50371


Scope each slot’s model resolution to its used roles.

resolveModelRoles resolves every declared spec.roles entry while constrained by a single slot’s executor providers. The flagship test fixture matches this: reviewer binds to a fixed executor, yet the per-slot resolution stores the entire template model set; a provider-scoped executor will incorrectly fail on unused roles that belong to another slot. Build a per-slot role set from bound agent steps and resolve only those roles.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/orchestration/src/resolve.ts` around lines 262 - 268, Update the
slot resolution flow around resolveModelRoles so each slot resolves only the
roles used by its bound agent steps, rather than the full compiled.spec.models
set. Build the per-slot role set from the slot’s bound agent steps, then pass
that scoped role collection to resolveModelRoles while preserving the existing
providers constraint.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Valid — acknowledged as a tracked follow-up (left unresolved on purpose): per-slot resolution should only cover the roles that slot's steps actually use, which needs the compiler to expose role→slot usage. Queued together with the other known follow-up (patch artifacts above the 64 KB inline cap failing the publish evidence check).

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@hutusi, acknowledged. I’ll leave this finding unresolved for the current PR. The follow-up should have the compiler expose role-to-slot usage so submit-time model resolution can be scoped to each slot’s bound agent steps.

Would you like me to open GitHub issues for this and the patch-evidence inline-size-cap follow-up?


✏️ Learnings added
Learnt from: hutusi
Repo: ainaive/agrippa PR: 5
File: packages/orchestration/src/resolve.ts:282-288
Timestamp: 2026-07-23T07:10:06.402Z
Learning: For the `agrippa/v2` requirement-delivery workflow, per-slot model resolution in `packages/orchestration/src/resolve.ts` should eventually resolve only model roles used by agent steps bound to that slot. This is intentionally deferred because the compiler must first expose role-to-slot usage. The related review finding remains unresolved in PR `#5`.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

hutusi added 4 commits July 23, 2026 09:44
…facts, unique branches

Addresses PR #5 review findings 1, 2, 3, and 8 — they interlock (the
schema tightening must ship with its enforcement, and branch uniqueness
must land before pr.open dup-recovery can be added safely).

Committed changes no longer vanish from patch artifacts: checkout pins
the clone-time HEAD as refs/agrippa/base (a ref, not a marker file —
gc-safe, cleanly testable) and diff() reports against it, so committed
+ staged + worktree changes all appear, with pathspec excludes keeping
the sanitization deletions (.claude/.mcp.json/.agrippa) out of every
patch. Old workspaces without the ref fall back to the previous
worktree diff. The engine additionally fails the producing step —
retryably, and before push/pr.open — when a required patch's auto-diff
comes back empty. This also repairs the latent empty-patch bug in v1
bug-localize-fix with no template change.

Malformed interaction artifacts no longer auto-pass their checkpoints:
artifacts that drive an input/review-gate are validated against the
shared schemas at store time, while the producing step's attempt is
still open — so a broken review report fails the step with
contract_violation and template retry/onFailure apply (the review step
gains retry: {max: 1}). The checkpoint-time read becomes a strict
backstop for resumed pre-fix rows: a review gate with an absent source
fails the run (a gate without evidence must never pass), invalid or
over-inline-limit sources fail with distinct messages, and only a
genuinely empty questions/findings list still auto-passes.

Question contracts are enforced end to end: select questions must carry
options (a required select without options was an unanswerable form —
input checkpoints have no reject, so the run deadlocked until timeout),
recommendations must match the question kind (boolean allowed now), and
the respond endpoint validates every answer against its snapshotted
kind and options. Review-report caps tighten so typical reports stay
inline-sized.

Work branches stop colliding across tasks: run numbers restart per
task, so the default branch name now appends the run id's random tail —
the LAST hex chars of the UUIDv7; the first are timestamp bits shared
by every run created in the same minute. The template drops its
explicit name and uses the default.

New real-git integration suite (temp repos, file:// remotes) covers the
committed/staged/untracked diff, sanitization exclusion, branch+push,
and the fallback path — the compliance suite's canned FakeWorkspace
diff is exactly how finding 1 shipped unnoticed. Compliance additions:
malformed report = retryable step failure that never becomes an
artifact row, absent report = failed run with no PR, optionless select
= failed producing step, and the default branch suffix.
…latest artifacts

Addresses PR #5 review findings 4 and 5.

Submission no longer accepts executors the deployment cannot run: the
static core catalog says what CAN exist, and a new
executor_registrations table (migration 0006) says what DOES — workers
upsert their registered executor ids at boot and heartbeat them on the
sweeper interval, and resolveAgentBindings rejects any FINAL resolved
binding (template-pinned executors included, not just overrides) whose
id has no recent registration, with executor_unavailable before model
resolution so the user gets the more actionable of the two errors. An
empty live set — fresh deployment, no worker booted yet — skips the
check rather than blocking every submission. The task-type detail
carries per-slot availability plus the live id list, and the submit
page's executor picker disables engines this deployment lacks. Known
limitation (documented): heterogeneous workers still race at pickup;
per-executor queues are future work.

Checkpoint panels stop presenting stale evidence: loop rounds re-produce
the same artifact key, the artifacts endpoint returned rows unordered
and without iteration, and the panel took the FIRST key match — after
'request changes' a responder could approve the previous round's plan or
patch. The endpoint now orders by creation (id tiebreak) and exposes
iteration, and the panel takes the last match — exact, because panels
render only pending checkpoints and a paused run produces nothing while
paused, so 'latest row per key' is precisely the responder's evidence.
Addresses PR #5 review findings 6 and 7, plus the documentation for the
whole review round.

pr.open survives retries: a lost provider response (or a crash between
the API call and storing the URL) used to make the retry's POST fail
with GitHub 422 / GitLab 409 even though the PR exists. Any 422/409 now
triggers a lookup of the open PR by head/base — honoring the GHES api
base — returning its URL when found and rethrowing the original error
body when not (no message string-matching, no wasted lookup-first
call). This is only safe because the previous commit made work branches
unique per run; with the old colliding names the recovery could have
returned another task's PR as this run's contract artifact. Unit-tested
against a local fake forge scripting create → 422 → recover and 422 →
nothing-to-recover; FakeScmService mirrors the semantics (same
head/base re-open returns the same URL).

Codex can no longer load capabilities the catalog says it lacks: every
codex exec invocation passes --ignore-user-config (a user-level
config.toml could register MCP servers, bypassing project resource
governance despite mcp:false) and --ignore-rules (repo-shipped
execpolicy rules are untrusted input). Auth still resolves from
CODEX_HOME per the CLI docs. The boot probe now checks exec --help for
the flags and refuses to register an older CLI — which would otherwise
fail every step on 'unexpected argument', or worse, run unisolated if
the flags were made conditional.

Docs: changelog entry for the review round; design 03/04 record the
executor-availability registry, the gate-without-evidence rule, and the
branch-naming scheme; ADR-0011 gains a post-review addendum.
Found by re-running the browser harness after the review fixes: the
empty-required-patch guard (correctly) fails a step whose diff is empty,
and the demo executor never touched the workspace — so the delivery
demo died at the implement step against a real checkout. A demo step
that owes a patch now appends a line to AGRIPPA_DEMO.md in the
workspace, keeping the guard meaningful and the demo diff honest.

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hutusi has reached the 50-credit limit for trial accounts. To continue receiving code reviews, upgrade your plan.

@hutusi

hutusi commented Jul 23, 2026

Copy link
Copy Markdown
Contributor Author

All eight review findings verified valid and addressed in 9687dd2, b4c0555, 3785c13 (+ 8afb151). Resolution per finding:

  1. Empty required patch (P1) — fixed. Checkout pins the clone-time HEAD as refs/agrippa/base; diff() reports against it (committed + staged + worktree), with pathspec excludes so the .claude/.mcp.json/.agrippa sanitization deletions don't pollute every patch, and a fallback to the old worktree diff for pre-fix workspaces. Additionally, a required patch whose auto-diff is empty now fails the producing step retryably — before push/pr.open, not at the end-of-flow contract check. Also repairs the same latent bug in v1 bug-localize-fix with no template change. New real-git integration tests (apps/worker/src/deps/workspace.test.ts, temp repos + file:// remotes) cover committed/staged/untracked diffs, sanitization exclusion, and the fallback — closing the fake-workspace verification gap that let this ship.

  2. Malformed review reports auto-pass (P1) — fixed, at two layers. Primary: interaction-source artifacts are validated against the shared schemas at store time, while the producing step's attempt is still open, so a malformed report fails the step with contract_violation and template retry applies (the review step gains retry: {max: 1}). Backstop (protects resumed pre-fix runs): the checkpoint-time read fails the run on an absent review report — a gate without evidence never passes — with a distinct error for over-inline-limit sources so a large legitimate report isn't misread as "no findings". An empty questions list keeps auto-passing (that's the designed "nothing to ask" signal). Compliance tests: malformed report = retryable step failure that never becomes an artifact row; absent report = failed run with no PR.

  3. Branch collisions (P1) — fixed. The default branch name is now agrippa/run-${run.number}-${run.shortId}. One correction to the obvious fix: run ids are UUIDv7, whose leading hex chars are timestamp bits shared by every run created in the same ~minute — shortId is therefore the id's random tail. The template drops its explicit with.name and uses the default.

  4. Unregistered executors accepted at submit (P1) — fixed. Workers advertise their registered executor ids in a new heartbeated executor_registrations table (upsert at boot + on the 60s sweeper; the API's live window is 15 min). resolveAgentBindings checks the final resolved binding — template-pinned executors included, not just overrides — and rejects with executor_unavailable before model resolution (the more actionable of the two errors). An empty live set (fresh deployment, no worker booted) skips the check. The task-type detail exposes per-slot availability and the live id list; the submit picker disables unavailable engines. Documented limitation: heterogeneous workers still race at pickup; per-executor queues are future work.

  5. Stale checkpoint artifacts (P1) — fixed, with a simpler mechanism than snapshotting: panels only render pending checkpoints, and a paused run produces nothing while paused, so "latest row per key" is exactly the responder's evidence (iteration alone can't even order implement vs a round-1 fix — both iteration 1). The artifacts endpoint now orders by creation (id tiebreak) and exposes iteration; the panel takes the last match per key. The inbox reuses the panel and is fixed for free.

  6. pr.open not idempotent (P2) — fixed. Any GitHub 422 / GitLab 409 triggers a lookup of the open PR by head/base (honoring the GHES api base); found → return its URL, not found → rethrow the original error body (no message string-matching, no lookup-first overhead). Deliberately sequenced after feat(infra): VM deployment without Docker (systemd + install/deploy scripts) #3: with the old colliding branch names, recovery could have returned another task's PR as this run's contract artifact. Unit-tested against a local fake forge (create → 422 → recover; 422 → nothing-to-recover → original error).

  7. Codex user config (P2) — fixed. Every codex exec invocation passes --ignore-user-config (auth still resolves from CODEX_HOME per the CLI docs) and --ignore-rules (repo-shipped execpolicy rules are untrusted too). The boot probe now checks exec --help for the flags and refuses to register an older CLI — which would otherwise fail every step on "unexpected argument", or run unisolated if the flags were conditional.

  8. Question contract gaps (P2) — fixed end to end. Schema: select questions must carry non-empty options (a required optionless select was an unanswerable form, and input checkpoints have no reject — deadlock until timeout); recommended is now string | boolean and must match the question kind. Enforcement rides the store-time validation from Security & correctness hardening (P0 + P1) #2, so a bad questions artifact fails the producing step retryably. The respond endpoint validates every answer against its snapshotted kind and options (booleans must be booleans, selects must be on-menu). The UI fills boolean recommendations correctly.

Verification: full gate green at each commit (bun run check, 169 tests incl. the new real-git suite, templates:validate, build), plus both end-to-end re-checks — the engine-level template smoke and the Playwright harness (zero console errors). The harness re-run caught one piece of intentional fallout: the demo executor never touched the real workspace, so the new empty-patch guard correctly failed it — it now makes an honest file change for patch-producing steps (8afb151).

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/worker/src/deps/workspace.test.ts`:
- Around line 224-227: Await the final rejects assertion in the “422 with
nothing to recover” test so the test waits for scm.openPullRequest(runId, spec)
to reject with /422/. Keep the existing try/finally cleanup and assertion
behavior unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: d1e1edd8-b47e-48a2-a44e-e9853832feac

📥 Commits

Reviewing files that changed from the base of the PR and between 8432022 and 8afb151.

📒 Files selected for processing (36)
  • CHANGELOG.md
  • apps/api/src/lib/executors.ts
  • apps/api/src/routes/catalog.ts
  • apps/api/src/routes/execution.ts
  • apps/api/src/test/checkpoints.integration.test.ts
  • apps/web/src/components/submit/AgentSlotPicker.tsx
  • apps/web/src/features/runs/CheckpointPanel.tsx
  • apps/web/src/features/runs/QuestionsForm.tsx
  • apps/web/src/lib/types.ts
  • apps/web/src/pages/SubmitTaskPage.tsx
  • apps/worker/src/deps/demo-executor.ts
  • apps/worker/src/deps/scm.ts
  • apps/worker/src/deps/workspace.test.ts
  • apps/worker/src/deps/workspace.ts
  • apps/worker/src/index.ts
  • docs/adr/0011-codex-executor-and-platform-scm.md
  • docs/design/03-executor-abstraction.md
  • docs/design/04-execution-runtime.md
  • packages/core/src/interaction-schemas.ts
  • packages/db/drizzle/0006_executor-registrations.sql
  • packages/db/drizzle/meta/0006_snapshot.json
  • packages/db/drizzle/meta/_journal.json
  • packages/db/src/schema/registry.ts
  • packages/executor-codex/src/cli.ts
  • packages/executor-codex/src/executor.test.ts
  • packages/executor-codex/src/executor.ts
  • packages/executor-codex/test/fixtures/fake-codex-cli.ts
  • packages/i18n/locales/en/catalog.json
  • packages/i18n/locales/en/runs.json
  • packages/i18n/locales/zh-CN/catalog.json
  • packages/i18n/locales/zh-CN/runs.json
  • packages/orchestration/src/engine/engine.integration.test.ts
  • packages/orchestration/src/engine/engine.ts
  • packages/orchestration/src/engine/fakes.ts
  • packages/orchestration/src/resolve.ts
  • templates/swdev/requirement-delivery.yaml
🚧 Files skipped from review as they are similar to previous changes (23)
  • packages/i18n/locales/zh-CN/catalog.json
  • packages/i18n/locales/en/catalog.json
  • apps/web/src/features/runs/QuestionsForm.tsx
  • apps/api/src/routes/catalog.ts
  • packages/orchestration/src/engine/fakes.ts
  • docs/adr/0011-codex-executor-and-platform-scm.md
  • packages/i18n/locales/zh-CN/runs.json
  • packages/i18n/locales/en/runs.json
  • apps/web/src/features/runs/CheckpointPanel.tsx
  • apps/worker/src/index.ts
  • packages/executor-codex/src/executor.test.ts
  • apps/api/src/test/checkpoints.integration.test.ts
  • docs/design/03-executor-abstraction.md
  • apps/web/src/pages/SubmitTaskPage.tsx
  • docs/design/04-execution-runtime.md
  • packages/executor-codex/src/executor.ts
  • templates/swdev/requirement-delivery.yaml
  • apps/worker/src/deps/scm.ts
  • packages/orchestration/src/resolve.ts
  • packages/orchestration/src/engine/engine.integration.test.ts
  • apps/web/src/lib/types.ts
  • apps/api/src/routes/execution.ts
  • packages/orchestration/src/engine/engine.ts

Comment thread apps/worker/src/deps/workspace.test.ts
hutusi added a commit that referenced this pull request Jul 23, 2026
…trict artifacts, 48-bit branches

Addresses round-2 review findings 1, 2, 3, and 5 (PR #5). Findings 1
and 3 are mirror-image evidence-integrity holes the round-1 fixes
introduced: the PR could silently DELETE sanitized files the patch
never showed, and the patch could SHOW uncommitted work the push never
shipped. The fix establishes one contract: the patch artifact and the
pushed PR contain exactly the same changes, and the sanitized paths
appear in neither, in either direction.

Sanitized paths become invisible to git itself instead of being hidden
from the diff: tracked .claude/.mcp.json/.agrippa entries get
--skip-worktree before removal (so an agent's add -A/commit -a cannot
ship the deletion — and reset --hard no longer casually restores
repo-supplied .claude, an improvement over the plain rm), and the
paths are appended to .git/info/exclude to cover the UNTRACKED side —
which the platform itself populates (materialized skills under
.claude/skills/, artifact scratch under .agrippa/artifacts/) and which
skip-worktree alone cannot reach. The diff pathspec excludes are then
dropped entirely.

Push gains a platform finalizing commit: add -A (now safe by the
above), staged-change detection via status --porcelain (diff --cached
--quiet exits 1 into the throwing git helper), and a commit under an
Agrippa identity with host gitconfig isolated (gpgsign/hooksPath must
not leak). A branch whose HEAD still equals the clone base fails with
'nothing to publish' instead of an opaque provider 422 later. And
because steps can touch the worktree AFTER the last patch was stored —
the reviewer runs between fix and publish — the engine's git.push
handler re-diffs and refreshes stale patch artifacts (with a
refreshed-artifact event) before pushing.

Interaction schemas turn strict with required arrays: {} and typo'd
keys ({"findingz": …}) previously parsed as clean empty reports via
defaults + zod's unknown-key stripping and auto-passed review gates.
Top-level objects are now strictObject with required lists; nested
finding/question objects stay tolerant of extra keys. Note for
in-flight runs: leniently-stored artifacts re-read on resume now fail
the gate-time backstop.

The work-branch suffix widens from 8 to 12 hex chars (32 → 48 random
bits of the UUIDv7 tail): ~1.2% birthday-collision odds at 10k runs is
too high for a scheme pr.open's duplicate-recovery depends on.

Real-git suite extended to pin the whole contract: agent commit-all
excludes the sanitized deletion, the pushed branch retains .claude at
origin, platform/agent files under sanitized paths appear in neither
diff nor finalize commit, uncommitted work ships via the finalize
commit, and empty runs refuse to publish. New core unit tests pin the
strict-schema rejections; a new compliance test pins the reviewer-drift
refresh.
hutusi added a commit that referenced this pull request Jul 23, 2026
Addresses round-2 review findings 4 and 6 (PR #5).

In a heterogeneous fleet (one worker has codex, one doesn't) a run
could land on the wrong worker and burn its pg-boss retries on a
condition that isn't transient for THIS worker but is perfectly
servable by another. The engine now throws a typed
ExecutorUnavailableError — with a stable `code` so the worker can
match across bundle boundaries — at the binding lookup, which happens
before any status transition. The worker handler catches it, re-reads
the run's status, and for queued/waiting_approval DECLINES the job:
warn log, one `run.deferred` timeline event (surfaced in the SPA
activity feed, both locales), job completes without consuming
retries. The existing reconciliation sweepers re-enqueue both states,
so the run bounces at most every ~60s until a capable worker claims
it — no max-bounce, because that would convert a capacity problem
into a run failure, and the submit-time availability gate already
confines endless bouncing to reconfiguration windows. A `running`
run still rethrows: nothing re-enqueues an unclaimed running run
today (the execution lease is ADR-0009 future work). A compliance
test pins the contract the worker depends on: typed error, stable
code, thrown while the run is still queued.

Docs are brought back in line with the runtime: ADR-0010's claim that
input and review-gate both auto-pass on absent-or-empty is rewritten
in place (the ADR was authored on this unmerged branch — rewriting is
honest, amending would fake history) to the asymmetric semantics the
engine actually enforces; design/03 replaces the known-limitation
note with the deferral mechanism and the host-affinity boundary it
exposes; design/04 cross-references it from the sweeper section.
CHANGELOG gains the round-2 block.

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hutusi has reached the 50-credit limit for trial accounts. To continue receiving code reviews, upgrade your plan.

@hutusi

hutusi commented Jul 23, 2026

Copy link
Copy Markdown
Contributor Author

Second-round findings are all valid — thank you. Findings 1 and 3 were mirror-image holes in the round-1 evidence fixes (the PR could delete what the patch never showed; the patch could show what the push never shipped), so the fix establishes one contract: the patch artifact and the pushed PR contain exactly the same changes, and the sanitized paths appear in neither, in either direction. Resolved in 2 commits (2bb5d73, 4102396).

1. Sanitized repository files silently deleted by the PR — fixed (2bb5d73). The sanitized paths are now invisible to git itself instead of merely hidden from the diff: tracked .claude/.mcp.json/.agrippa entries get --skip-worktree before removal (an agent's git add -A && commit can no longer ship the deletion, and reset --hard no longer restores repo-supplied hooks), and the paths join .git/info/exclude to cover the untracked side — which the platform itself populates (materialized skills under .claude/skills/, artifact scratch under .agrippa/) and which skip-worktree can't reach. The diff's pathspec excludes are deleted outright. Real-git tests pin it from both ends: the agent's commit-all excludes the deletion, and the pushed branch still has .claude/settings.json at origin.

2. Malformed artifacts still auto-pass ({}, findingz) — fixed (2bb5d73). Root cause was schema shape, not the validation call: .default([]) plus zod's unknown-key stripping meant {} and {"findingz": …} parsed successfully as empty artifacts. Both top-level schemas are now strictObject with required lists, so those inputs fail the producing step (contract_violation, template retry applies); nested finding/question objects stay tolerant of extra keys. A present {"findings": []} remains the legitimate auto-pass signal. Unit tests pin {}, typo'd keys, and stray top-level fields as rejections.

3. Patch evidence contains never-pushed work — fixed (2bb5d73). git.push now finalize-commits anything left uncommitted (platform identity, host gitconfig isolated via GIT_CONFIG_GLOBAL=/dev/null + -c commit.gpgsign=false), safe precisely because of fix 1 — add -A can no longer sweep in sanitized paths. A branch whose HEAD still equals refs/agrippa/base fails with "nothing to publish" instead of an opaque provider error later. And since the reviewer runs with write access after the last patch was stored, the engine re-diffs at push time and refreshes any stale patch artifact, emitting a refreshed artifact event so the timeline shows the evidence moved. Tests: finalize commit ships previously-uncommitted work to origin; empty runs refuse to publish; a compliance test pins the drift refresh.

4. Executor availability unsafe with heterogeneous workers — fixed (4102396). The engine throws a typed ExecutorUnavailableError (stable code) at the binding lookup, before any status transition; the worker catches it, re-reads the run status, and for queued/waiting_approval declines the job — warn log, one run.deferred timeline event (rendered in the activity feed, both locales), no pg-boss retries consumed — letting the existing sweepers re-enqueue until a capable worker claims it (≤ ~60 s per bounce). running runs still rethrow; the execution lease stays ADR-0009 future work, now documented. No max-bounce on purpose: that would convert a capacity problem into a run failure, and the submit-time gate already confines bouncing to reconfiguration windows.

5. Branch names only probabilistically unique — fixed (2bb5d73). run.shortId widens to the last 12 hex chars of the run id — 48 random bits, fully inside UUIDv7 rand_b — dropping the 10k-run collision odds from ~1.2% to ~2×10⁻⁷. Worth it because pr.open's duplicate-recovery (round 1, finding 6) assumes branch uniqueness when it adopts an existing PR by head/base.

6. Checkpoint docs contradict the runtime — fixed (4102396 + 2bb5d73). ADR-0010's "auto-pass when absent or empty" is rewritten in place to the asymmetric semantics the engine enforces (input: absent or valid-empty passes; review-gate: only present + valid + zero findings passes; malformed fails the producing step) — rewriting rather than amending since the ADR was authored on this same unmerged branch. design/04's checkpoint section, the template-schema and interaction-schemas comments, and design/03's heterogeneity note all now match, and the CHANGELOG carries the round.

Full gate re-verified per commit (bun run check, 177 tests / 0 fail / 0 skips with Postgres up, templates:validate, build), plus the two end-to-end harnesses: the engine-level delivery-workflow smoke (succeeds; branch now agrippa/run-1-<12hex>) and the Playwright screenshot smoke (all pages, no console errors).

hutusi added a commit that referenced this pull request Jul 23, 2026
…-only reviewer; evidence guards

Addresses the code side of all round-3 review findings (PR #5); the
ADR amendments and design-doc corrections follow separately.

Finding 1 (P0): every platform git call — evidence diffs, the
finalizing commit, the credentialed push — runs inside a directory the
agent could write, including .git/**, and inherited the worker's FULL
process.env. An agent-installed pre-push hook, a filter.*.clean, a
diff textconv driver, or core.hooksPath in repo-local .git/config
would have executed as the platform with DATABASE_URL,
AGRIPPA_SECRET_KEY, and provider keys in scope; url.*.insteadOf could
even rewrite the credentialed push URL and exfiltrate the repo token.
The round-2 GIT_CONFIG_GLOBAL/SYSTEM nulling covered one call and
never touched repo-local config.

Three independent layers close it (each alone is whack-a-mole):
the git() wrapper now spawns with the executor allow-list env scrub
(buildScrubbedEnv — PATH/HOME/locale/TLS only) plus global/system
config disabled on EVERY call; core.hooksPath and core.fsmonitor are
neutralized on every invocation (and clone-sample hooks are removed at
sanitize); and repo-local .git/config is snapshotted at provision into
a platform sidecar (<runId>.platform — a SIBLING of the workspace, out
of the write containment's reach) and restored before every diff and
push, discarding agent-added filters/textconv/insteadOf/credential
helpers with a warn log. The agent identity is pre-seeded into config
at provision so agents never have a legitimate reason to touch it.
The clone-base SHA moves into the same sidecar: diff() and the
nothing-to-publish check trust it over the agent-writable
refs/agrippa/base, closing the move-the-base-ref evidence blank-out
found while verifying. A hostile-workspace real-git test drives the
whole publish path with weaponized hooks, hooksPath, a clean filter,
and an insteadOf redirect, and asserts none of it executed and the
push landed at the real origin.

Finding 2 (P1): the reviewer inherited the workspace's readWrite
access, and the round-2 push-time refresh published post-approval
drift — refreshed evidence is not approved evidence. v2 agent steps
gain an optional per-step access override; the delivery template's
review step declares readOnly (both executors already enforce it:
claude via the isolation seam, codex via --sandbox read-only with
fenced-json artifact synthesis). The push-time guard now FAILS the run
(contract_violation) when the workspace differs from the stored patch
instead of refreshing it; with a read-only reviewer the legit flow can
never drift, so any hit is a real violation. The compliance drift test
asserts failure-and-no-push, plus that reviewer steps run readOnly
while implementer steps stay readWrite.

Finding 3 (P1): a resumed run whose checkout succeeded on another host
ran against the bare mkdir ensureDir() leaves — every step silently
operating on nothing. WorkspaceManager gains isIntact(); when a
succeeded workspace.checkout step has no repository behind it, the run
fails fast with workspace_lost. Re-provisioning is deliberately not
attempted: a fresh clone would lack the work branch and all agent
commits, fabricating an empty-but-working state. contract_violation
and workspace_lost get errors-namespace entries in both locales.

Finding 5 (P2): the strict schemas accepted duplicate question and
finding ids, but every consumer keys by id (answer records, selection
sets, waiver maps, React keys) — duplicates were inseparable
downstream. Both artifact schemas now reject duplicate ids.

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hutusi has reached the 50-credit limit for trial accounts. To continue receiving code reviews, upgrade your plan.

@hutusi

hutusi commented Jul 23, 2026

Copy link
Copy Markdown
Contributor Author

All five round-3 findings are valid — the P0 especially so, and verification turned up two aggravating details beyond the report: url.*.insteadOf in agent-writable .git/config could rewrite the credentialed push URL and exfiltrate the repo token, and an agent could move refs/agrippa/base to blank the patch evidence entirely. Resolved in 2 commits (f53847e code, cc7b623 docs); the organizing principle this round: the platform trusts nothing an agent can write.

1. [P0] Platform commit executes agent-controlled Git hooks with worker secrets — fixed (f53847e), with three independent layers because each alone is whack-a-mole:

  • Env scrub on every git call: the git() wrapper now spawns with the executor allow-list scrub (buildScrubbedEnv — PATH/HOME/locale/TLS only) instead of the full process.env, plus GIT_CONFIG_GLOBAL/SYSTEM=/dev/null on all calls, not just the finalize commit. Even if something executed, there are no secrets in its environment.
  • Exec vectors neutralized on every call: -c core.hooksPath=/dev/null -c core.fsmonitor=false is prepended to every invocation, and clone-sample hooks are removed at sanitize. No repo-local hook (pre-commit/pre-push/post-checkout) can ever fire under platform git.
  • Config restore from a platform sidecar: .git/config is snapshotted at provision (after the platform pre-seeds agent identity, so agents never have a reason to touch config) into <runId>.platform — a sibling of the workspace, outside the agent write containment — and restored before every diff and push, with tampering logged. That erases agent-added filter.*.clean, diff.*.textconv, url.insteadOf, and credential.helper definitions before any platform git runs. The clone-base SHA lives in the same sidecar, so diff/push no longer trust the agent-writable refs/agrippa/base.
  • A hostile-workspace real-git test drives the full publish path with weaponized hooks (writing $DATABASE_URL to a file), a custom hooksPath, a clean filter, and an insteadOf redirect — and asserts nothing executed, nothing leaked, config was restored, and the push landed at the real origin. A second test asserts deleting the base ref no longer changes the evidence.
  • Residual risk stated in design/03 and ADR-0011: a shell command escaping a degraded OS sandbox could reach the sidecar — that remains the documented container-layer boundary.

2. [P1] Reviewer changes published after approval of different evidence — fixed (f53847e), both ways you suggested. v2 agent steps gain a per-step access: override and the delivery template's review step declares readOnly (no executor work needed — claude enforces it via the isolation seam, codex via --sandbox read-only with fenced-json artifact synthesis, both already tested). And the push-time refresh is gone: git.push now fails the run (contract_violation, localized) when the workspace differs from the stored patch — refreshed evidence is not approved evidence, exactly as you put it. The compliance test you called out is reworked to assert failure-and-no-push, plus that reviewer requests carry access: readOnly while implementer requests stay readWrite.

3. [P1] Cross-host resume operates without the repository — fixed (f53847e) as an explicit failure, not re-provisioning: WorkspaceManager.isIntact() probes the checkout, and a run whose succeeded checkout has no repository behind it fails fast with workspace_lost (localized) instead of silently running every remaining step against an empty mkdir. Re-provisioning is deliberately not attempted — a fresh clone would lack the work branch and all agent commits, fabricating an empty-but-"working" state. The false design/03 claim is corrected (cc7b623). On the sub-point about running runs still burning retries on an incapable worker: that is intentional and now documented — each pg-boss retry is a fresh pickup that may land on a capable worker, so the retries are the routing mechanism; a permanently incapable fleet fails the run with the real error rather than bouncing forever. Per-executor queues / the execution lease remain the future fix.

4. [P2] ADR-0010 rewritten despite the append-only rule — accepted (cc7b623). You're right that pre-merge doesn't exempt it: the branch history had already published the original text for review. ADR-0010 now carries an appended Amendment recording the original symmetric wording, when and why it was corrected, and the rationale for the asymmetry (a silent clarifier has nothing to ask; a silent reviewer has not reviewed). ADR-0011 gains an addendum for this round's two consequential decisions (platform-git distrust; read-only reviewer + drift-fails-publish), superseding its "per-step access modes are future hardening" consequence.

5. [P2] Duplicate interaction IDs — fixed (f53847e). Both artifact schemas reject duplicate question/finding ids via superRefine (every consumer keys by id — answer records, selection sets, waiver maps, React keys — so duplicates were inseparable downstream). Unit tests pin both rejections.

Full gate re-verified per commit: bun run check, 182 tests / 0 fail / 0 skips with Postgres up, templates:validate, build — plus the engine-level delivery-workflow smoke (succeeds end-to-end under the hardened git path) and the Playwright screenshot smoke (all pages, no console errors).

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
CHANGELOG.md (1)

36-40: 🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win

Clarify the review-gate auto-pass rule.

Line 37 incorrectly groups input and review-gate as auto-passing on empty sources. Only input checkpoints may auto-pass when absent; review gates require a present, schema-valid report with zero findings. Missing review evidence must fail the gate, as documented in docs/design/04-execution-runtime.md.

Proposed wording
-  - *`agrippa/v2` template format* ... checkpoint steps in three kinds (`approval` incl. request-changes, `input` question forms, `review-gate` findings decisions — the latter two auto-pass on empty sources), ...
+  - *`agrippa/v2` template format* ... checkpoint steps in three kinds (`approval` incl. request-changes, `input` question forms, `review-gate` findings decisions — input auto-passes on absent/empty questions, while review-gate auto-passes only on a present valid report with zero findings), ...
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@CHANGELOG.md` around lines 36 - 40, Update the `agrippa/v2` template format
changelog entry to state that only empty `input` checkpoints auto-pass;
`review-gate` checkpoints require present, schema-valid review evidence with
zero findings, and missing evidence fails the gate. Keep the existing
descriptions of checkpoint kinds, loops, and other behavior unchanged.
apps/worker/src/deps/scm.ts (1)

97-188: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Add fetch timeouts and/or abort signals to the SCM API calls.

The GitHub and GitLab create/lookup calls in apps/worker/src/deps/scm.ts don’t pass signal; if the provider API stalls, these can hang. Thread in the run/step abort signal if available, or set per-call timeouts such as AbortSignal.timeout(30_000).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/worker/src/deps/scm.ts` around lines 97 - 188, Add abort signals or
30-second timeouts to every GitHub and GitLab fetch in openGithubPr and
openGitlabMr, including PR/MR creation and 422/409 lookup requests. Reuse the
run/step signal if available; otherwise apply AbortSignal.timeout(30_000) so
stalled provider calls terminate.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/web/src/features/runs/RunActivityFeed.tsx`:
- Around line 101-108: Update the "run.deferred" case in RunActivityFeed to read
the event payload reason and interpolate it into the translated activity label,
following the existing payload-detail pattern used by cases such as
"workspace.ready"; preserve the current icon, warning tone, and event
sequencing.

In `@apps/worker/src/deps/workspace.test.ts`:
- Around line 193-205: Await the rejects assertion in the test “refuses to
publish a run with no commits and no changes” so the test waits for scm.push to
reject and verifies the “nothing to publish” error before completing.

In `@apps/worker/src/index.ts`:
- Around line 137-146: Update the error-code check in the run error-handling
branch around the executor-unavailable condition to safely access code when err
is null or undefined, using optional chaining while preserving the existing
ExecutorUnavailableError check and queued/waiting_approval handling.
- Around line 147-153: Add the missing activity.runDeferred translation key to
every packages/i18n/locales/*/runs.json catalog, using the existing activity
labels and locale conventions. Ensure RunActivityFeed.tsx can resolve this key
for run.deferred without changing the event payload or routing logic.

---

Outside diff comments:
In `@apps/worker/src/deps/scm.ts`:
- Around line 97-188: Add abort signals or 30-second timeouts to every GitHub
and GitLab fetch in openGithubPr and openGitlabMr, including PR/MR creation and
422/409 lookup requests. Reuse the run/step signal if available; otherwise apply
AbortSignal.timeout(30_000) so stalled provider calls terminate.

In `@CHANGELOG.md`:
- Around line 36-40: Update the `agrippa/v2` template format changelog entry to
state that only empty `input` checkpoints auto-pass; `review-gate` checkpoints
require present, schema-valid review evidence with zero findings, and missing
evidence fails the gate. Keep the existing descriptions of checkpoint kinds,
loops, and other behavior unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 8c050556-a9b3-4946-96bd-0f5f775b5a91

📥 Commits

Reviewing files that changed from the base of the PR and between 8afb151 and cc7b623.

📒 Files selected for processing (24)
  • CHANGELOG.md
  • apps/api/src/test/checkpoints.integration.test.ts
  • apps/web/src/features/runs/RunActivityFeed.tsx
  • apps/worker/src/deps/scm.ts
  • apps/worker/src/deps/workspace.test.ts
  • apps/worker/src/deps/workspace.ts
  • apps/worker/src/index.ts
  • docs/adr/0010-agrippa-v2-slots-checkpoints-loops.md
  • docs/adr/0011-codex-executor-and-platform-scm.md
  • docs/design/02-orchestration-template.md
  • docs/design/03-executor-abstraction.md
  • docs/design/04-execution-runtime.md
  • packages/core/src/interaction-schemas.test.ts
  • packages/core/src/interaction-schemas.ts
  • packages/i18n/locales/en/errors.json
  • packages/i18n/locales/en/runs.json
  • packages/i18n/locales/zh-CN/errors.json
  • packages/i18n/locales/zh-CN/runs.json
  • packages/orchestration/src/engine/deps.ts
  • packages/orchestration/src/engine/engine.integration.test.ts
  • packages/orchestration/src/engine/engine.ts
  • packages/orchestration/src/engine/fakes.ts
  • packages/orchestration/src/template-schema.ts
  • templates/swdev/requirement-delivery.yaml
🚧 Files skipped from review as they are similar to previous changes (11)
  • docs/adr/0010-agrippa-v2-slots-checkpoints-loops.md
  • docs/design/02-orchestration-template.md
  • packages/i18n/locales/zh-CN/runs.json
  • packages/orchestration/src/engine/deps.ts
  • packages/i18n/locales/en/runs.json
  • docs/design/03-executor-abstraction.md
  • packages/orchestration/src/template-schema.ts
  • apps/api/src/test/checkpoints.integration.test.ts
  • templates/swdev/requirement-delivery.yaml
  • packages/orchestration/src/engine/engine.ts
  • packages/orchestration/src/engine/engine.integration.test.ts

Comment thread apps/web/src/features/runs/RunActivityFeed.tsx
Comment thread apps/worker/src/deps/workspace.test.ts
Comment thread apps/worker/src/index.ts
Comment thread apps/worker/src/index.ts
@greptile-apps

greptile-apps Bot commented Jul 23, 2026

Copy link
Copy Markdown

Too many files changed for review. (101 files found, 100 file limit)

Bypass the limit by tagging @greptile-apps to review.

@hutusi

hutusi commented Jul 23, 2026

Copy link
Copy Markdown
Contributor Author

Round 4 (fa91cfd) — this one was authored by Codex and reviewed by Claude, swapping the usual roles. Review verdict: approved. It supersedes round 3's config-restore model with a strictly stronger design (now ADR-0012):

Two independent git metadata domains. Checkout moves the pristine .git into the platform sidecar and hands the agent a byte-copy; evidence and publication run exclusively through the trusted gitdir with the workspace mounted as a work-tree (read-tree <base> → add -A with protected-path excludes → diff --cached --binary --no-ext-diff --no-textconv → write-tree). Publication is one idempotent Agrippa-authored commit-tree child of the clone base, re-verified inside the SCM operation against the exact stored patch — any mismatch returns a typed evidence_mismatch that fails the run with contract_violation. Agent-local commits remain working checkpoints but are intentionally not published.

It closes real residual holes in the round-3 design, each now pinned by a regression test:

  • the config restore could follow an agent-planted symlink or block on a FIFO (real-git tests for both);
  • the agent-writable .git/index and info/exclude could still defeat the protected-path rules of the finalizing commit — the platform index is now private, so protected paths retain their base-tree state no matter what the agent repo says;
  • platform git was receiving model-provider auth vars via the executor env allow-list — it now uses a system-only buildSystemEnv (a test asserts Anthropic/OpenAI/Codex keys never enter git children);
  • diff errors collapsed to empty evidence — they are now step tool_errors (retryable), and the empty-vs-nonempty truthiness shortcut in the evidence comparison is gone;
  • agent-left .claude settings/hooks/.mcp.json persisted across attempts — project configuration is now invocation-scoped, reset symlink-safe before every attempt, with realContained guarding skill materialization targets.

Review verification: bun run check, 190 tests / 0 fail / 0 skips with Postgres up, templates:validate, build, the engine-level delivery-workflow smoke (succeeds end-to-end under the dual-gitdir design), and the Playwright screenshot smoke (all pages, no console errors). The only fix from review was mechanical: the commit body exceeded commitlint's 100-char line limit and was rewrapped via amend before push.

Known follow-ups recorded during review (not in this commit):

  1. Patch artifacts over the 64 KB inline cap always fail at publish — large patches store on disk with no inline value, so the evidence comparison sees an empty string vs. the real diff and fails the run. Pre-existing since round 3, but the snapshot design makes this the single publish gate, so it is now the loudest failure mode for legitimately large changes. Fix direction: evidence identity that survives the inline cap (content hash on the artifact row, or an artifact-store read-back path). This is the next scoped fix.
  2. createBranch still touches the agent-visible .git (checkout -B) — safe only because git.branch precedes all agent steps in every template; a compiler ordering check would enforce that invariant.
  3. The publish path assumes one push per run; a multi-publish template with differing evidence would fail its second push by design.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/worker/src/deps/resources.test.ts`:
- Around line 22-25: Update the fixture setup around freshDir to write .mcp.json
first, then create the .claude directory and its hooks subdirectory before
writing .claude/settings.json and .claude/hooks/start.sh, ensuring all Bun.write
parent paths exist.

In `@apps/worker/src/deps/workspace.test.ts`:
- Around line 504-508: Await the rejects assertion in the test case for missing
trusted platform metadata by updating the workspace.diff(runId) expectation;
ensure the test waits for the promise to reject with the existing “trusted
platform git base is missing” matcher and preserves the fail-closed intact
check.

In `@docs/adr/0012-platform-owned-git-snapshots.md`:
- Line 22: Update the platform Git publish flow described in
docs/adr/0012-platform-owned-git-snapshots.md at lines 22 and 35 and
docs/design/03-executor-abstraction.md at line 110 so push() makes branch
publication concurrency-safe: replace the separate missing-existing commit and
ref-write operations with a create-if-absent/CAS update, or serialize publishing
per branch, while preserving idempotent reuse of matching refs and the
single-commit guarantee.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 787a75f3-40b5-4497-9b31-719dd1410073

📥 Commits

Reviewing files that changed from the base of the PR and between cc7b623 and fa91cfd.

📒 Files selected for processing (23)
  • ARCHITECTURE.md
  • CHANGELOG.md
  • apps/worker/src/deps/resources.test.ts
  • apps/worker/src/deps/resources.ts
  • apps/worker/src/deps/scm.ts
  • apps/worker/src/deps/workspace.test.ts
  • apps/worker/src/deps/workspace.ts
  • docs/adr/0012-platform-owned-git-snapshots.md
  • docs/design/03-executor-abstraction.md
  • docs/design/04-execution-runtime.md
  • docs/design/09-testing-and-ci.md
  • docs/manual/en/03-running-tasks.md
  • docs/manual/en/06-operations.md
  • docs/manual/zh-CN/03-running-tasks.md
  • docs/manual/zh-CN/06-operations.md
  • packages/executor-claude/src/executor.ts
  • packages/executor-core/src/isolation.ts
  • packages/orchestration/src/engine/deps.ts
  • packages/orchestration/src/engine/engine.integration.test.ts
  • packages/orchestration/src/engine/engine.ts
  • packages/orchestration/src/engine/fakes.ts
  • templates/_shared/skills/git-workflow/SKILL.md
  • templates/swdev/requirement-delivery.yaml
🚧 Files skipped from review as they are similar to previous changes (8)
  • packages/orchestration/src/engine/deps.ts
  • docs/manual/zh-CN/03-running-tasks.md
  • packages/executor-claude/src/executor.ts
  • packages/orchestration/src/engine/fakes.ts
  • docs/manual/en/03-running-tasks.md
  • templates/swdev/requirement-delivery.yaml
  • packages/orchestration/src/engine/engine.integration.test.ts
  • docs/design/04-execution-runtime.md

Comment thread apps/worker/src/deps/resources.test.ts
Comment thread apps/worker/src/deps/workspace.test.ts
Comment thread docs/adr/0012-platform-owned-git-snapshots.md Outdated
hutusi added 6 commits July 23, 2026 14:04
…trict schemas, 48-bit branches

Addresses round-2 review findings 1, 2, 3, and 5 (PR #5). Findings 1
and 3 are mirror-image evidence-integrity holes the round-1 fixes
introduced: the PR could silently DELETE sanitized files the patch
never showed, and the patch could SHOW uncommitted work the push never
shipped. The fix establishes one contract: the patch artifact and the
pushed PR contain exactly the same changes, and the sanitized paths
appear in neither, in either direction.

Sanitized paths become invisible to git itself instead of being hidden
from the diff: tracked .claude/.mcp.json/.agrippa entries get
--skip-worktree before removal (so an agent's add -A/commit -a cannot
ship the deletion — and reset --hard no longer casually restores
repo-supplied .claude, an improvement over the plain rm), and the
paths are appended to .git/info/exclude to cover the UNTRACKED side —
which the platform itself populates (materialized skills under
.claude/skills/, artifact scratch under .agrippa/artifacts/) and which
skip-worktree alone cannot reach. The diff pathspec excludes are then
dropped entirely.

Push gains a platform finalizing commit: add -A (now safe by the
above), staged-change detection via status --porcelain (diff --cached
--quiet exits 1 into the throwing git helper), and a commit under an
Agrippa identity with host gitconfig isolated (gpgsign/hooksPath must
not leak). A branch whose HEAD still equals the clone base fails with
'nothing to publish' instead of an opaque provider 422 later. And
because steps can touch the worktree AFTER the last patch was stored —
the reviewer runs between fix and publish — the engine's git.push
handler re-diffs and refreshes stale patch artifacts (with a
refreshed-artifact event) before pushing.

Interaction schemas turn strict with required arrays: {} and typo'd
keys ({"findingz": …}) previously parsed as clean empty reports via
defaults + zod's unknown-key stripping and auto-passed review gates.
Top-level objects are now strictObject with required lists; nested
finding/question objects stay tolerant of extra keys. Note for
in-flight runs: leniently-stored artifacts re-read on resume now fail
the gate-time backstop.

The work-branch suffix widens from 8 to 12 hex chars (32 → 48 random
bits of the UUIDv7 tail): ~1.2% birthday-collision odds at 10k runs is
too high for a scheme pr.open's duplicate-recovery depends on.

Real-git suite extended to pin the whole contract: agent commit-all
excludes the sanitized deletion, the pushed branch retains .claude at
origin, platform/agent files under sanitized paths appear in neither
diff nor finalize commit, uncommitted work ships via the finalize
commit, and empty runs refuse to publish. New core unit tests pin the
strict-schema rejections; a new compliance test pins the reviewer-drift
refresh.
Addresses round-2 review findings 4 and 6 (PR #5).

In a heterogeneous fleet (one worker has codex, one doesn't) a run
could land on the wrong worker and burn its pg-boss retries on a
condition that isn't transient for THIS worker but is perfectly
servable by another. The engine now throws a typed
ExecutorUnavailableError — with a stable `code` so the worker can
match across bundle boundaries — at the binding lookup, which happens
before any status transition. The worker handler catches it, re-reads
the run's status, and for queued/waiting_approval DECLINES the job:
warn log, one `run.deferred` timeline event (surfaced in the SPA
activity feed, both locales), job completes without consuming
retries. The existing reconciliation sweepers re-enqueue both states,
so the run bounces at most every ~60s until a capable worker claims
it — no max-bounce, because that would convert a capacity problem
into a run failure, and the submit-time availability gate already
confines endless bouncing to reconfiguration windows. A `running`
run still rethrows: nothing re-enqueues an unclaimed running run
today (the execution lease is ADR-0009 future work). A compliance
test pins the contract the worker depends on: typed error, stable
code, thrown while the run is still queued.

Docs are brought back in line with the runtime: ADR-0010's claim that
input and review-gate both auto-pass on absent-or-empty is rewritten
in place (the ADR was authored on this unmerged branch — rewriting is
honest, amending would fake history) to the asymmetric semantics the
engine actually enforces; design/03 replaces the known-limitation
note with the deferral mechanism and the host-affinity boundary it
exposes; design/04 cross-references it from the sweeper section.
CHANGELOG gains the round-2 block.
…-only reviewer; evidence guards

Addresses the code side of all round-3 review findings (PR #5); the
ADR amendments and design-doc corrections follow separately.

Finding 1 (P0): every platform git call — evidence diffs, the
finalizing commit, the credentialed push — runs inside a directory the
agent could write, including .git/**, and inherited the worker's FULL
process.env. An agent-installed pre-push hook, a filter.*.clean, a
diff textconv driver, or core.hooksPath in repo-local .git/config
would have executed as the platform with DATABASE_URL,
AGRIPPA_SECRET_KEY, and provider keys in scope; url.*.insteadOf could
even rewrite the credentialed push URL and exfiltrate the repo token.
The round-2 GIT_CONFIG_GLOBAL/SYSTEM nulling covered one call and
never touched repo-local config.

Three independent layers close it (each alone is whack-a-mole):
the git() wrapper now spawns with the executor allow-list env scrub
(buildScrubbedEnv — PATH/HOME/locale/TLS only) plus global/system
config disabled on EVERY call; core.hooksPath and core.fsmonitor are
neutralized on every invocation (and clone-sample hooks are removed at
sanitize); and repo-local .git/config is snapshotted at provision into
a platform sidecar (<runId>.platform — a SIBLING of the workspace, out
of the write containment's reach) and restored before every diff and
push, discarding agent-added filters/textconv/insteadOf/credential
helpers with a warn log. The agent identity is pre-seeded into config
at provision so agents never have a legitimate reason to touch it.
The clone-base SHA moves into the same sidecar: diff() and the
nothing-to-publish check trust it over the agent-writable
refs/agrippa/base, closing the move-the-base-ref evidence blank-out
found while verifying. A hostile-workspace real-git test drives the
whole publish path with weaponized hooks, hooksPath, a clean filter,
and an insteadOf redirect, and asserts none of it executed and the
push landed at the real origin.

Finding 2 (P1): the reviewer inherited the workspace's readWrite
access, and the round-2 push-time refresh published post-approval
drift — refreshed evidence is not approved evidence. v2 agent steps
gain an optional per-step access override; the delivery template's
review step declares readOnly (both executors already enforce it:
claude via the isolation seam, codex via --sandbox read-only with
fenced-json artifact synthesis). The push-time guard now FAILS the run
(contract_violation) when the workspace differs from the stored patch
instead of refreshing it; with a read-only reviewer the legit flow can
never drift, so any hit is a real violation. The compliance drift test
asserts failure-and-no-push, plus that reviewer steps run readOnly
while implementer steps stay readWrite.

Finding 3 (P1): a resumed run whose checkout succeeded on another host
ran against the bare mkdir ensureDir() leaves — every step silently
operating on nothing. WorkspaceManager gains isIntact(); when a
succeeded workspace.checkout step has no repository behind it, the run
fails fast with workspace_lost. Re-provisioning is deliberately not
attempted: a fresh clone would lack the work branch and all agent
commits, fabricating an empty-but-working state. contract_violation
and workspace_lost get errors-namespace entries in both locales.

Finding 5 (P2): the strict schemas accepted duplicate question and
finding ids, but every consumer keys by id (answer records, selection
sets, waiver maps, React keys) — duplicates were inseparable
downstream. Both artifact schemas now reject duplicate ids.
Addresses the doc side of round-3 findings 3, 4, and 6-adjacent text.

ADR-0010's semantics correction is put on the record as an appended
amendment: the original decision text claimed symmetric absent-or-
empty auto-pass, the second review round rewrote it in place, and the
third round rightly objected — the append-only rule applies even to an
ADR that has never been merged, because the branch history already
published it for review. ADR-0011 gains an addendum recording this
round's two consequential decisions (platform git distrusts the
workspace; per-step access shipped with the read-only reviewer and the
drift-fails-the-publish rule), superseding its 'per-step access modes
are future hardening' consequence.

design/03's claim that a cross-host resume 're-provisions its
workspace from the repo' was simply false — the engine only mkdir'd an
empty directory and skipped the succeeded checkout. The text now
states the implemented behavior (isIntact probe, workspace_lost fail-
fast, why re-provisioning is deliberately not attempted) plus the
rationale for letting running-state pickups consume retries: each
retry is a fresh pickup that may land on a capable worker, so retries
are the routing mechanism. design/04's resumability section documents
the probe; the CHANGELOG gains the round-3 block and the round-2
refresh clause is marked superseded.
Keep evidence and publication on a platform-owned gitdir and index so
agent-controlled config, refs, indexes, excludes, symlinks, and special
files cannot shape the pushed tree.

Reset executor project configuration per attempt, fail closed on diff
or evidence mismatches, publish one idempotent verified snapshot
commit, and document the history and upgrade tradeoffs. Add real-Git
and engine regressions for the reviewed attack paths.
…lback, deterministic publish

Valid findings from the four CodeRabbit review batches on PR #5;
false/stale ones are answered on the PR instead.

Publish determinism (from the ADR-0012 concurrency thread): commit-tree
now pins author/committer dates to the clone-base commit, so with
identity, tree, parent, and message already fixed, the snapshot commit
SHA is fully deterministic — a retry or racer that lost the local ref
recreates the byte-identical commit and the expected-old update-ref
(a CAS) chooses between equals rather than racing duplicates. The
idempotency test now deletes the platform branch ref between pushes
and asserts the recreated commit equals the first.

Checkpoint evidence fallback: when the artifacts query fails, the
panel previously disabled every action, so a review-gate could only
expire (the findings live in the checkpoint snapshot — only the
PREVIEW failed). Blind-waiver actions stay blocked (accept-all,
approve, and a partial fix, whose unchecked findings are implicitly
waived), but safe directions now work: fix-all, request-changes
(comment required), reject — plus a retry button that re-fires the
artifacts query (label in both locales).

Contract enforcement: request_changes requires a non-empty comment at
the schema level — the revision step interpolates the comment, so an
empty one would send the agent back with no instructions; the UI
already enforced this, the API now rejects it too (tests pin 400
before the kind check's 409). 'Accept all recommendations' could
submit answer sets missing required answers (only recommendable
questions were filled); it now fills-then-verifies and disables when
a required question has no recommendation.

Smaller correctness fixes: three un-awaited expect(...).rejects
assertions in the real-git suite could pass without verifying
(including the fail-closed diff check); the run timeline auto-scrolled
only on mount instead of following the stream; run.deferred timeline
entries now show the concrete reason the event already carried; the
worker's executor-unavailable match no longer explodes on a
null/undefined throw; codex executor tests clean up their temp dirs.

Docs aligned where review caught drift: executor diagram lists Codex,
request-changes qualified as loop-only in the RBAC text, Approvals nav
name explained as the generalized checkpoint inbox, agent-slot
interchangeability qualified by engine availability and granted
models (manual, both locales), ADR-0012/design-03 record the
CAS + determinism rationale.
@hutusi
hutusi force-pushed the feat/requirement-delivery-workflow branch from fa91cfd to cbf360e Compare July 23, 2026 06:14
@hutusi

hutusi commented Jul 23, 2026

Copy link
Copy Markdown
Contributor Author

CI + review sweep. Two changes landed (with one history rewrite, hence the force-push):

CI fix. The PR-range commitlint step was failing on 2bb5d73's 101-char subject (my round-2 local lint only covered the new commits, not the whole range). That commit's subject is reworded to 99 chars ("strict artifacts" → "strict schemas", body untouched) via an in-place rebase — content of all rewritten commits is byte-identical; only that one message changed. The full range 7b434db..HEAD now lints clean locally.

cbf360e — CodeRabbit review pass. Every one of the 24 unresolved threads was verified against current code. Resolution by thread:

Fixed:

  • workspace.test.ts (3 threads) — un-awaited expect(...).rejects assertions (nothing-to-publish, 422-no-recovery, fail-closed diff) now awaited.
  • QuestionsForm — "Accept all recommendations" could submit answer sets missing required answers (only recommendable questions were filled). It now fills-then-verifies and disables when a required question has no recommendation.
  • CheckpointPanel/FindingsTable — evidence-load failure no longer dead-ends the checkpoint: a retry button re-fires the artifacts query, and the safe directions work (fix-all, request-changes with comment, reject) while blind waivers stay blocked — including the subtle one: a partial fix implicitly waives the unchecked findings, so it obeys the stricter flag too.
  • RunTimeline — auto-scroll follows the stream ([itemCount] dep) instead of firing once on mount.
  • RunActivityFeed — run.deferred entries show the concrete reason carried by the event.
  • apps/worker/index.ts — executor-unavailable match guarded against null/undefined throws.
  • packages/core/schemas.ts — request_changes now requires a non-empty comment at the schema level (the revision step interpolates it; UI already enforced this). API tests pin 400-before-409.
  • ADR-0012 concurrency thread — adopted the determinism half: publish commit-tree pins author/committer dates to the clone base, so the snapshot commit SHA is fully deterministic and the expected-old update-ref (which is already a CAS, alongside the engine's per-run worker claim) chooses between byte-identical commits. Test: deleting the local platform branch ref between pushes reproduces the same commitSha. ADR-0012/design-03 record the rationale.
  • Docs: executor diagram lists Codex; request-changes qualified as loop-only in the RBAC text; "Approvals" nav name explained as the generalized checkpoint inbox; agent-slot interchangeability qualified by engine availability + granted models (manual, en & zh); codex executor tests clean their temp dirs.

Dismissed (with reasons):

  • resources.test.ts "Bun.write needs parent dirs" — Bun.write creates intermediate directories; the test passes in CI and locally.
  • "missing activity.runDeferred i18n key" — the key exists in both locales since round 2; the verification script grepped for the event name, not the key.
  • "pr.open not resume-safe" — GitHub-422/GitLab-409 duplicate recovery by head/base has been in since round 1, with a fake-forge test covering create → retry-recover → non-recoverable.
  • codex read-only synthesis duplicating content across same-kind artifacts — deliberate; read-only steps produce a single artifact in every shipped template.
  • seed model ids/pricing — flagged "verify per deployment" in the CHANGELOG; registry rows are admin-editable.

Deferred (tracked follow-ups):

  • resolve.ts per-slot model-role scoping — real: resolution should only cover the roles a slot's steps actually use; needs the compiler to expose role→slot usage. Queued with the other known follow-up (patch artifacts above the 64 KB inline cap failing the publish evidence check).

Verification: full gate green (bun run check, 190 tests / 0 fail / 0 skips, templates:validate, build), screenshot smoke clean after the UI changes, and bunx commitlint --from 7b434db --to HEAD → 0 problems.

@hutusi

hutusi commented Jul 23, 2026

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 23, 2026 •

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
CHANGELOG.md (1)

42-43: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Correct the review-gate auto-pass description.

This says both input and review-gate checkpoints auto-pass on empty sources. Review-gates only auto-pass on a present, schema-valid report with zero findings; missing review evidence must fail the gate.

Proposed wording
-  - ... the latter two auto-pass on empty sources ...
+  - ... input checkpoints auto-pass when questions are absent or validly empty;
+    review-gates auto-pass only on present, schema-valid zero-finding reports;
+    missing review evidence fails the gate ...
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@CHANGELOG.md` around lines 42 - 43, Update the `agrippa/v2` template format
changelog entry so only input checkpoints auto-pass on empty sources; specify
that review-gate checkpoints auto-pass only when a present, schema-valid report
contains zero findings, while missing review evidence fails the gate.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@CHANGELOG.md`:
- Around line 42-43: Update the `agrippa/v2` template format changelog entry so
only input checkpoints auto-pass on empty sources; specify that review-gate
checkpoints auto-pass only when a present, schema-valid report contains zero
findings, while missing review evidence fails the gate.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 4aabdb0d-eeb5-4264-936d-1ae354837e67

📥 Commits

Reviewing files that changed from the base of the PR and between fa91cfd and cbf360e.

📒 Files selected for processing (48)
  • ARCHITECTURE.md
  • CHANGELOG.md
  • apps/api/src/test/checkpoints.integration.test.ts
  • apps/web/src/features/runs/CheckpointPanel.tsx
  • apps/web/src/features/runs/FindingsTable.tsx
  • apps/web/src/features/runs/QuestionsForm.tsx
  • apps/web/src/features/runs/RunActivityFeed.tsx
  • apps/web/src/features/runs/RunTimeline.tsx
  • apps/web/src/pages/ApprovalsPage.tsx
  • apps/web/src/pages/RunDetailPage.tsx
  • apps/worker/src/deps/resources.test.ts
  • apps/worker/src/deps/resources.ts
  • apps/worker/src/deps/scm.ts
  • apps/worker/src/deps/workspace.test.ts
  • apps/worker/src/deps/workspace.ts
  • apps/worker/src/index.ts
  • docs/adr/0010-agrippa-v2-slots-checkpoints-loops.md
  • docs/adr/0011-codex-executor-and-platform-scm.md
  • docs/adr/0012-platform-owned-git-snapshots.md
  • docs/design/02-orchestration-template.md
  • docs/design/03-executor-abstraction.md
  • docs/design/04-execution-runtime.md
  • docs/design/05-api-and-auth.md
  • docs/design/06-frontend.md
  • docs/design/09-testing-and-ci.md
  • docs/manual/en/02-concepts.md
  • docs/manual/en/03-running-tasks.md
  • docs/manual/en/06-operations.md
  • docs/manual/zh-CN/02-concepts.md
  • docs/manual/zh-CN/03-running-tasks.md
  • docs/manual/zh-CN/06-operations.md
  • packages/core/src/interaction-schemas.test.ts
  • packages/core/src/interaction-schemas.ts
  • packages/core/src/schemas.ts
  • packages/executor-claude/src/executor.ts
  • packages/executor-codex/src/executor.test.ts
  • packages/executor-core/src/isolation.ts
  • packages/i18n/locales/en/errors.json
  • packages/i18n/locales/en/runs.json
  • packages/i18n/locales/zh-CN/errors.json
  • packages/i18n/locales/zh-CN/runs.json
  • packages/orchestration/src/engine/deps.ts
  • packages/orchestration/src/engine/engine.integration.test.ts
  • packages/orchestration/src/engine/engine.ts
  • packages/orchestration/src/engine/fakes.ts
  • packages/orchestration/src/template-schema.ts
  • templates/_shared/skills/git-workflow/SKILL.md
  • templates/swdev/requirement-delivery.yaml
🚧 Files skipped from review as they are similar to previous changes (41)
  • packages/i18n/locales/zh-CN/errors.json
  • packages/i18n/locales/en/errors.json
  • apps/web/src/features/runs/RunActivityFeed.tsx
  • apps/worker/src/deps/resources.test.ts
  • apps/web/src/pages/ApprovalsPage.tsx
  • docs/adr/0012-platform-owned-git-snapshots.md
  • templates/_shared/skills/git-workflow/SKILL.md
  • docs/manual/en/06-operations.md
  • docs/design/09-testing-and-ci.md
  • packages/core/src/interaction-schemas.test.ts
  • docs/design/02-orchestration-template.md
  • apps/web/src/features/runs/CheckpointPanel.tsx
  • apps/worker/src/deps/resources.ts
  • apps/web/src/features/runs/QuestionsForm.tsx
  • docs/manual/en/02-concepts.md
  • packages/i18n/locales/zh-CN/runs.json
  • packages/executor-core/src/isolation.ts
  • apps/web/src/features/runs/RunTimeline.tsx
  • templates/swdev/requirement-delivery.yaml
  • packages/core/src/schemas.ts
  • apps/web/src/pages/RunDetailPage.tsx
  • packages/orchestration/src/engine/fakes.ts
  • packages/orchestration/src/engine/deps.ts
  • docs/design/06-frontend.md
  • docs/design/05-api-and-auth.md
  • docs/adr/0010-agrippa-v2-slots-checkpoints-loops.md
  • packages/executor-codex/src/executor.test.ts
  • packages/executor-claude/src/executor.ts
  • apps/worker/src/deps/scm.ts
  • apps/worker/src/index.ts
  • apps/api/src/test/checkpoints.integration.test.ts
  • docs/adr/0011-codex-executor-and-platform-scm.md
  • ARCHITECTURE.md
  • docs/manual/zh-CN/03-running-tasks.md
  • packages/core/src/interaction-schemas.ts
  • packages/orchestration/src/engine/engine.integration.test.ts
  • apps/worker/src/deps/workspace.test.ts
  • docs/design/03-executor-abstraction.md
  • packages/i18n/locales/en/runs.json
  • apps/worker/src/deps/workspace.ts
  • packages/orchestration/src/engine/engine.ts

The round-1 Added entry still described the original symmetric rule
(input and review-gate both auto-passing on empty sources), which the
second review round corrected everywhere else — an absent review
report fails the gate. Unreleased ships as the release notes, so the
entry must state the final semantics. Caught by CodeRabbit's re-review
of the previous push.

Also generalize the manual's template definition from approval-only
checkpoints to all three kinds (both locales) — the last approval-era
wording an open review thread pointed at.
@hutusi
hutusi force-pushed the feat/requirement-delivery-workflow branch from 1abfb07 to 44abca0 Compare July 23, 2026 07:08
@hutusi
hutusi merged commit 028c2d4 into main Jul 23, 2026
5 checks passed
@hutusi
hutusi deleted the feat/requirement-delivery-workflow branch July 23, 2026 07:11
hutusi added a commit that referenced this pull request Jul 30, 2026
…ailing publish

The git.push evidence check compared the fresh workspace snapshot
against artifactValues, which holds "" for any patch artifact past the
64 KB inline threshold — so every run whose reviewed diff exceeded
64 KB died at publish with a phantom "workspace changed after the
reviewed evidence". This was the remaining follow-up from PR #5's
review rounds, and the sibling of the interaction-artifact fix earlier
on this branch: same threshold, different consumer.

A patch cannot get the interaction artifacts' raised inline allowance —
patches are capped at 25 MB, far past anything Postgres should inline —
so the check instead reads the stored bytes back via a new
ArtifactStore.read(storageRef). The stored patch IS the approved
evidence: drifted workspaces still fail exactly as before, and evidence
that cannot be read back (lost volume, corrupted row) fails the push
with a distinct contract_violation rather than publishing unverified.
DiskArtifactStore.read refuses refs outside the storage root, so a
corrupted row cannot become an arbitrary-file-read primitive; the
in-memory fake persists spilled content across engine legs the way the
real artifacts volume does.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant