Skip to content

feat(governance): require approved plans for Builder - #205

Merged
adrian-lorenzo merged 7 commits into
mainfrom
codex/require-builder-plan-policy
Aug 27, 2026
Merged

feat(governance): require approved plans for Builder#205
adrian-lorenzo merged 7 commits into
mainfrom
codex/require-builder-plan-policy

Conversation

@alvaroloes

@alvaroloes alvaroloes commented Aug 26, 2026

Copy link
Copy Markdown
Member

What changes

Adds an opt-in builderPlanPolicy project control with optional and required modes.

When required is enabled, Builder accepts only one exact, unexpired Architect plan that:

  • belongs to the same organization, project, repository and issue;
  • was opened by a succeeded Architect run with a sealed Facility receipt;
  • was approved by a distinct human principal;
  • has not been rejected, expired, made stale or consumed already;
  • binds the Architect workspace base SHA, receipt base SHA and a versioned digest of the issue scope.

The issue digest covers title, body, state, author, URL, normalized labels and material comments. Facility progress/publication comments and the exact /builder approval command are excluded because they are consequences of the plan; substantive text accompanying /builder remains material.

Freshness is checked against GitHub when the human approval is executed, again when the worker receives the queued run, and once more after the worker atomically claims it. The runner checkout is pinned to the approved base SHA, so a later branch-head change aborts before the model runs. A stale or unverifiable plan fails closed before credentials or sandbox creation and records the expected and observed hashes in the audit trail.

This revision composes the base-SHA provenance work from #166. Its original commit author is preserved in 8d6e40f.

It also makes Architect plan publication a durable, recoverable control-plane lane:

  • Architect success and the canonical plan_acceptance proposal/open event commit atomically;
  • a scheduled worker reconciles pending publication with durable exponential backoff and bounded cross-organization rotation;
  • a stable GitHub marker recovers ambiguous responses without intentionally creating a second comment;
  • legacy progress comments and deleted-comment 404 cases are handled without recreating a stale Gate;
  • rejected, expired, approved or executed proposals remove the live approval CTA and close or suppress the publication ledger;
  • publication failures cannot reopen a platform issue after a concurrent terminal outcome;
  • expiry and executor state/event transitions share the same proposal-row serialization boundary.

Why

This is the fail-closed Human Gate 1 for #204. It prevents Builder from using a merely similar, machine-approved or outdated plan while keeping the approval, plan, repository revision, issue revision and final worker decision attributable.

No legacy proposal is promoted or backfilled. After this control plane is merged and deployed, a project enabling required must run Architect again and approve the newly generated proposal.

Verification

  • Hosted CI is green on the current head
  • Behaviour verified beyond the test suite
  • Documentation updated

Exact governance implementation head fe208e235faa4d96ac0d70d591f5c291ae356257:

  • pnpm test:critical — DB 18/18, CLI 98/98, API 543/543, gateway 57/57
  • Builder plan integration — 30/30, including base drift, issue drift, approval-time outage, post-claim drift and post-claim outage
  • GitHub platform lane — 62/62 on a clean database
  • sandbox orchestrator — 37/37
  • runner workspace and signed delivery — 80/80 in serial with required local socket access
  • core — 34/34
  • issue revision unit tests — 2/2
  • full pnpm typecheck — 16/16 tasks; API typecheck repeated after the final delta
  • pnpm lint — 416 files passed
  • pnpm migrations:check — two compatible migrations checked (0042, 0043)
  • git diff --check — passed

Current PR head 2e121bff320d8cf4e2aa2dfe7ab68f69649904ec adds only the final operational UI/documentation wording correction; Biome and git diff --check pass locally. Hosted run 32997376591 passed verify in 5m46s, self-host-build in 8m44s and sandbox-e2e in 11m42s. Conventional commit-subject and PR-title checks also passed.

The full local pnpm verify wrapper is not claimed because its external dependency-audit phase was not run locally. The hosted workflow owns that final check.

Activation boundary

  • Keep the project policy optional until this PR is reviewed, merged and confirmed live on the internal Facility instance.
  • After deployment, create a fresh Architect proposal; proposals created before the provenance envelope intentionally return builder_plan_freshness_unavailable.
  • Human Gate 1 remains explicit. Facility never approves its own proposal and never merges the Builder PR.

GitHub cannot atomically lock an issue while Facility launches a sandbox. Facility therefore revalidates immediately after worker claim, executes only the immutable approved plan, and pins the repository checkout; a subsequent issue edit requires a new Architect run for any later approval.

Remote GitHub comment creation also remains technically at-least-once after an ambiguous response because the API provides no idempotency key. The stable marker, delayed backoff and reconciler bound and repair that residual window.

Refs #204
Incorporates #166

@alvaroloes
alvaroloes marked this pull request as ready for review August 26, 2026 18:09
@alvaroloes

alvaroloes commented Aug 26, 2026

Copy link
Copy Markdown
Member Author

Ready for maintainer review. The branch incorporates the unique base-SHA provenance commit from #166 as 8d6e40f, preserving Thanh Le Van as its original author, and composes it with the live issue/base freshness checks required for fail-closed Gate 1. Exact-head run 32997376591 is green: verify 5m46s, self-host-build 8m44s, and sandbox-e2e 11m42s.\n\n@jungle-lethanh, thank you for the provenance work — please flag any integration concern.

@alvaroloes

Copy link
Copy Markdown
Member Author

@adrian-lorenzo, the Gate 1 foundation is ready for maintainer review. Exact head 2e121bf is green on verify, self-host-build, and sandbox-e2e; the PR is intentionally not being merged or promoted without your review.

@adrian-lorenzo
adrian-lorenzo merged commit 1770945 into main Aug 27, 2026
10 checks passed
Bobbyap72 added a commit to Bobbyap72/facility that referenced this pull request Aug 28, 2026
Adds a client-side filter box on the Stories page that narrows
visible stories by title match, and lets a bare story number jump
straight to that story.

Ambiguity is now handled explicitly, both locally and via the API:
- Locally: if two repos on the board share the same number, that's
  reported as ambiguous rather than silently opening the first match.
- Off-board: the story-detail endpoint's 409 response now returns
  the candidate repos (repoId/repoOwner/repoName/storyType), so the
  UI can render a real picker instead of a dead-end message.

API failures (5xx, network errors) are now distinguished from a
genuine 404 — the UI never claims a story doesn't exist when the
truth is "couldn't check right now."

Tests: apps/web/test/story-jump.test.ts covers both local and API
ambiguity, not-found, and error paths via the extracted pure
resolveLocalJump/interpretJumpResponse functions.

Verified: pnpm --filter @facility/web typecheck / test, and
pnpm --filter @facility/api typecheck (43 pre-existing errors on
main, unrelated to this change, traced to theam#205 — confirmed by
running the same typecheck against a clean upstream/main checkout).
Bobbyap72 added a commit to Bobbyap72/facility that referenced this pull request Aug 28, 2026
Adds a client-side filter box on the Stories page that narrows
visible stories by title match, and lets a bare story number jump
straight to that story.

Ambiguity is handled explicitly, both locally and via the API:
- Locally: if two repos on the board share the same number, that's
  reported as ambiguous rather than silently opening the first match.
- Off-board: the story-detail endpoint's 409 response now returns
  the candidate repos (repoId/repoOwner/repoName/storyType), so the
  UI renders a real Link-based picker instead of a dead-end message.

A request-generation counter (jumpSeq) guards against out-of-order
async responses: submitting a new number invalidates any in-flight
lookup, so a stale response can never navigate the user to the
wrong story or overwrite newer state. Covered by a component-level
regression test (stories-board.test.tsx) that deliberately resolves
an older request after a newer one and asserts only the newer one
navigates.

The async jump status (checking/not-found/error/ambiguous) is
announced through an aria-live region.

API failures (5xx, network errors) are distinguished from a
genuine 404 — the UI never claims a story doesn't exist when the
truth is "couldn't check right now."

Tests:
- apps/web/test/story-jump.test.ts: pure local/API ambiguity,
  not-found, and error resolution logic.
- apps/web/test/stories-board.test.tsx: component-level regression
  for out-of-order async responses.
- services/api/test/github-platform-lane.test.ts: extended to
  assert the 409 response's error.details.matches carries the
  correct candidate repos.

Verified: pnpm --filter @facility/web typecheck / test, targeted
services/api test run for the modified test (both pass). The full
services/api suite currently has pre-existing failures on main
unrelated to this change, traced to theam#205 (builderPlanPolicy /
workspaceBaseSha schema drift) — confirmed by running the same
typecheck against a clean upstream/main checkout before any of
this branch's changes were applied.
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.

3 participants