Skip to content

Commit c7f3804

Browse files
chrisleekrclaude
andauthored
feat(repo-config): add per-repo .github-app.yaml control surface
Promotes the scheduler-only config reader into a first-class per-repo control surface under `src/repo-config/`, and adds the two gates that consume it. - Move `src/scheduler/config-schema.ts` + `config-fetcher.ts` to `src/repo-config/{schema,fetcher}.ts` and widen the document schema beyond scheduled actions. The fetcher now returns a discriminated `ok` / `absent` / `invalid` result instead of a nullable value. - Add `src/repo-config/effective.ts` to merge `workflows.<name>` over `defaults` and clamp the result against the server env ceilings, failing open to `DEFAULT_REPO_POLICY`. - Add `src/repo-config/gate.ts` (Gate 1): a narrowing-only pre-dispatch check. Every rule can refuse, none can permit, so no YAML value can readmit a repo the `ALLOWED_OWNERS` env allowlist rejected. - Add `src/repo-config/pr-check.ts`, the one module that reads a head-ref copy. It is read-only by construction and imports neither `fetchRepoConfig` nor `loadRepoPolicy`, so a head-ref read can never populate the fetcher caches or reach the applied policy. - Add `src/core/agent-policy.ts` (Gate 2) and apply the resolved knobs in the pipeline and prompt builder. - Rename `SCHEDULER_CONFIG_FILE` to `REPO_CONFIG_FILE`, keeping the old name as a deprecated alias with a startup warning. - Generate `schema/github-app.schema.json` from the zod schema and gate it in CI via `check:config-schema`, so the `$schema` modeline authors consume cannot advertise a surface the runtime rejects. Only the default branch's copy is ever applied: `fetchRepoConfig` calls `getContent` with no `ref`, so a config edit inside a pull request is inert for that pull request. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KUPpJPtxAaHWrBsjytRGyM
1 parent 1ad6048 commit c7f3804

55 files changed

Lines changed: 6751 additions & 600 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.env.example

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,11 @@ ANTHROPIC_API_KEY=
2626
# CLAUDE_CODE_OAUTH_TOKEN=
2727

2828
# Model override. Required when CLAUDE_PROVIDER=bedrock (Bedrock uses a different model ID
29-
# format than the Anthropic API). Optional for anthropic: defaults to claude-opus-4-7
29+
# format than the Anthropic API). Optional for anthropic: defaults to claude-opus-5
3030
# when unset.
3131
# Bedrock example: us.anthropic.claude-sonnet-4-6
32-
# Anthropic example: claude-opus-4-7
33-
# CLAUDE_MODEL=claude-opus-4-7
32+
# Anthropic example: claude-opus-5
33+
# CLAUDE_MODEL=claude-opus-5
3434

3535
# ──────────────────────────────────────────────────────────────────────────────
3636
# Amazon Bedrock (when CLAUDE_PROVIDER=bedrock)
@@ -141,6 +141,15 @@ AGENT_JOB_MODE=inline
141141
# the primary or this previous token (constant-time). Drop after rolling daemons.
142142
# DAEMON_AUTH_TOKEN_PREVIOUS=
143143

144+
# Controller-only HMAC root for deadline-bound workflow-runner capabilities.
145+
# Never mount either value on shared daemons or isolated runners, and never
146+
# reuse either DAEMON_AUTH_TOKEN rotation value.
147+
# Generate at least 32 random bytes (e.g. openssl rand -hex 32).
148+
# WORKFLOW_RUNNER_CAPABILITY_SECRET=
149+
# Optional rotation-window predecessor. Remove after every capability minted
150+
# with the old root has reached its signed expiry.
151+
# WORKFLOW_RUNNER_CAPABILITY_SECRET_PREVIOUS=
152+
144153
# Per-job cost ceiling in USD
145154
# JOB_MAX_COST_USD=80
146155

@@ -154,6 +163,8 @@ AGENT_JOB_MODE=inline
154163
# STALE_EXECUTION_THRESHOLD_MS=600000
155164
# DAEMON_DRAIN_TIMEOUT_MS=300000
156165
# JOB_MAX_RETRIES=3
166+
# Maximum wall-clock age for a queued structured-workflow dispatch.
167+
# WORKFLOW_DISPATCH_TIMEOUT_MS=4200000
157168
# OFFER_TIMEOUT_MS=5000
158169

159170
# Daemon-side settings (used by scripts/run-daemon.sh)
@@ -203,11 +214,14 @@ AGENT_JOB_MODE=inline
203214
# Enable debug logging of LLM prompts.
204215
# DEBUG_LLM_PROMPTS=1
205216

217+
# Per-repo config file, read from each installed repo's DEFAULT BRANCH root only.
218+
# Deprecated alias: SCHEDULER_CONFIG_FILE.
219+
# REPO_CONFIG_FILE=.github-app.yaml
220+
206221
# Scheduled actions (.github-app.yaml). The scheduler also requires
207222
# DATABASE_URL and a non-empty ALLOWED_OWNERS to start.
208223
# SCHEDULER_ENABLED=false
209224
# SCHEDULER_SCAN_INTERVAL_MS=300000
210-
# SCHEDULER_CONFIG_FILE=.github-app.yaml
211225
# Hard kill-switch for unattended auto-merge; per-action auto_merge is AND-ed
212226
# with this. Leave false unless you accept LLM-judged merges.
213227
# SCHEDULER_ALLOW_AUTO_MERGE=false

.github/workflows/ci.yml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,13 @@ jobs:
132132
# parity gate, so this keeps config.ts, the contract, and the docs aligned.
133133
run: bun run check:env-contract
134134

135+
- name: Config-schema guard
136+
# Fails when schema/github-app.schema.json drifts from
137+
# src/repo-config/schema.ts. That file is what editors consume via the
138+
# `# yaml-language-server: $schema=` modeline, so a stale copy would
139+
# advertise a config surface the runtime no longer accepts.
140+
run: bun run check:config-schema
141+
135142
- name: Docs-sync guard (bot workflows, FR-019)
136143
if: github.event_name == 'pull_request'
137144
env:

.prettierignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,4 @@ bun.lockb
88
# Generated/managed assets with intentional non-prettier formatting
99
.claude/skills/
1010
docs/index.md
11+
schema/github-app.schema.json

CLAUDE.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,7 @@ Validate locally with `bun run docs:build` before pushing. If no matching doc ex
171171
**CI-enforced doc gates.** Two project-specific checks run in `.github/workflows/docs.yml` ahead of `mkdocs build --strict` (which only validates internal links and snippet targets, not prose-vs-source agreement):
172172

173173
- Bun version strings in `docs/` **and root-level `README.md` / `CONTRIBUTING.md` / `CLAUDE.md`** are pinned to `.tool-versions` via `bun run scripts/check-docs-versions.ts` (also asserts `package.json` `engines.bun` / `packageManager` and the two `Dockerfile.*` `FROM oven/bun:<ver>` lines agree).
174-
- `src/<file>:<line>` citations in `docs/` **and the same three root-level files** are verified via `bun run scripts/check-docs-citations.ts` (file must exist; cited line / range must be in bounds). Citations may opt in to symbol anchoring with a trailing `#symbol` suffix (e.g. `` `src/core/prompt-builder.ts:155#buildPrompt` ``); when present, the anchor token must physically appear on the cited line range, which closes the silent line-shift hole the bounds-only path can't see (issue #158).
174+
- `src/<file>:<line>` citations in `docs/` **and the same three root-level files** are verified via `bun run scripts/check-docs-citations.ts` (file must exist; cited line / range must be in bounds). Citations may opt in to symbol anchoring with a trailing `#symbol` suffix (e.g. `` `src/core/prompt-builder.ts:179#buildPrompt` ``); when present, the anchor token must physically appear on the cited line range, which closes the silent line-shift hole the bounds-only path can't see (issue #158).
175175

176176
The `docs.yml` `pull_request:` trigger has no `paths:` filter, so these gates run on every PR, code-side bumps that invalidate doc facts (Renovate Bun bump, refactor that shifts cited line numbers) trip the build the same way doc edits do. `Deploy to GitHub Pages` is still gated on `push` / `workflow_dispatch`, so PRs validate but never publish.
177177

bun.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docs/build/architecture.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,7 @@ The agent executor (`src/core/executor.ts:208`) supports two prompt-layout strat
159159

160160
The `cacheable` layout splits the prompt by trust:
161161

162-
- **Trusted scaffolding** (`security_directive`, `freshness_directive`, workflow steps, commit / CAPABILITIES boilerplate) → `systemPrompt.append`. Built by `buildPromptParts()` in `src/core/prompt-builder.ts:448#buildPromptParts`. Byte-identical across jobs of the same shape, so the system-prompt prefix becomes a stable cache key.
162+
- **Trusted scaffolding** (`security_directive`, `freshness_directive`, workflow steps, commit / CAPABILITIES boilerplate) → `systemPrompt.append`. Built by `buildPromptParts()` in `src/core/prompt-builder.ts:481#buildPromptParts`. Byte-identical across jobs of the same shape, so the system-prompt prefix becomes a stable cache key.
163163
- **Attacker-influenceable data** (`formatted_context` with title / body / comments, `<untrusted_*>` spotlight blocks with per-call nonce, per-call metadata like delivery ID) → user-role message.
164164
- **Dynamic preset sections** stripped via `excludeDynamicSections: true`.
165165

0 commit comments

Comments
 (0)