Skip to content

feat(factory): wire the software factory — gate config, Action, issue template - #654

Merged
schmug merged 3 commits into
mainfrom
feat/software-factory
Aug 6, 2026
Merged

schmug merged 3 commits into
mainfrom
feat/software-factory

Conversation

@schmug

@schmug schmug commented Aug 5, 2026

Copy link
Copy Markdown
Owner

Adopts the autonomous issue→fix→merge loop from schmug/shipofclaudius#63.

⚠️ WIRED, NOT ARMED

secrets.FACTORY_GH_TOKEN does not exist, so the advance and land jobs no-op with a clear notice rather than half-running. Nothing can auto-merge today, and three independent things still gate that — see .factory/README.md:

  1. fix-verified is human-minted. requireFixtureEvidence: false because this repo has no reproduction harness, so the reproduce phase has no mechanical definition of done.
  2. The write token must be a distinct identity from @schmug. GitHub forbids self-approval, so a code-owner token can never satisfy require_code_owner_review on main-protection. That's claude-routines bot-identity split: run overnight routines as a non-admin GitHub identity #299.
  3. There is no rollback lever. Production deploys leave GitHub entirely via the Cloudflare Git integration, so a bad auto-merged change can't be reverted from here.

What's here

.factory/gate.json — the merge gate's config, read from main on every run and never from the PR. The denylist mirrors .github/CODEOWNERS and adds the security/money paths that aren't currently code-owned: src/auth/**, src/account/**, src/billing/**, src/webhooks/**, src/rate-limit-do.ts, mta-sts-worker/**, scripts/routine-gate/**. .factory/**, .github/workflows/** and CODEOWNERS are added automatically by the gate and cannot be removed by this file — a PR must never be able to widen the rules it's judged by.

.github/workflows/factory.ymlkillswitchadvanceland.

The land job runs no model at all: it's gh plus a zero-dependency Node program, so text injected into a public issue body has nothing to act on — an injected instruction cannot move a <= comparison. It checks out the base ref, never the PR head, which is what makes the pull_request_target trigger safe; there's no npm ci and no build step in it. workflow_dispatch inputs are allowlist-validated before they reach the driver's prompt, and all GitHub context flows through env:, never interpolated into run: bodies. Action SHAs match the existing pins so the action-pin integrity check passes.

.github/ISSUE_TEMPLATE/ — the repo had none at all. The bug form makes the ```scope block required, which is the gate's single most common escalation cause.

Verified end to end

Ran the real gate binary against this exact .factory/gate.json:

Case Result
A clean, in-scope PR exit 0 — merge
Real PR #653 (dependabot) exit 2 — unallowlisted author, no fix-verified, no Closes #N, mta-sts-worker/** risk path, 382 lines > 250, no scope block
Self-editing .factory/** / .github/workflows/** / CODEOWNERS exit 2 — no_risk_paths
Touching src/analyzers/** · src/shared/scoring.ts · src/billing/** exit 2 — no_risk_paths
Scope drift · oversize · pending required check · BLOCKED merge state exit 2 — the matching condition

That exercise also caught a real bug in the gate package (gh api takes no -R, which would have failed ci_green on every PR forever) — fixed upstream in shipofclaudius#63 before this PR was opened.

Settled empirically while wiring this

.github/CODEOWNERS is enforcing, not advisory. The main-protection ruleset is active with require_code_owner_review: true, required_approving_review_count: 0, one required check (check), and no bypass actors. CLAUDE.md is correct; MAINTAINERS.md, docs/OSPS-DEVIATIONS.md and .claude/workflows/pr-triage.js all say advisory and are wrong. Filed separately rather than mixed into this PR.

Not in this PR

The reproduction harness, the coverage floor, the rollback lever, the bot identity, and the src/index.ts bottleneck are filed as follow-up issues.

🤖 Generated with Claude Code

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Aug 5, 2026

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
✅ Deployment successful!
View logs
dmarcheck 1d9b108 Aug 05 2026, 12:02 PM

schmug added 2 commits August 5, 2026 06:54
… template

Adopts the autonomous issue→fix→merge loop from schmug/shipofclaudius. WIRED, NOT
ARMED: `secrets.FACTORY_GH_TOKEN` does not exist, so both write jobs no-op with a
clear message rather than half-running. Nothing can auto-merge today.

- .factory/gate.json — the deterministic merge gate's config, read from `main` on
  every run and never from the PR. The denylist mirrors .github/CODEOWNERS and adds
  the security/money paths that are not currently code-owned: src/auth/**,
  src/account/**, src/billing/**, src/webhooks/**, src/rate-limit-do.ts,
  mta-sts-worker/**, scripts/routine-gate/**. `.factory/**`,
  `.github/workflows/**` and CODEOWNERS are added automatically by the gate and
  cannot be removed here — a PR must never widen the rules it is judged by.
  requireFixtureEvidence stays false until a reproduction harness exists.
- .github/workflows/factory.yml — killswitch → advance → land. The land job runs NO
  model at all: it is `gh` plus a zero-dependency Node program, so injected issue
  text has nothing to act on. It checks out the BASE ref, never the PR head, which
  is what makes the `pull_request_target` trigger safe. Action SHAs match the
  existing pins so the action-pin integrity check passes.
- .github/ISSUE_TEMPLATE/ — the repo had none. The bug form makes the ```scope block
  required, which is the gate's single most common escalation cause.

Verified end to end against this config with the real gate binary:
  exit 0  a clean in-scope PR
  exit 2  real PR #653 (dependabot: unallowlisted author, no fix-verified, no
          Closes #N, mta-sts-worker/** risk path, 382 lines > 250, no scope block)
  exit 2  self-editing .factory/**, .github/workflows/**, CODEOWNERS
  exit 2  touching src/analyzers/**, src/shared/scoring.ts, src/billing/**
  exit 2  scope drift, oversize, pending required check, BLOCKED merge state

Before arming, see .factory/README.md: the write token must be a DISTINCT IDENTITY
from @schmug (GitHub forbids self-approval, so a code-owner token can never satisfy
require_code_owner_review on main-protection — #299), and this repo still has no
rollback path for a bad production deploy.

Signed-off-by: schmug <38227427+schmug@users.noreply.github.com>
…y runs

GitHub runs `run:` blocks as `bash -e {0}`, and `set -uo pipefail` does NOT clear
that inherited `-e`. So when the gate exited 2 — the NORMAL escalate path — the
step aborted before the exit code was captured, and the audit-comment and escalate
steps were then skipped by their implicit `success()`.

Net effect: the gate would post no verdict table and apply no `needs-you` label
exactly when it refused a PR, and a gate that genuinely crashed (exit 1) would look
identical to a normal refusal. The merge path was unaffected — it worked when the
gate said yes and silently did nothing when it said no.

`|| code=$?" now captures the code, and the two follow-on steps run under
`always()` so a PR is never left with no explanation of why it did not land.

Found by an adversarial review of the upstream branch; fixed in the shipofclaudius
template in the same pass.

Signed-off-by: schmug <38227427+schmug@users.noreply.github.com>
Two defects that would each have escalated 100% of factory PRs:

1. factory-issue-fix opens a DRAFT, a draft reports mergeStateStatus=DRAFT, and
   gate condition 8 rejects DRAFT — with nothing converting it. The land job now
   readies the PR before gating (safe, reversible, and only after a human applied
   `fix-verified`, which is the approval to land) and converts it back to a draft
   if the gate refuses.

2. The issue template used `render: text`, which GitHub emits as a \`\`\`text fence,
   while the gate's extractScopeGlobs matches only \`\`\`scope. Every issue filed
   through it would have failed condition 7. The template now pre-fills a literal
   \`\`\`scope fence, and applies `factory`+`needs-repro` so a filed bug actually
   enters the queue.

Also wires the optional --evidence passthrough and documents that condition 9's
red/green booleans must come from CI, never from the fix agent's self-report.

Found by an independent critical-path review; both confirmed by direct test before
fixing. Fixed upstream in the shipofclaudius template in the same pass.

Signed-off-by: schmug <38227427+schmug@users.noreply.github.com>
@schmug
schmug merged commit 9d487b8 into main Aug 6, 2026
6 checks passed
@schmug
schmug deleted the feat/software-factory branch August 6, 2026 11:17
schmug added a commit that referenced this pull request Aug 6, 2026
Closes the gap between .github/CODEOWNERS and the software factory's risk
denylist (.factory/gate.json, wired in #654): src/auth/, src/billing/,
src/webhooks/, src/rate-limit-do.ts, mta-sts-worker/, and
scripts/routine-gate/ were flagged as security/money-sensitive there but
CODEOWNERS hadn't caught up. src/account/ was already covered.

Co-authored-by: Claude <noreply@anthropic.com>
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